@svgrid/enterprise 3.0.1 → 3.0.3

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 (445) hide show
  1. package/LICENSE +94 -24
  2. package/README.md +36 -4
  3. package/dist/SvAdvancedFilter.svelte +1 -1
  4. package/dist/SvAlertsManager.svelte +2 -1
  5. package/dist/SvBoard.svelte +2 -1
  6. package/dist/SvExportMenu.svelte +4 -1
  7. package/dist/SvExportMenu.svelte.d.ts +0 -13
  8. package/dist/SvExpressionEditor.svelte +3 -3
  9. package/dist/SvFormulaBar.svelte +680 -0
  10. package/dist/SvFormulaBar.svelte.d.ts +69 -0
  11. package/dist/SvGridAlerts.svelte +49 -9
  12. package/dist/SvGridBoard.svelte +13 -8
  13. package/dist/SvGridBulkEditDrawer.svelte +26 -2
  14. package/dist/SvGridEditPanel.svelte +3 -0
  15. package/dist/SvGridScheduler.svelte +69 -40
  16. package/dist/SvGridSelectionBar.svelte +10 -3
  17. package/dist/SvPivotDesigner.svelte +263 -54
  18. package/dist/SvPivotDesigner.svelte.d.ts +27 -20
  19. package/dist/SvRibbonIcon.svelte +46 -0
  20. package/dist/SvRibbonIcon.svelte.d.ts +17 -0
  21. package/dist/SvSchemaChart.svelte +4 -2
  22. package/dist/SvSheet.svelte +7914 -0
  23. package/dist/SvSheet.svelte.d.ts +215 -0
  24. package/dist/SvSheetChartSetup.svelte +151 -0
  25. package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
  26. package/dist/SvSheetComment.svelte +283 -0
  27. package/dist/SvSheetComment.svelte.d.ts +22 -0
  28. package/dist/SvSheetConditionalFormat.svelte +159 -0
  29. package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
  30. package/dist/SvSheetDataValidation.svelte +273 -0
  31. package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
  32. package/dist/SvSheetEditRanges.svelte +217 -0
  33. package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
  34. package/dist/SvSheetErrors.svelte +125 -0
  35. package/dist/SvSheetErrors.svelte.d.ts +20 -0
  36. package/dist/SvSheetEvaluate.svelte +133 -0
  37. package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
  38. package/dist/SvSheetFilterMenu.svelte +320 -0
  39. package/dist/SvSheetFilterMenu.svelte.d.ts +22 -0
  40. package/dist/SvSheetFindReplace.svelte +132 -0
  41. package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
  42. package/dist/SvSheetFormatCells.svelte +465 -0
  43. package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
  44. package/dist/SvSheetGoalSeek.svelte +145 -0
  45. package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
  46. package/dist/SvSheetInsertFunction.svelte +149 -0
  47. package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
  48. package/dist/SvSheetIteration.svelte +81 -0
  49. package/dist/SvSheetIteration.svelte.d.ts +10 -0
  50. package/dist/SvSheetLink.svelte +99 -0
  51. package/dist/SvSheetLink.svelte.d.ts +17 -0
  52. package/dist/SvSheetListPicker.svelte +91 -0
  53. package/dist/SvSheetListPicker.svelte.d.ts +10 -0
  54. package/dist/SvSheetManageRules.svelte +187 -0
  55. package/dist/SvSheetManageRules.svelte.d.ts +16 -0
  56. package/dist/SvSheetNameManager.svelte +227 -0
  57. package/dist/SvSheetNameManager.svelte.d.ts +11 -0
  58. package/dist/SvSheetPageSetup.svelte +159 -0
  59. package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
  60. package/dist/SvSheetPasteSpecial.svelte +107 -0
  61. package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
  62. package/dist/SvSheetPivot.svelte +220 -0
  63. package/dist/SvSheetPivot.svelte.d.ts +21 -0
  64. package/dist/SvSheetProtectSheet.svelte +81 -0
  65. package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
  66. package/dist/SvSheetRemoveDuplicates.svelte +152 -0
  67. package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
  68. package/dist/SvSheetRibbon.svelte +1254 -0
  69. package/dist/SvSheetRibbon.svelte.d.ts +34 -0
  70. package/dist/SvSheetSizeDialog.svelte +60 -0
  71. package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
  72. package/dist/SvSheetSort.svelte +218 -0
  73. package/dist/SvSheetSort.svelte.d.ts +28 -0
  74. package/dist/SvSheetSparklines.svelte +135 -0
  75. package/dist/SvSheetSparklines.svelte.d.ts +14 -0
  76. package/dist/SvSheetTable.svelte +160 -0
  77. package/dist/SvSheetTable.svelte.d.ts +24 -0
  78. package/dist/SvSheetTabs.svelte +571 -0
  79. package/dist/SvSheetTabs.svelte.d.ts +50 -0
  80. package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
  81. package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
  82. package/dist/SvSheetTextToColumns.svelte +172 -0
  83. package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
  84. package/dist/SvSheetValidationAlert.svelte +85 -0
  85. package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
  86. package/dist/advanced-filter-enable.js +1 -1
  87. package/dist/alerts/alert-engine.d.ts +6 -0
  88. package/dist/alerts/alert-engine.js +21 -4
  89. package/dist/alerts/alert-formats.js +2 -0
  90. package/dist/alerts/alert-types.d.ts +1 -1
  91. package/dist/alerts.d.ts +1 -1
  92. package/dist/alerts.js +2 -2
  93. package/dist/board.js +2 -2
  94. package/dist/bulk-edit.d.ts +3 -1
  95. package/dist/bulk-edit.js +1 -1
  96. package/dist/cdn/svgrid-enterprise.svelte-external.js +43538 -6539
  97. package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
  98. package/dist/designer/assets/SvChartMenu-BDoeqGz6.js +1 -0
  99. package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
  100. package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
  101. package/dist/designer/assets/SvDateTimePicker-DCPg7WGN.js +1 -0
  102. package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
  103. package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
  104. package/dist/designer/assets/SvGridChart-Cy1ATrWh.js +3 -0
  105. package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
  106. package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
  107. package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
  108. package/dist/designer/assets/SvGridChartPanel-CATdVc9X.js +4 -0
  109. package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
  110. package/dist/designer/assets/SvGridChartView-CmsZtCED.js +1 -0
  111. package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
  112. package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
  113. package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
  114. package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
  115. package/dist/designer/assets/SvTabs-2mHC6oaz.js +1 -0
  116. package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
  117. package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
  118. package/dist/designer/assets/chart-Cn7MnNqA.js +1 -0
  119. package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
  120. package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
  121. package/dist/designer/assets/chart-panel-messages-Dz7PTIqT.js +1 -0
  122. package/dist/designer/assets/chart-summary-BD1qYB2R.js +1 -0
  123. package/dist/designer/assets/chart-trend-B8xr5LuD.js +1 -0
  124. package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
  125. package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
  126. package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
  127. package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
  128. package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
  129. package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
  130. package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
  131. package/dist/designer/assets/focus-trap-V2-6Fy9I.js +1 -0
  132. package/dist/designer/assets/{index-C_a9Cj8Y.js → index-D5bVbuzI.js} +318 -315
  133. package/dist/designer/assets/index-_JVFxt02.css +1 -0
  134. package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
  135. package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
  136. package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
  137. package/dist/designer/assets/src-CITpcm6b.js +1 -0
  138. package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
  139. package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
  140. package/dist/designer/index.html +29 -19
  141. package/dist/export-pdf.d.ts +164 -0
  142. package/dist/export-pdf.js +346 -15
  143. package/dist/export.js +25 -10
  144. package/dist/gantt/SvGridGantt.svelte +2890 -0
  145. package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
  146. package/dist/gantt/gantt-config.d.ts +73 -0
  147. package/dist/gantt/gantt-config.js +1 -0
  148. package/dist/gantt/gantt-critical-path.d.ts +67 -0
  149. package/dist/gantt/gantt-critical-path.js +207 -0
  150. package/dist/gantt/gantt-model.d.ts +257 -0
  151. package/dist/gantt/gantt-model.js +651 -0
  152. package/dist/gantt/gantt-resources.d.ts +59 -0
  153. package/dist/gantt/gantt-resources.js +71 -0
  154. package/dist/gantt/gantt.d.ts +7 -0
  155. package/dist/gantt/gantt.js +36 -0
  156. package/dist/gantt/index.d.ts +15 -0
  157. package/dist/gantt/index.js +22 -0
  158. package/dist/gantt/timeline-arrows.d.ts +102 -0
  159. package/dist/gantt/timeline-arrows.js +181 -0
  160. package/dist/import.d.ts +2 -2
  161. package/dist/import.js +1 -1
  162. package/dist/index.d.ts +93 -3
  163. package/dist/index.js +107 -3
  164. package/dist/install.js +13 -1
  165. package/dist/license-core.d.ts +17 -1
  166. package/dist/license-core.js +16 -5
  167. package/dist/license.d.ts +18 -2
  168. package/dist/license.js +71 -6
  169. package/dist/node/studio.js +203 -9
  170. package/dist/pivot-chart.d.ts +25 -1
  171. package/dist/pivot-chart.js +25 -69
  172. package/dist/pivot-enable.js +2 -2
  173. package/dist/scheduler-dependencies.d.ts +35 -0
  174. package/dist/scheduler-dependencies.js +36 -1
  175. package/dist/scheduler.js +2 -2
  176. package/dist/selection-bar.js +2 -2
  177. package/dist/server/SvGroupCell.svelte +213 -0
  178. package/dist/server/SvGroupCell.svelte.d.ts +62 -0
  179. package/dist/server/SvRowGroupPanel.svelte +322 -0
  180. package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
  181. package/dist/server/enable.d.ts +17 -0
  182. package/dist/server/enable.js +49 -0
  183. package/dist/server/index.d.ts +22 -0
  184. package/dist/server/index.js +24 -0
  185. package/dist/server/messages.d.ts +45 -0
  186. package/dist/server/messages.js +35 -0
  187. package/dist/server/server-group-model.d.ts +104 -0
  188. package/dist/server/server-group-model.js +272 -0
  189. package/dist/server/server-pivot.d.ts +46 -0
  190. package/dist/server/server-pivot.js +57 -0
  191. package/dist/server/server-row-model.d.ts +547 -0
  192. package/dist/server/server-row-model.js +1551 -0
  193. package/dist/server/server-selection.d.ts +99 -0
  194. package/dist/server/server-selection.js +215 -0
  195. package/dist/server/svgrid-adapter.d.ts +155 -0
  196. package/dist/server/svgrid-adapter.js +202 -0
  197. package/dist/sheet/address.d.ts +43 -0
  198. package/dist/sheet/address.js +81 -0
  199. package/dist/sheet/ast.d.ts +138 -0
  200. package/dist/sheet/ast.js +70 -0
  201. package/dist/sheet/auto-filter.d.ts +120 -0
  202. package/dist/sheet/auto-filter.js +295 -0
  203. package/dist/sheet/autocomplete.d.ts +51 -0
  204. package/dist/sheet/autocomplete.js +248 -0
  205. package/dist/sheet/cell-images.d.ts +39 -0
  206. package/dist/sheet/cell-images.js +90 -0
  207. package/dist/sheet/cell-types.d.ts +58 -0
  208. package/dist/sheet/cell-types.js +96 -0
  209. package/dist/sheet/coerce.d.ts +71 -0
  210. package/dist/sheet/coerce.js +289 -0
  211. package/dist/sheet/commands.d.ts +89 -0
  212. package/dist/sheet/commands.js +230 -0
  213. package/dist/sheet/comments.d.ts +63 -0
  214. package/dist/sheet/comments.js +131 -0
  215. package/dist/sheet/conditional-formats.d.ts +167 -0
  216. package/dist/sheet/conditional-formats.js +341 -0
  217. package/dist/sheet/csv.d.ts +32 -0
  218. package/dist/sheet/csv.js +158 -0
  219. package/dist/sheet/culture.d.ts +87 -0
  220. package/dist/sheet/culture.js +247 -0
  221. package/dist/sheet/delta.d.ts +102 -0
  222. package/dist/sheet/delta.js +203 -0
  223. package/dist/sheet/deps.d.ts +104 -0
  224. package/dist/sheet/deps.js +274 -0
  225. package/dist/sheet/document.d.ts +252 -0
  226. package/dist/sheet/document.js +465 -0
  227. package/dist/sheet/edit-keys.d.ts +14 -0
  228. package/dist/sheet/edit-keys.js +40 -0
  229. package/dist/sheet/engine.d.ts +67 -0
  230. package/dist/sheet/engine.js +19 -0
  231. package/dist/sheet/entry.d.ts +35 -0
  232. package/dist/sheet/entry.js +226 -0
  233. package/dist/sheet/error-check.d.ts +49 -0
  234. package/dist/sheet/error-check.js +83 -0
  235. package/dist/sheet/evaluate-steps.d.ts +48 -0
  236. package/dist/sheet/evaluate-steps.js +196 -0
  237. package/dist/sheet/evaluate.d.ts +128 -0
  238. package/dist/sheet/evaluate.js +1049 -0
  239. package/dist/sheet/filter-files.d.ts +37 -0
  240. package/dist/sheet/filter-files.js +465 -0
  241. package/dist/sheet/find-replace.d.ts +62 -0
  242. package/dist/sheet/find-replace.js +152 -0
  243. package/dist/sheet/format-store.d.ts +114 -0
  244. package/dist/sheet/format-store.js +243 -0
  245. package/dist/sheet/freeze.d.ts +54 -0
  246. package/dist/sheet/freeze.js +58 -0
  247. package/dist/sheet/from-rows.d.ts +14 -0
  248. package/dist/sheet/from-rows.js +50 -0
  249. package/dist/sheet/function-catalog.d.ts +10 -0
  250. package/dist/sheet/function-catalog.js +385 -0
  251. package/dist/sheet/functions.d.ts +28 -0
  252. package/dist/sheet/functions.js +634 -0
  253. package/dist/sheet/goal-seek.d.ts +73 -0
  254. package/dist/sheet/goal-seek.js +153 -0
  255. package/dist/sheet/index.d.ts +80 -0
  256. package/dist/sheet/index.js +80 -0
  257. package/dist/sheet/links.d.ts +57 -0
  258. package/dist/sheet/links.js +174 -0
  259. package/dist/sheet/merges.d.ts +71 -0
  260. package/dist/sheet/merges.js +137 -0
  261. package/dist/sheet/messages.d.ts +778 -0
  262. package/dist/sheet/messages.js +806 -0
  263. package/dist/sheet/names.d.ts +31 -0
  264. package/dist/sheet/names.js +87 -0
  265. package/dist/sheet/navigate.d.ts +52 -0
  266. package/dist/sheet/navigate.js +135 -0
  267. package/dist/sheet/number-format.d.ts +110 -0
  268. package/dist/sheet/number-format.js +905 -0
  269. package/dist/sheet/objects.d.ts +106 -0
  270. package/dist/sheet/objects.js +149 -0
  271. package/dist/sheet/ods-document.d.ts +44 -0
  272. package/dist/sheet/ods-document.js +1275 -0
  273. package/dist/sheet/open-file.d.ts +26 -0
  274. package/dist/sheet/open-file.js +56 -0
  275. package/dist/sheet/outline.d.ts +82 -0
  276. package/dist/sheet/outline.js +228 -0
  277. package/dist/sheet/packs/array.d.ts +16 -0
  278. package/dist/sheet/packs/array.js +172 -0
  279. package/dist/sheet/packs/database.d.ts +42 -0
  280. package/dist/sheet/packs/database.js +142 -0
  281. package/dist/sheet/packs/engineering.d.ts +2 -0
  282. package/dist/sheet/packs/engineering.js +296 -0
  283. package/dist/sheet/packs/financial.d.ts +5 -0
  284. package/dist/sheet/packs/financial.js +220 -0
  285. package/dist/sheet/packs/math-stats.d.ts +2 -0
  286. package/dist/sheet/packs/math-stats.js +372 -0
  287. package/dist/sheet/packs/special.d.ts +44 -0
  288. package/dist/sheet/packs/special.js +187 -0
  289. package/dist/sheet/packs/stats-dist.d.ts +2 -0
  290. package/dist/sheet/packs/stats-dist.js +785 -0
  291. package/dist/sheet/packs/text-date.d.ts +2 -0
  292. package/dist/sheet/packs/text-date.js +332 -0
  293. package/dist/sheet/packs/trig.d.ts +2 -0
  294. package/dist/sheet/packs/trig.js +73 -0
  295. package/dist/sheet/page-setup.d.ts +73 -0
  296. package/dist/sheet/page-setup.js +73 -0
  297. package/dist/sheet/palette.d.ts +19 -0
  298. package/dist/sheet/palette.js +84 -0
  299. package/dist/sheet/parse.d.ts +13 -0
  300. package/dist/sheet/parse.js +140 -0
  301. package/dist/sheet/paste-special.d.ts +186 -0
  302. package/dist/sheet/paste-special.js +601 -0
  303. package/dist/sheet/pivot-range.d.ts +160 -0
  304. package/dist/sheet/pivot-range.js +333 -0
  305. package/dist/sheet/presence.d.ts +73 -0
  306. package/dist/sheet/presence.js +79 -0
  307. package/dist/sheet/print.d.ts +104 -0
  308. package/dist/sheet/print.js +169 -0
  309. package/dist/sheet/protection.d.ts +75 -0
  310. package/dist/sheet/protection.js +101 -0
  311. package/dist/sheet/rects.d.ts +45 -0
  312. package/dist/sheet/rects.js +112 -0
  313. package/dist/sheet/refs.d.ts +130 -0
  314. package/dist/sheet/refs.js +413 -0
  315. package/dist/sheet/ribbon-icons.d.ts +31 -0
  316. package/dist/sheet/ribbon-icons.js +536 -0
  317. package/dist/sheet/ribbon.d.ts +173 -0
  318. package/dist/sheet/ribbon.js +1054 -0
  319. package/dist/sheet/sheet-kinds.d.ts +74 -0
  320. package/dist/sheet/sheet-kinds.js +91 -0
  321. package/dist/sheet/shortcuts.d.ts +174 -0
  322. package/dist/sheet/shortcuts.js +674 -0
  323. package/dist/sheet/sort.d.ts +51 -0
  324. package/dist/sheet/sort.js +107 -0
  325. package/dist/sheet/sparklines.d.ts +111 -0
  326. package/dist/sheet/sparklines.js +204 -0
  327. package/dist/sheet/structure.d.ts +87 -0
  328. package/dist/sheet/structure.js +166 -0
  329. package/dist/sheet/table-styles.d.ts +74 -0
  330. package/dist/sheet/table-styles.js +152 -0
  331. package/dist/sheet/tables.d.ts +89 -0
  332. package/dist/sheet/tables.js +178 -0
  333. package/dist/sheet/tokenize.d.ts +71 -0
  334. package/dist/sheet/tokenize.js +355 -0
  335. package/dist/sheet/transforms.d.ts +76 -0
  336. package/dist/sheet/transforms.js +154 -0
  337. package/dist/sheet/validation.d.ts +108 -0
  338. package/dist/sheet/validation.js +278 -0
  339. package/dist/sheet/workbook.d.ts +217 -0
  340. package/dist/sheet/workbook.js +1120 -0
  341. package/dist/sheet/xls-cfb.d.ts +32 -0
  342. package/dist/sheet/xls-cfb.js +290 -0
  343. package/dist/sheet/xls-document.d.ts +10 -0
  344. package/dist/sheet/xls-document.js +1243 -0
  345. package/dist/sheet/xls-formula.d.ts +40 -0
  346. package/dist/sheet/xls-formula.js +569 -0
  347. package/dist/sheet/xlsx-document.d.ts +108 -0
  348. package/dist/sheet/xlsx-document.js +1849 -0
  349. package/dist/sheet/xlsx-drawing.d.ts +99 -0
  350. package/dist/sheet/xlsx-drawing.js +420 -0
  351. package/dist/sheet-enable.d.ts +6 -0
  352. package/dist/sheet-enable.js +45 -0
  353. package/dist/sheet-text.d.ts +7 -0
  354. package/dist/sheet-text.js +29 -0
  355. package/dist/sources/auth-supabase.js +2 -2
  356. package/dist/sources/realtime-supabase.js +2 -2
  357. package/dist/sources/rest.d.ts +6 -2
  358. package/dist/sources/rest.js +42 -7
  359. package/dist/sources/supabase.d.ts +10 -1
  360. package/dist/sources/supabase.js +73 -40
  361. package/dist/studio/emit-project.js +14 -1
  362. package/dist/studio/index.js +1 -1
  363. package/dist/studio/project.d.ts +11 -2
  364. package/dist/studio/project.js +5 -2
  365. package/dist/studio/ui-components.generated.js +170 -3
  366. package/dist/sveltekit/in-memory.d.ts +2 -2
  367. package/dist/sveltekit/in-memory.js +125 -4
  368. package/dist/sveltekit/index.d.ts +1 -1
  369. package/dist/sveltekit/index.js +1 -1
  370. package/dist/sveltekit/query-plan.d.ts +50 -4
  371. package/dist/sveltekit/query-plan.js +25 -3
  372. package/dist/sveltekit/sql-source.d.ts +6 -0
  373. package/dist/sveltekit/sql-source.js +34 -6
  374. package/dist/sveltekit/sql.d.ts +31 -0
  375. package/dist/sveltekit/sql.js +135 -25
  376. package/dist/sveltekit/transport.d.ts +10 -2
  377. package/dist/sveltekit/transport.js +30 -6
  378. package/dist/upgrade-prompt.d.ts +1 -1
  379. package/dist/version.d.ts +1 -1
  380. package/dist/version.js +1 -1
  381. package/dist/watermark.js +35 -2
  382. package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
  383. package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
  384. package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
  385. package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
  386. package/dist/wc/SvField-B_kurx_P.js +149 -0
  387. package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
  388. package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
  389. package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
  390. package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
  391. package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
  392. package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
  393. package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
  394. package/dist/wc/SvModal-DrfOYYyX.js +189 -0
  395. package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
  396. package/dist/wc/chart-6OXlybVT.js +4341 -0
  397. package/dist/wc/chart-export-CeSADGfi.js +135 -0
  398. package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
  399. package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
  400. package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
  401. package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
  402. package/dist/wc/client-DQIzjDBG.js +3064 -0
  403. package/dist/wc/column-resize-DUPgynvc.js +101 -0
  404. package/dist/wc/date-format-DS-z_h9v.js +1130 -0
  405. package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
  406. package/dist/wc/editor-contract-peBDxir_.js +22 -0
  407. package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
  408. package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
  409. package/dist/wc/list-option-DrRyivWp.js +82 -0
  410. package/dist/wc/popover-WsGlvtQv.js +69 -0
  411. package/dist/wc/positioning-BiKLfu1E.js +112 -0
  412. package/dist/wc/react.d.ts +41 -0
  413. package/dist/wc/react.js +90 -0
  414. package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
  415. package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
  416. package/dist/wc/row-resize-limG_N1z.js +111 -0
  417. package/dist/wc/sv-sheet-element.d.ts +97 -0
  418. package/dist/wc/sv-sheet-element.js +44213 -0
  419. package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
  420. package/dist/wc/validate-BqDthkNr.js +88 -0
  421. package/dist/wc/vue.d.ts +31 -0
  422. package/dist/wc/vue.js +75 -0
  423. package/package.json +36 -6
  424. package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
  425. package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
  426. package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
  427. package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
  428. package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
  429. package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
  430. package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
  431. package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
  432. package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
  433. package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
  434. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
  435. package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
  436. package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
  437. package/dist/designer/assets/chart-DXFganU8.js +0 -1
  438. package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
  439. package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
  440. package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
  441. package/dist/designer/assets/easing-zaypKrr0.js +0 -3
  442. package/dist/designer/assets/index-k2stW6L8.css +0 -1
  443. package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
  444. package/dist/designer/assets/src-D9p27OGC.js +0 -58
  445. package/dist/designer/assets/validate-h-aslRV9.js +0 -1
