@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,120 @@
1
+ /**
2
+ * What the fields module's own surfaces (the view bar, the gallery and form
3
+ * views, a board view's moves) need from a grid: the grid behind an api, the
4
+ * data columns with their types, the primary field, and a way to write a
5
+ * value that goes through the grid's edit path.
6
+ */
7
+ import { getAdvancedFilterEngine } from '@svgrid/grid';
8
+ // The grid behind each api object (the grid keeps one api per grid).
9
+ const grids = new WeakMap();
10
+ /** Called on every draw of a grid with fields turned on. */
11
+ export function rememberGrid(ctrl) {
12
+ const api = ctrl?.buildApi?.();
13
+ if (api && !grids.has(api))
14
+ grids.set(api, ctrl);
15
+ }
16
+ /** The grid an api belongs to; undefined until that grid has drawn once. */
17
+ export function gridOf(api) {
18
+ return api && typeof api === 'object' ? grids.get(api) : undefined;
19
+ }
20
+ export const columnIdOf = (def) => String(def.id ?? def.field ?? '');
21
+ /** Every data column (declared ones with a field), in screen order, hidden ones last. */
22
+ export function fieldColumns(ctrl) {
23
+ const hidden = (ctrl?.hiddenColumns ?? {});
24
+ const declared = leaves((ctrl?.internalColumns ?? [])).filter((d) => d.field != null);
25
+ const byId = new Map(declared.map((d) => [columnIdOf(d), d]));
26
+ const shownOrder = (ctrl?.allColumns ?? []).map((c) => c.id).filter((id) => byId.has(id));
27
+ const rest = declared.map(columnIdOf).filter((id) => !shownOrder.includes(id));
28
+ return [...shownOrder, ...rest].map((id) => {
29
+ const def = byId.get(id);
30
+ return {
31
+ id,
32
+ field: String(def.field),
33
+ header: typeof def.header === 'string' ? def.header : id,
34
+ fieldType: def.fieldType,
35
+ def,
36
+ visible: !hidden[id] && def.visible !== false && shownOrder.includes(id),
37
+ };
38
+ });
39
+ }
40
+ function leaves(cols) {
41
+ return cols.flatMap((c) => (Array.isArray(c.columns) && c.columns.length ? leaves(c.columns) : [c]));
42
+ }
43
+ /** The primary field's column id: the `primaryField` prop, matched by id or field. */
44
+ export function primaryIdOf(ctrl) {
45
+ const p = ctrl?.props?.primaryField;
46
+ if (typeof p !== 'string' || !p)
47
+ return null;
48
+ const cols = leaves((ctrl?.internalColumns ?? []));
49
+ const hit = cols.find((d) => columnIdOf(d) === p) ?? cols.find((d) => d.field === p);
50
+ return hit ? columnIdOf(hit) : null;
51
+ }
52
+ /**
53
+ * Write a value the way an edit in the cell does (undo, validation,
54
+ * onCellValueChange, last-modified fields). False when the row is not shown
55
+ * or the cell cannot be edited.
56
+ */
57
+ export function writeCell(ctrl, rowId, columnId, value) {
58
+ const at = (ctrl?.allRows ?? []).findIndex((r) => r.id === rowId);
59
+ if (at < 0 || !ctrl.startEditing?.(at, columnId))
60
+ return false;
61
+ ctrl.updateEditingCellValue(value);
62
+ ctrl.saveEditingCell();
63
+ return true;
64
+ }
65
+ /** A row's value for a column, through the grid's edit overlay (as the cells read it). */
66
+ export function cellValue(ctrl, row, column) {
67
+ const base = row.getCellValueByColumnId ? row.getCellValueByColumnId(column.id) : row.original?.[column.field];
68
+ return ctrl?.getCellDisplayValue ? ctrl.getCellDisplayValue(row.id, column.id, base) : base;
69
+ }
70
+ /** A value as its cell writes it as text (the column's formatter), for titles and search. */
71
+ export function cellText(column, value, row) {
72
+ const f = column.def.formatter;
73
+ if (f) {
74
+ try {
75
+ return String(f({ value, row, column: { id: column.id, columnDef: column.def } }) ?? '');
76
+ }
77
+ catch {
78
+ /* fall through to the raw value */
79
+ }
80
+ }
81
+ if (Array.isArray(value))
82
+ return value.map((v) => (v && typeof v === 'object' ? (v.name ?? '') : String(v))).join(', ');
83
+ return value == null ? '' : String(value);
84
+ }
85
+ /** The rows the grid shows, filtered and sorted, without group header rows. */
86
+ export function dataRows(ctrl) {
87
+ return (ctrl?.allRows ?? []).filter((r) => r.__treeRow || !(typeof r.getCanExpand === 'function' && r.getCanExpand()));
88
+ }
89
+ const isBanner = (r) => !r.__treeRow && typeof r.getCanExpand === 'function' && r.getCanExpand();
90
+ /**
91
+ * The grid's advanced filter as a test of one record, or null when nothing
92
+ * filters. The grid filters after it groups, so a group's own rows still hold
93
+ * the records the filter hides; this is how a group header leaves them out.
94
+ */
95
+ export function leafFilter(ctrl) {
96
+ const expr = ctrl?.advancedFilter;
97
+ const engine = expr ? getAdvancedFilterEngine() : null;
98
+ if (!engine)
99
+ return null;
100
+ try {
101
+ return engine(expr, { getValue: ctrl.getRowColumnValue, rows: [] }) ?? null;
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ /** The records under a group row, those the filter keeps. */
108
+ export function groupLeaves(row, keep) {
109
+ const out = [];
110
+ const walk = (r) => {
111
+ for (const s of r.subRows ?? []) {
112
+ if (isBanner(s))
113
+ walk(s);
114
+ else if (!keep || keep(s))
115
+ out.push(s);
116
+ }
117
+ };
118
+ walk(row);
119
+ return out;
120
+ }
@@ -0,0 +1,34 @@
1
+ export { registerFieldType, getFieldType, listFieldTypes, fieldPropsOf, fillFieldColumn, type FieldTypeDef, type FieldCellProps, } from './registry.svelte.js';
2
+ export { linkHref, linkText, type LinkKind } from './links.js';
3
+ export { setFieldUsers, getFieldUsers, setCurrentUser, getCurrentUser, registerLinkedTable, unregisterLinkedTable, getLinkedTable, listLinkedTables, type FieldUser, type LinkedTable, } from './sources.svelte.js';
4
+ export { compileFormula, formulaError, FORMULA_FUNCTIONS, FormulaError } from './formula.js';
5
+ export { formatNumber, formatCurrency, formatPercent, formatDuration, parseDuration, formatDate } from './format.js';
6
+ export { BRIGHT, OPTION_COLORS } from './palette.js';
7
+ export { convertValue, countCleared } from './convert.js';
8
+ export { openFieldEditor, closeFieldEditor, openRecord, closeRecord, type OpenRecordRequest } from './editor-request.svelte.js';
9
+ export { default as SvBaseViewBar } from './SvBaseViewBar.svelte';
10
+ export { default as SvBaseForm } from './SvBaseForm.svelte';
11
+ export { default as SvBase } from './SvBase.svelte';
12
+ export { blankTable, newRecordId, type BaseTable } from './base-tables.js';
13
+ export { baseSource, createBaseSync, type BaseSource, type BaseSourceOptions, type BaseSyncStatus, type ForeignKeyLink, type JunctionLink, } from './base-sync.js';
14
+ export { baseFieldsFromSchema, baseTablesFromSchemas, baseSpecFromSchemas, baseTablesFromSpec, type BaseTableSpec, type BaseLinkSpec } from './base-schema.js';
15
+ export { baseToSql, sqlName, type BaseToSqlOptions, type BaseTableComment, type BaseJunctionComment } from './base-sql.js';
16
+ export { applyBaseComments, withInverses, type BaseComments } from './base-schema.js';
17
+ export { baseFromDatabase, describeDatabase, createBaseEndpoint, baseFromEndpoint, baseFromSupabase, type DatabaseOptions, type BaseEndpointOptions, type SupabaseBaseOptions } from './base-connect.js';
18
+ export { registerAutomationAction, listAutomationActions, MAX_DEPTH as AUTOMATION_MAX_DEPTH, type BaseAutomation, type AutomationTrigger, type AutomationAction, type AutomationRun, type AutomationStep, type AutomationActionContext, type RegisteredAutomationAction, } from './automations.svelte.js';
19
+ export { createAutomationServer, type AutomationServer, type AutomationServerOptions, type AutomationNotice } from './automation-server.js';
20
+ export type { WebhookAction, EmailAction, OutgoingEmail } from './automation-outbound.js';
21
+ export { setRecordComments, type RecordComment, type RecordCommentContext, type RecordCommentStore } from './activity.svelte.js';
22
+ export { baseCondition, baseFilterOf } from './base-alerts.js';
23
+ export { compileFieldFilter, describeFieldFilter, filterKindOf, operatorsFor, ME, type FieldFilter, type FieldCondition, type FieldConditionGroup, type FilterKind, type FilterOp, } from './filter-model.js';
24
+ export type { FieldView, FieldViewType, ColorSetting } from './view-model.js';
25
+ export type { FieldPropertyDef, FieldEditorContext, FieldFillContext, FieldRowContext } from './registry.svelte.js';
26
+ export { BUILTIN_FIELD_TYPES } from './builtin-types.js';
27
+ export { setAttachmentUploader, attachmentsFromText, attachmentsText, toAttachments, type Attachment, type AttachmentFieldProps, type AttachmentUploader, type AttachmentUploadContext, } from './attachments.js';
28
+ /**
29
+ * Turn field types on for every `<SvGrid>` on the page: registers the
30
+ * built-in types and plugs the cell drawing into the grid. Safe to call more
31
+ * than once. Part of SvGrid Base, a Suite product: without a Suite key the
32
+ * grid shows the Enterprise watermark and the feature still runs.
33
+ */
34
+ export declare function enableFields(): void;
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @svgrid/enterprise/fields - field types for `<SvGrid>`, the engine of SvGrid
3
+ * Base (a Suite product; `@svgrid/enterprise/base` is the product entry).
4
+ *
5
+ * ```ts
6
+ * import { enableFields } from '@svgrid/enterprise/fields'
7
+ * enableFields()
8
+ *
9
+ * const columns = [
10
+ * { field: 'site', header: 'Site', fieldType: 'url' },
11
+ * { field: 'score', header: 'Score', fieldType: 'rating' },
12
+ * { field: 'status', header: 'Status', fieldType: 'singleSelect',
13
+ * fieldProps: { options: [{ value: 'Done', color: '#16a34a' }] } },
14
+ * ]
15
+ * ```
16
+ *
17
+ * Also draws the grid's `rowAdder` line ("+ Add row" under the last row) and
18
+ * its `rowColor` marks (a status color on each row's header), the `columnAdder`
19
+ * "+" with its field editor, and the `fieldMenu` actions in the column menu.
20
+ *
21
+ * A separate module, not part of `installEnterprise()`: an app that does not
22
+ * call `enableFields()` does not ship it, and `@svgrid/grid` carries only the
23
+ * `registerGridExtension` seam it plugs into.
24
+ */
25
+ import { registerCellEditor, registerGridExtension, registerGridShortcuts } from '@svgrid/grid';
26
+ import { nudgeEnterpriseFor } from '../license.js';
27
+ import FieldCell from './FieldCell.svelte';
28
+ import RowAdder from './RowAdder.svelte';
29
+ import FootRowAdder from './FootRowAdder.svelte';
30
+ import AttachmentEditor from './AttachmentEditor.svelte';
31
+ import FieldsHeaderEnd from './FieldsHeaderEnd.svelte';
32
+ import { askDeleteField, openFieldEditor, openRecord } from './editor-request.svelte.js';
33
+ import { enableAdvancedFilter, registerFilterKind } from '../advanced-filter-enable.js';
34
+ import { compileFieldFilter, newCondition, resolveMeWith } from './filter-model.js';
35
+ import { hasBar, openBarPanel } from './bar-request.svelte.js';
36
+ import { groupRules, userSorts, writeSortState } from './viewbar/sort-state.js';
37
+ import { sortLabels } from './view-model.js';
38
+ import { noteChange } from './activity.svelte.js';
39
+ import { getLinkedTable } from './sources.svelte.js';
40
+ import { linkNames, registerBaseCondition } from './base-alerts.js';
41
+ import { duplicateField, hideField, indexBeside, removeField } from './field-ops.js';
42
+ import { linkReaders } from './base-tables.js';
43
+ import { formulaReaders } from './formula.js';
44
+ import { BUILTIN_FIELD_TYPES } from './builtin-types.js';
45
+ import { rowColorOf, rowColorRootClass, tallRowClass, rowMarkColor, rowMarkLabel } from './row-colors.js';
46
+ import { chooseRowColor } from './row-color-choice.svelte.js';
47
+ import { fieldPropsOf, fillFieldColumn, getFieldType, originalColumn, registerFieldType, typeDrawsCell } from './registry.svelte.js';
48
+ import RatingEditor from './RatingEditor.svelte';
49
+ import LinkPickerEditor from './LinkPickerEditor.svelte';
50
+ import SelectEditor from './SelectEditor.svelte';
51
+ import NumberEditor from './NumberEditor.svelte';
52
+ import LongTextEditor from './LongTextEditor.svelte';
53
+ import RowHeader from './RowHeader.svelte';
54
+ import GroupHeader from './GroupHeader.svelte';
55
+ import SummaryCell from './SummaryCell.svelte';
56
+ import { getCurrentUser } from './sources.svelte.js';
57
+ import { fieldColumns, gridOf, primaryIdOf, rememberGrid } from './grid-access.js';
58
+ export { registerFieldType, getFieldType, listFieldTypes, fieldPropsOf, fillFieldColumn, } from './registry.svelte.js';
59
+ export { linkHref, linkText } from './links.js';
60
+ export { setFieldUsers, getFieldUsers, setCurrentUser, getCurrentUser, registerLinkedTable, unregisterLinkedTable, getLinkedTable, listLinkedTables, } from './sources.svelte.js';
61
+ export { compileFormula, formulaError, FORMULA_FUNCTIONS, FormulaError } from './formula.js';
62
+ export { formatNumber, formatCurrency, formatPercent, formatDuration, parseDuration, formatDate } from './format.js';
63
+ export { BRIGHT, OPTION_COLORS } from './palette.js';
64
+ export { convertValue, countCleared } from './convert.js';
65
+ export { openFieldEditor, closeFieldEditor, openRecord, closeRecord } from './editor-request.svelte.js';
66
+ export { default as SvBaseViewBar } from './SvBaseViewBar.svelte';
67
+ export { default as SvBaseForm } from './SvBaseForm.svelte';
68
+ export { default as SvBase } from './SvBase.svelte';
69
+ export { blankTable, newRecordId } from './base-tables.js';
70
+ export { baseSource, createBaseSync, } from './base-sync.js';
71
+ export { baseFieldsFromSchema, baseTablesFromSchemas, baseSpecFromSchemas, baseTablesFromSpec } from './base-schema.js';
72
+ export { baseToSql, sqlName } from './base-sql.js';
73
+ export { applyBaseComments, withInverses } from './base-schema.js';
74
+ export { baseFromDatabase, describeDatabase, createBaseEndpoint, baseFromEndpoint, baseFromSupabase } from './base-connect.js';
75
+ export { registerAutomationAction, listAutomationActions, MAX_DEPTH as AUTOMATION_MAX_DEPTH, } from './automations.svelte.js';
76
+ export { createAutomationServer } from './automation-server.js';
77
+ export { setRecordComments } from './activity.svelte.js';
78
+ export { baseCondition, baseFilterOf } from './base-alerts.js';
79
+ export { compileFieldFilter, describeFieldFilter, filterKindOf, operatorsFor, ME, } from './filter-model.js';
80
+ export { BUILTIN_FIELD_TYPES } from './builtin-types.js';
81
+ export { setAttachmentUploader, attachmentsFromText, attachmentsText, toAttachments, } from './attachments.js';
82
+ const MENU_ICON = {
83
+ edit: 'M4 20h4L19 9l-4-4L4 16zM14 6l4 4',
84
+ left: 'M10 6l-6 6 6 6M4 12h11M20 5v14',
85
+ right: 'M14 6l6 6-6 6M20 12H9M4 5v14',
86
+ duplicate: 'M8 8h12v12H8zM16 8V4H4v12h4',
87
+ hide: 'M3 3l18 18M10.6 10.6a2 2 0 0 0 2.8 2.8M9.9 5.1A9.8 9.8 0 0 1 12 5c5 0 9 5 10 7a13.4 13.4 0 0 1-3 3.9M6.6 6.6A13.5 13.5 0 0 0 2 12c1 2 5 7 10 7a9.6 9.6 0 0 0 4.4-1.1',
88
+ remove: 'M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13',
89
+ color: 'M12 3l6 7a6.5 6.5 0 1 1-12 0l6-7z',
90
+ filter: 'M4 5h16l-6 7v6l-4 2v-8z',
91
+ group: 'M4 5h16M4 10h10M4 15h16M4 20h10',
92
+ info: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 11v5M12 8h.01',
93
+ };
94
+ function fieldMenuItems(ctrl, columnId) {
95
+ // A single select can color the rows (their header) by its option colors.
96
+ const def = (ctrl?.internalColumns ?? []).find((c) => String(c.id ?? c.field) === columnId);
97
+ const colorItem = def?.fieldType === 'singleSelect' && def.field
98
+ ? rowColorOf(ctrl)?.field === def.field
99
+ ? { key: 'field-rowcolor', label: 'Stop coloring rows', icon: MENU_ICON.color, run: () => chooseRowColor(ctrl, null) }
100
+ : {
101
+ key: 'field-rowcolor',
102
+ label: 'Color rows by this field',
103
+ icon: MENU_ICON.color,
104
+ run: () => chooseRowColor(ctrl, { field: String(def.field), ...(ctrl.props.rowColor?.target ? { target: ctrl.props.rowColor.target } : {}) }),
105
+ }
106
+ : null;
107
+ // The primary field is always first and always there.
108
+ const primary = primaryIdOf(ctrl) === columnId;
109
+ // With a view bar over the grid: filter or group by this field, in the bar's panel.
110
+ const barItems = def && hasBar(ctrl)
111
+ ? [
112
+ ...(newCondition(columnId, def)
113
+ ? [{ key: 'field-filter', label: 'Filter by this field', icon: MENU_ICON.filter, separatorBefore: true, run: () => filterByField(ctrl, columnId, def) }]
114
+ : []),
115
+ ...(ctrl.groupingControlsEnabled
116
+ ? []
117
+ : [{ key: 'field-group', label: 'Group by this field', icon: MENU_ICON.group, run: () => groupByField(ctrl, columnId) }]),
118
+ ]
119
+ : [];
120
+ return [
121
+ ...(colorItem ? [colorItem] : []),
122
+ { key: 'field-edit', label: 'Edit field', icon: MENU_ICON.edit, run: () => openFieldEditor({ ctrl, mode: 'edit', columnId }) },
123
+ ...(primary ? [] : [{ key: 'field-left', label: 'Insert left', icon: MENU_ICON.left, run: () => openFieldEditor({ ctrl, mode: 'add', index: indexBeside(ctrl, columnId, 'left') }) }]),
124
+ { key: 'field-right', label: 'Insert right', icon: MENU_ICON.right, run: () => openFieldEditor({ ctrl, mode: 'add', index: indexBeside(ctrl, columnId, 'right') }) },
125
+ { key: 'field-duplicate', label: 'Duplicate field', icon: MENU_ICON.duplicate, run: () => duplicateField(ctrl, columnId) },
126
+ ...barItems,
127
+ ...(primary
128
+ ? []
129
+ : [
130
+ { key: 'field-hide', label: 'Hide field', icon: MENU_ICON.hide, run: () => hideField(ctrl, columnId) },
131
+ {
132
+ key: 'field-delete', label: 'Delete field', icon: MENU_ICON.remove, separatorBefore: true,
133
+ // A link with a field back in another table, or with lookups that read it: they all go, so ask first.
134
+ // So does a field that formulas read: they would turn to #ERROR without a word.
135
+ run: () => (def?.fieldType === 'link' && ((def.fieldProps?.inverse && getLinkedTable(def.fieldProps.table)) || linkReaders({ columns: ctrl.internalColumns }, def).length)) ||
136
+ (def && formulaReaders(ctrl.internalColumns ?? [], def).length)
137
+ ? askDeleteField({ ctrl, columnId })
138
+ : removeField(ctrl, columnId),
139
+ },
140
+ ]),
141
+ ];
142
+ }
143
+ /** "Filter by this field": a new condition on it, in the bar's open Filter panel. */
144
+ function filterByField(ctrl, columnId, def) {
145
+ const cond = newCondition(columnId, def);
146
+ if (!cond)
147
+ return;
148
+ const now = ctrl.advancedFilter;
149
+ const base = now?.kind === 'fieldFilter' ? now : { kind: 'fieldFilter', conjunction: 'and', conditions: [] };
150
+ ctrl.buildApi().setAdvancedFilter({ ...base, conditions: [...base.conditions, cond] });
151
+ openBarPanel(ctrl, 'filter');
152
+ }
153
+ /** "Group by this field": one more group level, in the bar's open Group panel. */
154
+ function groupByField(ctrl, columnId) {
155
+ const groups = groupRules(ctrl);
156
+ if (!groups.some((g) => g.id === columnId))
157
+ writeSortState(ctrl.buildApi(), [...groups, { id: columnId, desc: false }], userSorts(ctrl));
158
+ openBarPanel(ctrl, 'group');
159
+ }
160
+ /** Last modified time / by after an edit. With nobody signed in, "by" keeps the last author. */
161
+ function stampModified(ctrl, row, columnId) {
162
+ if (!row)
163
+ return;
164
+ const author = getCurrentUser()?.id;
165
+ for (const c of (ctrl?.internalColumns ?? [])) {
166
+ if (c.fieldType !== 'lastModifiedTime' && c.fieldType !== 'lastModifiedBy')
167
+ continue;
168
+ const id = String(c.id ?? c.field);
169
+ if (id === columnId || !c.field)
170
+ continue;
171
+ const p = fieldPropsOf(originalColumn(c));
172
+ if (p.watch === 'specific' && !(p.watchFields ?? []).includes(columnId))
173
+ continue;
174
+ if (c.fieldType === 'lastModifiedTime')
175
+ row[c.field] = new Date().toISOString();
176
+ else if (author !== undefined)
177
+ row[c.field] = author;
178
+ }
179
+ }
180
+ /** The record's activity: what a cell now holds after an edit. */
181
+ function noteEdit(ctrl, row, columnId) {
182
+ if (!row)
183
+ return;
184
+ const def = (ctrl?.allColumns ?? []).find((c) => c.id === columnId)?.columnDef;
185
+ if (!def?.field)
186
+ return;
187
+ const rowId = ctrl.buildApi?.().getRowId?.(row);
188
+ if (rowId != null)
189
+ noteChange(ctrl, String(rowId), columnId, row[def.field]);
190
+ }
191
+ /** Space on a cell (not a checkbox, not while editing) expands its row, with `expandRows`. */
192
+ function spaceOpensRecord(e, cmd) {
193
+ if (e.key !== ' ' || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || cmd.editing || !cmd.activeCell)
194
+ return false;
195
+ const ctrl = gridOf(cmd.api);
196
+ if (!ctrl?.props?.expandRows)
197
+ return false;
198
+ const col = (ctrl.allColumns ?? []).find((c) => c.id === cmd.activeCell.columnId);
199
+ if (col?.columnDef.editorType === 'checkbox')
200
+ return false;
201
+ const row = (ctrl.allRows ?? [])[cmd.activeCell.rowIndex];
202
+ if (!row)
203
+ return false;
204
+ e.preventDefault();
205
+ openRecord({ ctrl, rowId: row.id });
206
+ return true;
207
+ }
208
+ let enabled = false;
209
+ /**
210
+ * Turn field types on for every `<SvGrid>` on the page: registers the
211
+ * built-in types and plugs the cell drawing into the grid. Safe to call more
212
+ * than once. Part of SvGrid Base, a Suite product: without a Suite key the
213
+ * grid shows the Enterprise watermark and the feature still runs.
214
+ */
215
+ export function enableFields() {
216
+ if (enabled)
217
+ return;
218
+ enabled = true;
219
+ for (const type of BUILTIN_FIELD_TYPES) {
220
+ // A type the app registered first (same id) wins over the built-in.
221
+ if (!getFieldType(type.id))
222
+ registerFieldType(type);
223
+ }
224
+ registerGridExtension({
225
+ column: (def, columns) => fillFieldColumn(def, columns),
226
+ cell: FieldCell,
227
+ hasCell: (def) => typeDrawsCell(def ?? {}),
228
+ // Last modified time / by: stamp the row when one of the fields they watch changes.
229
+ cellEdited: (ctrl, info) => {
230
+ stampModified(ctrl, info.row, info.columnId);
231
+ noteEdit(ctrl, info.row, info.columnId);
232
+ },
233
+ // The `rowAdder` prop's line; it draws nothing when the prop is off. Under
234
+ // the summary row when there is one, after the last record otherwise.
235
+ bodyEnd: RowAdder,
236
+ footEnd: FootRowAdder,
237
+ // The `rowColor` prop: a color per rendered row, its label, and where it shows.
238
+ rowMarkColor: (ctrl, row) => rowMarkColor(ctrl, row),
239
+ rowMarkLabel: (ctrl, row) => rowMarkLabel(ctrl, row),
240
+ // The type's icon before the header label, as the field editor shows it.
241
+ headerIcon: (def) => (def?.fieldType ? getFieldType(def.fieldType)?.icon : null),
242
+ // A field's description, on hover over an info mark after its name.
243
+ headerNote: (def) => (def?.fieldType && typeof def.description === 'string' && def.description ? { text: def.description, icon: MENU_ICON.info } : null),
244
+ // The column menu sorts "1 → 9" or "A → Z" as the field reads.
245
+ sortLabels: (def) => (def?.fieldType ? sortLabels(def) : null),
246
+ // The primary field stays pinned first.
247
+ lockColumn: (ctrl, columnId) => primaryIdOf(ctrl) === columnId,
248
+ // `expandRows`: the row number turns into a checkbox and an expand button.
249
+ rowHeader: (ctrl) => (ctrl?.props?.expandRows ? RowHeader : null),
250
+ // A group's header: the value as its cell draws it, the count, the group's summaries, "+".
251
+ groupRow: (ctrl) => (fieldColumns(ctrl).some((c) => c.fieldType) ? GroupHeader : null),
252
+ // The footer summary row: each field picks its own summary.
253
+ summaryCell: () => SummaryCell,
254
+ rootClass: (ctrl) => {
255
+ // Lets the view bar, given an api, find its grid.
256
+ rememberGrid(ctrl);
257
+ // The view bar's filter shows on its Filter button, not as the grid's chip.
258
+ const ownFilter = ctrl?.advancedFilter?.kind === 'fieldFilter' ? 'sv-fields-own-filter' : '';
259
+ return [rowColorRootClass(ctrl), tallRowClass(ctrl), ownFilter].filter(Boolean).join(' ') || null;
260
+ },
261
+ // The "+" column (`columnAdder`) and the host of the field editor.
262
+ headerEnd: FieldsHeaderEnd,
263
+ // `fieldMenu`: field actions in each column's menu.
264
+ columnMenuItems: (ctrl, columnId) => (ctrl?.props?.fieldMenu ? fieldMenuItems(ctrl, columnId) : []),
265
+ });
266
+ // The attachment field edits in a dialog; the editor needs the column's
267
+ // fieldProps (accept, maxFiles, upload) and the row, so map them through.
268
+ // The rating editor follows the field's maximum, icon and color; the link
269
+ // editor picks rows of the linked table. `typeToEdit` is which keys typed
270
+ // on a selected cell open the editor (`typed` tells it the value is that
271
+ // text): a digit sets a rating, a number starts the number box, any text
272
+ // searches a select's options.
273
+ const editorProps = (ctx) => ({ value: ctx.value, typed: ctx.typed, columnDef: ctx.columnDef, columnId: ctx.columnId, row: ctx.row, onCommit: ctx.onCommit, onCancel: ctx.onCancel });
274
+ registerCellEditor('fieldRating', { component: RatingEditor, props: editorProps, typeToEdit: (c) => /^[0-9]$/.test(c) });
275
+ registerCellEditor('fieldLink', { component: LinkPickerEditor, props: editorProps, typeToEdit: false });
276
+ registerCellEditor('fieldNumber', {
277
+ component: NumberEditor,
278
+ props: (ctx) => ({ ...editorProps(ctx), onCommitAndMove: ctx.onCommitAndMove, onCommitAndMoveDown: ctx.onCommitAndMoveDown }),
279
+ typeToEdit: (c) => /[0-9.,:+\-(]/.test(c),
280
+ });
281
+ registerCellEditor('fieldLongText', {
282
+ component: LongTextEditor,
283
+ props: (ctx) => ({ ...editorProps(ctx), onCommitAndMove: ctx.onCommitAndMove }),
284
+ });
285
+ registerCellEditor('fieldSelect', {
286
+ component: SelectEditor,
287
+ props: (ctx) => ({ ...editorProps(ctx), grid: ctx.grid, onChange: ctx.onChange, onCommitAndMove: ctx.onCommitAndMove }),
288
+ });
289
+ registerCellEditor('attachment', {
290
+ component: AttachmentEditor,
291
+ props: (ctx) => ({
292
+ value: ctx.value,
293
+ columnDef: ctx.columnDef,
294
+ columnId: ctx.columnId,
295
+ row: ctx.row,
296
+ onCommit: ctx.onCommit,
297
+ onCancel: ctx.onCancel,
298
+ }),
299
+ typeToEdit: false,
300
+ });
301
+ // The view bar's filter: conditions per field type, as an advanced filter kind.
302
+ registerFilterKind('fieldFilter', (expr, ctx) => compileFieldFilter(expr, ctx.getValue, linkNames));
303
+ // A person condition's "Me" is whoever is signed in when the filter runs.
304
+ resolveMeWith(() => getCurrentUser()?.id);
305
+ // The same filter as an alert rule's condition (`baseCondition`).
306
+ registerBaseCondition();
307
+ enableAdvancedFilter();
308
+ registerGridShortcuts(spaceOpensRecord, { id: 'svgrid-fields-space' });
309
+ // SvGrid Base is a Suite product: without a key, or with a Grid-edition key,
310
+ // the grid shows the watermark and a notice. It never blocks.
311
+ nudgeEnterpriseFor('base', 'SvGrid Base');
312
+ }
@@ -0,0 +1,8 @@
1
+ import { type LinkedField, type LinkedTable } from './sources.svelte.js';
2
+ export declare function linkedFields(table: LinkedTable, row?: Record<string, unknown>): LinkedField[];
3
+ /** A field's value in a linked record: computed for a formula, lookup, rollup or count. */
4
+ export declare function linkedValue(f: LinkedField, row: Record<string, unknown>): unknown;
5
+ /** A record of the table by id. */
6
+ export declare function linkedRow(table: LinkedTable, rowId: string): Record<string, unknown> | undefined;
7
+ /** A value as the field shows it, as text. */
8
+ export declare function linkedText(f: LinkedField, value: unknown, row: unknown): string;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A linked table's fields as columns a record view can draw: each declared
3
+ * field filled in by its type, or every key of the record when the table
4
+ * declares none.
5
+ */
6
+ import { fillFieldColumn } from './registry.svelte.js';
7
+ import { idOf, rowsOf } from './sources.svelte.js';
8
+ export function linkedFields(table, row) {
9
+ const declared = table.fields?.length
10
+ ? table.fields
11
+ : Object.keys(row ?? {})
12
+ .filter((k) => k !== 'id')
13
+ .map((k) => ({ field: k, header: k }));
14
+ const withPrimary = declared.some((f) => f.field === table.primaryField) ? declared : [{ field: table.primaryField, header: 'Name' }, ...declared];
15
+ // A rollup or count reads a link field of the same table: every field is its context.
16
+ const all = withPrimary.map((f) => ({ id: f.field, ...f }));
17
+ return withPrimary.map((f) => {
18
+ const typed = 'fieldType' in f && f.fieldType;
19
+ const def = typed ? fillFieldColumn({ field: f.field, header: f.header, fieldType: f.fieldType, fieldProps: f.fieldProps }, all) : { field: f.field, header: f.header };
20
+ return { id: f.field, header: f.header, def };
21
+ });
22
+ }
23
+ /** A field's value in a linked record: computed for a formula, lookup, rollup or count. */
24
+ export function linkedValue(f, row) {
25
+ const fn = f.def.fieldFn;
26
+ return fn ? fn(row) : row[f.def.field];
27
+ }
28
+ /** A record of the table by id. */
29
+ export function linkedRow(table, rowId) {
30
+ return rowsOf(table).find((r) => idOf(table, r) === rowId);
31
+ }
32
+ /** A value as the field shows it, as text. */
33
+ export function linkedText(f, value, row) {
34
+ const fmt = f.def.formatter;
35
+ if (fmt)
36
+ return String(fmt({ value, row, column: { id: f.id, columnDef: f.def } }) ?? '');
37
+ if (Array.isArray(value))
38
+ return value.join(', ');
39
+ return value == null ? '' : String(value);
40
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Hrefs for URL, email and phone cells. Cell values are data, so an href is
3
+ * built only for the schemes these fields mean: a value like
4
+ * `javascript:alert(1)` in a URL column must never become a link.
5
+ */
6
+ export type LinkKind = 'url' | 'email' | 'phone';
7
+ /** The href for a cell value, or null when the value is not a link of that kind. */
8
+ export declare function linkHref(kind: LinkKind, value: unknown): string | null;
9
+ /** What the cell shows: the value without the scheme noise. */
10
+ export declare function linkText(kind: LinkKind, value: unknown): string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Hrefs for URL, email and phone cells. Cell values are data, so an href is
3
+ * built only for the schemes these fields mean: a value like
4
+ * `javascript:alert(1)` in a URL column must never become a link.
5
+ */
6
+ const EMAIL = /^[^\s@<>()[\]\\,;:"]+@[^\s@<>()[\]\\,;:"]+\.[^\s@<>()[\]\\,;:"]+$/;
7
+ /** The href for a cell value, or null when the value is not a link of that kind. */
8
+ export function linkHref(kind, value) {
9
+ if (value == null)
10
+ return null;
11
+ const text = String(value).trim();
12
+ if (!text)
13
+ return null;
14
+ if (kind === 'email') {
15
+ const address = text.replace(/^mailto:/i, '');
16
+ return EMAIL.test(address) ? `mailto:${address}` : null;
17
+ }
18
+ if (kind === 'phone') {
19
+ // Digits, an optional leading +, and the separators people type.
20
+ const digits = text.replace(/^tel:/i, '').replace(/[\s().-]/g, '');
21
+ return /^\+?\d{3,20}$/.test(digits) ? `tel:${digits}` : null;
22
+ }
23
+ // url: only http(s). A bare host ("svgrid.com/docs") gets https://.
24
+ const withScheme = /^[a-z][a-z0-9+.-]*:/i.test(text) ? text : `https://${text}`;
25
+ let parsed;
26
+ try {
27
+ parsed = new URL(withScheme);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
33
+ return null;
34
+ if (!parsed.hostname.includes('.') && parsed.hostname !== 'localhost')
35
+ return null;
36
+ return parsed.href;
37
+ }
38
+ /** What the cell shows: the value without the scheme noise. */
39
+ export function linkText(kind, value) {
40
+ const text = value == null ? '' : String(value).trim();
41
+ if (kind === 'url')
42
+ return text.replace(/^https?:\/\//i, '').replace(/\/$/, '');
43
+ if (kind === 'email')
44
+ return text.replace(/^mailto:/i, '');
45
+ return text.replace(/^tel:/i, '');
46
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A list of names as one line of text, the way a copy writes link and user
3
+ * cells: "Northwind, Acme". A name with a comma or a double quote is quoted
4
+ * CSV style ("Acme, Inc.", inner quotes doubled), so a paste splits it back
5
+ * into the same names.
6
+ */
7
+ /** One name, quoted when it holds a comma or a double quote. */
8
+ export declare const quoteName: (name: string) => string;
9
+ /** Names joined with ", ", each quoted when it has to be. */
10
+ export declare const joinNames: (names: ReadonlyArray<string>) => string;
11
+ /**
12
+ * The names in a comma-separated line, trimmed, empty parts left out. A part
13
+ * that starts with a double quote runs to the closing quote, commas included,
14
+ * and `""` inside it is one quote. An unclosed quote runs to the end.
15
+ */
16
+ export declare function splitNames(text: string): string[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A list of names as one line of text, the way a copy writes link and user
3
+ * cells: "Northwind, Acme". A name with a comma or a double quote is quoted
4
+ * CSV style ("Acme, Inc.", inner quotes doubled), so a paste splits it back
5
+ * into the same names.
6
+ */
7
+ /** One name, quoted when it holds a comma or a double quote. */
8
+ export const quoteName = (name) => (/[",]/.test(name) ? `"${name.replace(/"/g, '""')}"` : name);
9
+ /** Names joined with ", ", each quoted when it has to be. */
10
+ export const joinNames = (names) => names.map(quoteName).join(', ');
11
+ /**
12
+ * The names in a comma-separated line, trimmed, empty parts left out. A part
13
+ * that starts with a double quote runs to the closing quote, commas included,
14
+ * and `""` inside it is one quote. An unclosed quote runs to the end.
15
+ */
16
+ export function splitNames(text) {
17
+ const out = [];
18
+ const n = text.length;
19
+ let i = 0;
20
+ while (i <= n) {
21
+ while (i < n && /\s/.test(text[i]))
22
+ i += 1;
23
+ let part = '';
24
+ if (text[i] === '"') {
25
+ i += 1;
26
+ while (i < n) {
27
+ if (text[i] === '"') {
28
+ if (text[i + 1] === '"') {
29
+ part += '"';
30
+ i += 2;
31
+ continue;
32
+ }
33
+ i += 1;
34
+ break;
35
+ }
36
+ part += text[i];
37
+ i += 1;
38
+ }
39
+ }
40
+ // Up to the next comma: a plain part, or what follows a closing quote.
41
+ const comma = text.indexOf(',', i);
42
+ const end = comma < 0 ? n : comma;
43
+ part = (part + text.slice(i, end)).trim();
44
+ if (part)
45
+ out.push(part);
46
+ i = end + 1;
47
+ }
48
+ return out;
49
+ }