@svgrid/enterprise 3.1.7 → 3.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +1 -0
  2. package/dist/SvAlertRuleEditor.svelte +16 -5
  3. package/dist/SvGridAlerts.svelte +8 -0
  4. package/dist/SvGridAlerts.svelte.d.ts +4 -0
  5. package/dist/SvGridBoard.svelte +69 -10
  6. package/dist/SvGridBulkEditDrawer.svelte +13 -5
  7. package/dist/SvGridGallery.svelte +286 -0
  8. package/dist/SvGridGallery.svelte.d.ts +30 -0
  9. package/dist/SvGridScheduler.svelte +33 -11
  10. package/dist/SvGridSelectionBar.svelte +47 -0
  11. package/dist/advanced-filter/expr-columns-from-grid.d.ts +1 -0
  12. package/dist/advanced-filter/expr-columns-from-grid.js +11 -7
  13. package/dist/advanced-filter-enable.d.ts +6 -0
  14. package/dist/advanced-filter-enable.js +8 -0
  15. package/dist/alerts/alert-engine.d.ts +7 -0
  16. package/dist/alerts/alert-engine.js +5 -2
  17. package/dist/alerts/condition-editor.d.ts +26 -0
  18. package/dist/alerts/condition-editor.js +17 -0
  19. package/dist/base/index.d.ts +20 -0
  20. package/dist/base/index.js +20 -0
  21. package/dist/bulk-edit.d.ts +11 -0
  22. package/dist/bulk-edit.js +18 -1
  23. package/dist/cdn/svgrid-enterprise.svelte-external.js +11375 -10805
  24. package/dist/designer/assets/GridFooter-Cu8_yBlQ.js +2 -0
  25. package/dist/designer/assets/GridMenus-BUjkkAU2.js +6 -0
  26. package/dist/designer/assets/PinSplitter-BgTpgh1d.css +1 -0
  27. package/dist/designer/assets/PinSplitter-C5AwA1xV.js +1 -0
  28. package/dist/designer/assets/SvGrid.helpers-CIrmIo9U.js +1 -0
  29. package/dist/designer/assets/SvGridCellEditor-CJxThKL_.js +4 -0
  30. package/dist/designer/assets/SvListBox-CS7oUAiX.js +2 -0
  31. package/dist/designer/assets/conditional-formatting-MDWbF8_Y.js +1 -0
  32. package/dist/designer/assets/grid-extension.svelte-BZ7AqyJI.js +1 -0
  33. package/dist/designer/assets/{index-DtBIaQno.js → index-BPU2KNc5.js} +396 -395
  34. package/dist/designer/assets/index-BmvG99kR.css +1 -0
  35. package/dist/designer/assets/sparkline-C-NcnB2i.js +1 -0
  36. package/dist/designer/assets/src-C_U1NKOY.js +58 -0
  37. package/dist/designer/assets/sv-grid-scrollbar-B9N6Wuj0.js +109 -0
  38. package/dist/designer/assets/validate-H5u2HEQh.js +1 -0
  39. package/dist/designer/index.html +11 -7
  40. package/dist/expressions/compile.js +10 -0
  41. package/dist/expressions/evaluate.js +18 -0
  42. package/dist/expressions/expression-types.d.ts +10 -0
  43. package/dist/expressions/kinds.d.ts +28 -0
  44. package/dist/expressions/kinds.js +19 -0
  45. package/dist/expressions/parse.js +9 -1
  46. package/dist/fields/AttachmentCell.svelte +242 -0
  47. package/dist/fields/AttachmentCell.svelte.d.ts +5 -0
  48. package/dist/fields/AttachmentDialog.svelte +357 -0
  49. package/dist/fields/AttachmentDialog.svelte.d.ts +13 -0
  50. package/dist/fields/AttachmentEditor.svelte +41 -0
  51. package/dist/fields/AttachmentEditor.svelte.d.ts +11 -0
  52. package/dist/fields/AttachmentPreview.svelte +72 -0
  53. package/dist/fields/AttachmentPreview.svelte.d.ts +8 -0
  54. package/dist/fields/BarcodeCell.svelte +20 -0
  55. package/dist/fields/BarcodeCell.svelte.d.ts +5 -0
  56. package/dist/fields/BaseAutomations.svelte +558 -0
  57. package/dist/fields/BaseAutomations.svelte.d.ts +18 -0
  58. package/dist/fields/BaseChartView.svelte +47 -0
  59. package/dist/fields/BaseChartView.svelte.d.ts +8 -0
  60. package/dist/fields/BaseTableView.svelte +185 -0
  61. package/dist/fields/BaseTableView.svelte.d.ts +37 -0
  62. package/dist/fields/BaseTabs.svelte +308 -0
  63. package/dist/fields/BaseTabs.svelte.d.ts +21 -0
  64. package/dist/fields/ButtonCell.svelte +47 -0
  65. package/dist/fields/ButtonCell.svelte.d.ts +20 -0
  66. package/dist/fields/CheckboxCell.svelte +46 -0
  67. package/dist/fields/CheckboxCell.svelte.d.ts +14 -0
  68. package/dist/fields/DeleteFieldConfirm.svelte +80 -0
  69. package/dist/fields/DeleteFieldConfirm.svelte.d.ts +8 -0
  70. package/dist/fields/DeleteRecordConfirm.svelte +48 -0
  71. package/dist/fields/DeleteRecordConfirm.svelte.d.ts +9 -0
  72. package/dist/fields/FieldCell.svelte +80 -0
  73. package/dist/fields/FieldCell.svelte.d.ts +9 -0
  74. package/dist/fields/FieldEditor.svelte +321 -0
  75. package/dist/fields/FieldEditor.svelte.d.ts +10 -0
  76. package/dist/fields/FieldInput.svelte +188 -0
  77. package/dist/fields/FieldInput.svelte.d.ts +29 -0
  78. package/dist/fields/FieldsHeaderEnd.svelte +146 -0
  79. package/dist/fields/FieldsHeaderEnd.svelte.d.ts +6 -0
  80. package/dist/fields/FootRowAdder.svelte +8 -0
  81. package/dist/fields/FootRowAdder.svelte.d.ts +6 -0
  82. package/dist/fields/GroupHeader.svelte +127 -0
  83. package/dist/fields/GroupHeader.svelte.d.ts +24 -0
  84. package/dist/fields/LinkCell.svelte +46 -0
  85. package/dist/fields/LinkCell.svelte.d.ts +9 -0
  86. package/dist/fields/LinkPickerEditor.svelte +172 -0
  87. package/dist/fields/LinkPickerEditor.svelte.d.ts +9 -0
  88. package/dist/fields/LinkPreview.svelte +60 -0
  89. package/dist/fields/LinkPreview.svelte.d.ts +7 -0
  90. package/dist/fields/LinkedRecordView.svelte +94 -0
  91. package/dist/fields/LinkedRecordView.svelte.d.ts +9 -0
  92. package/dist/fields/LinkedRecordsCell.svelte +59 -0
  93. package/dist/fields/LinkedRecordsCell.svelte.d.ts +11 -0
  94. package/dist/fields/LongTextEditor.svelte +157 -0
  95. package/dist/fields/LongTextEditor.svelte.d.ts +11 -0
  96. package/dist/fields/NumberEditor.svelte +309 -0
  97. package/dist/fields/NumberEditor.svelte.d.ts +12 -0
  98. package/dist/fields/OptionsEditor.svelte +117 -0
  99. package/dist/fields/OptionsEditor.svelte.d.ts +11 -0
  100. package/dist/fields/PercentCell.svelte +21 -0
  101. package/dist/fields/PercentCell.svelte.d.ts +8 -0
  102. package/dist/fields/PropertyInput.svelte +204 -0
  103. package/dist/fields/PropertyInput.svelte.d.ts +11 -0
  104. package/dist/fields/RatingCell.svelte +75 -0
  105. package/dist/fields/RatingCell.svelte.d.ts +16 -0
  106. package/dist/fields/RatingEditor.svelte +82 -0
  107. package/dist/fields/RatingEditor.svelte.d.ts +9 -0
  108. package/dist/fields/RecordView.svelte +310 -0
  109. package/dist/fields/RecordView.svelte.d.ts +11 -0
  110. package/dist/fields/RichTextCell.svelte +30 -0
  111. package/dist/fields/RichTextCell.svelte.d.ts +5 -0
  112. package/dist/fields/RowAdder.svelte +93 -0
  113. package/dist/fields/RowAdder.svelte.d.ts +7 -0
  114. package/dist/fields/RowHeader.svelte +99 -0
  115. package/dist/fields/RowHeader.svelte.d.ts +11 -0
  116. package/dist/fields/SelectCell.svelte +35 -0
  117. package/dist/fields/SelectCell.svelte.d.ts +15 -0
  118. package/dist/fields/SelectEditor.svelte +350 -0
  119. package/dist/fields/SelectEditor.svelte.d.ts +17 -0
  120. package/dist/fields/SummaryCell.svelte +160 -0
  121. package/dist/fields/SummaryCell.svelte.d.ts +11 -0
  122. package/dist/fields/SvBase.svelte +545 -0
  123. package/dist/fields/SvBase.svelte.d.ts +30 -0
  124. package/dist/fields/SvBaseForm.svelte +295 -0
  125. package/dist/fields/SvBaseForm.svelte.d.ts +22 -0
  126. package/dist/fields/SvBaseViewBar.svelte +644 -0
  127. package/dist/fields/SvBaseViewBar.svelte.d.ts +26 -0
  128. package/dist/fields/UserCell.svelte +39 -0
  129. package/dist/fields/UserCell.svelte.d.ts +9 -0
  130. package/dist/fields/UserFace.svelte +30 -0
  131. package/dist/fields/UserFace.svelte.d.ts +12 -0
  132. package/dist/fields/activity.svelte.d.ts +50 -0
  133. package/dist/fields/activity.svelte.js +110 -0
  134. package/dist/fields/attachments.d.ts +95 -0
  135. package/dist/fields/attachments.js +145 -0
  136. package/dist/fields/automation-outbound.d.ts +46 -0
  137. package/dist/fields/automation-outbound.js +61 -0
  138. package/dist/fields/automation-server.d.ts +53 -0
  139. package/dist/fields/automation-server.js +333 -0
  140. package/dist/fields/automations.svelte.d.ts +208 -0
  141. package/dist/fields/automations.svelte.js +437 -0
  142. package/dist/fields/bar-request.svelte.d.ts +16 -0
  143. package/dist/fields/bar-request.svelte.js +15 -0
  144. package/dist/fields/base-alerts.d.ts +24 -0
  145. package/dist/fields/base-alerts.js +64 -0
  146. package/dist/fields/base-connect.d.ts +82 -0
  147. package/dist/fields/base-connect.js +271 -0
  148. package/dist/fields/base-schema.d.ts +104 -0
  149. package/dist/fields/base-schema.js +266 -0
  150. package/dist/fields/base-sql.d.ts +62 -0
  151. package/dist/fields/base-sql.js +251 -0
  152. package/dist/fields/base-sync.d.ts +142 -0
  153. package/dist/fields/base-sync.js +499 -0
  154. package/dist/fields/base-tables.d.ts +89 -0
  155. package/dist/fields/base-tables.js +349 -0
  156. package/dist/fields/builtin-types.d.ts +13 -0
  157. package/dist/fields/builtin-types.js +773 -0
  158. package/dist/fields/button.d.ts +2 -0
  159. package/dist/fields/button.js +17 -0
  160. package/dist/fields/chart-view.d.ts +35 -0
  161. package/dist/fields/chart-view.js +154 -0
  162. package/dist/fields/column-order.d.ts +6 -0
  163. package/dist/fields/column-order.js +12 -0
  164. package/dist/fields/convert.d.ts +16 -0
  165. package/dist/fields/convert.js +176 -0
  166. package/dist/fields/copy-text.d.ts +13 -0
  167. package/dist/fields/copy-text.js +45 -0
  168. package/dist/fields/editor-request.svelte.d.ts +71 -0
  169. package/dist/fields/editor-request.svelte.js +52 -0
  170. package/dist/fields/field-ops.d.ts +72 -0
  171. package/dist/fields/field-ops.js +334 -0
  172. package/dist/fields/filter-model.d.ts +84 -0
  173. package/dist/fields/filter-model.js +446 -0
  174. package/dist/fields/format.d.ts +109 -0
  175. package/dist/fields/format.js +288 -0
  176. package/dist/fields/formula.d.ts +50 -0
  177. package/dist/fields/formula.js +530 -0
  178. package/dist/fields/grid-access.d.ts +60 -0
  179. package/dist/fields/grid-access.js +120 -0
  180. package/dist/fields/index.d.ts +34 -0
  181. package/dist/fields/index.js +312 -0
  182. package/dist/fields/linked-fields.d.ts +8 -0
  183. package/dist/fields/linked-fields.js +40 -0
  184. package/dist/fields/links.d.ts +10 -0
  185. package/dist/fields/links.js +46 -0
  186. package/dist/fields/name-list.d.ts +16 -0
  187. package/dist/fields/name-list.js +49 -0
  188. package/dist/fields/palette.d.ts +47 -0
  189. package/dist/fields/palette.js +69 -0
  190. package/dist/fields/record-delete.svelte.d.ts +12 -0
  191. package/dist/fields/record-delete.svelte.js +5 -0
  192. package/dist/fields/record-menu.d.ts +9 -0
  193. package/dist/fields/record-menu.js +78 -0
  194. package/dist/fields/registry.svelte.d.ts +176 -0
  195. package/dist/fields/registry.svelte.js +51 -0
  196. package/dist/fields/rich-text.d.ts +23 -0
  197. package/dist/fields/rich-text.js +48 -0
  198. package/dist/fields/row-color-choice.svelte.d.ts +31 -0
  199. package/dist/fields/row-color-choice.svelte.js +25 -0
  200. package/dist/fields/row-colors.d.ts +36 -0
  201. package/dist/fields/row-colors.js +90 -0
  202. package/dist/fields/row-height.d.ts +28 -0
  203. package/dist/fields/row-height.js +50 -0
  204. package/dist/fields/sources.svelte.d.ts +80 -0
  205. package/dist/fields/sources.svelte.js +131 -0
  206. package/dist/fields/summaries.d.ts +18 -0
  207. package/dist/fields/summaries.js +138 -0
  208. package/dist/fields/view-model.d.ts +129 -0
  209. package/dist/fields/view-model.js +492 -0
  210. package/dist/fields/viewbar/ChartPanel.svelte +61 -0
  211. package/dist/fields/viewbar/ChartPanel.svelte.d.ts +9 -0
  212. package/dist/fields/viewbar/ColorPanel.svelte +112 -0
  213. package/dist/fields/viewbar/ColorPanel.svelte.d.ts +9 -0
  214. package/dist/fields/viewbar/ConditionValue.svelte +153 -0
  215. package/dist/fields/viewbar/ConditionValue.svelte.d.ts +15 -0
  216. package/dist/fields/viewbar/FieldIcon.svelte +18 -0
  217. package/dist/fields/viewbar/FieldIcon.svelte.d.ts +9 -0
  218. package/dist/fields/viewbar/FilterPanel.svelte +192 -0
  219. package/dist/fields/viewbar/FilterPanel.svelte.d.ts +13 -0
  220. package/dist/fields/viewbar/GanttPanel.svelte +60 -0
  221. package/dist/fields/viewbar/GanttPanel.svelte.d.ts +10 -0
  222. package/dist/fields/viewbar/HideFieldsPanel.svelte +104 -0
  223. package/dist/fields/viewbar/HideFieldsPanel.svelte.d.ts +7 -0
  224. package/dist/fields/viewbar/SortPanel.svelte +85 -0
  225. package/dist/fields/viewbar/SortPanel.svelte.d.ts +8 -0
  226. package/dist/fields/viewbar/ViewsMenu.svelte +250 -0
  227. package/dist/fields/viewbar/ViewsMenu.svelte.d.ts +15 -0
  228. package/dist/fields/viewbar/panel-focus.d.ts +3 -0
  229. package/dist/fields/viewbar/panel-focus.js +22 -0
  230. package/dist/fields/viewbar/sort-state.d.ts +20 -0
  231. package/dist/fields/viewbar/sort-state.js +24 -0
  232. package/dist/gallery.d.ts +7 -0
  233. package/dist/gallery.js +36 -0
  234. package/dist/gantt/SvGridGantt.svelte +88 -16
  235. package/dist/gantt/gantt-config.d.ts +28 -0
  236. package/dist/index.d.ts +3 -0
  237. package/dist/index.js +6 -0
  238. package/dist/install.js +3 -0
  239. package/dist/license-core.d.ts +3 -3
  240. package/dist/license.js +1 -0
  241. package/dist/node/studio.js +141 -20
  242. package/dist/scheduler-config.d.ts +11 -0
  243. package/dist/sources/introspect-supabase.js +4 -1
  244. package/dist/sources/rest.js +6 -2
  245. package/dist/studio/emit-project.js +25 -6
  246. package/dist/studio/sveltekit3.js +13 -7
  247. package/dist/studio/ui-components.generated.js +94 -2
  248. package/dist/upgrade-prompt.d.ts +1 -1
  249. package/dist/version.d.ts +2 -2
  250. package/dist/version.js +2 -2
  251. package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
  252. package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
  253. package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
  254. package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
  255. package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
  256. package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
  257. package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
  258. package/dist/wc/sv-sheet-element.js +7561 -7618
  259. package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
  260. package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
  261. package/package.json +14 -3
  262. package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
  263. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
  264. package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
  265. package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
  266. package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
  267. package/dist/designer/assets/src-DqHCqori.js +0 -58
  268. package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
  269. package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
  270. package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