@@ -0,0 +1,1254 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The ribbon.
4
+ *
5
+ * Renders `RIBBON_TABS` and nothing else: the tab strip, then a band of
6
+ * groups, each a block of controls over a small centred group label.
7
+ * That label is the thing that makes a ribbon read as a ribbon rather than
8
+ * as a toolbar, so it is not optional.
9
+ *
10
+ * The geometry is Excel's. A group is three rows of 24px; a large button
11
+ * (icon over label) takes a whole column of that height and a small one
12
+ * takes a row, so Cut / Copy / Paste Special stack beside a tall Paste. A
13
+ * split button pairs a face that applies the last pick with an arrow that
14
+ * opens the menu, and the colour menus are Excel's picker: the theme row,
15
+ * five tints and shades under each colour, then the standard colours.
16
+ *
17
+ * It is responsive the way Excel's is, in Excel's two steps: when the band
18
+ * is narrower than its groups, groups go compact from the right (small
19
+ * buttons keep the icon and drop the label), and only when every group is
20
+ * compact do groups fold, again from the right, into one large button each
21
+ * that opens the group in a dropdown. Nothing is hidden, it is only folded.
22
+ * The three widths of every group are measured off invisible copies of the
23
+ * band, so a group that starts folded still knows how wide it would like
24
+ * to be.
25
+ *
26
+ * It collapses the way Excel's does: a double-click on a tab, the chevron
27
+ * at the band's end or Ctrl+F1 leaves the tab row alone, a click on a tab
28
+ * then shows the band over the sheet until a command runs, and the pin at
29
+ * its end brings it back for good (`collapsed`, bindable).
30
+ *
31
+ * Painted with the grid's own --sg-* tokens rather than Excel's palette, so
32
+ * it matches whatever theme the app runs. Pick the `excel` theme preset and
33
+ * it reads as Excel; pick a dark one and it stays legible.
34
+ *
35
+ * The ribbon owns no sheet state. Every button calls an action from
36
+ * `sheet/ribbon.ts`, which calls the same function the matching keystroke
37
+ * does, so a button and its shortcut cannot drift apart.
38
+ */
39
+ import { tick } from 'svelte'
40
+ import { SvPopover } from '@svgrid/grid'
41
+ import type { GridCommandContext } from '@svgrid/grid/shortcuts'
42
+ import SvRibbonIcon from './SvRibbonIcon.svelte'
43
+ import {
44
+ RIBBON_TABS,
45
+ type RibbonTab, type RibbonGroup, type RibbonItem, type RibbonOption, type RibbonActionId,
46
+ } from './sheet/ribbon'
47
+ import { THEME_COLOURS, STANDARD_COLOURS } from './sheet/palette'
48
+ import { useSheetText } from './sheet-text'
49
+ import { defaultSheetMessages } from './sheet/messages'
50
+
51
+ type Props = {
52
+ /**
53
+ * How to reach the grid. A getter rather than the context itself because
54
+ * the context is rebuilt per call and the ribbon needs a live one at the
55
+ * moment a button is pressed, not the one that existed at mount.
56
+ */
57
+ cmd: () => GridCommandContext | null
58
+ /** Called after any action that changed something, so the host re-renders. */
59
+ onChange?: () => void
60
+ /** Actions the ribbon does not run itself, because they need chrome. */
61
+ onAction?: (action: RibbonActionId, cmd: GridCommandContext) => void
62
+ /** Which of `onAction`'s toggles are currently on. The host owns that
63
+ * state (there is no store here to read it from), so it reports it. */
64
+ activeActions?: ReadonlyArray<RibbonActionId>
65
+ tabs?: ReadonlyArray<RibbonTab>
66
+ /** Start on a tab other than the first. */
67
+ tab?: string
68
+ /**
69
+ * Actions to leave off the ribbon: a button nobody answers is worse
70
+ * than no button. A group with nothing left in it goes too.
71
+ */
72
+ without?: ReadonlyArray<RibbonActionId>
73
+ /**
74
+ * Excel's collapsed ribbon: the tabs alone. A click on a tab shows the
75
+ * band over the sheet until a command runs or the pointer leaves;
76
+ * double-clicking a tab, the chevron and Ctrl+F1 toggle it.
77
+ */
78
+ collapsed?: boolean
79
+ }
80
+
81
+ let {
82
+ cmd,
83
+ onChange,
84
+ onAction,
85
+ activeActions = [],
86
+ tabs = RIBBON_TABS,
87
+ // Excel opens on Home, not on File, whatever tab comes first.
88
+ tab = $bindable(tabs.some((t) => t.id === 'home') ? 'home' : tabs[0]?.id ?? 'home'),
89
+ without = [],
90
+ collapsed = $bindable(false),
91
+ }: Props = $props()
92
+
93
+ /** The band shown over the sheet while the ribbon is collapsed. */
94
+ let peek = $state(false)
95
+ function onTabClick(id: string) {
96
+ if (collapsed) { peek = tab === id ? !peek : true }
97
+ tab = id
98
+ }
99
+ function toggleCollapsed() {
100
+ collapsed = !collapsed
101
+ peek = false
102
+ }
103
+ $effect(() => {
104
+ if (!collapsed) peek = false
105
+ })
106
+ $effect(() => {
107
+ if (!peek) return
108
+ // A click outside the ribbon, or Escape, puts the peeked band away.
109
+ const away = (e: PointerEvent) => { if (!rootEl?.contains(e.target as Node) && !(e.target as HTMLElement)?.closest?.('.sv-popover, .sv-modal')) peek = false }
110
+ const esc = (e: KeyboardEvent) => { if (e.key === 'Escape') peek = false }
111
+ window.addEventListener('pointerdown', away, true)
112
+ window.addEventListener('keydown', esc, true)
113
+ return () => { window.removeEventListener('pointerdown', away, true); window.removeEventListener('keydown', esc, true) }
114
+ })
115
+
116
+ /**
117
+ * Bumped after every action so the `isOn` / `isEnabled` / `current` reads
118
+ * below re-run.
119
+ *
120
+ * They read through the command context into the grid and the format store,
121
+ * neither of which is `$state`, so nothing would otherwise tell Svelte that
122
+ * pressing Bold should light the Bold button up.
123
+ */
124
+ let version = $state(0)
125
+
126
+ const current = $derived.by(() => {
127
+ const found = tabs.find((t) => t.id === tab) ?? tabs[0]
128
+ if (!found || without.length === 0) return found
129
+ const groups = found.groups
130
+ .map((g) => ({ ...g, items: g.items.filter((i) => !i.emits || !without.includes(i.emits)) }))
131
+ .filter((g) => g.items.length > 0)
132
+ return { ...found, groups }
133
+ })
134
+
135
+ function stateOf(item: RibbonItem): { on: boolean; enabled: boolean } {
136
+ void version
137
+ const context = cmd()
138
+ if (!context) return { on: false, enabled: false }
139
+ if (item.emits) {
140
+ return { on: activeActions.includes(item.emits), enabled: true }
141
+ }
142
+ return {
143
+ on: item.lit ? activeActions.includes(item.lit) : (item.isOn?.(context) ?? false),
144
+ enabled: item.isEnabled?.(context) ?? true,
145
+ }
146
+ }
147
+
148
+ function currentOf(item: RibbonItem): string {
149
+ void version
150
+ const context = cmd()
151
+ return context && item.current ? item.current(context) : ''
152
+ }
153
+
154
+ function run(item: RibbonItem, value?: string) {
155
+ const context = cmd()
156
+ if (!context) return
157
+ if (item.emits) {
158
+ onAction?.(item.emits, context)
159
+ version += 1
160
+ // Focus goes back to the sheet here too. A dialog the action opens
161
+ // takes it from there and, being what had focus when the dialog
162
+ // opened, the sheet is where the dialog returns it; an action with no
163
+ // dialog (Sort A to Z) leaves the next Ctrl+Z on the sheet rather
164
+ // than on the button.
165
+ context.focus()
166
+ peek = false
167
+ return
168
+ }
169
+ // Only report a change when the action says it did something. A declined
170
+ // action (nothing selected, no store attached) should not make the host
171
+ // re-render and should not look like it worked.
172
+ if (item.run?.(context, value)) onChange?.()
173
+ version += 1
174
+ // The click left focus on the button. Excel puts it back on the sheet, so
175
+ // the next keystroke - Ctrl+Z, or just typing - goes where the user is
176
+ // looking rather than re-pressing Bold.
177
+ context.focus()
178
+ // A command from the peeked band is the band's last act; Excel's goes
179
+ // away the same moment.
180
+ peek = false
181
+ }
182
+
183
+ function launch(action: RibbonActionId) {
184
+ const context = cmd()
185
+ if (context) onAction?.(action, context)
186
+ peek = false
187
+ }
188
+
189
+ /** Excel puts the shortcut after the name in the tooltip. */
190
+ // Every string the ribbon prints goes through the shell's map, keyed
191
+ // off the model, so a translation covers a button the model gains later.
192
+ const t = useSheetText()
193
+ const label = (item: RibbonItem): string => t(`ribbon.${item.id}.label`)
194
+ const title = (item: RibbonItem): string => t(`ribbon.${item.id}.title`)
195
+ /** An option's text, unless its key belongs to an earlier option with the
196
+ * same value (two palette colours on one hex): that one keeps its own label. */
197
+ const optionLabel = (item: RibbonItem, option: RibbonOption): string => {
198
+ const key = `ribbon.${item.id}.option.${option.value}`
199
+ return defaultSheetMessages[key] === option.label ? t(key) : option.label
200
+ }
201
+ const groupLabel = (group: RibbonGroup): string => t(`ribbon.group.${group.id}`)
202
+ function tip(item: RibbonItem): string {
203
+ return item.keys ? `${title(item)} (${item.keys})` : title(item)
204
+ }
205
+
206
+ /**
207
+ * Re-read the toggles when the selection moves, so Bold lights up when you
208
+ * click a bold cell rather than only after you press a ribbon button.
209
+ * Cheap: it only bumps a counter, and the reads are lazy.
210
+ */
211
+ function refresh() {
212
+ version += 1
213
+ }
214
+
215
+ // ---- split buttons -------------------------------------------------------
216
+ /** The last value picked from each menu, which its face re-applies. */
217
+ let picked = $state<Record<string, string>>({})
218
+ // Every key exists from the start: a popover's `open` is bound to one of
219
+ // these, and binding an undefined value to a prop with a fallback is an
220
+ // error in Svelte rather than a false.
221
+ // svelte-ignore state_referenced_locally
222
+ let menuOpen = $state<Record<string, boolean>>(
223
+ Object.fromEntries(tabs.flatMap((t) => t.groups.flatMap((g) => g.items.map((i) => [i.id, false])))),
224
+ )
225
+
226
+ function faceValue(item: RibbonItem): string {
227
+ return picked[item.id] ?? item.initial ?? item.options?.[0]?.value ?? ''
228
+ }
229
+
230
+ function pick(item: RibbonItem, value: string) {
231
+ picked = { ...picked, [item.id]: value }
232
+ menuOpen = { ...menuOpen, [item.id]: false }
233
+ run(item, value)
234
+ }
235
+
236
+ /** A dropdown's entry: its own action, or the item's run with its value. */
237
+ function pickOption(item: RibbonItem, option: RibbonOption) {
238
+ menuOpen = { ...menuOpen, [item.id]: false }
239
+ if (option.emits) {
240
+ const context = cmd()
241
+ if (!context) return
242
+ onAction?.(option.emits, context)
243
+ version += 1
244
+ context.focus()
245
+ peek = false
246
+ return
247
+ }
248
+ run(item, option.value)
249
+ }
250
+
251
+ /** Whether a face's colour bar has a colour to show, or means "none". */
252
+ function barColour(item: RibbonItem): string | null {
253
+ const value = faceValue(item)
254
+ return value === item.none?.value ? null : value
255
+ }
256
+
257
+ // ---- responsive collapse -------------------------------------------------
258
+ /** Natural width of each group, expanded, measured off the hidden band. */
259
+ const natural = new Map<string, number>()
260
+ /** The same with the small buttons' labels off: Excel's first step down. */
261
+ const compactWidth = new Map<string, number>()
262
+ /** And folded to one large button, whose width its label sets. */
263
+ const foldedWidth = new Map<string, number>()
264
+ let measured = $state(0)
265
+ let bandWidth = $state(0)
266
+ // svelte-ignore state_referenced_locally
267
+ let groupOpen = $state<Record<string, boolean>>(
268
+ Object.fromEntries(tabs.flatMap((t) => t.groups.map((g) => [g.id, false]))),
269
+ )
270
+
271
+ /** A folded group before its button has been measured. */
272
+ const COLLAPSED_WIDTH = 66
273
+ /** The band's own padding (4px a side; `clientWidth` already leaves the
274
+ * border out) and the collapse chevron at its end (20px and 4px of
275
+ * margins), so the fit is judged against the space the groups really
276
+ * get. Without the chevron in the sum the last group sat 18px past the
277
+ * edge and the band scrolled by that much. */
278
+ const BAND_PADDING = 8 + 24
279
+
280
+ function measure(node: HTMLElement, key: string) {
281
+ // "id" measures the group as drawn, "id:compact" with its labels off,
282
+ // "id:folded" as the one large button it folds into.
283
+ const colon = key.indexOf(':')
284
+ const id = colon < 0 ? key : key.slice(0, colon)
285
+ const into = key.endsWith(':compact') ? compactWidth : key.endsWith(':folded') ? foldedWidth : natural
286
+ const record = () => {
287
+ const width = node.getBoundingClientRect().width
288
+ if (width > 0 && into.get(id) !== width) {
289
+ into.set(id, width)
290
+ measured += 1
291
+ }
292
+ }
293
+ record()
294
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(record)
295
+ observer?.observe(node)
296
+ return { destroy: () => observer?.disconnect() }
297
+ }
298
+
299
+ /**
300
+ * How the band fits its width, Excel's way and in Excel's order: first
301
+ * the groups go compact from the right (their small buttons keep the
302
+ * icon and lose the label, so Fill Down is an arrow and Wrap Text a
303
+ * glyph), and only when every group is compact and the band is still too
304
+ * narrow do groups fold, again from the right, into one large button
305
+ * each. Recomputed from scratch on every width change rather than
306
+ * nudged, so growing the window undoes exactly what shrinking it did.
307
+ */
308
+ const fit = $derived.by(() => {
309
+ void measured
310
+ const groups = current?.groups ?? []
311
+ const compact = new Set<string>()
312
+ const folded = new Set<string>()
313
+ if (!bandWidth) return { compact, folded }
314
+ const wide = (g: RibbonGroup) => natural.get(g.id) ?? 120
315
+ const narrow = (g: RibbonGroup) => compactWidth.get(g.id) ?? wide(g)
316
+ const oneButton = (g: RibbonGroup) => foldedWidth.get(g.id) ?? COLLAPSED_WIDTH
317
+ let total = BAND_PADDING
318
+ for (const group of groups) total += wide(group)
319
+ for (let i = groups.length - 1; i >= 0 && total > bandWidth; i -= 1) {
320
+ const group = groups[i]!
321
+ if (narrow(group) < wide(group)) { total += narrow(group) - wide(group); compact.add(group.id) }
322
+ }
323
+ for (let i = groups.length - 1; i >= 0 && total > bandWidth; i -= 1) {
324
+ const group = groups[i]!
325
+ total += oneButton(group) - (compact.has(group.id) ? narrow(group) : wide(group))
326
+ folded.add(group.id)
327
+ }
328
+ return { compact, folded }
329
+ })
330
+ const collapsedGroups = $derived(fit.folded)
331
+
332
+ /**
333
+ * Excel's column model, made explicit. Items run down a column while
334
+ * their rows go up (1, 2, 3) and a new column starts when they do not,
335
+ * or at a large button, which is a column by itself. The grid's own
336
+ * auto-placement keeps a cursor per row, so a two-item column (Undo over
337
+ * Redo) had the next column's third item slide back under it.
338
+ */
339
+ function placed(items: ReadonlyArray<RibbonItem>): Array<{ item: RibbonItem; col: number }> {
340
+ let col = 0
341
+ let lastRow = 99
342
+ return items.map((item) => {
343
+ const row = item.size === 'large' ? 1 : (item.row ?? 1)
344
+ if (item.size === 'large' || row <= lastRow) col += 1
345
+ lastRow = item.size === 'large' ? 99 : row
346
+ return { item, col }
347
+ })
348
+ }
349
+
350
+ /** The icon a folded group shows: its own, else its large command's, else its first. */
351
+ function groupIcon(group: RibbonGroup) {
352
+ return group.icon ?? (group.items.find((i) => i.size === 'large') ?? group.items.find((i) => i.icon))?.icon ?? 'format-cells'
353
+ }
354
+
355
+ /**
356
+ * What the empty font option is called. A cell with no font of its own
357
+ * renders in the theme's font, and Excel's combo names the font a cell
358
+ * has ("Aptos Narrow"), so the option reads the theme's first family
359
+ * rather than "Default".
360
+ */
361
+ let rootEl = $state<HTMLDivElement | null>(null)
362
+ let themeFont = $state('Default')
363
+ $effect(() => {
364
+ if (!rootEl) return
365
+ const first = getComputedStyle(rootEl).fontFamily.split(',')[0]?.replace(/["']/g, '').trim()
366
+ if (first) themeFont = first
367
+ })
368
+
369
+ /**
370
+ * The tab strip's keyboard model, as WAI-ARIA asks for a tablist: the arrows
371
+ * move between tabs, Home and End jump to the ends, and FOCUS FOLLOWS the
372
+ * selection - without that last part a screen reader announces the old tab
373
+ * while the band under it has changed. The arrows are mirrored on a
374
+ * right-to-left ribbon, where the next tab is to the left.
375
+ */
376
+ function onTabKey(event: KeyboardEvent) {
377
+ const ids = tabs.map((t) => t.id)
378
+ const at = ids.indexOf(tab)
379
+ const strip = (event.currentTarget as HTMLElement).parentElement
380
+ const rtl = strip ? getComputedStyle(strip).direction === 'rtl' : false
381
+ const forward = rtl ? 'ArrowLeft' : 'ArrowRight'
382
+ const back = rtl ? 'ArrowRight' : 'ArrowLeft'
383
+ let next = -1
384
+ if (event.key === forward && at < ids.length - 1) next = at + 1
385
+ else if (event.key === back && at > 0) next = at - 1
386
+ else if (event.key === 'Home') next = 0
387
+ else if (event.key === 'End') next = ids.length - 1
388
+ if (next < 0 || next === at) return
389
+ event.preventDefault()
390
+ tab = ids[next]!
391
+ // After the strip re-renders, the selected tab is the one Tab reaches, so
392
+ // it is the one that should hold focus.
393
+ void tick().then(() => strip?.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]')?.focus())
394
+ }
395
+ </script>
396
+
397
+ <svelte:window onpointerup={refresh} onkeyup={refresh} />
398
+
399
+ <!--
400
+ One button, either size. `live` is off inside the hidden measuring band:
401
+ the copy there exists for its width alone, so it reads no state.
402
+ -->
403
+ {#snippet plainButton(item: RibbonItem, live: boolean)}
404
+ {@const state = live ? stateOf(item) : { on: false, enabled: true }}
405
+ <button
406
+ type="button"
407
+ class="btn"
408
+ class:large={item.size === 'large'}
409
+ class:wide={item.wide && item.size !== 'large'}
410
+ class:on={state.on}
411
+ disabled={!state.enabled}
412
+ title={tip(item)}
413
+ aria-label={title(item)}
414
+ aria-pressed={item.kind === 'toggle' ? state.on : undefined}
415
+ onclick={() => run(item)}
416
+ >
417
+ {#if item.icon}
418
+ <SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
419
+ {:else}
420
+ <span class="glyph" data-glyph={item.id}>{label(item)}</span>
421
+ {/if}
422
+ {#if item.size === 'large' || item.wide}
423
+ <span class="label">{label(item)}</span>
424
+ {/if}
425
+ </button>
426
+ {/snippet}
427
+
428
+ <!-- Excel's split button: a face that re-applies the last pick, an arrow that opens the menu. -->
429
+ {#snippet split(item: RibbonItem, live: boolean)}
430
+ {@const state = live ? stateOf(item) : { on: false, enabled: true }}
431
+ {@const colour = barColour(item)}
432
+ <div class="split" class:on={state.on} class:disabled={!state.enabled}>
433
+ <button
434
+ type="button"
435
+ class="btn face"
436
+ class:has-bar={item.palette}
437
+ disabled={!state.enabled}
438
+ title={tip(item)}
439
+ aria-label={title(item)}
440
+ onclick={() => run(item, faceValue(item))}
441
+ >
442
+ {#if item.icon && !(item.palette && item.id === 'text-colour')}
443
+ <SvRibbonIcon name={item.icon} />
444
+ {:else}
445
+ <span class="glyph" data-glyph={item.id}>{label(item)}</span>
446
+ {/if}
447
+ {#if item.palette}
448
+ <span class="bar" class:none={colour === null} style:background={colour ?? 'transparent'}></span>
449
+ {/if}
450
+ </button>
451
+ {#if live}
452
+ <SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
453
+ {#snippet anchor()}
454
+ <button
455
+ type="button"
456
+ class="btn arrow"
457
+ disabled={!state.enabled}
458
+ aria-label={t('menuOptions', { title: title(item) })}
459
+ aria-haspopup="menu"
460
+ aria-expanded={menuOpen[item.id] ?? false}
461
+ ><SvRibbonIcon name="chevron-down" size={10} /></button>
462
+ {/snippet}
463
+ {#if item.palette}
464
+ <div class="palette" role="menu" aria-label={title(item)}>
465
+ {#if item.none}
466
+ <button type="button" class="palette-none" role="menuitem" onclick={() => pick(item, item.none!.value)}>
467
+ <span class="swatch no-colour" aria-hidden="true"></span>{t(`ribbon.${item.id}.none`)}
468
+ </button>
469
+ {/if}
470
+ <div class="palette-title">{t('paletteThemeColors')}</div>
471
+ <div class="swatches">
472
+ {#each THEME_COLOURS as row, r (r)}
473
+ {#each row as c (c.value + r)}
474
+ <button type="button" class="swatch" class:shade={r > 0} role="menuitem" title={c.label} aria-label={c.label} style:background={c.value} onclick={() => pick(item, c.value)}></button>
475
+ {/each}
476
+ {/each}
477
+ </div>
478
+ <div class="palette-title">{t('paletteStandardColors')}</div>
479
+ <div class="swatches">
480
+ {#each STANDARD_COLOURS as c (c.value)}
481
+ <button type="button" class="swatch" role="menuitem" title={c.label} aria-label={c.label} style:background={c.value} onclick={() => pick(item, c.value)}></button>
482
+ {/each}
483
+ </div>
484
+ </div>
485
+ {:else}
486
+ <div class="menu" role="menu" aria-label={title(item)}>
487
+ {#each item.options ?? [] as option (option.value)}
488
+ <button type="button" class="menu-item" role="menuitem" onclick={() => pick(item, option.value)}>
489
+ {#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
490
+ <span>{optionLabel(item, option)}</span>
491
+ </button>
492
+ {/each}
493
+ </div>
494
+ {/if}
495
+ </SvPopover>
496
+ {:else}
497
+ <span class="btn arrow" aria-hidden="true"><SvRibbonIcon name="chevron-down" size={10} /></span>
498
+ {/if}
499
+ </div>
500
+ {/snippet}
501
+
502
+ <!-- Excel's plain menu button: the face opens the list, every entry acts on its own. -->
503
+ {#snippet dropdown(item: RibbonItem, live: boolean)}
504
+ {@const state = live ? stateOf(item) : { on: false, enabled: true }}
505
+ {#snippet face()}
506
+ <button
507
+ type="button"
508
+ class="btn dropdown"
509
+ class:wide={item.wide}
510
+ class:large={item.size === 'large'}
511
+ disabled={!state.enabled}
512
+ title={tip(item)}
513
+ aria-label={title(item)}
514
+ aria-haspopup="menu"
515
+ aria-expanded={live ? (menuOpen[item.id] ?? false) : false}
516
+ >
517
+ {#if item.icon}
518
+ <SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
519
+ {:else}
520
+ <span class="glyph" data-glyph={item.id}>{label(item)}</span>
521
+ {/if}
522
+ {#if item.size === 'large' || item.wide}
523
+ <span class="label">{label(item)}</span>
524
+ {/if}
525
+ <SvRibbonIcon name="chevron-down" size={8} />
526
+ </button>
527
+ {/snippet}
528
+ {#snippet entries()}
529
+ <div class="menu" role="menu" aria-label={title(item)}>
530
+ {#each item.options ?? [] as option (option.value)}
531
+ {#if option.heading}
532
+ <div class="menu-heading" role="presentation">{optionLabel(item, option)}</div>
533
+ {:else}
534
+ {#if option.toggle && option.emits}
535
+ {@const on = live && activeActions.includes(option.emits)}
536
+ <button type="button" class="menu-item" class:on role="menuitemcheckbox" aria-checked={on} onclick={() => pickOption(item, option)}>
537
+ {#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
538
+ <span>{optionLabel(item, option)}</span>
539
+ {#if option.keys}<span class="keys">{option.keys}</span>{/if}
540
+ </button>
541
+ {:else}
542
+ <button type="button" class="menu-item" role="menuitem" onclick={() => pickOption(item, option)}>
543
+ {#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
544
+ <span>{optionLabel(item, option)}</span>
545
+ {#if option.keys}<span class="keys">{option.keys}</span>{/if}
546
+ </button>
547
+ {/if}
548
+ {/if}
549
+ {/each}
550
+ </div>
551
+ {/snippet}
552
+ {#if item.split}
553
+ <!-- Excel's split button. Large: the icon runs the item and the label
554
+ with its chevron opens the entries. Small: face beside arrow, as
555
+ the colour menus. -->
556
+ <div class="split" class:split-large={item.size === 'large'} class:on={state.on} class:disabled={!state.enabled}>
557
+ <button
558
+ type="button"
559
+ class="btn face"
560
+ class:large={item.size === 'large'}
561
+ disabled={!state.enabled}
562
+ title={tip(item)}
563
+ aria-label={title(item)}
564
+ onclick={() => run(item)}
565
+ >
566
+ {#if item.icon}
567
+ <SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
568
+ {:else}
569
+ <span class="glyph" data-glyph={item.id}>{label(item)}</span>
570
+ {/if}
571
+ <!-- A small wide split (Merge & Center) prints its label on the face, as
572
+ Excel does; the colour splits stay icon-only. -->
573
+ {#if item.wide && item.size !== 'large'}<span class="label">{label(item)}</span>{/if}
574
+ </button>
575
+ {#if live}
576
+ <SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
577
+ {#snippet anchor()}
578
+ <button
579
+ type="button"
580
+ class="btn arrow"
581
+ disabled={!state.enabled}
582
+ aria-label={t('menuOptions', { title: title(item) })}
583
+ aria-haspopup="menu"
584
+ aria-expanded={menuOpen[item.id] ?? false}
585
+ >{#if item.size === 'large'}<span class="label">{label(item)}</span>{/if}<SvRibbonIcon name="chevron-down" size={10} /></button>
586
+ {/snippet}
587
+ {@render entries()}
588
+ </SvPopover>
589
+ {:else}
590
+ <span class="btn arrow" aria-hidden="true">{#if item.size === 'large'}<span class="label">{label(item)}</span>{/if}<SvRibbonIcon name="chevron-down" size={10} /></span>
591
+ {/if}
592
+ </div>
593
+ {:else if live}
594
+ <SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
595
+ {#snippet anchor()}
596
+ {@render face()}
597
+ {/snippet}
598
+ {@render entries()}
599
+ </SvPopover>
600
+ {:else}
601
+ {@render face()}
602
+ {/if}
603
+ {/snippet}
604
+
605
+ {#snippet control(item: RibbonItem, live: boolean)}
606
+ {#if item.kind === 'select'}
607
+ {@const state = live ? stateOf(item) : { on: false, enabled: true }}
608
+ <select
609
+ class="select"
610
+ data-select={item.id}
611
+ title={tip(item)}
612
+ aria-label={title(item)}
613
+ disabled={!state.enabled}
614
+ value={live ? currentOf(item) : ''}
615
+ onchange={(e) => run(item, e.currentTarget.value)}
616
+ >
617
+ {#if live && currentOf(item) && !item.options?.some((o) => o.value === currentOf(item))}
618
+ <!-- A value the list does not carry (a 17px title) still reads on the
619
+ face, the way Excel's combo shows whatever the cell has. -->
620
+ <option value={currentOf(item)}>{currentOf(item)}</option>
621
+ {/if}
622
+ {#each item.options ?? [] as option (option.value)}
623
+ <option value={option.value}>{option.value === '' && item.id === 'font-family' ? themeFont : optionLabel(item, option)}</option>
624
+ {/each}
625
+ </select>
626
+ {:else if item.kind === 'menu'}
627
+ {@render split(item, live)}
628
+ {:else if item.kind === 'dropdown'}
629
+ {@render dropdown(item, live)}
630
+ {:else}
631
+ {@render plainButton(item, live)}
632
+ {/if}
633
+ {/snippet}
634
+
635
+ <!-- The controls of one group, in Excel's three-row block. -->
636
+ {#snippet body(group: RibbonGroup, live: boolean)}
637
+ {#if group.layout === 'flow'}
638
+ <div class="controls flow">
639
+ {#each [1, 2, 3] as row (row)}
640
+ {@const items = group.items.filter((i) => (i.row ?? 1) === row)}
641
+ {#if items.length}
642
+ <div class="row">
643
+ {#each items as item (item.id)}{@render control(item, live)}{/each}
644
+ </div>
645
+ {/if}
646
+ {/each}
647
+ </div>
648
+ {:else}
649
+ <div class="controls grid">
650
+ {#each placed(group.items) as { item, col } (item.id)}
651
+ <div class="cell" style:grid-row={item.size === 'large' ? '1 / span 3' : String(item.row ?? 1)} style:grid-column={col}>
652
+ {@render control(item, live)}
653
+ </div>
654
+ {/each}
655
+ </div>
656
+ {/if}
657
+ {/snippet}
658
+
659
+ <div class="sv-ribbon" class:collapsed role="toolbar" aria-label={t('ribbonLabel')} aria-orientation="horizontal" bind:this={rootEl}>
660
+ <div class="tabs" role="tablist" aria-label={t('ribbonTabs')}>
661
+ {#each tabs as entry (entry.id)}
662
+ <button
663
+ type="button"
664
+ role="tab"
665
+ class="tab"
666
+ class:active={entry.id === current?.id}
667
+ aria-selected={entry.id === current?.id}
668
+ tabindex={entry.id === current?.id ? 0 : -1}
669
+ onclick={() => onTabClick(entry.id)}
670
+ ondblclick={toggleCollapsed}
671
+ onkeydown={onTabKey}
672
+ >{t(`ribbon.tab.${entry.id}`)}</button>
673
+ {/each}
674
+ <span class="tabs-grow"></span>
675
+ {#if collapsed && !peek}
676
+ <button type="button" class="ribbon-toggle" title={`${t('ribbonExpand')} (Ctrl+F1)`} aria-label={t('ribbonExpand')} aria-expanded="false" onclick={toggleCollapsed}>
677
+ <svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true"><path d="M2 3.5l3 3 3-3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
678
+ </button>
679
+ {/if}
680
+ </div>
681
+
682
+ <div class="band" class:away={collapsed && !peek} class:peek={collapsed && peek} bind:clientWidth={bandWidth}>
683
+ {#each current?.groups ?? [] as group (group.id)}
684
+ {#if collapsedGroups.has(group.id)}
685
+ <section class="group folded" role="group" aria-label={groupLabel(group)}>
686
+ <SvPopover bind:open={groupOpen[group.id]} placement="bottom-start" arrow={false} offset={4} ariaLabel={groupLabel(group)}>
687
+ {#snippet anchor()}
688
+ <button type="button" class="btn large" aria-haspopup="true" aria-expanded={groupOpen[group.id] ?? false} title={groupLabel(group)}>
689
+ <span class="fold-icon"><SvRibbonIcon name={groupIcon(group)} size={32} /></span>
690
+ <span class="label">{groupLabel(group)} <SvRibbonIcon name="chevron-down" size={10} /></span>
691
+ </button>
692
+ {/snippet}
693
+ <div class="popup-group">
694
+ {@render body(group, true)}
695
+ <div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
696
+ </div>
697
+ </SvPopover>
698
+ </section>
699
+ {:else}
700
+ <!-- A group of the toolbar, not a landmark: a named <section> is a
701
+ region, and a ribbon of twelve regions (or two ribbons on one
702
+ page) buries whatever landmarks the page really has. -->
703
+ <section class="group" class:compact={fit.compact.has(group.id)} role="group" aria-label={groupLabel(group)}>
704
+ {@render body(group, true)}
705
+ <div class="label-row">
706
+ <span class="group-label">{groupLabel(group)}</span>
707
+ {#if group.launcher}
708
+ <button type="button" class="launcher" title={t('groupSettings', { group: groupLabel(group) })} aria-label={t('groupSettings', { group: groupLabel(group) })} onclick={() => launch(group.launcher!)}>
709
+ <svg viewBox="0 0 8 8" width="8" height="8" aria-hidden="true"><path d="M1 1h3.5M1 1v3.5M1.5 1.5L6.5 6.5M6.5 3.5V6.5H3.5" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round" /></svg>
710
+ </button>
711
+ {/if}
712
+ </div>
713
+ </section>
714
+ {/if}
715
+ {/each}
716
+ <!-- Excel's chevron at the band's end: collapse, or pin the peeked band. -->
717
+ <span class="band-grow"></span>
718
+ <button type="button" class="ribbon-toggle in-band" title={`${collapsed ? t('ribbonPin') : t('ribbonCollapse')} (Ctrl+F1)`} aria-label={collapsed ? t('ribbonPin') : t('ribbonCollapse')} onclick={toggleCollapsed}>
719
+ {#if collapsed}
720
+ <svg viewBox="0 0 12 12" width="11" height="11" aria-hidden="true"><path d="M4 1.5h4M6 1.5v3.5M3.5 5l2.5 2.5L8.5 5M6 7.5V11" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
721
+ {:else}
722
+ <svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true"><path d="M2 6.5l3-3 3 3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
723
+ {/if}
724
+ </button>
725
+ </div>
726
+
727
+ <!--
728
+ The measuring bands: every group of the current tab expanded, then
729
+ every group compact, invisible and out of the tab order, so the fit
730
+ above has both widths for each group even when it never got to draw
731
+ itself either way.
732
+ -->
733
+ <div class="measure-well" aria-hidden="true" inert>
734
+ <div class="band measure">
735
+ {#each current?.groups ?? [] as group (group.id)}
736
+ <section class="group" use:measure={group.id}>
737
+ {@render body(group, false)}
738
+ <div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
739
+ </section>
740
+ {/each}
741
+ </div>
742
+ <div class="band measure">
743
+ {#each current?.groups ?? [] as group (group.id)}
744
+ <section class="group compact" use:measure={`${group.id}:compact`}>
745
+ {@render body(group, false)}
746
+ <div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
747
+ </section>
748
+ {/each}
749
+ </div>
750
+ <div class="band measure">
751
+ {#each current?.groups ?? [] as group (group.id)}
752
+ <section class="group folded" use:measure={`${group.id}:folded`}>
753
+ <button type="button" class="btn large" tabindex="-1">
754
+ <span class="fold-icon"><SvRibbonIcon name={groupIcon(group)} size={32} /></span>
755
+ <span class="label">{groupLabel(group)} <SvRibbonIcon name="chevron-down" size={10} /></span>
756
+ </button>
757
+ </section>
758
+ {/each}
759
+ </div>
760
+ </div>
761
+ </div>
762
+
763
+ <style>
764
+ .sv-ribbon {
765
+ position: relative;
766
+ display: flex;
767
+ flex-direction: column;
768
+ font-family: var(--sg-font, "Segoe UI", system-ui, sans-serif);
769
+ font-size: 12px;
770
+ line-height: 1.2;
771
+ background: var(--sg-bg-subtle, var(--sg-header-bg, #f3f3f3));
772
+ color: var(--sg-fg, #242424);
773
+ border: 1px solid var(--sg-border, #e0e0e0);
774
+ border-radius: var(--sg-radius, 4px);
775
+ }
776
+ /* Collapsed, the peeked band hangs below the tabs over the sheet: the
777
+ ribbon has to sit above the formula bar and the grid's sticky header
778
+ (z-index 30) for it to show. */
779
+ .sv-ribbon.collapsed { z-index: 70; }
780
+
781
+ /* ---- tabs ---------------------------------------------------------- */
782
+ .tabs {
783
+ display: flex;
784
+ align-items: stretch;
785
+ gap: 0;
786
+ height: 30px;
787
+ padding: 0 8px;
788
+ /* On a phone the eight labels are wider than the screen. They scroll
789
+ sideways rather than shrink into each other, with no bar showing. */
790
+ overflow-x: auto;
791
+ scrollbar-width: none;
792
+ }
793
+ .tabs::-webkit-scrollbar { display: none; }
794
+ .tab {
795
+ flex: 0 0 auto;
796
+ position: relative;
797
+ font: inherit;
798
+ border: 0;
799
+ background: transparent;
800
+ color: var(--sg-fg, #242424);
801
+ padding: 0 11px;
802
+ cursor: pointer;
803
+ white-space: nowrap;
804
+ border-radius: 4px 4px 0 0;
805
+ }
806
+ .tab:hover { background: var(--sg-row-hover-bg, rgba(0, 0, 0, 0.04)); }
807
+ /* The label is the accent mixed toward the text colour, not the raw
808
+ accent: a 12px label in a host theme's accent (an orange, a cyan) lands
809
+ under 4.5:1 on the ribbon's near-white band, which axe fails and a
810
+ low-vision reader feels. The underline below keeps the accent pure, so
811
+ the tab still reads as Excel's. */
812
+ .tab.active {
813
+ color: color-mix(in srgb, var(--sg-accent, #107c41) 72%, var(--sg-fg, #242424));
814
+ font-weight: 600;
815
+ }
816
+ /* Excel's selected-tab mark: a short rounded underline, not a full-width
817
+ border, inset from the label's edges. */
818
+ .tab.active::after {
819
+ content: "";
820
+ position: absolute;
821
+ left: 9px;
822
+ right: 9px;
823
+ bottom: 0;
824
+ height: 3px;
825
+ border-radius: 3px 3px 0 0;
826
+ background: var(--sg-accent, #107c41);
827
+ }
828
+ .tab:focus-visible {
829
+ outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
830
+ outline-offset: -2px;
831
+ }
832
+
833
+ /* ---- band ---------------------------------------------------------- */
834
+ /* Excel's band is a white card on the ribbon's grey ground, inset a few
835
+ pixels and rounded, with the groups divided by hairlines inside it. */
836
+ .band {
837
+ display: flex;
838
+ align-items: stretch;
839
+ margin: 0 6px 6px;
840
+ padding: 4px 4px 2px;
841
+ background: var(--sg-bg, #fff);
842
+ border: 1px solid var(--sg-border, #e0e0e0);
843
+ border-radius: var(--sg-radius-lg, 6px);
844
+ /* Seven folded groups are 472px; on a phone the band is narrower than
845
+ that, and a hidden overflow put Cells and Editing out of reach. The
846
+ band scrolls sideways instead, as Excel's does, without a scrollbar
847
+ stealing a row. */
848
+ overflow-x: auto;
849
+ overflow-y: hidden;
850
+ scrollbar-width: none;
851
+ }
852
+ .band::-webkit-scrollbar {
853
+ display: none;
854
+ }
855
+ /* The measuring bands live in a well of no size that clips them, so
856
+ their max-content widths never reach the page's scroll width. */
857
+ .measure-well {
858
+ position: absolute;
859
+ left: 0;
860
+ top: 0;
861
+ width: 0;
862
+ height: 0;
863
+ overflow: hidden;
864
+ visibility: hidden;
865
+ pointer-events: none;
866
+ }
867
+ .band.measure {
868
+ position: absolute;
869
+ left: 0;
870
+ top: 0;
871
+ width: max-content;
872
+ height: 0;
873
+ margin: 0;
874
+ padding-block: 0;
875
+ border: 0;
876
+ }
877
+
878
+ .group {
879
+ display: flex;
880
+ flex-direction: column;
881
+ flex: 0 0 auto;
882
+ /* Three rows of controls and the label row: the band keeps this
883
+ height when every group has folded to a button. */
884
+ min-height: 92px;
885
+ padding: 0 4px;
886
+ border-right: 1px solid var(--sg-border, #e0e0e0);
887
+ }
888
+ .group:last-of-type { border-right: 0; }
889
+ /* Folded: one button the height of the group, its icon centred where the
890
+ controls sit and its label down on the group-label row, so the row of
891
+ labels reads straight across folded and open groups alike. */
892
+ .group.folded { padding: 0 2px; }
893
+ /* The popover's anchor wrapper sits between the group and its button;
894
+ it has to stretch for the button's 100% height to mean the group's. */
895
+ .group.folded :global(.sv-pop__anchor) { flex: 1 1 auto; display: flex; align-items: stretch; }
896
+ .group.folded :global(.btn.large) { height: 100%; padding-top: 0; padding-bottom: 0; gap: 0; }
897
+ .group.folded :global(.btn.large .fold-icon) { flex: 1 1 auto; display: flex; align-items: center; }
898
+ .group.folded :global(.btn.large .label) { height: 16px; padding-bottom: 1px; color: var(--sg-muted, #616161); }
899
+ .group.folded :global(.btn.large:hover .label) { color: inherit; }
900
+ /* Compact: the small buttons keep their icon and drop their label. */
901
+ .group.compact :global(.btn.wide:not(.large) .label) { display: none; }
902
+ .group.compact :global(.btn.wide:not(.large)) { justify-content: center; padding-right: 3px; }
903
+ .group.compact :global(.split .btn.face .label) { display: none; }
904
+
905
+ /* Collapsed: the tabs alone; peeked: the band floats over the sheet. */
906
+ /* Not "hidden": a host reset owns that name too. */
907
+ .band.away { display: none; }
908
+ .band.peek {
909
+ position: absolute;
910
+ left: 0;
911
+ right: 0;
912
+ top: 30px;
913
+ z-index: 60;
914
+ margin: 0;
915
+ border-radius: 0 0 var(--sg-radius-lg, 6px) var(--sg-radius-lg, 6px);
916
+ box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
917
+ }
918
+ .tabs-grow, .band-grow { flex: 1 1 auto; }
919
+ /* Not "collapse": that is a visibility utility in a host's reset. */
920
+ .ribbon-toggle {
921
+ align-self: center;
922
+ width: 20px;
923
+ height: 20px;
924
+ padding: 0;
925
+ border: 1px solid transparent;
926
+ border-radius: 3px;
927
+ background: transparent;
928
+ color: var(--sg-muted, #616161);
929
+ cursor: pointer;
930
+ display: inline-flex;
931
+ align-items: center;
932
+ justify-content: center;
933
+ }
934
+ .ribbon-toggle.in-band { align-self: flex-end; margin: 0 2px 2px 2px; }
935
+ .ribbon-toggle:hover { background: var(--sg-row-hover-bg, #f0f0f0); border-color: var(--sg-border, #e0e0e0); color: var(--sg-fg, #242424); }
936
+
937
+ /* Three rows of 24px: what a large button fills and a small one takes one of. */
938
+ .controls {
939
+ flex: 1 0 auto;
940
+ min-height: 76px;
941
+ }
942
+ .controls.grid {
943
+ display: grid;
944
+ grid-template-rows: repeat(3, 24px);
945
+ grid-auto-columns: max-content;
946
+ grid-auto-flow: row;
947
+ align-content: start;
948
+ gap: 2px 3px;
949
+ }
950
+ .cell { display: flex; align-items: stretch; }
951
+ .cell > :global(*) { flex: 1 1 auto; }
952
+ .controls.flow {
953
+ display: flex;
954
+ flex-direction: column;
955
+ justify-content: space-evenly;
956
+ gap: 2px;
957
+ }
958
+ .controls.flow .row {
959
+ display: flex;
960
+ align-items: center;
961
+ gap: 2px;
962
+ }
963
+
964
+ .label-row {
965
+ position: relative;
966
+ display: flex;
967
+ align-items: center;
968
+ justify-content: center;
969
+ height: 16px;
970
+ margin-top: 2px;
971
+ }
972
+ .group-label {
973
+ font-size: 10.5px;
974
+ color: var(--sg-muted, #616161);
975
+ white-space: nowrap;
976
+ padding: 0 8px;
977
+ }
978
+ /* The dialog box launcher, in the corner where Excel keeps it. */
979
+ .launcher {
980
+ position: absolute;
981
+ right: -4px;
982
+ bottom: 1px;
983
+ width: 14px;
984
+ height: 14px;
985
+ padding: 0;
986
+ border: 1px solid transparent;
987
+ border-radius: 3px;
988
+ background: transparent;
989
+ color: var(--sg-muted, #616161);
990
+ cursor: pointer;
991
+ display: inline-flex;
992
+ align-items: center;
993
+ justify-content: center;
994
+ }
995
+ .launcher:hover { background: var(--sg-row-hover-bg, #f0f0f0); border-color: var(--sg-border, #e0e0e0); color: var(--sg-fg, #242424); }
996
+
997
+ /* ---- buttons ------------------------------------------------------- */
998
+ .btn {
999
+ font: inherit;
1000
+ display: inline-flex;
1001
+ align-items: center;
1002
+ justify-content: center;
1003
+ gap: 5px;
1004
+ min-width: 22px;
1005
+ height: 24px;
1006
+ padding: 0 2px;
1007
+ border: 1px solid transparent;
1008
+ border-radius: 4px;
1009
+ background: transparent;
1010
+ color: inherit;
1011
+ cursor: pointer;
1012
+ white-space: nowrap;
1013
+ position: relative;
1014
+ }
1015
+ .btn.wide { justify-content: flex-start; padding-right: 6px; }
1016
+ .btn:hover:not(:disabled) {
1017
+ background: var(--sg-row-hover-bg, #f0f0f0);
1018
+ border-color: var(--sg-border, #d1d1d1);
1019
+ }
1020
+ .btn:active:not(:disabled) { background: var(--sg-muted-bg, #e6e6e6); }
1021
+ /* A pressed toggle: Excel tints it, it does not invert it. */
1022
+ .btn.on,
1023
+ .split.on .btn {
1024
+ background: var(--sg-selection-bg, rgba(16, 124, 65, 0.12));
1025
+ border-color: var(--sg-border, #d1d1d1);
1026
+ }
1027
+ .btn:disabled { opacity: 0.38; cursor: default; }
1028
+ .btn:focus-visible {
1029
+ outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
1030
+ outline-offset: -2px;
1031
+ }
1032
+ .btn.large {
1033
+ flex-direction: column;
1034
+ justify-content: flex-start;
1035
+ gap: 3px;
1036
+ height: 100%;
1037
+ min-width: 40px;
1038
+ padding: 5px 3px 2px;
1039
+ }
1040
+ .btn.large .label {
1041
+ white-space: normal;
1042
+ text-align: center;
1043
+ line-height: 1.15;
1044
+ max-width: 68px;
1045
+ display: inline-flex;
1046
+ flex-wrap: wrap;
1047
+ justify-content: center;
1048
+ align-items: center;
1049
+ gap: 2px;
1050
+ }
1051
+
1052
+ /* Typographic faces: B I U S read as their own styling, as Excel's do. */
1053
+ .glyph {
1054
+ display: inline-flex;
1055
+ align-items: center;
1056
+ justify-content: center;
1057
+ min-width: 16px;
1058
+ font-size: 13px;
1059
+ line-height: 1;
1060
+ }
1061
+ .glyph[data-glyph='bold'] { font-weight: 800; }
1062
+ .glyph[data-glyph='italic'] { font-style: italic; font-family: Georgia, "Times New Roman", serif; }
1063
+ .glyph[data-glyph='underline'] { text-decoration: underline; }
1064
+ .glyph[data-glyph='strike'] { text-decoration: line-through; }
1065
+ .glyph[data-glyph='text-colour'] { font-weight: 700; font-size: 14px; transform: translateY(-2px); }
1066
+ .glyph[data-glyph='fmt-currency'],
1067
+ .glyph[data-glyph='fmt-percent'],
1068
+ .glyph[data-glyph='fmt-number'] { font-weight: 600; }
1069
+
1070
+ /* ---- split buttons ------------------------------------------------- */
1071
+ .split {
1072
+ display: inline-flex;
1073
+ align-items: stretch;
1074
+ height: 24px;
1075
+ border-radius: 4px;
1076
+ }
1077
+ .split .face { border-radius: 4px 0 0 4px; padding: 0 3px; }
1078
+ .split .arrow {
1079
+ border-radius: 0 4px 4px 0;
1080
+ min-width: 12px;
1081
+ width: 12px;
1082
+ padding: 0;
1083
+ color: var(--sg-muted, #616161);
1084
+ }
1085
+ .split:hover:not(.disabled) .btn { border-color: var(--sg-border, #d1d1d1); }
1086
+ .split:hover:not(.disabled) .face { border-right-color: transparent; }
1087
+ .split .face.has-bar { padding-bottom: 4px; }
1088
+ /* Excel's colour bar under the icon: the colour the face will apply. */
1089
+ .bar {
1090
+ position: absolute;
1091
+ left: 3px;
1092
+ right: 3px;
1093
+ bottom: 2px;
1094
+ height: 3px;
1095
+ border-radius: 1px;
1096
+ }
1097
+ .bar.none {
1098
+ background: repeating-linear-gradient(90deg, var(--sg-muted, #8a8886) 0 3px, transparent 3px 5px) !important;
1099
+ }
1100
+
1101
+ /* ---- selects ------------------------------------------------------- */
1102
+ .select {
1103
+ font: inherit;
1104
+ height: 22px;
1105
+ padding: 0 2px 0 5px;
1106
+ border: 1px solid var(--sg-input-border, var(--sg-border, #d1d1d1));
1107
+ border-radius: 3px;
1108
+ /* Not transparent: a native select paints its option list with this
1109
+ background, and a see-through one is unreadable on a dark theme. */
1110
+ background: var(--sg-input-bg, var(--sg-bg, #fff));
1111
+ color: var(--sg-fg, #242424);
1112
+ }
1113
+ .select[data-select='font-family'] { width: 112px; }
1114
+ .select[data-select='font-size'] { width: 46px; }
1115
+ .select[data-select='num-format'] { width: 108px; }
1116
+ .select:disabled { opacity: 0.38; }
1117
+ .select:focus-visible {
1118
+ outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
1119
+ outline-offset: -1px;
1120
+ }
1121
+
1122
+ /* ---- menus --------------------------------------------------------- */
1123
+ .menu {
1124
+ min-width: 200px;
1125
+ padding: 4px;
1126
+ }
1127
+ .menu-item {
1128
+ display: flex;
1129
+ align-items: center;
1130
+ gap: 10px;
1131
+ width: 100%;
1132
+ padding: 5px 10px;
1133
+ font: inherit;
1134
+ font-size: 12px;
1135
+ border: 0;
1136
+ border-radius: 3px;
1137
+ background: transparent;
1138
+ color: inherit;
1139
+ text-align: left;
1140
+ cursor: pointer;
1141
+ white-space: nowrap;
1142
+ }
1143
+ .menu-item:hover { background: var(--sg-row-hover-bg, #f0f0f0); }
1144
+ /* A toggle entry that is on (Lock Cell on a locked cell): the same tint
1145
+ as a pressed button, so the menu and the ribbon agree. */
1146
+ .menu-item.on { background: var(--sg-selection-bg, rgba(16, 124, 65, 0.12)); }
1147
+ .menu-item.on:hover { background: var(--sg-muted-bg, #e6e6e6); }
1148
+ .menu-item .keys {
1149
+ margin-left: auto;
1150
+ padding-left: 18px;
1151
+ color: var(--sg-muted, #616161);
1152
+ font-size: 11px;
1153
+ }
1154
+ /* Excel's section labels between a menu's groups: small caps, a rule above. */
1155
+ .menu-heading {
1156
+ margin: 4px 0 2px;
1157
+ padding: 6px 10px 2px;
1158
+ border-top: 1px solid var(--sg-border, #e1e1e1);
1159
+ color: var(--sg-muted, #616161);
1160
+ font-size: 11px;
1161
+ font-weight: 600;
1162
+ }
1163
+ .menu-heading:first-child { border-top: 0; margin-top: 0; padding-top: 2px; }
1164
+ /* The chevron sits close to the label, as Excel's does, so the face is a
1165
+ few pixels wider than the plain button and the band still fits 1050px
1166
+ with every group open. */
1167
+ .btn.dropdown { padding-right: 3px; }
1168
+ /* The large split: icon on top pastes, "Paste v" under it opens the menu. */
1169
+ .split.split-large {
1170
+ flex-direction: column;
1171
+ align-items: stretch;
1172
+ height: 100%;
1173
+ min-width: 46px;
1174
+ }
1175
+ .split.split-large .face {
1176
+ flex: 1 1 auto;
1177
+ border-radius: 4px 4px 0 0;
1178
+ justify-content: flex-start;
1179
+ padding: 5px 4px 0;
1180
+ }
1181
+ .split.split-large .arrow {
1182
+ width: auto;
1183
+ min-width: 0;
1184
+ height: auto;
1185
+ padding: 0 2px 3px;
1186
+ gap: 2px;
1187
+ border-radius: 0 0 4px 4px;
1188
+ color: inherit;
1189
+ font-size: 12px;
1190
+ line-height: 1.15;
1191
+ }
1192
+ .btn.dropdown .label { margin-right: -3px; }
1193
+
1194
+ .palette {
1195
+ width: 232px;
1196
+ padding: 8px;
1197
+ font-size: 12px;
1198
+ }
1199
+ .palette-none {
1200
+ display: flex;
1201
+ align-items: center;
1202
+ gap: 8px;
1203
+ width: 100%;
1204
+ padding: 4px 6px;
1205
+ margin-bottom: 4px;
1206
+ font: inherit;
1207
+ border: 0;
1208
+ border-radius: 3px;
1209
+ background: transparent;
1210
+ color: inherit;
1211
+ text-align: left;
1212
+ cursor: pointer;
1213
+ }
1214
+ .palette-none:hover { background: var(--sg-row-hover-bg, #f0f0f0); }
1215
+ .palette-title {
1216
+ font-size: 11px;
1217
+ font-weight: 600;
1218
+ color: var(--sg-muted, #616161);
1219
+ margin: 6px 0 4px;
1220
+ }
1221
+ .swatches {
1222
+ display: grid;
1223
+ grid-template-columns: repeat(10, 18px);
1224
+ gap: 0 4px;
1225
+ }
1226
+ /* The five shades under each theme colour stack touching, one column per
1227
+ colour; the base row stands a little apart above them, as in Excel. */
1228
+ .swatches .swatch:nth-child(-n + 10) { margin-bottom: 4px; }
1229
+ .swatches .swatch.shade { border-radius: 0; border-top-width: 0; }
1230
+ .swatches .swatch.shade:nth-child(n + 11):nth-child(-n + 20) { border-top-width: 1px; }
1231
+ .swatch {
1232
+ width: 18px;
1233
+ height: 18px;
1234
+ padding: 0;
1235
+ border: 1px solid rgba(0, 0, 0, 0.18);
1236
+ border-radius: 2px;
1237
+ cursor: pointer;
1238
+ }
1239
+ .swatch:hover { outline: 2px solid var(--sg-accent, #107c41); outline-offset: 1px; position: relative; z-index: 1; }
1240
+ .swatch.no-colour {
1241
+ cursor: default;
1242
+ background:
1243
+ linear-gradient(to top right,
1244
+ transparent calc(50% - 1px), var(--sg-danger, #dc2626) 50%,
1245
+ transparent calc(50% + 1px)),
1246
+ var(--sg-bg, #fff);
1247
+ }
1248
+
1249
+ .popup-group {
1250
+ display: flex;
1251
+ flex-direction: column;
1252
+ padding: 6px 8px 2px;
1253
+ }
1254
+ </style>