@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,644 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvBaseViewBar - the bar over a fields table: the view switcher (saved
4
+ * views of any type), then Hide fields, Filter, Group, Sort, Color and Row
5
+ * height, the record count and search. Mounted above the grid with the
6
+ * grid's api; every setting goes through the grid's own state, so a view
7
+ * is what `api.getState()` says plus its row height and colors.
8
+ *
9
+ * ```svelte
10
+ * <SvBaseViewBar {api} bind:views bind:activeView />
11
+ * <SvGrid {data} {columns} onApiReady={(a) => (api = a)} />
12
+ * ```
13
+ */
14
+ import { panelFocus } from './viewbar/panel-focus'
15
+ import { tick, untrack } from 'svelte'
16
+ import { SvPopover, hasGanttView } from '@svgrid/grid'
17
+ import { conditionCount, isGroup, isComplete, type FieldFilter, type LinkNames } from './filter-model'
18
+ import { fieldColumns, gridOf, groupLeaves, leafFilter, primaryIdOf } from './grid-access'
19
+ import { getLinkedTable, linkedRows } from './sources.svelte'
20
+ import { PLAIN_STATE, applyColor, applyGanttSettings, applyView, boardConfig, captureView, galleryConfig, colorSettingOf, newViewId, viewTypeInfo, type ColorSetting, type FieldView, type FieldViewType } from './view-model'
21
+ import { groupRules, userSorts } from './viewbar/sort-state'
22
+ import { recordHeightOf, syncGroupRowHeight } from './row-height'
23
+ import HideFieldsPanel from './viewbar/HideFieldsPanel.svelte'
24
+ import FilterPanel from './viewbar/FilterPanel.svelte'
25
+ import SortPanel from './viewbar/SortPanel.svelte'
26
+ import ColorPanel from './viewbar/ColorPanel.svelte'
27
+ import ChartPanel from './viewbar/ChartPanel.svelte'
28
+ import GanttPanel from './viewbar/GanttPanel.svelte'
29
+ import type { ChartSetting } from './chart-view'
30
+ import ViewsMenu from './viewbar/ViewsMenu.svelte'
31
+ import RecordView from './RecordView.svelte'
32
+ import { closeRecord, getRecordRequest } from './editor-request.svelte'
33
+ import DeleteRecordConfirm from './DeleteRecordConfirm.svelte'
34
+ import { askDeleteRecord, getDeleteRecordRequest } from './record-delete.svelte'
35
+ import { clearBarRequest, getBarRequest, registerBar, type BarPanel } from './bar-request.svelte'
36
+
37
+ type Props = {
38
+ /** The grid's api (`onApiReady`). */
39
+ api: any
40
+ /** The saved views (bindable). Default: one grid view. */
41
+ views?: FieldView[]
42
+ /** The id of the view shown (bindable). */
43
+ activeView?: string
44
+ /** The views changed: one was created, renamed, deleted, or the shown one's settings changed. */
45
+ onViewsChange?: (views: FieldView[]) => void
46
+ /** The view types "Create" offers. Default: grid, Kanban, calendar, and Gantt when it is enabled. */
47
+ viewTypes?: ReadonlyArray<FieldViewType>
48
+ /** The row heights the row height menu offers. */
49
+ rowHeights?: ReadonlyArray<{ label: string; height: number }>
50
+ /** The Automations button: how many rules, and what its buttons open. */
51
+ automations?: { count: number; onManage: () => void } | null
52
+ }
53
+
54
+ let {
55
+ api,
56
+ views = $bindable<FieldView[]>([{ id: 'grid', name: 'Grid view', type: 'grid' }]),
57
+ activeView = $bindable<string>(''),
58
+ onViewsChange,
59
+ viewTypes,
60
+ automations = null,
61
+ rowHeights = [
62
+ { label: 'Short', height: 34 },
63
+ { label: 'Medium', height: 56 },
64
+ { label: 'Tall', height: 88 },
65
+ { label: 'Extra tall', height: 128 },
66
+ ],
67
+ }: Props = $props()
68
+
69
+ const ctrl = $derived(api ? gridOf(api) : undefined)
70
+ const active = $derived(views.find((v) => v.id === activeView) ?? views[0])
71
+ const types = $derived(viewTypes ?? (['grid', 'kanban', 'calendar', ...(hasGanttView() ? ['gantt'] : [])] as FieldViewType[]))
72
+
73
+ const linkNames: LinkNames = (table, value) => {
74
+ const t = getLinkedTable(table)
75
+ return t ? linkedRows(t, value).map((r) => String(r[t.primaryField] ?? '')) : []
76
+ }
77
+ const primaryField = $derived.by(() => {
78
+ if (!ctrl) return undefined
79
+ const id = primaryIdOf(ctrl)
80
+ return fieldColumns(ctrl).find((c) => c.id === id)?.field
81
+ })
82
+
83
+ // ---- Views ----------------------------------------------------------------
84
+
85
+ // The view the grid shows, as it was when applied or last written here. A
86
+ // list or an active id set from outside that differs from it is applied;
87
+ // the autosave never writes into a view that has not been applied yet.
88
+ let appliedId: string | null = null
89
+ let appliedKey = ''
90
+ let applying = false
91
+ let showSeq = 0
92
+ const keyOf = (v: FieldView) => JSON.stringify(v)
93
+
94
+ function setViews(next: FieldView[]) {
95
+ views = next
96
+ const shown = next.find((v) => v.id === appliedId)
97
+ if (shown) appliedKey = keyOf(shown)
98
+ onViewsChange?.(next)
99
+ }
100
+ /** The renderers a view type needs, loaded when a view of that type is first shown. */
101
+ async function ready(t: FieldViewType) {
102
+ if (t === 'kanban') (await import('../board')).enableBoardView()
103
+ else if (t === 'calendar') (await import('../scheduler')).enableSchedulerView()
104
+ else if (t === 'gallery') (await import('../gallery')).enableGalleryView()
105
+ }
106
+ async function show(view: FieldView) {
107
+ const seq = (showSeq += 1)
108
+ appliedId = view.id
109
+ appliedKey = keyOf(view)
110
+ applying = true
111
+ await ready(view.type)
112
+ // A later show won while this one loaded its renderer.
113
+ if (seq !== showSeq) return
114
+ applying = false
115
+ if (!ctrl) return
116
+ applyView(ctrl, api, view, { primaryField, linkNames })
117
+ colorSetting = view.color ?? colorSettingOf(ctrl)
118
+ }
119
+ /** The shown view as the grid now has it (its condition colors are the bar's). */
120
+ // Plain data, not the bar's state proxy: the app stores views and a copy is cloned.
121
+ const capture = (v: FieldView) => captureView(ctrl, api, { ...v, color: $state.snapshot(colorSetting) as ColorSetting })
122
+ function switchTo(id: string) {
123
+ if (!ctrl || !active || id === active.id) return
124
+ const next = views.map((v) => (v.id === active.id ? capture(v) : v))
125
+ const target = next.find((v) => v.id === id)
126
+ if (!target) return
127
+ setViews(next)
128
+ activeView = id
129
+ void show(target)
130
+ viewsOpen = false
131
+ }
132
+ function create(partial: Omit<FieldView, 'id'>) {
133
+ if (!ctrl || !active) return
134
+ // A new view starts plain: no filter, sort or groups, every field shown.
135
+ // A calendar or Gantt is colored by the first single select (a status), so
136
+ // its events and bars say something; any other view starts uncolored.
137
+ const select = columns.find((c) => c.fieldType === 'singleSelect')
138
+ const timed = partial.type === 'calendar' || partial.type === 'gantt'
139
+ const view: FieldView = {
140
+ ...partial,
141
+ id: newViewId(),
142
+ state: { ...PLAIN_STATE },
143
+ color: timed && select ? { mode: 'field', field: select.field } : { mode: 'none' },
144
+ }
145
+ const next = [...views.map((v) => (v.id === active.id ? capture(v) : v)), view]
146
+ setViews(next)
147
+ activeView = view.id
148
+ void show(view)
149
+ viewsOpen = false
150
+ }
151
+ function duplicate(id: string) {
152
+ if (!ctrl) return
153
+ const src = id === active?.id ? capture(active) : views.find((v) => v.id === id)
154
+ if (!src) return
155
+ const copy: FieldView = { ...(JSON.parse(JSON.stringify(src)) as FieldView), id: newViewId(), name: `${src.name} copy` }
156
+ const at = views.findIndex((v) => v.id === id)
157
+ const next = [...views]
158
+ next.splice(at + 1, 0, copy)
159
+ setViews(next)
160
+ }
161
+ function remove(id: string) {
162
+ if (views.length < 2) return
163
+ const next = views.filter((v) => v.id !== id)
164
+ setViews(next)
165
+ if (id === active?.id) {
166
+ activeView = next[0]!.id
167
+ void show(next[0]!)
168
+ }
169
+ }
170
+ const rename = (id: string, name: string) => setViews(views.map((v) => (v.id === id ? { ...v, name } : v)))
171
+ /** The shown Gantt's fields changed: kept in the view, and drawn at once. */
172
+ function setGantt(next: Pick<FieldView, 'dateField' | 'endField' | 'progressField' | 'dependencyField'>) {
173
+ if (!ctrl || !active) return
174
+ const updated: FieldView = { ...active, ...next }
175
+ setViews(views.map((v) => (v.id === active.id ? updated : v)))
176
+ applyGanttSettings(ctrl, api, updated, primaryField)
177
+ }
178
+ // A chart view declared without a setting: the panel opens with no field picked.
179
+ const NO_CHART: ChartSetting = { kind: 'bar', x: '' }
180
+ /** The shown chart's setting changed: kept in the view at once. */
181
+ const setChart = (chart: ChartSetting) => setViews(views.map((v) => (v.id === active?.id ? { ...v, chart } : v)))
182
+
183
+ // The shown view: apply what it saved (a view stored by the app), first when
184
+ // the grid starts, then whenever the app swaps the list or the active id and
185
+ // the shown view is not the one applied.
186
+ $effect(() => {
187
+ if (!ctrl || !active) return
188
+ const view = active
189
+ const key = keyOf(view)
190
+ untrack(() => {
191
+ if (!activeView) activeView = view.id
192
+ if (view.id === appliedId && key === appliedKey) return
193
+ // A plain grid view first: the grid keeps the state its own props gave it.
194
+ if (appliedId === null && !(view.state || view.type !== 'grid' || view.color || view.rowHeight != null)) {
195
+ appliedId = view.id
196
+ appliedKey = key
197
+ return
198
+ }
199
+ void show(view)
200
+ })
201
+ })
202
+
203
+ // Keep the shown view up to date as it changes (as a table app saves a
204
+ // view's settings), a moment after the last change.
205
+ $effect(() => {
206
+ if (!ctrl || !active) return
207
+ // Sorting and grouping live in the grid's store; its version says they changed.
208
+ const snapshot = JSON.stringify([ctrl.gridStateVersion, api.getState(), recordHeightOf(api.getOption('rowHeight')), effectiveColor])
209
+ const view = active
210
+ const timer = setTimeout(() => {
211
+ untrack(() => {
212
+ if (applying || view.id !== appliedId || keyOf(view) !== appliedKey) return
213
+ const kept = capture(view)
214
+ const before = JSON.stringify([view.state, view.rowHeight, view.color])
215
+ const after = JSON.stringify([kept.state, kept.rowHeight, kept.color])
216
+ if (before !== after) setViews(views.map((v) => (v.id === view.id ? kept : v)))
217
+ })
218
+ }, 400)
219
+ void snapshot
220
+ return () => clearTimeout(timer)
221
+ })
222
+
223
+ // ---- The buttons' state -----------------------------------------------------
224
+
225
+ let viewsOpen = $state(false)
226
+ // The panel open, one at a time; the column menu can ask for one ("Filter by this field").
227
+ let panel = $state<BarPanel | null>(null)
228
+ function setPanel(p: BarPanel, open: boolean) {
229
+ if (open) panel = p
230
+ else if (panel === p) panel = null
231
+ }
232
+ $effect(() => {
233
+ if (ctrl) return registerBar(ctrl)
234
+ })
235
+ $effect(() => {
236
+ const r = getBarRequest()
237
+ if (!r || r.ctrl !== ctrl) return
238
+ untrack(() => {
239
+ panel = r.panel
240
+ clearBarRequest()
241
+ })
242
+ })
243
+ let colorSetting = $state<ColorSetting>({ mode: 'none' })
244
+ $effect(() => {
245
+ if (ctrl) untrack(() => (colorSetting = active?.color ?? colorSettingOf(ctrl)))
246
+ })
247
+ function setColor(s: ColorSetting) {
248
+ colorSetting = s
249
+ if (ctrl) applyColor(ctrl, s, linkNames)
250
+ // A calendar's events and a Gantt's bars take the view's colors.
251
+ if (ctrl && active && (active.type === 'calendar' || active.type === 'gantt')) {
252
+ const updated: FieldView = { ...active, color: $state.snapshot(s) as ColorSetting }
253
+ setViews(views.map((v) => (v.id === active.id ? updated : v)))
254
+ applyGanttSettings(ctrl, api, updated, primaryField)
255
+ }
256
+ }
257
+
258
+ const columns = $derived(ctrl ? fieldColumns(ctrl) : [])
259
+ const hiddenCount = $derived(columns.filter((c) => !c.visible).length)
260
+ const filter = $derived.by<FieldFilter | null>(() => {
261
+ const f = ctrl?.advancedFilter as FieldFilter | null | undefined
262
+ return f?.kind === 'fieldFilter' ? f : null
263
+ })
264
+ const filterCount = $derived(conditionCount(filter))
265
+ const filterLabel = $derived.by(() => {
266
+ if (!filter || !filterCount) return 'Filter'
267
+ const ids = new Set<string>()
268
+ for (const c of filter.conditions) for (const x of isGroup(c) ? c.conditions : [c]) if (isComplete(x)) ids.add(x.column)
269
+ if (ids.size === 1) return `Filtered by ${columns.find((c) => ids.has(c.id))?.header ?? '1 field'}`
270
+ return `Filtered by ${ids.size} fields`
271
+ })
272
+ const groupCount = $derived(ctrl ? groupRules(ctrl).length : 0)
273
+ const sortCount = $derived(ctrl ? userSorts(ctrl).length : 0)
274
+ // What the rows show now: a column menu can color them by a field too.
275
+ const effectiveColor = $derived(ctrl ? colorSettingOf(ctrl, colorSetting) : colorSetting)
276
+ const colorLabel = $derived(
277
+ effectiveColor.mode === 'field'
278
+ ? `Colored by ${columns.find((c) => c.field === (effectiveColor as { field: string }).field)?.header ?? 'field'}`
279
+ : effectiveColor.mode === 'conditions' && effectiveColor.rules.length
280
+ ? 'Colored by conditions'
281
+ : 'Color',
282
+ )
283
+ const rowHeight = $derived(recordHeightOf(api?.getOption?.('rowHeight')) ?? 0)
284
+ // Grouped, the group rows are drawn taller than the records: room for the field's name over its value.
285
+ $effect(() => {
286
+ const grouped = groupCount > 0
287
+ void rowHeight
288
+ untrack(() => syncGroupRowHeight(ctrl, api, grouped))
289
+ })
290
+ // Grouped, a collapsed group hides its rows but they are still records: count the top groups' sizes.
291
+ const records = $derived.by(() => {
292
+ if (!ctrl) return 0
293
+ const tops = ((ctrl.allRows ?? []) as Array<{ depth?: number; leafCount?: number; getCanExpand?: () => boolean; __treeRow?: boolean }>).filter(
294
+ (r) => (r.depth ?? 0) === 0 && !r.__treeRow && typeof r.getCanExpand === 'function' && r.getCanExpand() && r.leafCount != null,
295
+ )
296
+ if (!groupCount || !tops.length) return api.getDisplayedRows().length
297
+ // The grid groups before it filters: a group's size still holds the filtered-out records.
298
+ const keep = leafFilter(ctrl)
299
+ return keep ? tops.reduce((n, r) => n + groupLeaves(r, keep).length, 0) : tops.reduce((n, r) => n + (r.leafCount ?? 0), 0)
300
+ })
301
+ const plural = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`
302
+
303
+ // Search: in a grid view the grid's find bar, which marks every match; a
304
+ // board, calendar or gallery has no cells to mark, so there the bar shows a
305
+ // box that narrows the records (the grid's quick filter).
306
+ let searching = $state(false)
307
+ const quickSearch = $derived(active?.type !== 'grid')
308
+ /** The find bar or the search box, with its box focused (the find bar only autofocuses on page load). */
309
+ async function openSearch() {
310
+ if (quickSearch) {
311
+ searching = true
312
+ return
313
+ }
314
+ ctrl.findOpen = true
315
+ await tick()
316
+ const root = (ctrl.scrollContainer as HTMLElement | null)?.closest('.sv-grid-root')
317
+ root?.querySelector<HTMLInputElement>('.sv-grid-find-input')?.focus()
318
+ }
319
+ function focusBox(node: HTMLInputElement) {
320
+ node.focus()
321
+ }
322
+
323
+ // A gallery's cards show the fields the grid shows, in its order: build its
324
+ // config again when a field is hidden or moved (only when that changed, as
325
+ // setting it changes the grid's options, which this reads).
326
+ let galleryKey = ''
327
+ $effect(() => {
328
+ if (!ctrl || active?.type !== 'gallery') {
329
+ galleryKey = ''
330
+ return
331
+ }
332
+ const view = active
333
+ const cfg = galleryConfig(ctrl, api, view)
334
+ const key = JSON.stringify([cfg.coverField, cfg.titleField, cfg.cardFields])
335
+ if (key === galleryKey) return
336
+ galleryKey = key
337
+ untrack(() => api.setOption('gallery', cfg))
338
+ })
339
+ // The same for a board's cards.
340
+ let boardKey = ''
341
+ $effect(() => {
342
+ if (!ctrl || active?.type !== 'kanban') {
343
+ boardKey = ''
344
+ return
345
+ }
346
+ const cfg = boardConfig(ctrl, api, active)
347
+ if (!cfg) return
348
+ const key = JSON.stringify([cfg.groupBy, cfg.titleField, cfg.cardFields])
349
+ if (key === boardKey) return
350
+ boardKey = key
351
+ untrack(() => api.setOption('board', cfg))
352
+ })
353
+
354
+ // A record opened while the grid is a board, a calendar or a gallery: the table's header,
355
+ // which shows records otherwise, is not drawn then, so the bar shows it.
356
+ const record = $derived.by(() => {
357
+ const r = getRecordRequest()
358
+ const t = active?.type
359
+ return r && ctrl && r.ctrl === ctrl && (t === 'kanban' || t === 'calendar' || t === 'gantt' || t === 'gallery') ? r : null
360
+ })
361
+
362
+ // "Delete record?" from a card's menu, for the same reason.
363
+ const deleting = $derived.by(() => {
364
+ const r = getDeleteRecordRequest()
365
+ const t = active?.type
366
+ return r && ctrl && r.ctrl === ctrl && (t === 'kanban' || t === 'calendar' || t === 'gantt' || t === 'gallery') ? r : null
367
+ })
368
+
369
+ // Which buttons a view type has.
370
+ const has = $derived.by(() => {
371
+ const t = active?.type ?? 'grid'
372
+ if (t === 'form') return new Set<string>()
373
+ if (t === 'grid') return new Set(['hide', 'filter', 'group', 'sort', 'color', 'height'])
374
+ // A chart reads the filtered records; how it groups them is its own setting.
375
+ if (t === 'chart') return new Set(['filter', 'chart'])
376
+ if (t === 'gantt') return new Set(['hide', 'filter', 'sort', 'color', 'gantt'])
377
+ if (t === 'calendar') return new Set(['hide', 'filter', 'sort', 'color'])
378
+ return new Set(['hide', 'filter', 'sort'])
379
+ })
380
+
381
+ const ICON = {
382
+ 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',
383
+ filter: 'M4 5h16l-6 7v6l-4 2v-8z',
384
+ group: 'M4 5h16M4 10h10M4 15h16M4 20h10',
385
+ sort: 'M7 4v16M3 16l4 4 4-4M17 20V4M13 8l4-4 4 4',
386
+ color: 'M12 3l6 7a6.5 6.5 0 1 1-12 0l6-7z',
387
+ chart: 'M4 20h16M6 20V11M11 20V5M16 20v-7',
388
+ gantt: 'M4 5h9M7 10h11M10 15h8M4 20h16',
389
+ height: 'M4 6h16M4 12h16M4 18h16M20 3v18',
390
+ search: 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4',
391
+ chevron: 'M6 9l6 6 6-6',
392
+ bolt: 'M13 3L5 14h6l-1 7 8-11h-6z',
393
+ }
394
+ </script>
395
+
396
+ {#snippet glyph(path: string, size = 15)}
397
+ <svg viewBox="0 0 24 24" width={size} height={size} aria-hidden="true"><path d={path} fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" /></svg>
398
+ {/snippet}
399
+
400
+ {#if ctrl && active}
401
+ <div class="sv-fvb" role="toolbar" aria-label="View settings">
402
+ <SvPopover bind:open={viewsOpen} arrow={false} placement="bottom-start" ariaLabel="Views">
403
+ {#snippet anchor()}
404
+ <button type="button" class="sv-fvb-btn sv-fvb-view" aria-haspopup="dialog" aria-expanded={viewsOpen}>
405
+ {@render glyph(viewTypeInfo(active.type).icon)}
406
+ <span class="sv-fvb-view-name">{active.name}</span>
407
+ {@render glyph(ICON.chevron, 13)}
408
+ </button>
409
+ {/snippet}
410
+ <ViewsMenu
411
+ {ctrl}
412
+ {views}
413
+ activeId={active.id}
414
+ {types}
415
+ onSwitch={switchTo}
416
+ onCreate={create}
417
+ onRename={rename}
418
+ onDuplicate={duplicate}
419
+ onDelete={remove}
420
+ />
421
+ </SvPopover>
422
+ <span class="sv-fvb-sep" aria-hidden="true"></span>
423
+
424
+ {#if has.has('hide')}
425
+ <SvPopover open={panel === 'hide'} onOpenChange={(o) => setPanel('hide', o)} arrow={false} placement="bottom-start" ariaLabel="Hide fields">
426
+ {#snippet anchor()}
427
+ <button type="button" class="sv-fvb-btn" class:is-set={hiddenCount > 0}>
428
+ {@render glyph(ICON.hide)}<span>{hiddenCount ? plural(hiddenCount, 'hidden field', 'hidden fields') : 'Hide fields'}</span>
429
+ </button>
430
+ {/snippet}
431
+ <HideFieldsPanel {ctrl} {api} />
432
+ </SvPopover>
433
+ {/if}
434
+ {#if has.has('filter')}
435
+ <SvPopover open={panel === 'filter'} onOpenChange={(o) => setPanel('filter', o)} arrow={false} placement="bottom-start" ariaLabel="Filter">
436
+ {#snippet anchor()}
437
+ <button type="button" class="sv-fvb-btn" class:is-set={filterCount > 0}>{@render glyph(ICON.filter)}<span>{filterLabel}</span></button>
438
+ {/snippet}
439
+ <FilterPanel {ctrl} {api} />
440
+ </SvPopover>
441
+ {/if}
442
+ {#if has.has('group')}
443
+ <SvPopover open={panel === 'group'} onOpenChange={(o) => setPanel('group', o)} arrow={false} placement="bottom-start" ariaLabel="Group">
444
+ {#snippet anchor()}
445
+ <button type="button" class="sv-fvb-btn" class:is-set={groupCount > 0}>
446
+ {@render glyph(ICON.group)}<span>{groupCount ? `Grouped by ${plural(groupCount, 'field', 'fields')}` : 'Group'}</span>
447
+ </button>
448
+ {/snippet}
449
+ <SortPanel {ctrl} {api} group />
450
+ </SvPopover>
451
+ {/if}
452
+ {#if has.has('sort')}
453
+ <SvPopover open={panel === 'sort'} onOpenChange={(o) => setPanel('sort', o)} arrow={false} placement="bottom-start" ariaLabel="Sort">
454
+ {#snippet anchor()}
455
+ <button type="button" class="sv-fvb-btn" class:is-set={sortCount > 0}>
456
+ {@render glyph(ICON.sort)}<span>{sortCount ? `Sorted by ${plural(sortCount, 'field', 'fields')}` : 'Sort'}</span>
457
+ </button>
458
+ {/snippet}
459
+ <SortPanel {ctrl} {api} />
460
+ </SvPopover>
461
+ {/if}
462
+ {#if has.has('color')}
463
+ <SvPopover open={panel === 'color'} onOpenChange={(o) => setPanel('color', o)} arrow={false} placement="bottom-start" ariaLabel="Color">
464
+ {#snippet anchor()}
465
+ <button type="button" class="sv-fvb-btn" class:is-set={effectiveColor.mode !== 'none'}>{@render glyph(ICON.color)}<span>{colorLabel}</span></button>
466
+ {/snippet}
467
+ <ColorPanel {ctrl} setting={effectiveColor} onChange={setColor} />
468
+ </SvPopover>
469
+ {/if}
470
+ {#if has.has('chart')}
471
+ {@const chart = active.chart ?? NO_CHART}
472
+ <SvPopover open={panel === 'chart'} onOpenChange={(o) => setPanel('chart', o)} arrow={false} placement="bottom-start" ariaLabel="Customize chart">
473
+ {#snippet anchor()}
474
+ <button type="button" class="sv-fvb-btn">{@render glyph(ICON.chart)}<span>Customize chart</span></button>
475
+ {/snippet}
476
+ <ChartPanel {ctrl} setting={chart} onChange={setChart} />
477
+ </SvPopover>
478
+ {/if}
479
+ {#if has.has('gantt') && active}
480
+ {@const settings = active}
481
+ <SvPopover open={panel === 'gantt'} onOpenChange={(o) => setPanel('gantt', o)} arrow={false} placement="bottom-start" ariaLabel="Customize timeline">
482
+ {#snippet anchor()}
483
+ <button type="button" class="sv-fvb-btn">{@render glyph(ICON.gantt)}<span>Customize timeline</span></button>
484
+ {/snippet}
485
+ <GanttPanel {ctrl} view={settings} onChange={setGantt} />
486
+ </SvPopover>
487
+ {/if}
488
+ {#if has.has('height')}
489
+ <SvPopover arrow={false} placement="bottom-start" ariaLabel="Row height">
490
+ {#snippet anchor()}
491
+ <button type="button" class="sv-fvb-btn sv-fvb-icon-only" aria-label="Row height" title="Row height">{@render glyph(ICON.height)}</button>
492
+ {/snippet}
493
+ <div class="sv-fvb-panel sv-fvb-heights" role="menu" aria-label="Row height" use:panelFocus>
494
+ {#each rowHeights as h (h.height)}
495
+ <button
496
+ type="button"
497
+ role="menuitemradio"
498
+ aria-checked={rowHeight === h.height}
499
+ class="sv-fvb-height"
500
+ class:is-on={rowHeight === h.height}
501
+ onclick={() => api.setOption('rowHeight', h.height)}
502
+ >
503
+ <span class="sv-fvb-height-mark" style:--lines={Math.min(4, Math.max(1, Math.round(h.height / 30)))} aria-hidden="true"></span>
504
+ {h.label}
505
+ </button>
506
+ {/each}
507
+ </div>
508
+ </SvPopover>
509
+ {/if}
510
+
511
+ <span class="sv-fvb-spacer"></span>
512
+ {#if automations}
513
+ <button type="button" class="sv-fvb-btn" class:is-set={automations.count > 0} onclick={automations.onManage}>
514
+ {@render glyph(ICON.bolt)}<span>{automations.count ? plural(automations.count, 'automation', 'automations') : 'Automations'}</span>
515
+ </button>
516
+ {/if}
517
+ {#if active.type !== 'form'}
518
+ <span class="sv-fvb-count" aria-live="polite">{plural(records, 'record', 'records')}</span>
519
+ {#if quickSearch && (searching || ctrl.globalFilter)}
520
+ <label class="sv-fvb-search">
521
+ {@render glyph(ICON.search, 14)}
522
+ <input
523
+ type="search"
524
+ placeholder="Find a record"
525
+ aria-label="Find a record"
526
+ bind:value={ctrl.globalFilter}
527
+ use:focusBox
528
+ onblur={() => (searching = false)}
529
+ onkeydown={(e) => {
530
+ if (e.key === 'Escape') {
531
+ ctrl.globalFilter = ''
532
+ searching = false
533
+ }
534
+ }}
535
+ />
536
+ </label>
537
+ {:else}
538
+ <button type="button" class="sv-fvb-btn sv-fvb-icon-only" aria-label="Search" title={quickSearch ? 'Search' : 'Search (Ctrl+F)'} onclick={openSearch}>
539
+ {@render glyph(ICON.search)}
540
+ </button>
541
+ {/if}
542
+ {/if}
543
+ </div>
544
+ {/if}
545
+ {#if record}
546
+ {#key record}
547
+ <RecordView {ctrl} rowId={record.rowId} editPrimary={record.editPrimary} onClose={closeRecord} />
548
+ {/key}
549
+ {/if}
550
+ {#if deleting}
551
+ {#key deleting}
552
+ <DeleteRecordConfirm {ctrl} api={deleting.api} row={deleting.row} onClose={() => askDeleteRecord(null)} />
553
+ {/key}
554
+ {/if}
555
+
556
+ <style>
557
+ .sv-fvb {
558
+ container-type: inline-size;
559
+ display: flex; align-items: center; gap: 2px; flex-wrap: wrap; min-height: 40px; padding: 4px 6px;
560
+ font-family: var(--sg-font, inherit); font-size: 13px; color: var(--sg-fg, #0f172a);
561
+ border-bottom: 1px solid var(--sg-border, #e2e8f0); background: var(--sg-bg, #fff);
562
+ }
563
+ .sv-fvb-btn {
564
+ position: relative; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px; border: 0; border-radius: 6px;
565
+ background: none; color: inherit; font: inherit; cursor: pointer; white-space: nowrap;
566
+ }
567
+ .sv-fvb-btn:hover { background: var(--sg-row-hover-bg, #f1f5f9); }
568
+ .sv-fvb-btn:focus-visible { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: -2px; }
569
+ .sv-fvb-btn.is-set { background: color-mix(in srgb, var(--sg-accent, #2563eb) 13%, transparent); }
570
+ .sv-fvb-view { font-weight: 600; max-width: 240px; }
571
+ .sv-fvb-view-name { overflow: hidden; text-overflow: ellipsis; }
572
+ .sv-fvb-icon-only { padding: 0 6px; }
573
+ .sv-fvb-sep { width: 1px; height: 18px; margin: 0 6px; background: var(--sg-border, #e2e8f0); }
574
+ .sv-fvb-spacer { flex: 1; }
575
+ /* A narrow bar keeps the icons and the counts; the labels stay for screen readers. */
576
+ @container (max-width: 640px) {
577
+ .sv-fvb-btn:not(.sv-fvb-view) > span {
578
+ position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
579
+ }
580
+ .sv-fvb-btn { padding: 0 6px; }
581
+ .sv-fvb-view { max-width: 150px; }
582
+ }
583
+ /* A phone: the count gives way, so the bar stays one line. */
584
+ @container (max-width: 420px) {
585
+ .sv-fvb-count { display: none; }
586
+ }
587
+ .sv-fvb-search {
588
+ display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px; border-radius: 6px;
589
+ border: 1px solid var(--sg-border, #cbd5e1); color: var(--sg-muted, #64748b);
590
+ }
591
+ .sv-fvb-search input { width: 160px; border: 0; outline: none; background: none; color: var(--sg-fg, #0f172a); font: inherit; }
592
+ .sv-fvb-search:focus-within { border-color: var(--sg-accent, #2563eb); }
593
+ .sv-fvb-count { color: var(--sg-muted, #64748b); font-size: 12px; margin-inline-end: 4px; font-variant-numeric: tabular-nums; }
594
+
595
+ /* Shared by the panels (portalled to <body>). */
596
+ :global(.sv-pop__panel:has(.sv-fvb-panel-wide)) { max-width: min(94vw, 760px); }
597
+ :global(.sv-fvb-panel) { font-size: 13px; max-width: 100%; }
598
+ :global(.sv-fvb-title) { margin: 0 0 6px; color: var(--sg-muted, #64748b); font-size: 12px; }
599
+ :global(.sv-fvb-empty) { margin: 2px 0 8px; color: var(--sg-muted, #64748b); }
600
+ :global(.sv-fvb-foot) { display: flex; gap: 12px; margin-top: 8px; align-items: center; }
601
+ :global(.sv-fvb-select), :global(.sv-fvb-input) {
602
+ width: 100%; min-width: 0; height: 28px; box-sizing: border-box; padding: 0 6px; font: inherit; font-size: 13px;
603
+ color: inherit; background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #cbd5e1); border-radius: 6px;
604
+ }
605
+ :global(.sv-fvb-select:focus-visible), :global(.sv-fvb-input:focus-visible) { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: -1px; }
606
+ :global(.sv-fvb-text-btn) {
607
+ border: 0; padding: 2px 0; background: none; font: inherit; color: var(--sg-accent, #2563eb); cursor: pointer;
608
+ }
609
+ :global(.sv-fvb-text-btn:hover) { text-decoration: underline; }
610
+ :global(.sv-fvb-solid-btn) {
611
+ border: 0; border-radius: 6px; padding: 5px 12px; font: inherit; cursor: pointer;
612
+ background: var(--sg-accent, #2563eb); color: var(--sg-accent-fg, #fff);
613
+ }
614
+ :global(.sv-fvb-icon-btn) {
615
+ display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0;
616
+ border: 0; border-radius: 6px; background: none; color: var(--sg-muted, #64748b); cursor: pointer;
617
+ }
618
+ :global(.sv-fvb-icon-btn:hover) { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
619
+ /* A switch, not a checkbox: on is the accent with the knob at the end. */
620
+ :global(.sv-fvb-switch) {
621
+ appearance: none; flex: none; position: relative; width: 26px; height: 15px; margin: 0; border-radius: 999px; cursor: pointer;
622
+ background: color-mix(in srgb, var(--sg-fg, #0f172a) 22%, transparent); transition: background-color 0.12s;
623
+ }
624
+ :global(.sv-fvb-switch::before) {
625
+ content: ''; position: absolute; top: 2px; left: 2px; width: 11px; height: 11px; border-radius: 50%;
626
+ background: var(--sg-bg, #fff); transition: transform 0.12s;
627
+ }
628
+ :global(.sv-fvb-switch:checked) { background: var(--sg-accent, #2563eb); }
629
+ :global(.sv-fvb-switch:checked::before) { transform: translateX(11px); }
630
+ :global(.sv-fvb-switch:disabled) { opacity: 0.5; cursor: default; }
631
+ :global(.sv-fvb-switch:focus-visible) { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 2px; }
632
+ .sv-fvb-heights { display: grid; min-width: 150px; }
633
+ .sv-fvb-height {
634
+ display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 0; border-radius: 6px;
635
+ background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
636
+ }
637
+ .sv-fvb-height:hover, .sv-fvb-height.is-on { background: var(--sg-row-hover-bg, #f1f5f9); }
638
+ .sv-fvb-height.is-on { font-weight: 600; }
639
+ /* As many lines as the height holds text. */
640
+ .sv-fvb-height-mark {
641
+ width: 14px; height: 14px; border: 1.5px solid currentColor; border-radius: 3px; box-sizing: border-box;
642
+ background: repeating-linear-gradient(to bottom, transparent 0 2px, currentColor 2px 3px) center / 8px calc(var(--lines) * 3px) no-repeat;
643
+ }
644
+ </style>
@@ -0,0 +1,26 @@
1
+ import { type FieldView, type FieldViewType } from './view-model.js';
2
+ type Props = {
3
+ /** The grid's api (`onApiReady`). */
4
+ api: any;
5
+ /** The saved views (bindable). Default: one grid view. */
6
+ views?: FieldView[];
7
+ /** The id of the view shown (bindable). */
8
+ activeView?: string;
9
+ /** The views changed: one was created, renamed, deleted, or the shown one's settings changed. */
10
+ onViewsChange?: (views: FieldView[]) => void;
11
+ /** The view types "Create" offers. Default: grid, Kanban, calendar, and Gantt when it is enabled. */
12
+ viewTypes?: ReadonlyArray<FieldViewType>;
13
+ /** The row heights the row height menu offers. */
14
+ rowHeights?: ReadonlyArray<{
15
+ label: string;
16
+ height: number;
17
+ }>;
18
+ /** The Automations button: how many rules, and what its buttons open. */
19
+ automations?: {
20
+ count: number;
21
+ onManage: () => void;
22
+ } | null;
23
+ };
24
+ declare const SvBaseViewBar: import("svelte").Component<Props, {}, "views" | "activeView">;
25
+ type SvBaseViewBar = ReturnType<typeof SvBaseViewBar>;
26
+ export default SvBaseViewBar;