package/README.md CHANGED
@@ -35,6 +35,7 @@
35
35
  - **Scheduling automation** - a client-side cron / one-off scheduler (`createScheduler`, `parseCron`, `CRON_PRESETS`) to drive recurring exports and alerts with no backend.
36
36
  - **Spreadsheet** (Suite) - `<SvSheet>` and the `<sv-sheet>` custom element over a built-in formula engine: around 185 functions, cross-sheet references, named ranges, absolute and relative refs, dynamic arrays with spill, iterative calculation with convergence, circular-reference detection, volatile-function tracking, and dependency-graph recalculation. In-house, with no runtime dependency.
37
37
  - **Spreadsheet file formats** (Suite) - read and write `.xlsx`, `.xls` (BIFF8) and `.ods`, plus `.csv`. Formulas, tables, named ranges, merges, frozen panes, sparklines, charts and images survive an `.xlsx` round trip. Pivot tables round-trip as their computed cells, not as live PivotTable parts, and `.xls` keeps the BIFF8 row and column ceilings.
38
+ - **SvGrid Base** (Suite) - `<SvBase>` from `@svgrid/enterprise/base`: a table database inside your app. Typed fields (select, user, attachment, rating, linked record, lookup, rollup, formula and more), a primary field, saved views (grid, Kanban, calendar, gallery, form) each with its own filter, sorts, groups and row colors, and an expanded record with changes and comments.
38
39
  - **SvGrid Studio** (Suite) - a schema-driven data-app layer: `EntitySchema`, memory / SQL / Supabase / REST data sources, a SvelteKit adapter, edit panels, master/detail, schema charts + KPI dashboards, and the project model + codegen behind the visual designer. See [`@svgrid/studio`](https://www.npmjs.com/package/@svgrid/studio).
39
40
 
40
41
  ## Install
@@ -4,11 +4,13 @@
4
4
  * severity / scope, the trigger (data change, relative change, validation,
5
5
  * scheduled), the predicate (embedded SvExpressionEditor), and the actions
6
6
  * (toast / badge / highlight / flash / prevent-edit / log). Reuses the grid
7
- * UI kit so it matches the rest of the app.
7
+ * UI kit so it matches the rest of the app. Under an SvGrid Base table the
8
+ * condition is written with the Base's filter instead (`condition-editor.ts`).
8
9
  */
9
10
  import { SvModal, SvField, SvTextInput, SvDropDownList, SvColorInput, SvTagsInput } from '@svgrid/grid'
10
11
  import SvExpressionEditor from './SvExpressionEditor.svelte'
11
12
  import { CRON_PRESETS } from './scheduling'
13
+ import { getAlertConditionEditor } from './alerts/condition-editor'
12
14
  import type { ExprColumn } from './expressions/expression-columns'
13
15
  import type { PredicateExpr } from './expressions/expression-types'
14
16
  import type {
@@ -61,6 +63,10 @@
61
63
  { value: 'crossed', label: 'crossed a threshold' },
62
64
  ]
63
65
 
66
+ const custom = getAlertConditionEditor()
67
+ const DEFAULT_TOKENS = ['{value}', '{column}', '{field}']
68
+ const tokens = $derived(custom?.tokens?.() ?? DEFAULT_TOKENS)
69
+
64
70
  // --- Draft state ---------------------------------------------------------
65
71
 
66
72
  let name = $state('')
@@ -100,7 +106,7 @@
100
106
  severity = rule?.severity ?? 'warning'
101
107
  scope = rule?.scope ?? 'row'
102
108
  triggerType = rule?.trigger.type ?? 'dataChange'
103
- predicate = rule?.predicate ?? { kind: 'const', value: true }
109
+ predicate = rule?.predicate ?? custom?.initial() ?? { kind: 'const', value: true }
104
110
  selected = new Set(rule?.actions.map((a) => a.kind) ?? ['toast'])
105
111
  message = rule?.actions.find((a) => a.message)?.message ?? ''
106
112
  const styled = rule?.actions.find((a) => a.style)
@@ -133,6 +139,7 @@
133
139
  selected = next
134
140
  }
