@svgrid/enterprise 3.0.0 → 3.0.2

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 (390) hide show
  1. package/README.md +95 -95
  2. package/dist/ExpressionEditorHarness.svelte.d.ts +2 -2
  3. package/dist/SvAdvancedFilter.svelte.d.ts +2 -2
  4. package/dist/SvAlertRuleEditor.svelte.d.ts +2 -2
  5. package/dist/SvAlertsManager.svelte.d.ts +3 -3
  6. package/dist/SvAlertsPanel.svelte.d.ts +1 -1
  7. package/dist/SvAuthGate.svelte +115 -115
  8. package/dist/SvAuthGate.svelte.d.ts +1 -1
  9. package/dist/SvBoard.svelte.d.ts +1 -1
  10. package/dist/SvExportMenu.svelte +553 -553
  11. package/dist/SvExportMenu.svelte.d.ts +2 -2
  12. package/dist/SvExpressionEditor.svelte.d.ts +2 -2
  13. package/dist/SvFileInput.svelte +113 -113
  14. package/dist/SvFormulaBar.svelte +606 -0
  15. package/dist/SvFormulaBar.svelte.d.ts +62 -0
  16. package/dist/SvGridAlerts.svelte.d.ts +2 -2
  17. package/dist/SvGridBoard.svelte +2358 -2358
  18. package/dist/SvGridBulkEditDrawer.svelte.d.ts +1 -1
  19. package/dist/SvGridEditPanel.svelte +1157 -1157
  20. package/dist/SvGridEditPanel.svelte.d.ts +3 -3
  21. package/dist/SvGridMasterDetail.svelte +116 -116
  22. package/dist/SvGridMasterDetail.svelte.d.ts +1 -1
  23. package/dist/SvGridScheduler.svelte +5358 -5358
  24. package/dist/SvImportDialog.svelte +1020 -1020
  25. package/dist/SvImportDialog.svelte.d.ts +2 -2
  26. package/dist/SvLookupInput.svelte +180 -180
  27. package/dist/SvLookupInput.svelte.d.ts +1 -1
  28. package/dist/SvPivotDesigner.svelte +2041 -1990
  29. package/dist/SvPivotDesigner.svelte.d.ts +2 -2
  30. package/dist/SvRecordDetail.svelte.d.ts +1 -1
  31. package/dist/SvRibbonIcon.svelte +46 -0
  32. package/dist/SvRibbonIcon.svelte.d.ts +17 -0
  33. package/dist/SvSchedule.svelte.d.ts +1 -1
  34. package/dist/SvSchemaChart.svelte +236 -234
  35. package/dist/SvSchemaChart.svelte.d.ts +2 -2
  36. package/dist/SvSchemaDashboard.svelte +130 -130
  37. package/dist/SvSchemaDashboard.svelte.d.ts +3 -3
  38. package/dist/SvSheet.svelte +4114 -0
  39. package/dist/SvSheet.svelte.d.ts +142 -0
  40. package/dist/SvSheetComment.svelte +98 -0
  41. package/dist/SvSheetComment.svelte.d.ts +22 -0
  42. package/dist/SvSheetConditionalFormat.svelte +162 -0
  43. package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
  44. package/dist/SvSheetDataValidation.svelte +249 -0
  45. package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
  46. package/dist/SvSheetFilterMenu.svelte +217 -0
  47. package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
  48. package/dist/SvSheetFindReplace.svelte +132 -0
  49. package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
  50. package/dist/SvSheetFormatCells.svelte +421 -0
  51. package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
  52. package/dist/SvSheetGoalSeek.svelte +144 -0
  53. package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
  54. package/dist/SvSheetInsertFunction.svelte +147 -0
  55. package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
  56. package/dist/SvSheetListPicker.svelte +89 -0
  57. package/dist/SvSheetListPicker.svelte.d.ts +16 -0
  58. package/dist/SvSheetManageRules.svelte +185 -0
  59. package/dist/SvSheetManageRules.svelte.d.ts +16 -0
  60. package/dist/SvSheetNameManager.svelte +225 -0
  61. package/dist/SvSheetNameManager.svelte.d.ts +11 -0
  62. package/dist/SvSheetPasteSpecial.svelte +105 -0
  63. package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
  64. package/dist/SvSheetRemoveDuplicates.svelte +149 -0
  65. package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
  66. package/dist/SvSheetRibbon.svelte +1201 -0
  67. package/dist/SvSheetRibbon.svelte.d.ts +34 -0
  68. package/dist/SvSheetSizeDialog.svelte +58 -0
  69. package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
  70. package/dist/SvSheetTabs.svelte +441 -0
  71. package/dist/SvSheetTabs.svelte.d.ts +36 -0
  72. package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
  73. package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
  74. package/dist/SvSheetTextToColumns.svelte +172 -0
  75. package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
  76. package/dist/SvSheetValidationAlert.svelte +83 -0
  77. package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
  78. package/dist/advanced-filter/expr-columns-from-grid.d.ts +1 -1
  79. package/dist/advanced-filter-enable.js +3 -3
  80. package/dist/alerts/alert-engine-attach.d.ts +2 -2
  81. package/dist/alerts/alert-engine-attach.js +3 -3
  82. package/dist/alerts/alert-engine.d.ts +1 -1
  83. package/dist/alerts/alert-engine.js +2 -2
  84. package/dist/alerts/alert-formats.d.ts +1 -1
  85. package/dist/alerts/alert-formats.js +1 -1
  86. package/dist/alerts/alert-observer.d.ts +3 -3
  87. package/dist/alerts/alert-observer.js +1 -1
  88. package/dist/alerts/alert-storage.d.ts +1 -1
  89. package/dist/alerts/alert-store.svelte.d.ts +1 -1
  90. package/dist/alerts/alert-types.d.ts +2 -2
  91. package/dist/alerts.js +2 -2
  92. package/dist/board.js +2 -2
  93. package/dist/cdn/svgrid-enterprise.svelte-external.js +19046 -5428
  94. package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
  95. package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
  96. package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
  97. package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
  98. package/dist/designer/assets/SvDateRangeInput-C0iCCZ8o.css +1 -0
  99. package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
  100. package/dist/designer/assets/SvDateTimePicker-DPv09DIT.css +1 -0
  101. package/dist/designer/assets/{preload-helper-CG0n22O7.js → SvGrid.helpers-BgT-gnCo.js} +1 -1
  102. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
  103. package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
  104. package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
  105. package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
  106. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
  107. package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
  108. package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
  109. package/dist/designer/assets/SvGridChartView-7msfnWZT.css +1 -0
  110. package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
  111. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +1 -0
  112. package/dist/designer/assets/SvGridDropdown-CcidB3rm.css +1 -0
  113. package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
  114. package/dist/designer/assets/SvListBox-BXfyqy4a.css +1 -0
  115. package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
  116. package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
  117. package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
  118. package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
  119. package/dist/designer/assets/cell-formatting-wr5wm7US.js +1 -0
  120. package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
  121. package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
  122. package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
  123. package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
  124. package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
  125. package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
  126. package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
  127. package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
  128. package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
  129. package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
  130. package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
  131. package/dist/designer/assets/export-format-D-eP4b2l.js +6 -0
  132. package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
  133. package/dist/designer/assets/index-CMFgAoVV.css +1 -0
  134. package/dist/designer/assets/{index-zUBix-JQ.js → index-DwU7CAMm.js} +2985 -141
  135. package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
  136. package/dist/designer/assets/preload-helper-HclGiUj8.js +1 -0
  137. package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
  138. package/dist/designer/assets/src-CITpcm6b.js +1 -0
  139. package/dist/designer/assets/src-EdI3Xqds.js +58 -0
  140. package/dist/designer/index.html +32 -23
  141. package/dist/export-conditional.d.ts +1 -1
  142. package/dist/export-pdf.d.ts +89 -0
  143. package/dist/export-pdf.js +130 -0
  144. package/dist/export-print.js +19 -19
  145. package/dist/export-xls.d.ts +2 -2
  146. package/dist/export.d.ts +2 -2
  147. package/dist/export.js +23 -16
  148. package/dist/expressions/builder-tree.d.ts +2 -2
  149. package/dist/expressions/builder-tree.js +1 -1
  150. package/dist/expressions/compile.d.ts +1 -1
  151. package/dist/import.js +1 -1
  152. package/dist/index.d.ts +116 -53
  153. package/dist/index.js +117 -50
  154. package/dist/install.d.ts +4 -4
  155. package/dist/install.js +16 -11
  156. package/dist/license.d.ts +3 -2
  157. package/dist/license.js +9 -4
  158. package/dist/node/studio.js +187 -116
  159. package/dist/pivot-chart.d.ts +26 -2
  160. package/dist/pivot-chart.js +25 -69
  161. package/dist/pivot-designer.d.ts +1 -1
  162. package/dist/pivot-enable.js +3 -3
  163. package/dist/print.d.ts +1 -1
  164. package/dist/print.js +4 -4
  165. package/dist/scheduler-config.d.ts +5 -5
  166. package/dist/scheduler-freebusy.d.ts +1 -1
  167. package/dist/scheduler-freebusy.js +1 -1
  168. package/dist/scheduler.js +2 -2
  169. package/dist/schema-designer.d.ts +1 -1
  170. package/dist/schema-designer.js +2 -2
  171. package/dist/selection-bar.js +2 -2
  172. package/dist/sheet/address.d.ts +43 -0
  173. package/dist/sheet/address.js +81 -0
  174. package/dist/sheet/ast.d.ts +94 -0
  175. package/dist/sheet/ast.js +56 -0
  176. package/dist/sheet/auto-filter.d.ts +66 -0
  177. package/dist/sheet/auto-filter.js +147 -0
  178. package/dist/sheet/autocomplete.d.ts +42 -0
  179. package/dist/sheet/autocomplete.js +162 -0
  180. package/dist/sheet/coerce.d.ts +31 -0
  181. package/dist/sheet/coerce.js +122 -0
  182. package/dist/sheet/commands.d.ts +89 -0
  183. package/dist/sheet/commands.js +230 -0
  184. package/dist/sheet/comments.d.ts +29 -0
  185. package/dist/sheet/comments.js +73 -0
  186. package/dist/sheet/conditional-formats.d.ts +144 -0
  187. package/dist/sheet/conditional-formats.js +316 -0
  188. package/dist/sheet/deps.d.ts +60 -0
  189. package/dist/sheet/deps.js +199 -0
  190. package/dist/sheet/document.d.ts +150 -0
  191. package/dist/sheet/document.js +264 -0
  192. package/dist/sheet/edit-keys.d.ts +14 -0
  193. package/dist/sheet/edit-keys.js +40 -0
  194. package/dist/sheet/entry.d.ts +31 -0
  195. package/dist/sheet/entry.js +76 -0
  196. package/dist/sheet/evaluate.d.ts +61 -0
  197. package/dist/sheet/evaluate.js +337 -0
  198. package/dist/sheet/find-replace.d.ts +62 -0
  199. package/dist/sheet/find-replace.js +152 -0
  200. package/dist/sheet/format-store.d.ts +99 -0
  201. package/dist/sheet/format-store.js +233 -0
  202. package/dist/sheet/freeze.d.ts +54 -0
  203. package/dist/sheet/freeze.js +58 -0
  204. package/dist/sheet/function-catalog.d.ts +10 -0
  205. package/dist/sheet/function-catalog.js +94 -0
  206. package/dist/sheet/functions.d.ts +28 -0
  207. package/dist/sheet/functions.js +362 -0
  208. package/dist/sheet/goal-seek.d.ts +73 -0
  209. package/dist/sheet/goal-seek.js +153 -0
  210. package/dist/sheet/index.d.ts +53 -0
  211. package/dist/sheet/index.js +53 -0
  212. package/dist/sheet/merges.d.ts +71 -0
  213. package/dist/sheet/merges.js +137 -0
  214. package/dist/sheet/names.d.ts +31 -0
  215. package/dist/sheet/names.js +87 -0
  216. package/dist/sheet/navigate.d.ts +52 -0
  217. package/dist/sheet/navigate.js +135 -0
  218. package/dist/sheet/number-format.d.ts +63 -0
  219. package/dist/sheet/number-format.js +508 -0
  220. package/dist/sheet/palette.d.ts +19 -0
  221. package/dist/sheet/palette.js +84 -0
  222. package/dist/sheet/parse.d.ts +13 -0
  223. package/dist/sheet/parse.js +108 -0
  224. package/dist/sheet/paste-special.d.ts +164 -0
  225. package/dist/sheet/paste-special.js +592 -0
  226. package/dist/sheet/protection.d.ts +21 -0
  227. package/dist/sheet/protection.js +48 -0
  228. package/dist/sheet/rects.d.ts +45 -0
  229. package/dist/sheet/rects.js +112 -0
  230. package/dist/sheet/refs.d.ts +82 -0
  231. package/dist/sheet/refs.js +266 -0
  232. package/dist/sheet/ribbon-icons.d.ts +31 -0
  233. package/dist/sheet/ribbon-icons.js +350 -0
  234. package/dist/sheet/ribbon.d.ts +173 -0
  235. package/dist/sheet/ribbon.js +753 -0
  236. package/dist/sheet/shortcuts.d.ts +164 -0
  237. package/dist/sheet/shortcuts.js +623 -0
  238. package/dist/sheet/structure.d.ts +87 -0
  239. package/dist/sheet/structure.js +166 -0
  240. package/dist/sheet/tables.d.ts +70 -0
  241. package/dist/sheet/tables.js +147 -0
  242. package/dist/sheet/tokenize.d.ts +54 -0
  243. package/dist/sheet/tokenize.js +304 -0
  244. package/dist/sheet/transforms.d.ts +76 -0
  245. package/dist/sheet/transforms.js +154 -0
  246. package/dist/sheet/validation.d.ts +92 -0
  247. package/dist/sheet/validation.js +241 -0
  248. package/dist/sheet/workbook.d.ts +72 -0
  249. package/dist/sheet/workbook.js +409 -0
  250. package/dist/sheet-enable.d.ts +6 -0
  251. package/dist/sheet-enable.js +46 -0
  252. package/dist/sources/aggregate.d.ts +1 -1
  253. package/dist/sources/auth-supabase.js +1 -1
  254. package/dist/sources/dashboard.d.ts +2 -2
  255. package/dist/sources/dashboard.js +1 -1
  256. package/dist/sources/index.d.ts +13 -13
  257. package/dist/sources/index.js +13 -13
  258. package/dist/sources/realtime-supabase.js +1 -1
  259. package/dist/sources/relation-lookup.d.ts +1 -1
  260. package/dist/sources/relation-lookup.js +1 -1
  261. package/dist/sources/rest-adapters.d.ts +1 -1
  262. package/dist/sources/rest-adapters.js +1 -1
  263. package/dist/sources/rest.d.ts +2 -2
  264. package/dist/sources/rest.js +3 -3
  265. package/dist/sources/supabase.d.ts +2 -2
  266. package/dist/sources/supabase.js +3 -3
  267. package/dist/sources/with-entity-rules.d.ts +1 -1
  268. package/dist/sources/with-entity-rules.js +1 -1
  269. package/dist/sources/with-relation-labels.d.ts +1 -1
  270. package/dist/studio/emit-project.js +2161 -2127
  271. package/dist/studio/emit-schema.js +369 -369
  272. package/dist/studio/index.d.ts +1 -1
  273. package/dist/studio/index.js +1 -1
  274. package/dist/studio/introspect-db.js +45 -45
  275. package/dist/studio/project.d.ts +10 -0
  276. package/dist/studio/samples/crm.js +6 -6
  277. package/dist/studio/samples/fleet.js +7 -7
  278. package/dist/studio/samples/insurance.js +9 -9
  279. package/dist/studio/samples/inventory.js +12 -12
  280. package/dist/studio/samples/projects.js +5 -5
  281. package/dist/studio/samples/support.js +8 -8
  282. package/dist/studio/scaffold-app.js +104 -104
  283. package/dist/studio/scaffold.js +151 -151
  284. package/dist/studio/ui-components.d.ts +7 -0
  285. package/dist/studio/ui-components.generated.js +63 -1
  286. package/dist/studio/ui-components.js +35 -0
  287. package/dist/sveltekit/in-memory.d.ts +3 -3
  288. package/dist/sveltekit/in-memory.js +4 -4
  289. package/dist/sveltekit/index.d.ts +6 -6
  290. package/dist/sveltekit/index.js +5 -5
  291. package/dist/sveltekit/query-plan.d.ts +1 -1
  292. package/dist/sveltekit/sql-source.d.ts +4 -4
  293. package/dist/sveltekit/sql-source.js +4 -4
  294. package/dist/sveltekit/sql.d.ts +1 -1
  295. package/dist/sveltekit/transport.d.ts +2 -2
  296. package/dist/sveltekit/transport.js +1 -1
  297. package/dist/upgrade-prompt.js +25 -25
  298. package/dist/version.d.ts +1 -1
  299. package/dist/version.js +1 -1
  300. package/package.json +12 -4
  301. package/dist/designer/assets/GridMenus-Cgilexpk.js +0 -8
  302. package/dist/designer/assets/SvDateRangeInput-BV5OXYQ2.css +0 -1
  303. package/dist/designer/assets/SvDateRangeInput-CVn-yqrQ.js +0 -1
  304. package/dist/designer/assets/SvDateTimePicker-BdioTrJQ.js +0 -1
  305. package/dist/designer/assets/SvDateTimePicker-CY8TK4BT.css +0 -1
  306. package/dist/designer/assets/SvField-BF-9TJSd.css +0 -1
  307. package/dist/designer/assets/SvField-BsqiA5me.js +0 -1
  308. package/dist/designer/assets/SvGridCellEditor-dLY7_kk9.js +0 -2
  309. package/dist/designer/assets/SvGridChart-BEDNTdc5.js +0 -2
  310. package/dist/designer/assets/SvGridChart-Vu4Nvjiv.css +0 -1
  311. package/dist/designer/assets/SvGridChartPanel-x3lzSxuf.js +0 -10
  312. package/dist/designer/assets/SvGridChartView-CAKhPELi.js +0 -1
  313. package/dist/designer/assets/SvGridChartView-DX9HfkBR.css +0 -1
  314. package/dist/designer/assets/SvGridDropdown-CAYG7Y-O.js +0 -1
  315. package/dist/designer/assets/SvGridDropdown-Crwb2P3Q.css +0 -1
  316. package/dist/designer/assets/SvListBox-CVYQcLX9.js +0 -1
  317. package/dist/designer/assets/SvListBox-DgN8kvjt.css +0 -1
  318. package/dist/designer/assets/chart-DHEgt4lN.js +0 -1
  319. package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
  320. package/dist/designer/assets/date-format-4DJvNtG2.js +0 -1
  321. package/dist/designer/assets/date-format-Dzy4mTGh.css +0 -1
  322. package/dist/designer/assets/easing-zaypKrr0.js +0 -3
  323. package/dist/designer/assets/export-format-DwjuTfrQ.js +0 -6
  324. package/dist/designer/assets/index-Cp7fDJ3b.css +0 -1
  325. package/dist/designer/assets/js-scroller.svelte-Bhxqb3-c.js +0 -110
  326. package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
  327. package/dist/designer/assets/src-B7DNcoQK.css +0 -1
  328. package/dist/designer/assets/src-BoYyigAC.js +0 -2896
  329. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -1
  330. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -178
  331. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +0 -3875
  332. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +0 -1
  333. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +0 -21
  334. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +0 -1
  335. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +0 -1938
  336. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +0 -1
  337. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +0 -205
  338. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +0 -1
  339. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +0 -18
  340. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +0 -1
  341. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +0 -264
  342. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +0 -1
  343. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +0 -697
  344. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +0 -1
  345. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -3
  346. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -134
  347. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -1
  348. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -55
  349. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -1
  350. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -2
  351. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -46843
  352. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -1
  353. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -237
  354. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -1
  355. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -2
  356. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -4
  357. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -1
  358. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -7
  359. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -4
  360. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -1
  361. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -5
  362. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -1
  363. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -5
  364. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -1
  365. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -5
  366. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -1
  367. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -6
  368. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -83
  369. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -1
  370. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -559
  371. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -1
  372. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -24
  373. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -1
  374. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -122
  375. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -1
  376. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -2
  377. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -89
  378. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -1
  379. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -241
  380. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -1
  381. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +0 -4938
  382. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +0 -1
  383. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +0 -495
  384. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +0 -1
  385. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +0 -190
  386. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +0 -1
  387. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +0 -8
  388. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +0 -1
  389. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +0 -138
  390. package/dist/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +0 -1
