@svgrid/enterprise 3.1.7 → 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 (270) 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 +11375 -10805
  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-DtBIaQno.js → index-BPU2KNc5.js} +396 -395
  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 +141 -20
  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/emit-project.js +25 -6
  246. package/dist/studio/sveltekit3.js +13 -7
  247. package/dist/studio/ui-components.generated.js +94 -2
  248. package/dist/upgrade-prompt.d.ts +1 -1
  249. package/dist/version.d.ts +2 -2
  250. package/dist/version.js +2 -2
  251. package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
  252. package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
  253. package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
  254. package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
  255. package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
  256. package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
  257. package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
  258. package/dist/wc/sv-sheet-element.js +7561 -7618
  259. package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
  260. package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
  261. package/package.json +14 -3
  262. package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
  263. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
  264. package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
  265. package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
  266. package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
  267. package/dist/designer/assets/src-DqHCqori.js +0 -58
  268. package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
  269. package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
  270. package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
@@ -0,0 +1,545 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvBase - a table database: one table with views, or several tables as
4
+ * tabs with links between them.
5
+ *
6
+ * ```svelte
7
+ * <SvBase {data} {columns} primaryField="name" bind:views editable />
8
+ * <SvBase bind:tables editable onTablesChange={save} />
9
+ * ```
10
+ *
11
+ * With `tables`, a link field from one table to another gets a field in the
12
+ * other table that links back, and the two stay in step: linking a record
13
+ * on one side lists it on the other, deleting records unlinks them, pointing
14
+ * the field at another table moves the field back, and deleting either
15
+ * field or a table removes what links to it. `onTablesChange` hands over the
16
+ * whole list after every change, so the app can store it.
17
+ *
18
+ * A table with a `source` (`baseSource`) loads its records from a database
19
+ * or an API and writes each change back (`base-sync.ts`); the end of the
20
+ * tab bar says when it is loading, saving, or a save failed.
21
+ */
22
+ import { onMount, untrack } from 'svelte'
23
+ import { toast } from '@svgrid/grid'
24
+ import { createBaseSync, type BaseSyncStatus } from './base-sync'
25
+ import BaseTableView from './BaseTableView.svelte'
26
+ import BaseTabs from './BaseTabs.svelte'
27
+ import { registerLinkedTable, unregisterLinkedTable } from './sources.svelte'
28
+ import { gridOf } from './grid-access'
29
+ import { openRecord, closeRecord } from './editor-request.svelte'
30
+ import {
31
+ addInverse,
32
+ blankTable,
33
+ checkLinks,
34
+ colId,
35
+ copyTable,
36
+ fieldChanged,
37
+ newRecordId,
38
+ primaryOf,
39
+ removeLinkField,
40
+ removeTable,
41
+ rowIdOf,
42
+ syncAllInverses,
43
+ syncInverse,
44
+ type BaseTable,
45
+ } from './base-tables'
46
+ import type { FieldView, FieldViewType } from './view-model'
47
+ import { baseToSql } from './base-sql'
48
+ import { createAutomationRunner, type AutomationRun, type BaseAutomation } from './automations.svelte'
49
+
50
+ type Row = Record<string, unknown>
51
+ // A table without views shows one grid view; one list, so it never reads as a change.
52
+ const GRID_ONLY: FieldView[] = [{ id: 'grid', name: 'Grid view', type: 'grid' }]
53
+
54
+ let {
55
+ tables = $bindable<BaseTable[] | undefined>(undefined),
56
+ activeTable = $bindable<string>(''),
57
+ onTablesChange,
58
+ data,
59
+ columns,
60
+ views = $bindable<FieldView[]>([{ id: 'grid', name: 'Grid view', type: 'grid' }]),
61
+ activeView = $bindable<string>(''),
62
+ onViewsChange,
63
+ viewTypes,
64
+ rowHeights,
65
+ containerHeight = 560,
66
+ onApiReady,
67
+ serverRuns,
68
+ automations = true,
69
+ ...grid
70
+ }: {
71
+ /** Several tables, shown as tabs. Without it, `data` and `columns` are the one table. */
72
+ tables?: BaseTable[]
73
+ activeTable?: string
74
+ onTablesChange?: (tables: BaseTable[]) => void
75
+ data?: any[]
76
+ columns?: any[]
77
+ views?: FieldView[]
78
+ activeView?: string
79
+ onViewsChange?: (views: FieldView[]) => void
80
+ viewTypes?: ReadonlyArray<FieldViewType>
81
+ /** The row heights the view bar's row height menu offers. */
82
+ rowHeights?: ReadonlyArray<{ label: string; height: number }>
83
+ containerHeight?: number | string
84
+ onApiReady?: (api: any) => void
85
+ /** The server runner's latest runs, shown with this page's under Runs (an endpoint returning `server.runs`). */
86
+ serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>
87
+ /** The view bar's Automations; false hides it (automations already set still run). */
88
+ automations?: boolean
89
+ [prop: string]: any
90
+ } = $props()
91
+
92
+ // ---- Several tables ------------------------------------------------------
93
+
94
+ const current = $derived(tables ? (tables.find((t) => t.id === activeTable) ?? tables[0]) : undefined)
95
+ const currentId = $derived(current?.id ?? '')
96
+ // Raw: the fields module finds the grid by the api object itself.
97
+ let api = $state.raw<any>(null)
98
+ // The rows handed to the grid. They change when another table is shown, not
99
+ // on every edit: the grid writes edits into these rows and owns the array.
100
+ // svelte-ignore state_referenced_locally
101
+ let gridData = $state.raw<Row[]>(current?.data ?? [])
102
+ // svelte-ignore state_referenced_locally
103
+ let shownId = current?.id ?? ''
104
+ // A record to open once the table it is in has its grid (from "Open in").
105
+ let pendingOpen: string | null = null
106
+ // The view each table showed last ('' is its first view).
107
+ // svelte-ignore state_referenced_locally
108
+ let viewOf = $state<Record<string, string>>(Object.fromEntries((tables ?? []).map((t) => [t.id, ''])))
109
+
110
+ // Another table shown: its rows go to the grid, and it remembers its view.
111
+ $effect.pre(() => {
112
+ const t = current
113
+ if (!t) return
114
+ untrack(() => {
115
+ if (viewOf[t.id] === undefined) viewOf[t.id] = ''
116
+ if (t.id === shownId) return
117
+ shownId = t.id
118
+ gridData = t.data
119
+ if (activeTable !== t.id) activeTable = t.id
120
+ })
121
+ })
122
+
123
+ const height = $derived(typeof containerHeight === 'number' ? `${containerHeight}px` : containerHeight)
124
+
125
+ const tableOf = (id: string) => tables?.find((t) => t.id === id)
126
+
127
+ function commit(next: BaseTable[]) {
128
+ const before = tables
129
+ put(next)
130
+ // Tables with a source: what changed goes to the database.
131
+ sync.push(next)
132
+ linkedChanges(before, next)
133
+ }
134
+
135
+ /**
136
+ * Records of other tables a change reached through a link (a task added to
137
+ * a project lists it on the project): they changed too, so their
138
+ * automations hear of it. The shown table's own edits are heard from the
139
+ * grid. A link sync gives a changed record a new object, so identity tells
140
+ * which ones moved.
141
+ */
142
+ function linkedChanges(prev: BaseTable[] | undefined, next: BaseTable[]) {
143
+ if (!prev) return
144
+ for (const t of next) {
145
+ if (t.id === current?.id || t.id === chainWrote || !(t.automations ?? []).length) continue
146
+ const old = prev.find((x) => x.id === t.id)
147
+ if (!old || old.data === t.data) continue
148
+ const byId = new Map(old.data.map((r) => [rowIdOf(old, r), r]))
149
+ for (const r of t.data) {
150
+ const was = byId.get(rowIdOf(t, r))
151
+ if (!was || was === r) continue
152
+ const fields = t.columns.filter((d) => d.field != null && JSON.stringify(was[String(d.field)] ?? null) !== JSON.stringify(r[String(d.field)] ?? null)).map((d) => colId(d))
153
+ if (fields.length) void runner.handle({ type: 'updated', table: t.id, record: r, before: was, fields, depth: chainDepth })
154
+ }
155
+ }
156
+ }
157
+
158
+ /** The tables in the page; the shown grid takes its table's rows when they changed outside it. */
159
+ function put(next: BaseTable[]) {
160
+ const before = current
161
+ tables = next
162
+ // The shown table's rows changed outside its grid (a link pointed
163
+ // elsewhere and its values were cleared): the grid takes the new rows.
164
+ const after = next.find((t) => t.id === before?.id)
165
+ if (after && before && after.data !== before.data && after.data !== liveRows()) gridData = after.data
166
+ onTablesChange?.(next)
167
+ }
168
+
169
+ // ---- Tables on a database ---------------------------------------------------
170
+
171
+ let syncState = $state<BaseSyncStatus>({ state: 'idle' })
172
+ let savedOnce = $state(false)
173
+ const sync = createBaseSync({
174
+ // The shown table as its grid holds it: an edit is in its rows already.
175
+ getTables: () => (tables ? pulled() : undefined),
176
+ apply: put,
177
+ onStatus: (s) => {
178
+ if (s.state === 'saving') savedOnce = true
179
+ syncState = s
180
+ },
181
+ onError: (e) => toast.error(`Could not ${e.action === 'load' ? 'load' : 'save'} ${e.table}: ${e.message}`),
182
+ })
183
+ onMount(() => {
184
+ void sync.load()
185
+ return () => {
186
+ sync.dispose()
187
+ runner.stop()
188
+ }
189
+ })
190
+
191
+ // ---- Automations --------------------------------------------------------------
192
+
193
+ // What an automation changes goes through commit, so a table with a source
194
+ // writes it to the database like any other change.
195
+ // How deep in a chain of automations the change being committed was made.
196
+ let chainDepth = 0
197
+ // The table an automation changed itself: it reports that change.
198
+ let chainWrote: string | undefined
199
+ // This base's own key for its records' comments and activity (`baseScope`).
200
+ const baseScope = {}
201
+ const runner = createAutomationRunner({
202
+ scope: baseScope,
203
+ getTables: () => (tables ? pulled() : undefined),
204
+ apply: (next, depth = 0, wrote) => {
205
+ chainDepth = depth
206
+ chainWrote = wrote
207
+ try {
208
+ commit(next)
209
+ } finally {
210
+ chainDepth = 0
211
+ chainWrote = undefined
212
+ }
213
+ },
214
+ })
215
+ // Scheduled automations are armed again when any table's automations change.
216
+ $effect(() => {
217
+ void tables?.map((t) => t.automations)
218
+ untrack(() => runner.schedule())
219
+ })
220
+
221
+ // Edits to one record in one go (a paste, a fill) are one change to it.
222
+ const touched = new Map<string, { table: string; record: Row; before: Row; fields: Set<string> }>()
223
+ function noteUpdate(tableId: string, record: Row | undefined, columnId: string, oldValue: unknown) {
224
+ const t = tableOf(tableId)
225
+ if (!t || !record) return
226
+ const key = `${tableId}:${rowIdOf(t, record)}`
227
+ let hit = touched.get(key)
228
+ if (!hit) {
229
+ hit = { table: tableId, record, before: { ...record }, fields: new Set() }
230
+ touched.set(key, hit)
231
+ if (touched.size === 1) queueMicrotask(flushTouched)
232
+ }
233
+ const field = t.columns.find((d) => colId(d) === columnId)?.field
234
+ if (field != null && !hit.fields.has(columnId)) hit.before[String(field)] = oldValue
235
+ hit.fields.add(columnId)
236
+ }
237
+ function flushTouched() {
238
+ const list = [...touched.values()]
239
+ touched.clear()
240
+ for (const u of list) void runner.handle({ type: 'updated', table: u.table, record: u.record, before: u.before, fields: [...u.fields] })
241
+ }
242
+ /** The handler of a button field, when an automation runs on it. */
243
+ function buttonFor(tableId: string, columnId: string): ((row: Row) => void) | null {
244
+ const on = (tableOf(tableId)?.automations ?? []).some((a) => a.enabled && a.trigger.type === 'button' && a.trigger.field === columnId)
245
+ return on ? (row) => void runner.handle({ type: 'button', table: tableId, record: row, field: columnId }) : null
246
+ }
247
+ const syncLine = $derived.by(() => {
248
+ const s = syncState
249
+ if (!tables?.some((t) => t.source)) return null
250
+ if (s.state === 'loading') return { text: 'Loading records…' }
251
+ if (s.state === 'saving') return { text: 'Saving…' }
252
+ if (s.state === 'error') return { text: `Not saved: ${s.message}`, tone: 'error' as const, title: `${s.table}: ${s.message}` }
253
+ if (s.state === 'limited') return { text: `${s.table}: the first ${s.limit.toLocaleString()} records` }
254
+ return savedOnce ? { text: 'All changes saved' } : null
255
+ })
256
+
257
+ /** The rows the shown table's grid holds now (records added, deleted). */
258
+ function liveRows(): Row[] | undefined {
259
+ return api ? (gridOf(api)?.internalData as Row[] | undefined) : undefined
260
+ }
261
+
262
+ /** The tables with the shown one's rows as its grid holds them. */
263
+ function pulled(change: Partial<BaseTable> = {}): BaseTable[] {
264
+ const list = tables ?? []
265
+ const id = current?.id
266
+ const live = liveRows()
267
+ return list.map((t) => (t.id === id ? { ...t, ...(live ? { data: live } : {}), ...change } : t))
268
+ }
269
+
270
+ function switchTo(id: string) {
271
+ if (!tables || id === current?.id) return
272
+ commit(pulled())
273
+ show(id)
274
+ }
275
+
276
+ /** Show table `id`: a new grid with its rows. */
277
+ function show(id: string) {
278
+ const t = tableOf(id)
279
+ if (!t) return
280
+ closeRecord()
281
+ api = null
282
+ if (viewOf[id] === undefined) viewOf[id] = ''
283
+ shownId = id
284
+ gridData = t.data
285
+ activeTable = id
286
+ }
287
+
288
+ // ---- What the shown table's grid reports ----------------------------------
289
+
290
+ const linkFieldOf = (t: BaseTable | undefined, id: string) => t?.columns.find((d) => colId(d) === id && d.fieldType === 'link')
291
+
292
+ function call(name: string, e: unknown) {
293
+ ;(current?.props?.[name] as ((e: unknown) => void) | undefined)?.(e)
294
+ ;(grid[name] as ((e: unknown) => void) | undefined)?.(e)
295
+ }
296
+
297
+ const handlers = {
298
+ onColumnAdd: (e: { column: Record<string, any>; columns: any[]; data?: Row[] }) => {
299
+ const id = current!.id
300
+ let next = pulled({ columns: e.columns, ...(e.data ? { data: e.data } : {}) })
301
+ if (e.column?.fieldType === 'link') next = addInverse(next, id, colId(e.column))
302
+ commit(next)
303
+ call('onColumnAdd', e)
304
+ },
305
+ onColumnChange: (e: { column: Record<string, any>; previous?: Record<string, any>; columns: any[]; data?: Row[] }) => {
306
+ const id = current!.id
307
+ let next = pulled({ columns: e.columns, ...(e.data ? { data: e.data } : {}) })
308
+ if (e.previous?.fieldType === 'link' || e.column?.fieldType === 'link') next = fieldChanged(next, id, e.previous, colId(e.column))
309
+ commit(next)
310
+ call('onColumnChange', e)
311
+ },
312
+ onColumnRemove: (e: { columnId: string; columns: any[] }) => {
313
+ const removed = linkFieldOf(current, e.columnId)
314
+ commit(removeLinkField(pulled({ columns: e.columns }), current!.id, removed))
315
+ call('onColumnRemove', e)
316
+ },
317
+ onCellValueChange: (e: { columnId: string; row?: Row; oldValue?: unknown }) => {
318
+ const id = current!.id
319
+ const next = pulled()
320
+ commit(linkFieldOf(current, e.columnId)?.fieldProps?.inverse ? syncInverse(next, id, e.columnId) : next)
321
+ noteUpdate(id, e.row, e.columnId, e.oldValue)
322
+ call('onCellValueChange', e)
323
+ },
324
+ onRowAdd: (e: { row?: Row }) => {
325
+ const id = current!.id
326
+ commit(syncAllInverses(pulled(), id))
327
+ if (e?.row) void runner.handle({ type: 'created', table: id, record: e.row })
328
+ call('onRowAdd', e)
329
+ },
330
+ onRowsDelete: (e: { data: Row[] }) => {
331
+ commit(syncAllInverses(pulled({ data: e.data }), current!.id))
332
+ call('onRowsDelete', e)
333
+ },
334
+ }
335
+
336
+ // ---- Every table is a table link fields can point at ----------------------
337
+
338
+ /** A record named `name` in table `id` (the link picker's "+ Create"). */
339
+ function createIn(id: string, name: string): Row {
340
+ const t = tableOf(id)!
341
+ const row: Row = { id: newRecordId(), [primaryOf(t)]: name }
342
+ if (id === current?.id && api) {
343
+ api.addRow(row, 'bottom')
344
+ commit(pulled())
345
+ } else {
346
+ commit(pulled().map((x) => (x.id === id ? { ...x, data: [...x.data, row] } : x)))
347
+ }
348
+ return row
349
+ }
350
+
351
+ /** Show record `rowId` in its own table, expanded. */
352
+ function openIn(id: string, rowId: string) {
353
+ if (id === current?.id) {
354
+ const ctrl = api ? gridOf(api) : null
355
+ if (ctrl) openRecord({ ctrl, rowId })
356
+ return
357
+ }
358
+ pendingOpen = rowId
359
+ switchTo(id)
360
+ }
361
+
362
+ // What was registered for each table: registering again only when its name or fields change.
363
+ const registered = new Map<string, { name: string; columns: unknown; primary: string }>()
364
+ // This base's registrations: unregistering them leaves another base's
365
+ // table, or the app's, with the same id alone.
366
+ const owner = {}
367
+ onMount(() => {
368
+ // Loaded links that will not sync (an inverse naming no field) or that disagree: said once, in the console.
369
+ if (tables) checkLinks(tables)
370
+ return () => {
371
+ for (const id of registered.keys()) unregisterLinkedTable(id, owner)
372
+ }
373
+ })
374
+ $effect(() => {
375
+ const list = tables
376
+ if (!list) return
377
+ for (const t of list) {
378
+ const primary = primaryOf(t)
379
+ const seen = registered.get(t.id)
380
+ if (seen && seen.name === t.name && seen.columns === t.columns && seen.primary === primary) continue
381
+ registered.set(t.id, { name: t.name, columns: t.columns, primary })
382
+ const id = t.id
383
+ registerLinkedTable({
384
+ id,
385
+ name: t.name,
386
+ // Read each time: the shown table from its grid, the others from the list.
387
+ rows: () => (id === current?.id ? (liveRows() ?? tableOf(id)?.data ?? []) : (tableOf(id)?.data ?? [])),
388
+ getRowId: (r) => {
389
+ const own = tableOf(id)
390
+ return own ? rowIdOf(own, r) : String(r.id ?? '')
391
+ },
392
+ primaryField: primary,
393
+ fields: t.columns
394
+ .filter((d) => d.field)
395
+ .map((d) => ({ field: String(d.field), header: String(d.header ?? d.field), fieldType: d.fieldType, fieldProps: d.fieldProps })),
396
+ create: (name) => createIn(id, name),
397
+ open: (rowId) => openIn(id, rowId),
398
+ }, owner)
399
+ }
400
+ for (const id of [...registered.keys()]) {
401
+ if (list.some((t) => t.id === id)) continue
402
+ registered.delete(id)
403
+ unregisterLinkedTable(id, owner)
404
+ }
405
+ })
406
+
407
+ // ---- The tabs ---------------------------------------------------------------
408
+
409
+ function addTable() {
410
+ const list = pulled()
411
+ const t = blankTable(list)
412
+ commit([...list, t])
413
+ switchTo(t.id)
414
+ }
415
+ function renameTable(id: string, name: string) {
416
+ commit(pulled().map((t) => (t.id === id ? { ...t, name } : t)))
417
+ }
418
+ function duplicateTable(id: string) {
419
+ const list = pulled()
420
+ const source = list.find((t) => t.id === id)
421
+ if (!source) return
422
+ const copy = copyTable(list, source)
423
+ let next = [...list, copy]
424
+ for (const d of copy.columns) if (d.fieldType === 'link') next = addInverse(next, copy.id, colId(d))
425
+ commit(next)
426
+ switchTo(copy.id)
427
+ }
428
+ /** The base as Postgres SQL, downloaded: tables, links, records, views and automations. */
429
+ function exportSql() {
430
+ const list = pulled()
431
+ const sql = baseToSql(list, { title: list.map((t) => t.name).join(', ') })
432
+ const url = URL.createObjectURL(new Blob([sql], { type: 'application/sql' }))
433
+ const a = document.createElement('a')
434
+ a.href = url
435
+ a.download = 'base.sql'
436
+ a.click()
437
+ setTimeout(() => URL.revokeObjectURL(url), 1000)
438
+ }
439
+ function deleteTable(id: string) {
440
+ const list = pulled()
441
+ if (list.length < 2) return
442
+ const i = list.findIndex((t) => t.id === id)
443
+ const next = removeTable(list, id)
444
+ const shown = id === current?.id
445
+ commit(next)
446
+ if (shown) show(next[Math.max(0, i - 1)]!.id)
447
+ }
448
+
449
+ // One table: rows keyed by their id field, as a table of `tables` is, so
450
+ // comments, activity and an open record stay with the record when a row
451
+ // above it is deleted. A row without an id falls back to its index; a
452
+ // `getRowId` prop wins.
453
+ const singleRowId = (r: Row, i: number): string =>
454
+ grid.getRowId ? grid.getRowId(r, i) : r?.id != null && r.id !== '' ? String(r.id) : String(i)
455
+
456
+ // Per table, not per change: an edit gives the grid no new getRowId or rowAdder.
457
+ const gridProps = $derived.by(() => {
458
+ const id = currentId
459
+ if (!id) return {}
460
+ const t = untrack(() => tableOf(id))
461
+ if (!t) return {}
462
+ const own = (t.props ?? {}) as Record<string, any>
463
+ const adder = own.rowAdder ?? grid.rowAdder ?? true
464
+ // A new record gets an id, as every record of a base has one.
465
+ const rowAdder =
466
+ adder === false
467
+ ? false
468
+ : {
469
+ ...(typeof adder === 'object' ? adder : {}),
470
+ defaults: () => ({ id: newRecordId(), ...(typeof adder === 'object' && typeof adder.defaults === 'function' ? adder.defaults() : {}) }),
471
+ }
472
+ return {
473
+ ...grid,
474
+ ...own,
475
+ ...handlers,
476
+ rowAdder,
477
+ primaryField: primaryOf(t),
478
+ getRowId: (r: Row) => rowIdOf(tableOf(id) ?? t, r),
479
+ // Comments and activity are kept by base and table id, so they outlive this grid.
480
+ baseTableId: id,
481
+ baseScope,
482
+ // A button field an automation runs on.
483
+ fieldButtons: (columnId: string) => buttonFor(id, columnId),
484
+ }
485
+ })
486
+ </script>
487
+
488
+ {#if tables && current}
489
+ <div class="sv-fbase-tables" style:height>
490
+ <BaseTabs
491
+ {tables}
492
+ activeId={current.id}
493
+ onSwitch={switchTo}
494
+ onAdd={addTable}
495
+ onRename={renameTable}
496
+ onDuplicate={duplicateTable}
497
+ onDelete={deleteTable}
498
+ onExportSql={exportSql}
499
+ status={syncLine}
500
+ />
501
+ <div class="sv-fbase-slot">
502
+ {#key current.id}
503
+ <BaseTableView
504
+ {...gridProps}
505
+ bare
506
+ data={gridData}
507
+ columns={current.columns}
508
+ views={current.views ?? GRID_ONLY}
509
+ bind:activeView={viewOf[current.id]}
510
+ onViewsChange={(v: FieldView[]) => commit(pulled({ views: v }))}
511
+ table={current}
512
+ tables={() => pulled()}
513
+ runner={automations ? runner : undefined}
514
+ {serverRuns}
515
+ onAutomationsChange={(list: BaseAutomation[]) => commit(pulled({ automations: list }))}
516
+ onFormSubmit={(row: Row, viewId: string) => void runner.handle({ type: 'form', table: current!.id, record: row, view: viewId })}
517
+ newRecord={() => ({ id: newRecordId() })}
518
+ {viewTypes}
519
+ {rowHeights}
520
+ containerHeight="100%"
521
+ onApiReady={(a: any) => {
522
+ api = a
523
+ onApiReady?.(a)
524
+ const rowId = pendingOpen
525
+ pendingOpen = null
526
+ if (rowId) queueMicrotask(() => {
527
+ const ctrl = gridOf(a)
528
+ if (ctrl) openRecord({ ctrl, rowId })
529
+ })
530
+ }}
531
+ />
532
+ {/key}
533
+ </div>
534
+ </div>
535
+ {:else}
536
+ <BaseTableView {...grid} getRowId={singleRowId} data={data ?? []} columns={columns ?? []} bind:views bind:activeView {onViewsChange} {viewTypes} {rowHeights} {containerHeight} {onApiReady} />
537
+ {/if}
538
+
539
+ <style>
540
+ .sv-fbase-tables {
541
+ display: flex; flex-direction: column; min-height: 0; box-sizing: border-box; overflow: hidden;
542
+ border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius-lg, 10px); background: var(--sg-bg, #fff);
543
+ }
544
+ .sv-fbase-slot { flex: 1 1 auto; min-height: 0; }
545
+ </style>
@@ -0,0 +1,30 @@
1
+ import { type BaseTable } from './base-tables.js';
2
+ import type { FieldView, FieldViewType } from './view-model.js';
3
+ import { type AutomationRun } from './automations.svelte.js';
4
+ type $$ComponentProps = {
5
+ /** Several tables, shown as tabs. Without it, `data` and `columns` are the one table. */
6
+ tables?: BaseTable[];
7
+ activeTable?: string;
8
+ onTablesChange?: (tables: BaseTable[]) => void;
9
+ data?: any[];
10
+ columns?: any[];
11
+ views?: FieldView[];
12
+ activeView?: string;
13
+ onViewsChange?: (views: FieldView[]) => void;
14
+ viewTypes?: ReadonlyArray<FieldViewType>;
15
+ /** The row heights the view bar's row height menu offers. */
16
+ rowHeights?: ReadonlyArray<{
17
+ label: string;
18
+ height: number;
19
+ }>;
20
+ containerHeight?: number | string;
21
+ onApiReady?: (api: any) => void;
22
+ /** The server runner's latest runs, shown with this page's under Runs (an endpoint returning `server.runs`). */
23
+ serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>;
24
+ /** The view bar's Automations; false hides it (automations already set still run). */
25
+ automations?: boolean;
26
+ [prop: string]: any;
27
+ };
28
+ declare const SvBase: import("svelte").Component<$$ComponentProps, {}, "tables" | "views" | "activeView" | "activeTable">;
29
+ type SvBase = ReturnType<typeof SvBase>;
30
+ export default SvBase;