@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,310 @@
1
+ <script lang="ts">
2
+ /**
3
+ * A row opened as a record (`expandRows`, Space on a cell, a gallery card,
4
+ * a linked record's chip): every field of the row, its name with the
5
+ * type's icon over its value, the value drawn as the cell draws it. A
6
+ * click on an editable value opens the same editor the cell uses; a value
7
+ * is written through the grid's own edit path, so undo, validation,
8
+ * onCellValueChange and "last modified" fields behave as in the cell.
9
+ * Fields hidden in the grid are listed under the others, folded. Beside
10
+ * the fields, the record's activity: its changes and its comments.
11
+ * Previous and next step through the rows in the grid's order.
12
+ */
13
+ import { onMount, untrack } from 'svelte'
14
+ import { getRecordRequest, type RecordRequest } from './editor-request.svelte'
15
+ import { SvModal } from '@svgrid/grid'
16
+ import FieldInput from './FieldInput.svelte'
17
+ import { getFieldType } from './registry.svelte'
18
+ import { cellText, fieldColumns, primaryIdOf, type FieldColumn } from './grid-access'
19
+ import { addComment, changesOf, commentsOf, timeAgo } from './activity.svelte'
20
+ import { findFieldUser } from './sources.svelte'
21
+
22
+ let {
23
+ ctrl,
24
+ rowId,
25
+ editPrimary = false,
26
+ focus,
27
+ onClose,
28
+ }: {
29
+ ctrl: any
30
+ rowId: string
31
+ editPrimary?: boolean
32
+ /** 'comment': open with the comment box focused (also read from the open request). */
33
+ focus?: 'comment'
34
+ onClose: () => void
35
+ } = $props()
36
+
37
+ type Col = { id: string; columnDef: Record<string, any> }
38
+ type Row = { id: string; original: unknown; getCellValueByColumnId: (id: string) => unknown }
39
+
40
+ // svelte-ignore state_referenced_locally
41
+ let current = $state(rowId)
42
+ let open = $state(true)
43
+ let editing = $state<string | null>(null)
44
+ let showHidden = $state(false)
45
+ let comment = $state('')
46
+
47
+ const rows = $derived((ctrl.allRows ?? []) as Row[])
48
+ const index = $derived(rows.findIndex((r) => r.id === current))
49
+ const row = $derived(index >= 0 ? rows[index]! : null)
50
+ // Where the record was last seen. An edit can take it out of the view (a
51
+ // filter it no longer matches); Previous and Next then go on from there:
52
+ // the row that followed it now sits at that position.
53
+ let lastIndex = 0
54
+ const at = $derived.by(() => {
55
+ if (index >= 0) lastIndex = index
56
+ return lastIndex
57
+ })
58
+ // Both read `at` on every change of `index`, so it never misses a position.
59
+ const canPrev = $derived(at > 0 && rows.length > 0)
60
+ const canNext = $derived(at < rows.length - (index >= 0 ? 1 : 0))
61
+ const columns = $derived(
62
+ ((ctrl.allColumns ?? []) as Col[]).filter((c) => !c.id.startsWith('__') && (c.columnDef.field != null || c.columnDef.fieldFn != null)),
63
+ )
64
+ // Fields the grid hides: shown here, read-only (an edit goes through the grid's cells).
65
+ const hidden = $derived.by<Col[]>(() => {
66
+ const shownIds = new Set(columns.map((c) => c.id))
67
+ return fieldColumns(ctrl)
68
+ .filter((c) => !shownIds.has(c.id))
69
+ .map((c) => ({ id: c.id, columnDef: c.def }))
70
+ })
71
+
72
+ // Through the grid's edit overlay, as the cells read it: an edit writes
73
+ // there (and into the row object), so a value changed here or in the grid
74
+ // shows at once.
75
+ function valueOf(c: Col): unknown {
76
+ if (!row) return undefined
77
+ const base = c.columnDef.fieldFn ? c.columnDef.fieldFn(row.original) : (row.original as Record<string, unknown>)?.[c.columnDef.field]
78
+ return ctrl.getCellDisplayValue(row.id, c.id, columns.includes(c) ? row.getCellValueByColumnId(c.id) : base)
79
+ }
80
+ const asField = (c: Col): FieldColumn => ({ id: c.id, field: String(c.columnDef.field ?? c.id), header: label(c), fieldType: c.columnDef.fieldType, def: c.columnDef, visible: true })
81
+ // The record's name: the primary field, else its first text field, else the first field.
82
+ const primaryId = $derived(primaryIdOf(ctrl))
83
+ const primary = $derived(columns.find((c) => c.id === primaryId) ?? columns.find((c) => c.columnDef.fieldType === 'text' || (!c.columnDef.fieldType && c.columnDef.editorType == null)) ?? columns[0])
84
+ const title = $derived(row && primary ? cellText(asField(primary), valueOf(primary), row) || 'Untitled' : 'Record')
85
+ function label(c: Col): string {
86
+ return typeof c.columnDef.header === 'string' ? c.columnDef.header : c.id
87
+ }
88
+ const iconOf = (c: Col) => (c.columnDef.fieldType ? getFieldType(c.columnDef.fieldType)?.icon : undefined)
89
+ function editable(c: Col): boolean {
90
+ if (!row || !ctrl.editingEnabled) return false
91
+ const ci = (ctrl.allColumns as Col[]).findIndex((x) => x.id === c.id)
92
+ if (ci < 0) return false
93
+ return ctrl.isCellEditableAt ? !!ctrl.isCellEditableAt(index, ci) : c.columnDef.editable !== false
94
+ }
95
+
96
+ // A record just made opens with its name being typed.
97
+ onMount(() => {
98
+ if (editPrimary && primary && editable(primary)) editing = primary.id
99
+ })
100
+
101
+ // "Comment" in a record or card menu opens the record with `focus: 'comment'`:
102
+ // the comment box takes focus, on open or when the request comes later.
103
+ let commentBox = $state<HTMLTextAreaElement | null>(null)
104
+ const commentFocusFor = (r: RecordRequest | null) => r?.focus === 'comment' && r.ctrl === ctrl
105
+ const initialFocus = () => (focus === 'comment' || commentFocusFor(getRecordRequest()) ? commentBox : null)
106
+ $effect(() => {
107
+ const r = getRecordRequest()
108
+ if (!commentFocusFor(r)) return
109
+ untrack(() => {
110
+ if (r!.rowId !== current) current = r!.rowId
111
+ queueMicrotask(() => commentBox?.focus())
112
+ })
113
+ })
114
+
115
+ /** Write a value the way an edit in the cell does. */
116
+ function commit(c: Col, v: unknown) {
117
+ editing = null
118
+ if (!row || v === undefined) return
119
+ const at = (ctrl.allRows as Row[]).findIndex((r) => r.id === current)
120
+ if (at < 0 || !ctrl.startEditing(at, c.id)) return
121
+ ctrl.updateEditingCellValue(v)
122
+ ctrl.saveEditingCell()
123
+ }
124
+ /** Tab from a field: the next (or previous) editable one. */
125
+ function editBeside(c: Col, dir: 1 | -1) {
126
+ const at = columns.findIndex((x) => x.id === c.id)
127
+ for (let i = at + dir; i >= 0 && i < columns.length; i += dir) {
128
+ const next = columns[i]!
129
+ if (editable(next) && next.columnDef.editorType !== 'checkbox') {
130
+ editing = next.id
131
+ return
132
+ }
133
+ }
134
+ }
135
+ function go(by: 1 | -1) {
136
+ const next = index >= 0 ? rows[index + by] : rows[Math.min(at, rows.length) - (by < 0 ? 1 : 0)]
137
+ if (!next) return
138
+ editing = null
139
+ current = next.id
140
+ }
141
+
142
+ // ---- Activity ---------------------------------------------------------------
143
+ const allCols = $derived([...columns, ...hidden])
144
+ const feed = $derived.by(() => {
145
+ if (!row) return []
146
+ const edits = changesOf(ctrl, row.id).map((ch) => {
147
+ const c = allCols.find((x) => x.id === ch.columnId)
148
+ return { key: `e${ch.at}${ch.columnId}`, at: ch.at, by: ch.by, automation: ch.automation, kind: 'edit' as const, field: c ? label(c) : ch.columnId, text: c ? cellText(asField(c), ch.value, row) : String(ch.value ?? '') }
149
+ })
150
+ const notes = commentsOf(ctrl, row.id).map((n) => ({ key: n.id, at: n.at, by: n.by, automation: undefined, kind: 'comment' as const, field: '', text: n.text }))
151
+ return [...edits, ...notes].sort((a, b) => a.at - b.at)
152
+ })
153
+ const who = (item: { by?: string; automation?: string }) =>
154
+ item.automation ? `Automation: ${item.automation}` : item.by ? (findFieldUser(item.by)?.name ?? 'Someone') : 'You'
155
+ function send() {
156
+ if (!row || !comment.trim()) return
157
+ addComment(ctrl, row.id, comment)
158
+ comment = ''
159
+ }
160
+ </script>
161
+
162
+ {#snippet field(c: Col, canEdit: boolean)}
163
+ <div class="sv-frv-field">
164
+ <div class="sv-frv-label">
165
+ {#if iconOf(c)}<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d={iconOf(c)} fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" /></svg>{/if}
166
+ <span>{label(c)}</span>
167
+ {#if !canEdit}<span class="sv-frv-ro">{columns.includes(c) ? 'read-only' : 'hidden in the grid'}</span>{/if}
168
+ </div>
169
+ <FieldInput
170
+ {ctrl}
171
+ column={c}
172
+ value={valueOf(c)}
173
+ row={row ? { id: row.id, original: row.original } : null}
174
+ rowId={row?.id ?? ''}
175
+ label={label(c)}
176
+ editable={canEdit}
177
+ editing={editing === c.id}
178
+ onStart={() => (editing = c.id)}
179
+ onStop={() => (editing = null)}
180
+ onCommit={(v) => commit(c, v)}
181
+ onTab={(dir) => editBeside(c, dir)}
182
+ />
183
+ </div>
184
+ {/snippet}
185
+
186
+ <SvModal bind:open {title} width={940} {onClose} {initialFocus}>
187
+ {#if !row}
188
+ <p class="sv-frv-gone">This row is not in the grid's view any more.</p>
189
+ {:else}
190
+ <div class="sv-frv-layout">
191
+ <div class="sv-frv-fields">
192
+ {#each columns as c (c.id)}
193
+ {@render field(c, editable(c))}
194
+ {/each}
195
+ {#if hidden.length}
196
+ <button type="button" class="sv-frv-hidden-toggle" aria-expanded={showHidden} onclick={() => (showHidden = !showHidden)}>
197
+ {showHidden ? 'Hide' : 'Show'} {hidden.length} hidden field{hidden.length === 1 ? '' : 's'}
198
+ </button>
199
+ {#if showHidden}
200
+ {#each hidden as c (c.id)}
201
+ {@render field(c, false)}
202
+ {/each}
203
+ {/if}
204
+ {/if}
205
+ </div>
206
+ <aside class="sv-frv-activity" aria-label="Activity">
207
+ <h3 class="sv-frv-activity-title">Activity</h3>
208
+ <ol class="sv-frv-feed">
209
+ {#each feed as item (item.key)}
210
+ <li class="sv-frv-item" class:is-comment={item.kind === 'comment'}>
211
+ <div class="sv-frv-item-head"><strong>{who(item)}</strong>{item.kind === 'edit' ? ` changed ${item.field}` : ''}<span class="sv-frv-when">{timeAgo(item.at)}</span></div>
212
+ <div class="sv-frv-item-body">{item.text || (item.kind === 'edit' ? '(cleared)' : '')}</div>
213
+ </li>
214
+ {:else}
215
+ <li class="sv-frv-none">No changes or comments yet.</li>
216
+ {/each}
217
+ </ol>
218
+ <form
219
+ class="sv-frv-comment"
220
+ onsubmit={(e) => {
221
+ e.preventDefault()
222
+ send()
223
+ }}
224
+ >
225
+ <textarea
226
+ bind:this={commentBox}
227
+ rows="2"
228
+ placeholder="Leave a comment"
229
+ aria-label="Leave a comment"
230
+ bind:value={comment}
231
+ onkeydown={(e) => {
232
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
233
+ e.preventDefault()
234
+ send()
235
+ }
236
+ }}
237
+ ></textarea>
238
+ <button type="submit" class="sv-frv-btn" disabled={!comment.trim()}>Comment</button>
239
+ </form>
240
+ </aside>
241
+ </div>
242
+ {/if}
243
+ {#snippet footer()}
244
+ <div class="sv-frv-footer">
245
+ <button type="button" class="sv-frv-btn" disabled={!canPrev} onclick={() => go(-1)} aria-label="Previous record">
246
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 15l6-6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
247
+ </button>
248
+ <button type="button" class="sv-frv-btn" disabled={!canNext} onclick={() => go(1)} aria-label="Next record">
249
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
250
+ </button>
251
+ <span class="sv-frv-pos">{index >= 0 ? `${index + 1} of ${rows.length}` : `Not in view (${rows.length} records)`}</span>
252
+ <!-- The modal tells onClose only when it closes itself (X, Escape, backdrop). -->
253
+ <button
254
+ type="button"
255
+ class="sv-frv-btn is-primary"
256
+ onclick={() => {
257
+ open = false
258
+ onClose()
259
+ }}>Close</button
260
+ >
261
+ </div>
262
+ {/snippet}
263
+ </SvModal>
264
+
265
+ <style>
266
+ .sv-frv-layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 20px; align-items: start; }
267
+ .sv-frv-fields { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
268
+ .sv-frv-field { display: flex; flex-direction: column; gap: 6px; }
269
+ .sv-frv-label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--sg-muted, #64748b); }
270
+ .sv-frv-ro { font-weight: 400; font-size: 11px; opacity: 0.8; }
271
+ .sv-frv-hidden-toggle {
272
+ align-self: flex-start; border: 0; padding: 2px 0; background: none; font: inherit; font-size: 13px;
273
+ color: var(--sg-accent, #2563eb); cursor: pointer;
274
+ }
275
+ .sv-frv-activity {
276
+ position: sticky; top: 0; display: flex; flex-direction: column; gap: 8px; max-height: 70vh;
277
+ padding-inline-start: 16px; border-inline-start: 1px solid var(--sg-border, #e2e8f0);
278
+ }
279
+ .sv-frv-activity-title { margin: 0; font-size: 13px; font-weight: 600; }
280
+ .sv-frv-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; overflow: auto; min-height: 60px; }
281
+ .sv-frv-item { font-size: 12px; }
282
+ .sv-frv-item-head { color: var(--sg-muted, #64748b); }
283
+ .sv-frv-item-head strong { color: var(--sg-fg, #0f172a); font-weight: 600; }
284
+ .sv-frv-when { margin-inline-start: 6px; opacity: 0.8; }
285
+ .sv-frv-item-body { margin-top: 2px; white-space: pre-wrap; word-break: break-word; }
286
+ .sv-frv-item.is-comment .sv-frv-item-body {
287
+ padding: 6px 8px; border-radius: 8px; background: color-mix(in srgb, var(--sg-fg, #0f172a) 5%, transparent);
288
+ }
289
+ .sv-frv-none { color: var(--sg-muted, #64748b); font-size: 12px; }
290
+ .sv-frv-comment { display: grid; gap: 6px; }
291
+ .sv-frv-comment textarea {
292
+ width: 100%; box-sizing: border-box; padding: 6px 8px; font: inherit; font-size: 13px; resize: vertical;
293
+ color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0); border-radius: 6px;
294
+ }
295
+ .sv-frv-comment .sv-frv-btn { justify-self: end; }
296
+ .sv-frv-gone { color: var(--sg-muted, #64748b); }
297
+ .sv-frv-footer { display: flex; align-items: center; gap: 6px; width: 100%; }
298
+ .sv-frv-pos { flex: 1; font-size: 13px; color: var(--sg-muted, #64748b); }
299
+ .sv-frv-btn {
300
+ display: inline-flex; align-items: center; justify-content: center; height: 32px; min-width: 32px; padding: 0 12px;
301
+ font: inherit; font-size: 13px; cursor: pointer; border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
302
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
303
+ }
304
+ .sv-frv-btn:disabled { opacity: 0.4; cursor: default; }
305
+ .sv-frv-btn.is-primary { background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff); border-color: var(--sg-fg, #0f172a); }
306
+ @media (max-width: 760px) {
307
+ .sv-frv-layout { grid-template-columns: minmax(0, 1fr); }
308
+ .sv-frv-activity { position: static; padding-inline-start: 0; border-inline-start: 0; border-top: 1px solid var(--sg-border, #e2e8f0); padding-top: 12px; }
309
+ }
310
+ </style>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ ctrl: any;
3
+ rowId: string;
4
+ editPrimary?: boolean;
5
+ /** 'comment': open with the comment box focused (also read from the open request). */
6
+ focus?: 'comment';
7
+ onClose: () => void;
8
+ };
9
+ declare const RecordView: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type RecordView = ReturnType<typeof RecordView>;
11
+ export default RecordView;
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ /** Long-text cells with rich text on: the formatted lines, one after another, clipped to the row. */
3
+ import type { FieldCellProps } from './registry.svelte'
4
+ import { parseRichText } from './rich-text'
5
+
6
+ let { value }: FieldCellProps = $props()
7
+ const lines = $derived(parseRichText(value))
8
+ </script>
9
+
10
+ <span class="sv-frx">
11
+ {#each lines as line, i (i)}
12
+ {#if i > 0}<span class="sv-frx-sep" aria-hidden="true"> </span>{/if}
13
+ {#if line.bullet}<span class="sv-frx-bullet" aria-hidden="true">•</span>{/if}
14
+ {#each line.parts as p, j (j)}
15
+ {#if p.kind === 'b'}<strong>{p.text}</strong>
16
+ {:else if p.kind === 'i'}<em>{p.text}</em>
17
+ {:else if p.kind === 'code'}<code>{p.text}</code>
18
+ {:else if p.kind === 'a'}<a href={p.href} target="_blank" rel="noopener noreferrer" onpointerdown={(e) => e.stopPropagation()}>{p.text}</a>
19
+ {:else}{p.text}{/if}
20
+ {/each}
21
+ {/each}
22
+ </span>
23
+
24
+ <style>
25
+ .sv-frx { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
26
+ .sv-frx code { font-size: 0.9em; padding: 0 3px; border-radius: 3px; background: var(--sg-header-bg, #f8fafc); }
27
+ .sv-frx a { color: inherit; text-decoration: underline; text-decoration-color: var(--sg-muted, #94a3b8); }
28
+ .sv-frx-bullet { margin-inline-end: 4px; color: var(--sg-muted, #64748b); }
29
+ .sv-frx-sep { margin-inline: 4px; }
30
+ </style>
@@ -0,0 +1,5 @@
1
+ /** Long-text cells with rich text on: the formatted lines, one after another, clipped to the row. */
2
+ import type { FieldCellProps } from './registry.svelte.js';
3
+ declare const RichTextCell: import("svelte").Component<FieldCellProps, {}, "">;
4
+ type RichTextCell = ReturnType<typeof RichTextCell>;
5
+ export default RichTextCell;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The "+ Add row" line after the last body row (`rowAdder` prop). The grid
4
+ * renders it through registerGridExtension's `bodyEnd` slot, inside the
5
+ * body, so it scrolls with the rows and sits under the last one.
6
+ *
7
+ * A click appends a row (with `rowAdder.defaults()`), scrolls to it and
8
+ * starts editing its first editable cell, then fires `onRowAdd` so the app
9
+ * can persist the row.
10
+ */
11
+ import { tick } from 'svelte'
12
+ import { newRowValues } from './field-ops'
13
+
14
+ type Config = { label?: string; defaults?: () => Record<string, unknown> }
15
+ let { ctrl, foot = false }: { ctrl: any; foot?: boolean } = $props()
16
+
17
+ // With a summary row the line goes under it (`foot`), so the sums sit right after the records.
18
+ const config = $derived<Config | null>(
19
+ ctrl.props.rowAdder && foot === !!ctrl.rowSummariesEnabled ? (typeof ctrl.props.rowAdder === 'object' ? ctrl.props.rowAdder : {}) : null,
20
+ )
21
+ const label = $derived(config?.label ?? 'Add row')
22
+ const span = $derived((ctrl.allColumns?.length ?? 0) + 3)
23
+
24
+ async function add() {
25
+ const api = ctrl.buildApi?.()
26
+ if (!api || !config) return
27
+ // Field defaults, the next autonumber, the created time; the prop's own defaults win.
28
+ const row: Record<string, unknown> = newRowValues(ctrl, config.defaults?.() ?? {})
29
+ api.addRow(row, 'bottom')
30
+ await tick()
31
+ // Sorting or grouping can put the new row anywhere; find it by identity.
32
+ const index = (ctrl.allRows as Array<{ original: unknown }>).findIndex((r) => r.original === row)
33
+ if (index >= 0) {
34
+ api.scrollToRow(index)
35
+ await tick()
36
+ for (const column of ctrl.allColumns as Array<{ id: string }>) {
37
+ if (api.startEditing(index, column.id)) break
38
+ }
39
+ }
40
+ ctrl.props.onRowAdd?.({ row })
41
+ }
42
+ </script>
43
+
44
+ {#if config}
45
+ <tr class="sv-grid-row sv-field-row-adder" class:is-foot={foot}>
46
+ <!-- overflow and flex inline, over the grid's cell rules: a clipping cell is its own
47
+ scroll box, and the sticky button would not stay at the left edge. -->
48
+ <td class="sv-grid-cell sv-field-row-adder-cell" colspan={span} style="overflow: visible; flex: 1 1 auto;">
49
+ <button type="button" class="sv-field-row-adder-button" onclick={add}>
50
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
51
+ <path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
52
+ </svg>
53
+ {label}
54
+ </button>
55
+ </td>
56
+ </tr>
57
+ {/if}
58
+
59
+ <style>
60
+ .sv-field-row-adder-cell {
61
+ flex: 1 1 auto;
62
+ padding: 0;
63
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
64
+ }
65
+ .sv-field-row-adder:hover > .sv-field-row-adder-cell { background: var(--sg-row-hover-bg, #f8fafc); }
66
+ /* The column headers' ground, as the summary row has: the grid's chrome, not
67
+ a record. In the footer the summary row draws the line between them. */
68
+ .sv-field-row-adder,
69
+ .sv-field-row-adder > .sv-field-row-adder-cell { background: var(--sg-summary-bg, var(--sg-header-bg, #f5f7fb)); }
70
+ .sv-field-row-adder.is-foot > .sv-field-row-adder-cell { border-bottom: 0; }
71
+ .sv-field-row-adder-button {
72
+ position: sticky;
73
+ inset-inline-start: 0;
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: 6px;
77
+ height: 100%;
78
+ min-height: 34px;
79
+ padding: 0 14px;
80
+ border: 0;
81
+ background: transparent;
82
+ color: var(--sg-muted, #64748b);
83
+ font: inherit;
84
+ /* A secondary action: the size of the summary text, not of the records. */
85
+ font-size: 13px;
86
+ cursor: pointer;
87
+ }
88
+ .sv-field-row-adder-button:hover { color: var(--sg-fg, #0f172a); }
89
+ .sv-field-row-adder-button:focus-visible {
90
+ outline: 2px solid var(--sg-accent, #2563eb);
91
+ outline-offset: -2px;
92
+ }
93
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ ctrl: any;
3
+ foot?: boolean;
4
+ };
5
+ declare const RowAdder: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type RowAdder = ReturnType<typeof RowAdder>;
7
+ export default RowAdder;
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The row-number cell with `expandRows`: the number, which on hover (or
4
+ * while the row is selected) turns into a checkbox that selects the row,
5
+ * and an expand button that opens the row as a record. A record with
6
+ * comments shows how many; a click opens it at its discussion.
7
+ */
8
+ import { openRecord } from './editor-request.svelte'
9
+ import { commentsOf } from './activity.svelte'
10
+
11
+ let { ctrl, row, rowIndex }: { ctrl: any; row: { id: string; original: unknown }; rowIndex: number } = $props()
12
+
13
+ const selected = $derived(!!ctrl.isRowSelected?.(row.id, row.original))
14
+ const comments = $derived(commentsOf(ctrl, row.id).length)
15
+ </script>
16
+
17
+ <span class="sv-frh" class:is-selected={selected} class:has-comments={comments > 0}>
18
+ <span class="sv-frh-num" aria-hidden="true">{rowIndex + 1}</span>
19
+ <button
20
+ type="button"
21
+ class="sv-frh-check"
22
+ role="checkbox"
23
+ aria-checked={selected}
24
+ aria-label={`Select row ${rowIndex + 1}`}
25
+ onclick={(e) => {
26
+ e.stopPropagation()
27
+ ctrl.toggleRowSelectionById(row.id, row.original)
28
+ }}
29
+ >
30
+ {#if selected}
31
+ <svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d="M5 12l5 5 9-10" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" /></svg>
32
+ {/if}
33
+ </button>
34
+ {#if comments}
35
+ <button
36
+ type="button"
37
+ class="sv-frh-comments"
38
+ aria-label={`${comments} ${comments === 1 ? 'comment' : 'comments'} on row ${rowIndex + 1}`}
39
+ title={`${comments} ${comments === 1 ? 'comment' : 'comments'}`}
40
+ onclick={(e) => {
41
+ e.stopPropagation()
42
+ openRecord({ ctrl, rowId: row.id, focus: 'comment' })
43
+ }}
44
+ >
45
+ <svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true"><path d="M2 3h12v8H8l-3 2v-2H2z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" /></svg>
46
+ {comments}
47
+ </button>
48
+ {/if}
49
+ <button
50
+ type="button"
51
+ class="sv-frh-expand"
52
+ aria-label={`Expand row ${rowIndex + 1}`}
53
+ title="Expand row"
54
+ onclick={(e) => {
55
+ e.stopPropagation()
56
+ openRecord({ ctrl, rowId: row.id })
57
+ }}
58
+ >
59
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M14 4h6v6M20 4l-7 7M10 20H4v-6M4 20l7-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
60
+ </button>
61
+ </span>
62
+
63
+ <style>
64
+ .sv-frh { display: flex; align-items: center; gap: 4px; height: 100%; padding: 0 4px 0 8px; box-sizing: border-box; }
65
+ .sv-frh-num { flex: 1; min-width: 0; text-align: center; color: var(--sg-muted, #64748b); font-variant-numeric: tabular-nums; }
66
+ .sv-frh-check {
67
+ display: none; flex: none; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0;
68
+ border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 4px; background: var(--sg-bg, #fff); color: var(--sg-bg, #fff); cursor: pointer;
69
+ }
70
+ .sv-frh-check[aria-checked='true'] { border-color: var(--sg-accent, #2563eb); background: var(--sg-accent, #2563eb); }
71
+ .sv-frh-expand {
72
+ display: inline-flex; visibility: hidden; flex: none; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0;
73
+ margin-inline-start: auto; border: 0; border-radius: 4px; background: none; color: var(--sg-muted, #64748b); cursor: pointer;
74
+ }
75
+ .sv-frh-comments {
76
+ display: inline-flex; flex: none; align-items: center; gap: 2px; height: 18px; padding: 0 4px; border: 0; border-radius: 9px;
77
+ background: color-mix(in srgb, var(--sg-fg, #0f172a) 8%, transparent); color: var(--sg-fg, #0f172a);
78
+ font: inherit; font-size: 11px; font-variant-numeric: tabular-nums; cursor: pointer;
79
+ }
80
+ .sv-frh-comments:hover { background: color-mix(in srgb, var(--sg-fg, #0f172a) 16%, transparent); }
81
+ .sv-frh-expand:hover { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
82
+ /* Hover, keyboard focus or a selected row: the checkbox takes the number's place. */
83
+ /* A record with comments keeps its count in the expand button's place: the
84
+ gutter has no room for both, and the count opens the same record, at its
85
+ discussion. */
86
+ .sv-frh.has-comments .sv-frh-expand { display: none; }
87
+ /* The checkbox and the count side by side: into the cell's start padding, gaps tightened. */
88
+ :global(.sv-grid-row:hover) .sv-frh.has-comments,
89
+ .sv-frh.has-comments:focus-within,
90
+ .sv-frh.has-comments.is-selected { margin-inline-start: -10px; padding-inline-start: 4px; gap: 2px; }
91
+ :global(.sv-grid-row:hover) .sv-frh-num,
92
+ .sv-frh:focus-within .sv-frh-num,
93
+ .sv-frh.is-selected .sv-frh-num { display: none; }
94
+ :global(.sv-grid-row:hover) .sv-frh-check,
95
+ .sv-frh:focus-within .sv-frh-check,
96
+ .sv-frh.is-selected .sv-frh-check { display: inline-flex; }
97
+ :global(.sv-grid-row:hover) .sv-frh-expand,
98
+ .sv-frh:focus-within .sv-frh-expand { visibility: visible; }
99
+ </style>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ ctrl: any;
3
+ row: {
4
+ id: string;
5
+ original: unknown;
6
+ };
7
+ rowIndex: number;
8
+ };
9
+ declare const RowHeader: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type RowHeader = ReturnType<typeof RowHeader>;
11
+ export default RowHeader;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Single and multiple select cells: each choice as a pill filled with its
4
+ * option's color, the text dark or white for contrast. A value that is no
5
+ * longer one of the options keeps a plain gray pill, so it stays visible.
6
+ */
7
+ import type { FieldCellProps } from './registry.svelte'
8
+ import { textOn } from './palette'
9
+
10
+ type Option = { value: unknown; label?: string; color?: string }
11
+ let { value, props }: FieldCellProps<{ options?: Option[] }> = $props()
12
+
13
+ const byValue = $derived(new Map((props.options ?? []).map((o) => [String(o.value), o])))
14
+ const items = $derived(
15
+ (Array.isArray(value) ? value : value == null || value === '' ? [] : [value]).map((v) => {
16
+ const o = byValue.get(String(v))
17
+ return { key: String(v), label: o?.label ?? String(v), color: o?.color }
18
+ }),
19
+ )
20
+ </script>
21
+
22
+ <span class="sv-fsl">
23
+ {#each items as it (it.key)}
24
+ <span class="sv-fsl-pill" class:is-plain={!it.color} style:background={it.color} style:color={it.color ? textOn(it.color) : undefined}>{it.label}</span>
25
+ {/each}
26
+ </span>
27
+
28
+ <style>
29
+ .sv-fsl { display: flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; }
30
+ .sv-fsl-pill {
31
+ flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
32
+ padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 20px;
33
+ }
34
+ .sv-fsl-pill.is-plain { background: var(--sg-header-bg, #eef0f3); color: var(--sg-fg, #0f172a); border: 1px solid var(--sg-border, #e2e8f0); line-height: 18px; }
35
+ </style>
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Single and multiple select cells: each choice as a pill filled with its
3
+ * option's color, the text dark or white for contrast. A value that is no
4
+ * longer one of the options keeps a plain gray pill, so it stays visible.
5
+ */
6
+ import type { FieldCellProps } from './registry.svelte.js';
7
+ declare const SelectCell: import("svelte").Component<FieldCellProps<{
8
+ options?: {
9
+ value: unknown;
10
+ label?: string;
11
+ color?: string;
12
+ }[];
13
+ }>, {}, "">;
14
+ type SelectCell = ReturnType<typeof SelectCell>;
15
+ export default SelectCell;