135
141
 
142
+ const ownEditor = $derived(custom && custom.accepts(predicate) ? custom : null)
136
143
  const showStyle = $derived(selected.has('highlight') || selected.has('badge'))
137
144
  const showTargets = $derived(showStyle || selected.has('cellFlash') || scope === 'cell')
138
145
 
@@ -230,7 +237,11 @@
230
237
  {/if}
231
238
 
232
239
  <SvField label={triggerType === 'validation' ? 'Reject the edit when' : 'Condition'}>
233
- <SvExpressionEditor {columns} bind:value={predicate} {rows} />
240
+ {#if ownEditor}
241
+ <ownEditor.component value={predicate} onChange={(next) => (predicate = next)} />
242
+ {:else}
243
+ <SvExpressionEditor {columns} bind:value={predicate} {rows} />
244
+ {/if}
234
245
  </SvField>
235
246
 
236
247
  <fieldset class="ar-fieldset">
@@ -244,8 +255,8 @@
244
255
  {/each}
245
256
  </div>
246
257
  <div class="ar-message">
247
- <SvField label="Message (tokens: {'{value}'}, {'{column}'}, {'{field}'})">
248
- <SvTextInput value={message} onChange={(v) => (message = v)} placeholder="e.g. {'{'}region{'}'} price hit {'{'}value{'}'}" />
258
+ <SvField label="Message" hint={`Tokens: ${tokens.slice(0, 4).join(', ')}`}>
259
+ <SvTextInput value={message} onChange={(v) => (message = v)} placeholder={custom ? `e.g. ${tokens[0] ?? ''} is overdue` : 'e.g. {region} price hit {value}'} />
249
260
  </SvField>
250
261
  </div>
251
262
  {#if showStyle}
@@ -61,6 +61,10 @@
61
61
  storageKey?: string
62
62
  getValue?: (row: TRow, columnId: string) => unknown
63
63
  locale?: string | ReadonlyArray<string>
64
+ /** A message token the built-in ones do not cover, as text (`{Due date}` in SvGrid Base). */
65
+ token?: (row: TRow, name: string) => string | undefined
66
+ /** The rules changed in the manager: added, edited, turned on or off, deleted. */
67
+ onRulesChange?: (rules: AlertRule[]) => void
64
68
  /**
65
69
  * Reactively scan `data` for changes (default). Set `false` when a consumer
66
70
  * drives evaluation through `pushChanged` (large / streaming feeds) - the
@@ -92,6 +96,8 @@
92
96
  storageKey,
93
97
  getValue,
94
98
  locale,
99
+ token,
100
+ onRulesChange,
95
101
  watch = true,
96
102
  onReady,
97
103
  toastCooldownMs = 0,
@@ -131,6 +137,7 @@
131
137
  getRowId: rowIdOf,
132
138
  get getValue() { return getValue },
133
139
  get locale() { return locale },
140
+ get token() { return token },
134
141
  })
135
142
 
136
143
  // The observer owns all scheduling + change tracking. It runs evaluation on a
@@ -239,6 +246,7 @@
239
246
  function onRulesChanged(next: AlertRule[]) {
240
247
  rules = next
241
248
  rulesVersion += 1
249
+ onRulesChange?.(next)
242
250
  }
243
251
  </script>
244
252
 
@@ -17,6 +17,10 @@ declare function $$render<TRow extends Record<string, unknown>>(): {
17
17
  storageKey?: string;
18
18
  getValue?: (row: TRow, columnId: string) => unknown;
19
19
  locale?: string | ReadonlyArray<string>;
20
+ /** A message token the built-in ones do not cover, as text (`{Due date}` in SvGrid Base). */
21
+ token?: (row: TRow, name: string) => string | undefined;
22
+ /** The rules changed in the manager: added, edited, turned on or off, deleted. */
23
+ onRulesChange?: (rules: AlertRule[]) => void;
20
24
  /**
21
25
  * Reactively scan `data` for changes (default). Set `false` when a consumer
22
26
  * drives evaluation through `pushChanged` (large / streaming feeds) - the
@@ -33,6 +33,7 @@
33
33
  popIn,
34
34
  createDismissableLayer,
35
35
  onScrollOutside,
36
+ getGridExtension,
36
37
  } from "@svgrid/grid";
37
38
 
38
39
  let {
@@ -258,6 +259,36 @@
258
259
  const fieldColumns = $derived(
259
260
  columns.filter((c) => typeof c.field === "string"),
260
261
  );
262
+ // Columns with a `fieldType` (the fields module) filled in as the grid fills
263
+ // them, so a card shows a select as its pill, files as thumbnails, a date
264
+ // in its format - not the raw value.
265
+ const gridExt = $derived(getGridExtension());
266
+ const filledByField = $derived.by(() => {
267
+ const fill = gridExt?.column;
268
+ const out = new Map<string, Record<string, any>>();
269
+ for (const c of fieldColumns as Array<Record<string, any>>) {
270
+ out.set(c.field as string, c.fieldType && fill ? fill(c, fieldColumns) : c);
271
+ }
272
+ return out;
273
+ });
274
+ // What a field cell reads off the grid it is in (the grid itself when the
275
+ // board hands it over); a card has a width of its own.
276
+ // `board` is gone for a moment while the view switches away: cells still read it.
277
+ const CARD_CELL_GRID = $derived(board?.cellGrid?.(220) ?? { getColumnWidth: () => 220 });
278
+ function drawsFieldCell(field: string): Record<string, any> | null {
279
+ const def = filledByField.get(field);
280
+ if (!def?.fieldType || def.cell != null || !gridExt?.cell || gridExt.hasCell?.(def) === false) return null;
281
+ return def;
282
+ }
283
+ function cardText(row: TData, field: string): string {
284
+ const def = filledByField.get(field);
285
+ const value = fieldValue(row, field);
286
+ const f = def?.formatter as ((x: { value: unknown; row: unknown; column: unknown }) => unknown) | undefined;
287
+ // The row as a grid cell hands it to a formatter: its id and the record.
288
+ if (f) return String(f({ value, row: { id: key(row), original: row }, column: { id: def?.id ?? field, columnDef: def } }) ?? "");
289
+ return fmt(value);
290
+ }
291
+ const isBlank = (v: unknown) => v == null || v === "" || (Array.isArray(v) && v.length === 0);
261
292
  function headerLabel(field: string): string {
262
293
  const col = fieldColumns.find((c) => c.field === field);
263
294
  return col && typeof col.header === "string" ? col.header : field;
@@ -647,6 +678,11 @@
647
678
  }
648
679
  }
649
680
  }
681
+ // A lane marked hideWhenEmpty shows only while a card is in it.
682
+ if (base.some((l) => l.hideWhenEmpty)) {
683
+ const used = new Set(data.map((r) => effectiveLane(r)));
684
+ base = base.filter((l) => !l.hideWhenEmpty || used.has(l.id));
685
+ }
650
686
  if (!laneOrder.length) return base;
651
687
  // Apply the drag-reorder overlay: ordered ids first, then any new lanes.
652
688
  const byId = new Map(base.map((l) => [l.id, l]));
@@ -1445,12 +1481,23 @@
1445
1481
  {#if metaFields.length}
1446
1482
  <dl class="sv-board-card-meta">
1447
1483
  {#each metaFields as field (field)}
1448
- {@const v = fmt(fieldValue(row, field))}
1449
- {#if v !== ""}
1450
- <div class="sv-board-card-metarow">
1451
- <dt>{headerLabel(field)}</dt>
1452
- <dd>{v}</dd>
1453
- </div>
1484
+ {@const cellDef = drawsFieldCell(field)}
1485
+ {#if cellDef && gridExt?.cell}
1486
+ {#if !isBlank(fieldValue(row, field))}
1487
+ {@const FieldCellView = gridExt.cell}
1488
+ <div class="sv-board-card-metarow">
1489
+ <dt>{headerLabel(field)}</dt>
1490
+ <dd class="sv-board-card-fieldcell"><FieldCellView ctrl={CARD_CELL_GRID} row={{ id: key(row), original: row }} column={{ id: cellDef.id ?? field, columnDef: cellDef }} value={fieldValue(row, field)} /></dd>
1491
+ </div>
1492
+ {/if}
1493
+ {:else}
1494
+ {@const v = cardText(row, field)}
1495
+ {#if v !== ""}
1496
+ <div class="sv-board-card-metarow">
1497
+ <dt>{headerLabel(field)}</dt>
1498
+ <dd>{v}</dd>
1499
+ </div>
1500
+ {/if}
1454
1501
  {/if}
1455
1502
  {/each}
1456
1503
  </dl>
@@ -1583,7 +1630,7 @@
1583
1630
  {@const labels = labelsOf(row)}
1584
1631
  {@const due = dueOf(row)}
1585
1632
  {@const asg = assigneesOf(row)}
1586
- {@const comments = countOf(row, board.commentsField)}
1633
+ {@const comments = board.commentCount ? board.commentCount(row) : countOf(row, board.commentsField)}
1587
1634
  {@const attachments = countOf(row, board.attachmentsField)}
1588
1635
  {@const age = ageOf(row)}
1589
1636
  {@const blocked = isFlagged(row)}
@@ -1608,9 +1655,11 @@
1608
1655
  type="button"
1609
1656
  class="sv-board-badge is-meta is-btn"
1610
1657
  title="Comments"
1658
+ aria-label={`${comments} ${comments === 1 ? "comment" : "comments"}`}
1611
1659
  onclick={(e) => {
1612
1660
  e.stopPropagation();
1613
- commentsOpen[key(row)] = !commentsOpen[key(row)];
1661
+ if (board.onCommentsOpen) board.onCommentsOpen(row);
1662
+ else commentsOpen[key(row)] = !commentsOpen[key(row)];
1614
1663
  }}
1615
1664
  >
1616
1665
  <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.3" stroke-linejoin="round"/></svg>
@@ -1943,10 +1992,10 @@
1943
1992
  line-height: 1.3;
1944
1993
  }
1945
1994
  .sv-board-card-meta {
1946
- margin: 6px 0 0;
1995
+ margin: 8px 0 0;
1947
1996
  display: flex;
1948
1997
  flex-direction: column;
1949
- gap: 2px;
1998
+ gap: 6px;
1950
1999
  }
1951
2000
  .sv-board-card-metarow {
1952
2001
  display: flex;
@@ -1964,6 +2013,16 @@
1964
2013
  text-overflow: ellipsis;
1965
2014
  white-space: nowrap;
1966
2015
  }
2016
+ /* A field drawn as its cell draws it (pills, avatars, thumbnails), on one line. */
2017
+ .sv-board-card-metarow {
2018
+ align-items: center;
2019
+ }
2020
+ .sv-board-card-fieldcell {
2021
+ display: flex;
2022
+ align-items: center;
2023
+ min-width: 0;
2024
+ flex: 1;
2025
+ }
1967
2026
  .sv-board-lane-empty {
1968
2027
  text-align: center;
1969
2028
  font-size: 0.76rem;
@@ -48,8 +48,14 @@
48
48
  )
49
49
 
50
50
  /** A column's editor type mapped onto the form's control vocabulary. */
51
- function controlFor(editorType: string, hasOptions: boolean): FormFieldType {
52
- if (hasOptions) return 'select'
51
+ function controlFor(c: BulkEditField): FormFieldType {
52
+ const { editorType } = c
53
+ if (c.options?.length) return c.multiple ? 'multiselect' : 'select'
54
+ // Field editors: a number typed as the field writes it ("$1,200", "1:30"),
55
+ // read by the column's own parser; a rating as stars; long text as lines.
56
+ if (editorType === 'fieldNumber') return 'text'
57
+ if (editorType === 'fieldRating') return 'rating'
58
+ if (editorType === 'fieldLongText') return 'textarea'
53
59
  switch (editorType) {
54
60
  case 'number': return 'number'
55
61
  case 'checkbox': return 'checkbox'
@@ -87,7 +93,8 @@
87
93
  columns.map((c) => ({
88
94
  name: c.id,
89
95
  label: c.label,
90
- type: controlFor(c.editorType, !!c.options?.length),
96
+ type: controlFor(c),
97
+ ...(c.max ? { max: c.max } : {}),
91
98
  // Placeholder only. The same sentence repeated under every differing
92
99
  // field was five identical lines of noise - the lead says it once.
93
100
  placeholder: mixed.has(c.id) ? messages.bulkEditMixed : undefined,
@@ -104,10 +111,11 @@
104
111
  for (const [name, raw] of Object.entries(values)) {
105
112
  const column = byId.get(name)
106
113
  if (!column) continue
107
- const next = coerceBulkValue(column.editorType, raw)
114
+ const next = column.parse && raw !== '' && raw != null ? column.parse(raw) : coerceBulkValue(column.editorType, raw)
108
115
  // Untouched fields are the ones that keep every row's own value. A field
109
116
  // that opened "Multiple values" and was left blank is untouched too.
110
- if (next === baseline[name]) continue
117
+ // By content: a multiple select's list is a new array each time.
118
+ if (next === baseline[name] || JSON.stringify(next) === JSON.stringify(baseline[name])) continue
111
119
  if (mixed.has(name) && (raw === '' || raw == null)) continue
112
120
  edits[name] = next
113
121
  }
@@ -0,0 +1,286 @@
1
+ <script
2
+ lang="ts"
3
+ generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData"
4
+ >
5
+ // Gallery renderer for SvGrid: rendered in place of the table when the
6
+ // `gallery` prop is set. Each row is a card - a cover image, a title and a
7
+ // few fields - in a wall that fills the width. The rows are the grid's
8
+ // filtered + sorted rows, so its search box, filters and sort flow through.
9
+ // - the cover is an image URL or the first image of a list of files
10
+ // - a column with a `fieldType` (the fields module) draws as its cell does
11
+ // (select pills, avatars, stars), other columns through their formatter
12
+ // or `format`
13
+ // - cards render in batches as the wall scrolls, so a big table does not
14
+ // mount thousands of cards up front
15
+ // Themes entirely from the grid's `--sg-*` tokens.
16
+ import type { ColumnDef, GalleryConfig, RowData, TableFeatures } from "@svgrid/grid";
17
+ import { formatValueForExport, getGridExtension } from "@svgrid/grid";
18
+
19
+ let {
20
+ data,
21
+ columns,
22
+ gallery,
23
+ getRowId,
24
+ }: {
25
+ data: ReadonlyArray<TData>;
26
+ columns: ReadonlyArray<ColumnDef<TFeatures, TData>>;
27
+ gallery: GalleryConfig<TFeatures, TData>;
28
+ getRowId?: (row: TData, index: number) => string;
29
+ } = $props();
30
+
31
+ type Def = Record<string, any>;
32
+ const ext = $derived(getGridExtension());
33
+ const leaves = $derived.by(() => {
34
+ const out: Def[] = [];
35
+ const walk = (cols: ReadonlyArray<Def>) => {
36
+ for (const c of cols) {
37
+ if (Array.isArray(c.columns) && c.columns.length) walk(c.columns);
38
+ else if (typeof c.field === "string") out.push(c);
39
+ }
40
+ };
41
+ walk(columns as unknown as Def[]);
42
+ // Field-typed columns filled in as the grid fills them (formatter, cell).
43
+ const fill = ext?.column;
44
+ return fill ? out.map((c) => (c.fieldType ? fill(c, out) : c)) : out;
45
+ });
46
+ const byField = $derived(new Map(leaves.map((c) => [c.field as string, c])));
47
+ const titleField = $derived((gallery.titleField as string | undefined) ?? (leaves[0]?.field as string | undefined));
48
+ const coverField = $derived(gallery.coverField as string | undefined);
49
+ const MAX_FIELDS = 6;
50
+ const cardFields = $derived(
51
+ (gallery.cardFields as ReadonlyArray<string> | undefined) ??
52
+ leaves
53
+ .map((c) => c.field as string)
54
+ .filter((f) => f !== titleField && f !== coverField)
55
+ .slice(0, MAX_FIELDS),
56
+ );
57
+ const SIZES = { small: 180, medium: 240, large: 320 } as const;
58
+ const cardWidth = $derived(
59
+ typeof gallery?.cardSize === "number" ? gallery.cardSize : SIZES[gallery?.cardSize ?? "medium"],
60
+ );
61
+ const showLabels = $derived(gallery.showLabels !== false);
62
+
63
+ // ---- values ----
64
+ const valueOf = (row: TData, field: string) => {
65
+ const def = byField.get(field);
66
+ return def?.fieldFn ? def.fieldFn(row) : (row as Record<string, unknown>)[field];
67
+ };
68
+ // `id`: the card's key, so a formatter gets the row as a grid cell hands it over.
69
+ function text(row: TData, field: string, id = ""): string {
70
+ const def = byField.get(field);
71
+ const value = valueOf(row, field);
72
+ if (typeof def?.formatter === "function") {
73
+ return String(def.formatter({ value, row: { id, original: row }, column: { id: def.id ?? field, columnDef: def } }) ?? "");
74
+ }
75
+ if (def?.format) return formatValueForExport(value, def.format);
76
+ if (value == null) return "";
77
+ if (value instanceof Date) return value.toLocaleDateString();
78
+ if (typeof value === "boolean") return value ? "Yes" : "No";
79
+ if (Array.isArray(value)) return value.map((v) => (v && typeof v === "object" ? (v.name ?? v.label ?? "") : String(v))).join(", ");
80
+ return String(value);
81
+ }
82
+ const isBlank = (v: unknown) => v == null || v === "" || (Array.isArray(v) && v.length === 0);
83
+ /** The column when a field type draws its own cell, else null. */
84
+ function cellDef(field: string): Def | null {
85
+ const def = byField.get(field);
86
+ if (!def?.fieldType || def.cell != null || !ext?.cell || ext.hasCell?.(def) === false) return null;
87
+ return def;
88
+ }
89
+ // What a field cell reads off the grid it is in (the grid itself when the
90
+ // gallery hands it over); a card has a width of its own.
91
+ // `gallery` is gone for a moment while the view switches away: cells still read it.
92
+ const CARD_GRID = $derived(gallery?.cellGrid?.(cardWidth - 24) ?? { getColumnWidth: () => cardWidth - 24 });
93
+
94
+ // ---- cover ----
95
+ const IMAGE_URL = /^(https?:|blob:|data:image\/(png|jpeg|gif|webp|avif|svg\+xml);)/i;
96
+ const isImageFile = (f: { type?: string; name?: string; url?: string }) =>
97
+ (f.type ?? "").startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)$/i.test(f.name ?? f.url ?? "");
98
+ function coverUrl(row: TData): string | null {
99
+ if (!coverField) return null;
100
+ const v = valueOf(row, coverField) as unknown;
101
+ const pick = (u: unknown) => (typeof u === "string" && IMAGE_URL.test(u.trim()) ? u.trim() : null);
102
+ if (typeof v === "string") return pick(v);
103
+ if (Array.isArray(v)) {
104
+ for (const f of v) {
105
+ if (typeof f === "string" && pick(f)) return pick(f);
106
+ if (f && typeof f === "object" && isImageFile(f)) return pick(f.thumbnailUrl ?? f.url);
107
+ }
108
+ return null;
109
+ }
110
+ if (v && typeof v === "object") return pick((v as { url?: unknown }).url);
111
+ return null;
112
+ }
113
+
114
+ // ---- identity + batches ----
115
+ const keyOf = (row: TData, i: number) => (getRowId ? getRowId(row, i) : String(i));
116
+ const BATCH = 120;
117
+ let shownCount = $state(BATCH);
118
+ const shown = $derived(data.slice(0, shownCount));
119
+ // A new filter or search starts the wall from the top batch again.
120
+ $effect(() => {
121
+ void data;
122
+ shownCount = BATCH;
123
+ });
124
+ function more(node: HTMLElement) {
125
+ const io = new IntersectionObserver((entries) => {
126
+ if (entries.some((e) => e.isIntersecting)) shownCount += BATCH;
127
+ }, { rootMargin: "400px" });
128
+ io.observe(node);
129
+ return { destroy: () => io.disconnect() };
130
+ }
131
+
132
+ const open = (row: TData) => gallery.onCardClick?.(row);
133
+ const fieldLabel = (field: string) => {
134
+ const h = byField.get(field)?.header;
135
+ return typeof h === "string" ? h : field;
136
+ };
137
+ const iconOf = (field: string) => {
138
+ const def = byField.get(field);
139
+ return def ? (ext?.headerIcon?.(def) ?? null) : null;
140
+ };
141
+ </script>
142
+
143
+ <div class="sv-gal" style:--sv-gal-card={`${cardWidth}px`} role="list" aria-label="Records">
144
+ {#each shown as row, i (keyOf(row, i))}
145
+ {@const url = coverUrl(row)}
146
+ {@const title = titleField ? text(row, titleField, keyOf(row, i)) : ""}
147
+ {#if gallery.card}
148
+ <div class="sv-gal-custom" role="listitem">
149
+ {#if gallery.onCardClick}
150
+ <!-- A drawn card is one button: a click or Enter opens it. -->
151
+ <div
152
+ class="sv-gal-custom-hit"
153
+ role="button"
154
+ tabindex="0"
155
+ onclick={() => open(row)}
156
+ onkeydown={(e) => {
157
+ if (e.target !== e.currentTarget || (e.key !== 'Enter' && e.key !== ' ')) return
158
+ e.preventDefault()
159
+ open(row)
160
+ }}
161
+ >{@render gallery.card(row)}</div>
162
+ {:else}
163
+ {@render gallery.card(row)}
164
+ {/if}
165
+ </div>
166
+ {:else}
167
+ <!-- The title is the card's button; a click anywhere else on the card does the same. -->
168
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
169
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
170
+ <div class="sv-gal-card" class:is-clickable={!!gallery.onCardClick} role="listitem" onclick={() => open(row)}>
171
+ {#if coverField}
172
+ <div class="sv-gal-cover" class:is-contain={gallery.coverFit === "contain"} class:is-empty={!url}>
173
+ {#if url}<img src={url} alt="" loading="lazy" decoding="async" />{/if}
174
+ </div>
175
+ {/if}
176
+ <div class="sv-gal-body">
177
+ {#if gallery.onCardClick}
178
+ <button
179
+ type="button"
180
+ class="sv-gal-title"
181
+ onclick={(e) => {
182
+ e.stopPropagation();
183
+ open(row);
184
+ }}>{title || "Untitled"}</button
185
+ >
186
+ {:else}
187
+ <div class="sv-gal-title">{title || "Untitled"}</div>
188
+ {/if}
189
+ {#each cardFields as field (field)}
190
+ {@const def = cellDef(field)}
191
+ {@const value = valueOf(row, field)}
192
+ {#if !isBlank(value)}
193
+ <div class="sv-gal-field">
194
+ {#if showLabels}
195
+ <div class="sv-gal-label">
196
+ {#if iconOf(field)}
197
+ <svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d={iconOf(field)} fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" /></svg>
198
+ {/if}
199
+ {fieldLabel(field)}
200
+ </div>
201
+ {/if}
202
+ <div class="sv-gal-value">
203
+ {#if def && ext?.cell}
204
+ {@const Cell = ext.cell}
205
+ <Cell ctrl={CARD_GRID} row={{ id: keyOf(row, i), original: row }} column={{ id: def.id ?? field, columnDef: def }} {value} />
206
+ {:else}
207
+ {text(row, field, keyOf(row, i))}
208
+ {/if}
209
+ </div>
210
+ </div>
211
+ {/if}
212
+ {/each}
213
+ </div>
214
+ </div>
215
+ {/if}
216
+ {:else}
217
+ <p class="sv-gal-empty">{gallery.emptyText ?? "No records"}</p>
218
+ {/each}
219
+ {#if shownCount < data.length}
220
+ <div class="sv-gal-more" use:more aria-hidden="true"></div>
221
+ {/if}
222
+ </div>
223
+
224
+ <style>
225
+ .sv-gal {
226
+ display: grid;
227
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--sv-gal-card)), 1fr));
228
+ grid-auto-rows: max-content;
229
+ gap: 18px;
230
+ padding: 18px;
231
+ height: 100%;
232
+ box-sizing: border-box;
233
+ overflow: auto;
234
+ align-content: start;
235
+ background: color-mix(in srgb, var(--sg-fg, #0f172a) 3%, var(--sg-bg, #fff));
236
+ color: var(--sg-fg, #0f172a);
237
+ font-family: var(--sg-font, inherit);
238
+ font-size: 13px;
239
+ }
240
+ .sv-gal-card {
241
+ min-width: 0;
242
+ overflow: hidden;
243
+ background: var(--sg-bg, #fff);
244
+ border: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 9%, transparent);
245
+ border-radius: 12px;
246
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
247
+ transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
248
+ }
249
+ .sv-gal-card.is-clickable { cursor: pointer; }
250
+ .sv-gal-card.is-clickable:hover {
251
+ transform: translateY(-2px);
252
+ border-color: color-mix(in srgb, var(--sg-fg, #0f172a) 14%, transparent);
253
+ box-shadow: 0 2px 4px rgba(15, 23, 42, 0.06), 0 12px 28px -12px rgba(15, 23, 42, 0.32);
254
+ }
255
+ .sv-gal-card:has(.sv-gal-title:focus-visible) { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 2px; }
256
+ .sv-gal-cover {
257
+ aspect-ratio: 4 / 3;
258
+ overflow: hidden;
259
+ background: color-mix(in srgb, var(--sg-fg, #0f172a) 5%, transparent);
260
+ }
261
+ .sv-gal-cover img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.4s ease; }
262
+ .sv-gal-cover.is-contain img { object-fit: contain; }
263
+ .sv-gal-card.is-clickable:hover .sv-gal-cover img { transform: scale(1.04); }
264
+ .sv-gal-body { padding: 12px 14px 14px; display: grid; gap: 9px; min-width: 0; }
265
+ .sv-gal-title {
266
+ padding: 0; border: 0; background: none; color: inherit; font: inherit; font-weight: 600; font-size: 14.5px; line-height: 1.3;
267
+ letter-spacing: -0.005em; text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: inherit; outline: none;
268
+ }
269
+ .sv-gal-field { min-width: 0; }
270
+ .sv-gal-label {
271
+ display: flex; align-items: center; gap: 4px; margin-bottom: 2px;
272
+ font-size: 10.5px; font-weight: 500; letter-spacing: 0.03em; text-transform: uppercase; color: var(--sg-muted, #64748b);
273
+ }
274
+ .sv-gal-label svg { flex: none; }
275
+ /* Values are read here, not edited: a click on one opens the card. */
276
+ .sv-gal-value { min-height: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; }
277
+ .sv-gal-custom { min-width: 0; }
278
+ .sv-gal-custom-hit { cursor: pointer; border-radius: 12px; outline: none; }
279
+ .sv-gal-custom-hit:focus-visible { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 2px; }
280
+ .sv-gal-empty { grid-column: 1 / -1; color: var(--sg-muted, #64748b); margin: 8px 2px; }
281
+ .sv-gal-more { grid-column: 1 / -1; height: 1px; }
282
+ @media (prefers-reduced-motion: reduce) {
283
+ .sv-gal-card, .sv-gal-cover img { transition: none; }
284
+ .sv-gal-card.is-clickable:hover, .sv-gal-card.is-clickable:hover .sv-gal-cover img { transform: none; }
285
+ }
286
+ </style>
@@ -0,0 +1,30 @@
1
+ import type { ColumnDef, GalleryConfig, RowData, TableFeatures } from "@svgrid/grid";
2
+ declare function $$render<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(): {
3
+ props: {
4
+ data: ReadonlyArray<TData>;
5
+ columns: ReadonlyArray<ColumnDef<TFeatures, TData>>;
6
+ gallery: GalleryConfig<TFeatures, TData>;
7
+ getRowId?: (row: TData, index: number) => string;
8
+ };
9
+ exports: {};
10
+ bindings: "";
11
+ slots: {};
12
+ events: {};
13
+ };
14
+ declare class __sveltets_Render<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> {
15
+ props(): ReturnType<typeof $$render<TFeatures, TData>>['props'];
16
+ events(): ReturnType<typeof $$render<TFeatures, TData>>['events'];
17
+ slots(): ReturnType<typeof $$render<TFeatures, TData>>['slots'];
18
+ bindings(): "";
19
+ exports(): {};
20
+ }
21
+ interface $$IsomorphicComponent {
22
+ new <TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TFeatures, TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TFeatures, TData>['props']>, ReturnType<__sveltets_Render<TFeatures, TData>['events']>, ReturnType<__sveltets_Render<TFeatures, TData>['slots']>> & {
23
+ $$bindings?: ReturnType<__sveltets_Render<TFeatures, TData>['bindings']>;
24
+ } & ReturnType<__sveltets_Render<TFeatures, TData>['exports']>;
25
+ <TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(internal: unknown, props: ReturnType<__sveltets_Render<TFeatures, TData>['props']> & {}): ReturnType<__sveltets_Render<TFeatures, TData>['exports']>;
26
+ z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
27
+ }
28
+ declare const SvGridGallery: $$IsomorphicComponent;
29
+ type SvGridGallery<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> = InstanceType<typeof SvGridGallery<TFeatures, TData>>;
30
+ export default SvGridGallery;