@@ -1,553 +1,553 @@
1
- <script lang="ts" generics="TFeatures extends TableFeatures, TData extends RowData">
2
- /**
3
- * SvExportMenu - a drop-in export button + menu for a licensed grid. Wraps
4
- * `api.exportData` / `api.copyExport` / `api.print` so consumers don't
5
- * hand-roll a toolbar in every app:
6
- * - a sectioned format picker with per-format icons + descriptions,
7
- * - a row-scope toggle (current view / selected / all rows),
8
- * - a per-column include picker,
9
- * - copy-to-clipboard + optional print actions,
10
- * - a progress bar + Cancel for large exports (via AbortController),
11
- * - inline error surface when a peer dep is missing or the grid is empty.
12
- *
13
- * Minimal, unopinionated styling via a few overridable classes / CSS vars.
14
- */
15
- import type { RowData, TableFeatures } from '@svgrid/grid'
16
- import type { ConditionalFormat } from '@svgrid/grid/format'
17
- import type { EnterpriseGridApi } from './install'
18
- import type {
19
- ClipboardFormat,
20
- ExportColumn,
21
- ExportFormat,
22
- ExportProgress,
23
- ExportRowSource,
24
- } from './export'
25
-
26
- type Props = {
27
- api: EnterpriseGridApi<TFeatures, TData> | null | undefined
28
- /** Base filename (no extension). Default 'grid'. */
29
- filename?: string
30
- /** Which formats to offer, in order. Default xlsx/xls/pdf/csv/tsv/html. */
31
- formats?: ReadonlyArray<ExportFormat>
32
- /** Show the "current view / selected / all" scope toggle. Default true. */
33
- allowScope?: boolean
34
- /** Show the per-column include checkboxes. Default true. */
35
- allowColumns?: boolean
36
- /** Show the "Copy to clipboard" actions. Default true. */
37
- allowCopy?: boolean
38
- /** Show a "Print / Save as PDF" action. Default false. */
39
- allowPrint?: boolean
40
- /** Initial scope. Default 'displayed'. */
41
- scope?: Extract<ExportRowSource<TData>, string>
42
- /** Button label. Default 'Export'. */
43
- label?: string
44
- /** Conditional formatting rules to carry into styled exports (pdf/xlsx/html). */
45
- conditionalFormats?: ReadonlyArray<ConditionalFormat<TData>>
46
- }
47
-
48
- let {
49
- api,
50
- filename = 'grid',
51
- formats = ['xlsx', 'xls', 'pdf', 'csv', 'tsv', 'html'],
52
- allowScope = true,
53
- allowColumns = true,
54
- allowCopy = true,
55
- allowPrint = false,
56
- scope = 'displayed',
57
- label = 'Export',
58
- conditionalFormats,
59
- }: Props = $props()
60
-
61
- type IconKind = 'sheet' | 'doc' | 'text' | 'code'
62
- const FORMAT_META: Record<ExportFormat, { label: string; desc: string; icon: IconKind }> = {
63
- xlsx: { label: 'Excel', desc: '.xlsx, formatted', icon: 'sheet' },
64
- xls: { label: 'Excel 97-2003', desc: 'Legacy .xls', icon: 'sheet' },
65
- pdf: { label: 'PDF', desc: 'Paginated document', icon: 'doc' },
66
- csv: { label: 'CSV', desc: 'Comma-separated', icon: 'text' },
67
- tsv: { label: 'TSV', desc: 'Tab-separated', icon: 'text' },
68
- html: { label: 'HTML', desc: 'Styled table', icon: 'code' },
69
- json: { label: 'JSON', desc: 'Data array', icon: 'code' },
70
- xml: { label: 'XML', desc: 'Rows + fields', icon: 'code' },
71
- md: { label: 'Markdown', desc: 'GFM table', icon: 'text' },
72
- }
73
- const SCOPES: Array<{ value: Extract<ExportRowSource<TData>, string>; label: string }> = [
74
- { value: 'displayed', label: 'Current view' },
75
- { value: 'selected', label: 'Selected rows' },
76
- { value: 'all', label: 'All rows' },
77
- ]
78
-
79
- let open = $state(false)
80
- let currentScope = $state<Extract<ExportRowSource<TData>, string>>(scope)
81
- let busy = $state(false)
82
- let progress = $state(0)
83
- let error = $state<string | null>(null)
84
- let copied = $state<string | null>(null)
85
- let controller: AbortController | null = null
86
- let rootEl: HTMLDivElement | undefined = $state()
87
- let menuEl: HTMLDivElement | undefined = $state()
88
- let triggerEl: HTMLButtonElement | undefined = $state()
89
-
90
- // --- Keyboard accessibility -------------------------------------------
91
- /** Focusable controls inside the menu, in DOM order. */
92
- function menuItems(): HTMLElement[] {
93
- if (!menuEl) return []
94
- return Array.from(
95
- menuEl.querySelectorAll<HTMLElement>(
96
- 'button:not([disabled]), input:not([disabled]), [tabindex="0"]',
97
- ),
98
- )
99
- }
100
-
101
- function focusFirst() {
102
- // Wait a tick so the menu is rendered, then focus its first control.
103
- requestAnimationFrame(() => menuItems()[0]?.focus())
104
- }
105
-
106
- function closeMenu(returnFocus = true) {
107
- open = false
108
- if (returnFocus) triggerEl?.focus()
109
- }
110
-
111
- function onMenuKeydown(e: KeyboardEvent) {
112
- const items = menuItems()
113
- if (items.length === 0) return
114
- const idx = items.indexOf(document.activeElement as HTMLElement)
115
- if (e.key === 'Escape') {
116
- e.preventDefault()
117
- closeMenu()
118
- } else if (e.key === 'ArrowDown') {
119
- e.preventDefault()
120
- items[(idx + 1 + items.length) % items.length]?.focus()
121
- } else if (e.key === 'ArrowUp') {
122
- e.preventDefault()
123
- items[(idx - 1 + items.length) % items.length]?.focus()
124
- } else if (e.key === 'Home') {
125
- e.preventDefault()
126
- items[0]?.focus()
127
- } else if (e.key === 'End') {
128
- e.preventDefault()
129
- items[items.length - 1]?.focus()
130
- } else if (e.key === 'Tab') {
131
- // Trap focus within the menu (wrap at the ends).
132
- if (e.shiftKey && (idx <= 0)) {
133
- e.preventDefault()
134
- items[items.length - 1]?.focus()
135
- } else if (!e.shiftKey && idx === items.length - 1) {
136
- e.preventDefault()
137
- items[0]?.focus()
138
- }
139
- }
140
- }
141
-
142
- let picks = $state<Array<{ field: string; header: string; checked: boolean }>>([])
143
- let showColumns = $state(false)
144
-
145
- function refreshColumns() {
146
- if (!api) return
147
- picks = api
148
- .getColumns()
149
- .filter((c) => c.visible && c.field)
150
- .map((c) => ({ field: c.field!, header: c.header, checked: true }))
151
- }
152
-
153
- function toggleOpen() {
154
- open = !open
155
- copied = null
156
- error = null
157
- if (open) {
158
- refreshColumns()
159
- focusFirst()
160
- }
161
- }
162
-
163
- function selectedColumns(): ExportColumn<TData>[] | undefined {
164
- if (!allowColumns || picks.length === 0) return undefined
165
- const chosen = picks.filter((p) => p.checked)
166
- if (chosen.length === picks.length) return undefined
167
- return chosen.map((p) => ({ field: p.field, header: p.header }))
168
- }
169
-
170
- function onDocClick(e: MouseEvent) {
171
- if (rootEl && !rootEl.contains(e.target as Node)) open = false
172
- }
173
-
174
- async function withBusy(fn: () => Promise<unknown>, closeOnDone = true) {
175
- if (!api || busy) return
176
- error = null
177
- copied = null
178
- busy = true
179
- progress = 0
180
- controller = new AbortController()
181
- try {
182
- await fn()
183
- if (closeOnDone) closeMenu()
184
- } catch (err) {
185
- if ((err as { name?: string })?.name === 'AbortError') error = 'Export cancelled.'
186
- else error = err instanceof Error ? err.message : String(err)
187
- } finally {
188
- busy = false
189
- controller = null
190
- }
191
- }
192
-
193
- async function run(format: ExportFormat) {
194
- await withBusy(() =>
195
- api!.exportData({
196
- format,
197
- filename,
198
- rows: currentScope,
199
- columns: selectedColumns(),
200
- conditionalFormats,
201
- signal: controller!.signal,
202
- onProgress: (p: ExportProgress) => {
203
- progress = p.ratio
204
- },
205
- }),
206
- )
207
- }
208
-
209
- async function copy(format: ClipboardFormat) {
210
- await withBusy(
211
- () => api!.copyExport({ format, rows: currentScope, columns: selectedColumns() }),
212
- false,
213
- )
214
- if (!error) copied = format === 'tsv' ? 'for Excel' : format
215
- }
216
-
217
- async function print() {
218
- await withBusy(() =>
219
- api!.print({ title: filename, rows: currentScope, columns: selectedColumns() }),
220
- )
221
- }
222
-
223
- function cancel() {
224
- controller?.abort()
225
- }
226
- </script>
227
-
228
- <svelte:document onclick={onDocClick} />
229
-
230
- {#snippet formatIcon(kind: IconKind)}
231
- <svg class="sv-export-icon" width="16" height="16" viewBox="0 0 24 24" fill="none"
232
- stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
233
- {#if kind === 'sheet'}
234
- <rect x="3" y="3" width="18" height="18" rx="1.5" /><path d="M3 9h18M3 15h18M9 3v18M15 3v18" />
235
- {:else if kind === 'doc'}
236
- <path d="M6 2.5h8l4 4v15H6z" /><path d="M14 2.5v4h4" />
237
- {:else if kind === 'text'}
238
- <path d="M5 6h14M5 12h14M5 18h9" />
239
- {:else}
240
- <path d="M8 7l-4 5 4 5M16 7l4 5-4 5" />
241
- {/if}
242
- </svg>
243
- {/snippet}
244
-
245
- <div class="sv-export" bind:this={rootEl}>
246
- <button
247
- type="button"
248
- class="sv-export-btn"
249
- aria-haspopup="menu"
250
- aria-expanded={open}
251
- disabled={!api}
252
- bind:this={triggerEl}
253
- onclick={toggleOpen}
254
- >
255
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
256
- stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
257
- <path d="M12 3v12M7 10l5 5 5-5M5 21h14" />
258
- </svg>
259
- {label}
260
- <span class="sv-export-caret" aria-hidden="true">▾</span>
261
- </button>
262
-
263
- {#if open}
264
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
265
- <div
266
- class="sv-export-menu"
267
- role="menu"
268
- aria-label="Export options"
269
- bind:this={menuEl}
270
- onkeydown={onMenuKeydown}
271
- >
272
- {#if allowScope}
273
- <div class="sv-export-section">
274
- <span class="sv-export-section-label">Rows</span>
275
- <div class="sv-export-scope">
276
- {#each SCOPES as s (s.value)}
277
- <label class="sv-export-opt">
278
- <input
279
- type="radio"
280
- name="sv-export-scope"
281
- value={s.value}
282
- checked={currentScope === s.value}
283
- onchange={() => (currentScope = s.value)}
284
- />
285
- {s.label}
286
- </label>
287
- {/each}
288
- </div>
289
- </div>
290
- {/if}
291
-
292
- {#if allowColumns && picks.length > 0}
293
- <div class="sv-export-section">
294
- <button type="button" class="sv-export-cols-toggle" onclick={() => (showColumns = !showColumns)}>
295
- Columns <span class="sv-export-count">{picks.filter((p) => p.checked).length}/{picks.length}</span>
296
- <span aria-hidden="true">{showColumns ? '▴' : '▾'}</span>
297
- </button>
298
- {#if showColumns}
299
- <div class="sv-export-cols-list">
300
- {#each picks as p (p.field)}
301
- <label class="sv-export-opt">
302
- <input type="checkbox" bind:checked={p.checked} />
303
- {p.header}
304
- </label>
305
- {/each}
306
- </div>
307
- {/if}
308
- </div>
309
- {/if}
310
-
311
- <div class="sv-export-section">
312
- <span class="sv-export-section-label">Download</span>
313
- {#each formats as f (f)}
314
- <button type="button" class="sv-export-item" role="menuitem" disabled={busy} onclick={() => run(f)}>
315
- {@render formatIcon(FORMAT_META[f].icon)}
316
- <span class="sv-export-item-text">
317
- <span class="sv-export-item-label">{FORMAT_META[f].label}</span>
318
- <span class="sv-export-item-desc">{FORMAT_META[f].desc}</span>
319
- </span>
320
- </button>
321
- {/each}
322
- </div>
323
-
324
- {#if allowCopy || allowPrint}
325
- <div class="sv-export-actions">
326
- {#if allowCopy}
327
- <button type="button" class="sv-export-chip" disabled={busy} onclick={() => copy('tsv')}>Copy for Excel</button>
328
- <button type="button" class="sv-export-chip" disabled={busy} onclick={() => copy('md')}>Copy Markdown</button>
329
- {/if}
330
- {#if allowPrint}
331
- <button type="button" class="sv-export-chip" disabled={busy} onclick={print}>Print…</button>
332
- {/if}
333
- {#if copied}<span class="sv-export-copied">Copied {copied} ✓</span>{/if}
334
- </div>
335
- {/if}
336
-
337
- {#if busy}
338
- <div class="sv-export-progress">
339
- <div class="sv-export-track"><div class="sv-export-bar" style:width={`${Math.round(progress * 100)}%`}></div></div>
340
- <button type="button" class="sv-export-cancel" onclick={cancel}>Cancel</button>
341
- </div>
342
- {/if}
343
-
344
- {#if error}
345
- <p class="sv-export-error" role="alert">{error}</p>
346
- {/if}
347
- </div>
348
- {/if}
349
- </div>
350
-
351
- <style>
352
- .sv-export {
353
- position: relative;
354
- display: inline-block;
355
- }
356
- .sv-export-btn {
357
- display: inline-flex;
358
- align-items: center;
359
- gap: 6px;
360
- padding: 6px 12px;
361
- font: inherit;
362
- font-size: 13px;
363
- font-weight: 500;
364
- border: 1px solid var(--sg-border, #d0d7de);
365
- border-radius: 8px;
366
- background: var(--sg-header-bg, #f6f8fa);
367
- color: var(--sg-fg, #1f2328);
368
- cursor: pointer;
369
- }
370
- .sv-export-btn:disabled {
371
- opacity: 0.5;
372
- cursor: not-allowed;
373
- }
374
- .sv-export-caret {
375
- font-size: 10px;
376
- opacity: 0.7;
377
- }
378
- .sv-export-menu {
379
- position: absolute;
380
- z-index: 50;
381
- top: calc(100% + 6px);
382
- left: 0;
383
- min-width: 232px;
384
- padding: 6px;
385
- border: 1px solid var(--sg-border, #d0d7de);
386
- border-radius: 12px;
387
- background: var(--sg-bg, #fff);
388
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14);
389
- }
390
- .sv-export-section {
391
- padding: 6px 6px 8px;
392
- border-bottom: 1px solid var(--sg-border, #eaecef);
393
- }
394
- .sv-export-section:last-of-type {
395
- border-bottom: none;
396
- }
397
- .sv-export-section-label {
398
- display: block;
399
- padding: 2px 6px 6px;
400
- font-size: 10px;
401
- font-weight: 700;
402
- text-transform: uppercase;
403
- letter-spacing: 0.07em;
404
- color: var(--sg-muted, #8b949e);
405
- }
406
- .sv-export-scope {
407
- display: flex;
408
- flex-direction: column;
409
- gap: 1px;
410
- }
411
- .sv-export-opt {
412
- display: flex;
413
- align-items: center;
414
- gap: 7px;
415
- padding: 3px 6px;
416
- font-size: 12.5px;
417
- color: var(--sg-fg, #1f2328);
418
- cursor: pointer;
419
- border-radius: 6px;
420
- }
421
- .sv-export-opt:hover {
422
- background: var(--sg-header-bg, #f6f8fa);
423
- }
424
- .sv-export-item {
425
- display: flex;
426
- align-items: center;
427
- gap: 10px;
428
- width: 100%;
429
- text-align: left;
430
- padding: 6px 8px;
431
- font: inherit;
432
- border: none;
433
- border-radius: 8px;
434
- background: transparent;
435
- color: var(--sg-fg, #1f2328);
436
- cursor: pointer;
437
- }
438
- .sv-export-item:hover:not(:disabled) {
439
- background: var(--sg-header-bg, #f6f8fa);
440
- }
441
- .sv-export-item:disabled {
442
- opacity: 0.5;
443
- cursor: default;
444
- }
445
- .sv-export-icon {
446
- flex-shrink: 0;
447
- color: var(--site-accent-2, #6366f1);
448
- }
449
- .sv-export-item-text {
450
- display: flex;
451
- flex-direction: column;
452
- line-height: 1.25;
453
- }
454
- .sv-export-item-label {
455
- font-size: 13px;
456
- font-weight: 500;
457
- }
458
- .sv-export-item-desc {
459
- font-size: 11px;
460
- color: var(--sg-muted, #8b949e);
461
- }
462
- .sv-export-cols-toggle {
463
- display: flex;
464
- align-items: center;
465
- gap: 6px;
466
- width: 100%;
467
- font: inherit;
468
- font-size: 11px;
469
- font-weight: 700;
470
- text-transform: uppercase;
471
- letter-spacing: 0.07em;
472
- color: var(--sg-muted, #8b949e);
473
- background: transparent;
474
- border: none;
475
- padding: 2px 6px;
476
- cursor: pointer;
477
- }
478
- .sv-export-count {
479
- font-weight: 600;
480
- text-transform: none;
481
- letter-spacing: 0;
482
- color: var(--sg-fg, #1f2328);
483
- }
484
- .sv-export-cols-toggle > span:last-child {
485
- margin-left: auto;
486
- }
487
- .sv-export-cols-list {
488
- max-height: 168px;
489
- overflow-y: auto;
490
- margin-top: 4px;
491
- }
492
- .sv-export-actions {
493
- display: flex;
494
- align-items: center;
495
- flex-wrap: wrap;
496
- gap: 6px;
497
- padding: 8px 6px 4px;
498
- }
499
- .sv-export-chip {
500
- font: inherit;
501
- font-size: 12px;
502
- padding: 4px 10px;
503
- border-radius: 999px;
504
- border: 1px solid var(--sg-border, #d0d7de);
505
- background: var(--sg-header-bg, #f6f8fa);
506
- color: var(--sg-fg, #1f2328);
507
- cursor: pointer;
508
- }
509
- .sv-export-chip:hover:not(:disabled) {
510
- border-color: var(--site-accent, #3b82f6);
511
- }
512
- .sv-export-chip:disabled {
513
- opacity: 0.5;
514
- cursor: default;
515
- }
516
- .sv-export-copied {
517
- font-size: 11px;
518
- color: #16a34a;
519
- }
520
- .sv-export-progress {
521
- display: flex;
522
- align-items: center;
523
- gap: 8px;
524
- padding: 8px 6px 4px;
525
- }
526
- .sv-export-track {
527
- flex: 1;
528
- min-width: 0;
529
- height: 6px;
530
- border-radius: 999px;
531
- background: var(--sg-border, #eaecef);
532
- overflow: hidden;
533
- }
534
- .sv-export-bar {
535
- height: 100%;
536
- border-radius: 999px;
537
- background: var(--site-accent, #3b82f6);
538
- transition: width 120ms ease;
539
- }
540
- .sv-export-cancel {
541
- font: inherit;
542
- font-size: 12px;
543
- border: none;
544
- background: transparent;
545
- color: var(--site-accent-2, #6366f1);
546
- cursor: pointer;
547
- }
548
- .sv-export-error {
549
- margin: 6px 6px 2px;
550
- font-size: 12px;
551
- color: #dc2626;
552
- }
553
- </style>
1
+ <script lang="ts" generics="TFeatures extends TableFeatures, TData extends RowData">
2
+ /**
3
+ * SvExportMenu - a drop-in export button + menu for a licensed grid. Wraps
4
+ * `api.exportData` / `api.copyExport` / `api.print` so consumers don't
5
+ * hand-roll a toolbar in every app:
6
+ * - a sectioned format picker with per-format icons + descriptions,
7
+ * - a row-scope toggle (current view / selected / all rows),
8
+ * - a per-column include picker,
9
+ * - copy-to-clipboard + optional print actions,
10
+ * - a progress bar + Cancel for large exports (via AbortController),
11
+ * - inline error surface when a peer dep is missing or the grid is empty.
12
+ *
13
+ * Minimal, unopinionated styling via a few overridable classes / CSS vars.
14
+ */
15
+ import type { RowData, TableFeatures } from '@svgrid/grid'
16
+ import type { ConditionalFormat } from '@svgrid/grid/format'
17
+ import type { EnterpriseGridApi } from './install'
18
+ import type {
19
+ ClipboardFormat,
20
+ ExportColumn,
21
+ ExportFormat,
22
+ ExportProgress,
23
+ ExportRowSource,
24
+ } from './export'
25
+
26
+ type Props = {
27
+ api: EnterpriseGridApi<TFeatures, TData> | null | undefined
28
+ /** Base filename (no extension). Default 'grid'. */
29
+ filename?: string
30
+ /** Which formats to offer, in order. Default xlsx/xls/pdf/csv/tsv/html. */
31
+ formats?: ReadonlyArray<ExportFormat>
32
+ /** Show the "current view / selected / all" scope toggle. Default true. */
33
+ allowScope?: boolean
34
+ /** Show the per-column include checkboxes. Default true. */
35
+ allowColumns?: boolean
36
+ /** Show the "Copy to clipboard" actions. Default true. */
37
+ allowCopy?: boolean
38
+ /** Show a "Print / Save as PDF" action. Default false. */
39
+ allowPrint?: boolean
40
+ /** Initial scope. Default 'displayed'. */
41
+ scope?: Extract<ExportRowSource<TData>, string>
42
+ /** Button label. Default 'Export'. */
43
+ label?: string
44
+ /** Conditional formatting rules to carry into styled exports (pdf/xlsx/html). */
45
+ conditionalFormats?: ReadonlyArray<ConditionalFormat<TData>>
46
+ }
47
+
48
+ let {
49
+ api,
50
+ filename = 'grid',
51
+ formats = ['xlsx', 'xls', 'pdf', 'csv', 'tsv', 'html'],
52
+ allowScope = true,
53
+ allowColumns = true,
54
+ allowCopy = true,
55
+ allowPrint = false,
56
+ scope = 'displayed',
57
+ label = 'Export',
58
+ conditionalFormats,
59
+ }: Props = $props()
60
+
61
+ type IconKind = 'sheet' | 'doc' | 'text' | 'code'
62
+ const FORMAT_META: Record<ExportFormat, { label: string; desc: string; icon: IconKind }> = {
63
+ xlsx: { label: 'Excel', desc: '.xlsx, formatted', icon: 'sheet' },
64
+ xls: { label: 'Excel 97-2003', desc: 'Legacy .xls', icon: 'sheet' },
65
+ pdf: { label: 'PDF', desc: 'Paginated document', icon: 'doc' },
66
+ csv: { label: 'CSV', desc: 'Comma-separated', icon: 'text' },
67
+ tsv: { label: 'TSV', desc: 'Tab-separated', icon: 'text' },
68
+ html: { label: 'HTML', desc: 'Styled table', icon: 'code' },
69
+ json: { label: 'JSON', desc: 'Data array', icon: 'code' },
70
+ xml: { label: 'XML', desc: 'Rows + fields', icon: 'code' },
71
+ md: { label: 'Markdown', desc: 'GFM table', icon: 'text' },
72
+ }
73
+ const SCOPES: Array<{ value: Extract<ExportRowSource<TData>, string>; label: string }> = [
74
+ { value: 'displayed', label: 'Current view' },
75
+ { value: 'selected', label: 'Selected rows' },
76
+ { value: 'all', label: 'All rows' },
77
+ ]
78
+
79
+ let open = $state(false)
80
+ let currentScope = $state<Extract<ExportRowSource<TData>, string>>(scope)
81
+ let busy = $state(false)
82
+ let progress = $state(0)
83
+ let error = $state<string | null>(null)
84
+ let copied = $state<string | null>(null)
85
+ let controller: AbortController | null = null
86
+ let rootEl: HTMLDivElement | undefined = $state()
87
+ let menuEl: HTMLDivElement | undefined = $state()
88
+ let triggerEl: HTMLButtonElement | undefined = $state()
89
+
90
+ // --- Keyboard accessibility -------------------------------------------
91
+ /** Focusable controls inside the menu, in DOM order. */
92
+ function menuItems(): HTMLElement[] {
93
+ if (!menuEl) return []
94
+ return Array.from(
95
+ menuEl.querySelectorAll<HTMLElement>(
96
+ 'button:not([disabled]), input:not([disabled]), [tabindex="0"]',
97
+ ),
98
+ )
99
+ }
100
+
101
+ function focusFirst() {
102
+ // Wait a tick so the menu is rendered, then focus its first control.
103
+ requestAnimationFrame(() => menuItems()[0]?.focus())
104
+ }
105
+
106
+ function closeMenu(returnFocus = true) {
107
+ open = false
108
+ if (returnFocus) triggerEl?.focus()
109
+ }
110
+
111
+ function onMenuKeydown(e: KeyboardEvent) {
112
+ const items = menuItems()
113
+ if (items.length === 0) return
114
+ const idx = items.indexOf(document.activeElement as HTMLElement)
115
+ if (e.key === 'Escape') {
116
+ e.preventDefault()
117
+ closeMenu()
118
+ } else if (e.key === 'ArrowDown') {
119
+ e.preventDefault()
120
+ items[(idx + 1 + items.length) % items.length]?.focus()
121
+ } else if (e.key === 'ArrowUp') {
122
+ e.preventDefault()
123
+ items[(idx - 1 + items.length) % items.length]?.focus()
124
+ } else if (e.key === 'Home') {
125
+ e.preventDefault()
126
+ items[0]?.focus()
127
+ } else if (e.key === 'End') {
128
+ e.preventDefault()
129
+ items[items.length - 1]?.focus()
130
+ } else if (e.key === 'Tab') {
131
+ // Trap focus within the menu (wrap at the ends).
132
+ if (e.shiftKey && (idx <= 0)) {
133
+ e.preventDefault()
134
+ items[items.length - 1]?.focus()
135
+ } else if (!e.shiftKey && idx === items.length - 1) {
136
+ e.preventDefault()
137
+ items[0]?.focus()
138
+ }
139
+ }
140
+ }
141
+
142
+ let picks = $state<Array<{ field: string; header: string; checked: boolean }>>([])
143
+ let showColumns = $state(false)
144
+
145
+ function refreshColumns() {
146
+ if (!api) return
147
+ picks = api
148
+ .getColumns()
149
+ .filter((c) => c.visible && c.field)
150
+ .map((c) => ({ field: c.field!, header: c.header, checked: true }))
151
+ }
152
+
153
+ function toggleOpen() {
154
+ open = !open
155
+ copied = null
156
+ error = null
157
+ if (open) {
158
+ refreshColumns()
159
+ focusFirst()
160
+ }
161
+ }
162
+
163
+ function selectedColumns(): ExportColumn<TData>[] | undefined {
164
+ if (!allowColumns || picks.length === 0) return undefined
165
+ const chosen = picks.filter((p) => p.checked)
166
+ if (chosen.length === picks.length) return undefined
167
+ return chosen.map((p) => ({ field: p.field, header: p.header }))
168
+ }
169
+
170
+ function onDocClick(e: MouseEvent) {
171
+ if (rootEl && !rootEl.contains(e.target as Node)) open = false
172
+ }
173
+
174
+ async function withBusy(fn: () => Promise<unknown>, closeOnDone = true) {
175
+ if (!api || busy) return
176
+ error = null
177
+ copied = null
178
+ busy = true
179
+ progress = 0
180
+ controller = new AbortController()
181
+ try {
182
+ await fn()
183
+ if (closeOnDone) closeMenu()
184
+ } catch (err) {
185
+ if ((err as { name?: string })?.name === 'AbortError') error = 'Export cancelled.'
186
+ else error = err instanceof Error ? err.message : String(err)
187
+ } finally {
188
+ busy = false
189
+ controller = null
190
+ }
191
+ }
192
+
193
+ async function run(format: ExportFormat) {
194
+ await withBusy(() =>
195
+ api!.exportData({
196
+ format,
197
+ filename,
198
+ rows: currentScope,
199
+ columns: selectedColumns(),
200
+ conditionalFormats,
201
+ signal: controller!.signal,
202
+ onProgress: (p: ExportProgress) => {
203
+ progress = p.ratio
204
+ },
205
+ }),
206
+ )
207
+ }
208
+
209
+ async function copy(format: ClipboardFormat) {
210
+ await withBusy(
211
+ () => api!.copyExport({ format, rows: currentScope, columns: selectedColumns() }),
212
+ false,
213
+ )
214
+ if (!error) copied = format === 'tsv' ? 'for Excel' : format
215
+ }
216
+
217
+ async function print() {
218
+ await withBusy(() =>
219
+ api!.print({ title: filename, rows: currentScope, columns: selectedColumns() }),
220
+ )
221
+ }
222
+
223
+ function cancel() {
224
+ controller?.abort()
225
+ }
226
+ </script>
227
+
228
+ <svelte:document onclick={onDocClick} />
229
+
230
+ {#snippet formatIcon(kind: IconKind)}
231
+ <svg class="sv-export-icon" width="16" height="16" viewBox="0 0 24 24" fill="none"
232
+ stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
233
+ {#if kind === 'sheet'}
234
+ <rect x="3" y="3" width="18" height="18" rx="1.5" /><path d="M3 9h18M3 15h18M9 3v18M15 3v18" />
235
+ {:else if kind === 'doc'}
236
+ <path d="M6 2.5h8l4 4v15H6z" /><path d="M14 2.5v4h4" />
237
+ {:else if kind === 'text'}
238
+ <path d="M5 6h14M5 12h14M5 18h9" />
239
+ {:else}
240
+ <path d="M8 7l-4 5 4 5M16 7l4 5-4 5" />
241
+ {/if}
242
+ </svg>
243
+ {/snippet}
244
+
245
+ <div class="sv-export" bind:this={rootEl}>
246
+ <button
247
+ type="button"
248
+ class="sv-export-btn"
249
+ aria-haspopup="menu"
250
+ aria-expanded={open}
251
+ disabled={!api}
252
+ bind:this={triggerEl}
253
+ onclick={toggleOpen}
254
+ >
255
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
256
+ stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
257
+ <path d="M12 3v12M7 10l5 5 5-5M5 21h14" />
258
+ </svg>
259
+ {label}
260
+ <span class="sv-export-caret" aria-hidden="true">▾</span>
261
+ </button>
262
+
263
+ {#if open}
264
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
265
+ <div
266
+ class="sv-export-menu"
267
+ role="menu"
268
+ aria-label="Export options"
269
+ bind:this={menuEl}
270
+ onkeydown={onMenuKeydown}
271
+ >
272
+ {#if allowScope}
273
+ <div class="sv-export-section">
274
+ <span class="sv-export-section-label">Rows</span>
275
+ <div class="sv-export-scope">
276
+ {#each SCOPES as s (s.value)}
277
+ <label class="sv-export-opt">
278
+ <input
279
+ type="radio"
280
+ name="sv-export-scope"
281
+ value={s.value}
282
+ checked={currentScope === s.value}
283
+ onchange={() => (currentScope = s.value)}
284
+ />
285
+ {s.label}
286
+ </label>
287
+ {/each}
288
+ </div>
289
+ </div>
290
+ {/if}
291
+
292
+ {#if allowColumns && picks.length > 0}
293
+ <div class="sv-export-section">
294
+ <button type="button" class="sv-export-cols-toggle" onclick={() => (showColumns = !showColumns)}>
295
+ Columns <span class="sv-export-count">{picks.filter((p) => p.checked).length}/{picks.length}</span>
296
+ <span aria-hidden="true">{showColumns ? '▴' : '▾'}</span>
297
+ </button>
298
+ {#if showColumns}
299
+ <div class="sv-export-cols-list">
300
+ {#each picks as p (p.field)}
301
+ <label class="sv-export-opt">
302
+ <input type="checkbox" bind:checked={p.checked} />
303
+ {p.header}
304
+ </label>
305
+ {/each}
306
+ </div>
307
+ {/if}
308
+ </div>
309
+ {/if}
310
+
311
+ <div class="sv-export-section">
312
+ <span class="sv-export-section-label">Download</span>
313
+ {#each formats as f (f)}
314
+ <button type="button" class="sv-export-item" role="menuitem" disabled={busy} onclick={() => run(f)}>
315
+ {@render formatIcon(FORMAT_META[f].icon)}
316
+ <span class="sv-export-item-text">
317
+ <span class="sv-export-item-label">{FORMAT_META[f].label}</span>
318
+ <span class="sv-export-item-desc">{FORMAT_META[f].desc}</span>
319
+ </span>
320
+ </button>
321
+ {/each}
322
+ </div>
323
+
324
+ {#if allowCopy || allowPrint}
325
+ <div class="sv-export-actions">
326
+ {#if allowCopy}
327
+ <button type="button" class="sv-export-chip" disabled={busy} onclick={() => copy('tsv')}>Copy for Excel</button>
328
+ <button type="button" class="sv-export-chip" disabled={busy} onclick={() => copy('md')}>Copy Markdown</button>
329
+ {/if}
330
+ {#if allowPrint}
331
+ <button type="button" class="sv-export-chip" disabled={busy} onclick={print}>Print…</button>
332
+ {/if}
333
+ {#if copied}<span class="sv-export-copied">Copied {copied} ✓</span>{/if}
334
+ </div>
335
+ {/if}
336
+
337
+ {#if busy}
338
+ <div class="sv-export-progress">
339
+ <div class="sv-export-track"><div class="sv-export-bar" style:width={`${Math.round(progress * 100)}%`}></div></div>
340
+ <button type="button" class="sv-export-cancel" onclick={cancel}>Cancel</button>
341
+ </div>
342
+ {/if}
343
+
344
+ {#if error}
345
+ <p class="sv-export-error" role="alert">{error}</p>
346
+ {/if}
347
+ </div>
348
+ {/if}
349
+ </div>
350
+
351
+ <style>
352
+ .sv-export {
353
+ position: relative;
354
+ display: inline-block;
355
+ }
356
+ .sv-export-btn {
357
+ display: inline-flex;
358
+ align-items: center;
359
+ gap: 6px;
360
+ padding: 6px 12px;
361
+ font: inherit;
362
+ font-size: 13px;
363
+ font-weight: 500;
364
+ border: 1px solid var(--sg-border, #d0d7de);
365
+ border-radius: 8px;
366
+ background: var(--sg-header-bg, #f6f8fa);
367
+ color: var(--sg-fg, #1f2328);
368
+ cursor: pointer;
369
+ }
370
+ .sv-export-btn:disabled {
371
+ opacity: 0.5;
372
+ cursor: not-allowed;
373
+ }
374
+ .sv-export-caret {
375
+ font-size: 10px;
376
+ opacity: 0.7;
377
+ }
378
+ .sv-export-menu {
379
+ position: absolute;
380
+ z-index: 50;
381
+ top: calc(100% + 6px);
382
+ left: 0;
383
+ min-width: 232px;
384
+ padding: 6px;
385
+ border: 1px solid var(--sg-border, #d0d7de);
386
+ border-radius: 12px;
387
+ background: var(--sg-bg, #fff);
388
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14);
389
+ }
390
+ .sv-export-section {
391
+ padding: 6px 6px 8px;
392
+ border-bottom: 1px solid var(--sg-border, #eaecef);
393
+ }
394
+ .sv-export-section:last-of-type {
395
+ border-bottom: none;
396
+ }
397
+ .sv-export-section-label {
398
+ display: block;
399
+ padding: 2px 6px 6px;
400
+ font-size: 10px;
401
+ font-weight: 700;
402
+ text-transform: uppercase;
403
+ letter-spacing: 0.07em;
404
+ color: var(--sg-muted, #8b949e);
405
+ }
406
+ .sv-export-scope {
407
+ display: flex;
408
+ flex-direction: column;
409
+ gap: 1px;
410
+ }
411
+ .sv-export-opt {
412
+ display: flex;
413
+ align-items: center;
414
+ gap: 7px;
415
+ padding: 3px 6px;
416
+ font-size: 12.5px;
417
+ color: var(--sg-fg, #1f2328);
418
+ cursor: pointer;
419
+ border-radius: 6px;
420
+ }
421
+ .sv-export-opt:hover {
422
+ background: var(--sg-header-bg, #f6f8fa);
423
+ }
424
+ .sv-export-item {
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 10px;
428
+ width: 100%;
429
+ text-align: left;
430
+ padding: 6px 8px;
431
+ font: inherit;
432
+ border: none;
433
+ border-radius: 8px;
434
+ background: transparent;
435
+ color: var(--sg-fg, #1f2328);
436
+ cursor: pointer;
437
+ }
438
+ .sv-export-item:hover:not(:disabled) {
439
+ background: var(--sg-header-bg, #f6f8fa);
440
+ }
441
+ .sv-export-item:disabled {
442
+ opacity: 0.5;
443
+ cursor: default;
444
+ }
445
+ .sv-export-icon {
446
+ flex-shrink: 0;
447
+ color: var(--site-accent-2, #6366f1);
448
+ }
449
+ .sv-export-item-text {
450
+ display: flex;
451
+ flex-direction: column;
452
+ line-height: 1.25;
453
+ }
454
+ .sv-export-item-label {
455
+ font-size: 13px;
456
+ font-weight: 500;
457
+ }
458
+ .sv-export-item-desc {
459
+ font-size: 11px;
460
+ color: var(--sg-muted, #8b949e);
461
+ }
462
+ .sv-export-cols-toggle {
463
+ display: flex;
464
+ align-items: center;
465
+ gap: 6px;
466
+ width: 100%;
467
+ font: inherit;
468
+ font-size: 11px;
469
+ font-weight: 700;
470
+ text-transform: uppercase;
471
+ letter-spacing: 0.07em;
472
+ color: var(--sg-muted, #8b949e);
473
+ background: transparent;
474
+ border: none;
475
+ padding: 2px 6px;
476
+ cursor: pointer;
477
+ }
478
+ .sv-export-count {
479
+ font-weight: 600;
480
+ text-transform: none;
481
+ letter-spacing: 0;
482
+ color: var(--sg-fg, #1f2328);
483
+ }
484
+ .sv-export-cols-toggle > span:last-child {
485
+ margin-left: auto;
486
+ }
487
+ .sv-export-cols-list {
488
+ max-height: 168px;
489
+ overflow-y: auto;
490
+ margin-top: 4px;
491
+ }
492
+ .sv-export-actions {
493
+ display: flex;
494
+ align-items: center;
495
+ flex-wrap: wrap;
496
+ gap: 6px;
497
+ padding: 8px 6px 4px;
498
+ }
499
+ .sv-export-chip {
500
+ font: inherit;
501
+ font-size: 12px;
502
+ padding: 4px 10px;
503
+ border-radius: 999px;
504
+ border: 1px solid var(--sg-border, #d0d7de);
505
+ background: var(--sg-header-bg, #f6f8fa);
506
+ color: var(--sg-fg, #1f2328);
507
+ cursor: pointer;
508
+ }
509
+ .sv-export-chip:hover:not(:disabled) {
510
+ border-color: var(--site-accent, #3b82f6);
511
+ }
512
+ .sv-export-chip:disabled {
513
+ opacity: 0.5;
514
+ cursor: default;
515
+ }
516
+ .sv-export-copied {
517
+ font-size: 11px;
518
+ color: #16a34a;
519
+ }
520
+ .sv-export-progress {
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 8px;
524
+ padding: 8px 6px 4px;
525
+ }
526
+ .sv-export-track {
527
+ flex: 1;
528
+ min-width: 0;
529
+ height: 6px;
530
+ border-radius: 999px;
531
+ background: var(--sg-border, #eaecef);
532
+ overflow: hidden;
533
+ }
534
+ .sv-export-bar {
535
+ height: 100%;
536
+ border-radius: 999px;
537
+ background: var(--site-accent, #3b82f6);
538
+ transition: width 120ms ease;
539
+ }
540
+ .sv-export-cancel {
541
+ font: inherit;
542
+ font-size: 12px;
543
+ border: none;
544
+ background: transparent;
545
+ color: var(--site-accent-2, #6366f1);
546
+ cursor: pointer;
547
+ }
548
+ .sv-export-error {
549
+ margin: 6px 6px 2px;
550
+ font-size: 12px;
551
+ color: #dc2626;
552
+ }
553
+ </style>