@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.0

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/cell-types.js +20 -0
  4. package/dist/cell-types.js.map +1 -0
  5. package/dist/code/code-formula-bar.js +6 -0
  6. package/dist/code/code-formula-bar.js.map +1 -0
  7. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  8. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +12 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/language.js +51 -0
  12. package/dist/code/language.js.map +1 -0
  13. package/dist/code/style.d.ts +1 -0
  14. package/dist/code/style.js +2 -0
  15. package/dist/collaboration/anchor.js +143 -0
  16. package/dist/collaboration/anchor.js.map +1 -0
  17. package/dist/collaboration/index.js +770 -0
  18. package/dist/collaboration/index.js.map +1 -0
  19. package/dist/collaboration/peers.js +87 -0
  20. package/dist/collaboration/peers.js.map +1 -0
  21. package/dist/collaboration/shared.js +450 -0
  22. package/dist/collaboration/shared.js.map +1 -0
  23. package/dist/components/cell-panel.js +6 -0
  24. package/dist/components/cell-panel.js.map +1 -0
  25. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
  26. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/components/cell-renders.js +40 -0
  28. package/dist/components/cell-renders.js.map +1 -0
  29. package/dist/components/date-panel.js +6 -0
  30. package/dist/components/date-panel.js.map +1 -0
  31. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
  32. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/filter-panel.js +6 -0
  34. package/dist/components/filter-panel.js.map +1 -0
  35. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
  36. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/format-dialog.js +6 -0
  38. package/dist/components/format-dialog.js.map +1 -0
  39. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
  40. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/format-list.js +6 -0
  42. package/dist/components/format-list.js.map +1 -0
  43. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
  44. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/components/format-presets.js +97 -0
  46. package/dist/components/format-presets.js.map +1 -0
  47. package/dist/components/option-panel.js +6 -0
  48. package/dist/components/option-panel.js.map +1 -0
  49. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
  50. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/sheet-comment.js +6 -0
  52. package/dist/components/sheet-comment.js.map +1 -0
  53. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
  54. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/sheet-formula-bar.js +6 -0
  56. package/dist/components/sheet-formula-bar.js.map +1 -0
  57. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
  58. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/sheet-link.js +6 -0
  60. package/dist/components/sheet-link.js.map +1 -0
  61. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
  62. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/sheet-menu.js +6 -0
  64. package/dist/components/sheet-menu.js.map +1 -0
  65. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
  66. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/sheet-name-box.js +6 -0
  68. package/dist/components/sheet-name-box.js.map +1 -0
  69. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
  70. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/sheet-note.js +6 -0
  72. package/dist/components/sheet-note.js.map +1 -0
  73. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
  74. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/sheet-outline.js +6 -0
  76. package/dist/components/sheet-outline.js.map +1 -0
  77. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
  78. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/components/sheet-tabs.js +6 -0
  80. package/dist/components/sheet-tabs.js.map +1 -0
  81. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
  82. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/components/sheet-toolbar.js +6 -0
  84. package/dist/components/sheet-toolbar.js.map +1 -0
  85. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
  86. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/components/validation-dialog.js +6 -0
  88. package/dist/components/validation-dialog.js.map +1 -0
  89. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
  90. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  91. package/dist/context.js +12 -0
  92. package/dist/context.js.map +1 -0
  93. package/dist/core/address.js +256 -0
  94. package/dist/core/address.js.map +1 -0
  95. package/dist/core/axis.js +72 -0
  96. package/dist/core/axis.js.map +1 -0
  97. package/dist/core/cell-type.js +30 -0
  98. package/dist/core/cell-type.js.map +1 -0
  99. package/dist/core/clipboard.js +141 -0
  100. package/dist/core/clipboard.js.map +1 -0
  101. package/dist/core/commands.js +63 -0
  102. package/dist/core/commands.js.map +1 -0
  103. package/dist/core/comments.js +74 -0
  104. package/dist/core/comments.js.map +1 -0
  105. package/dist/core/conditional.js +362 -0
  106. package/dist/core/conditional.js.map +1 -0
  107. package/dist/core/csv.js +191 -0
  108. package/dist/core/csv.js.map +1 -0
  109. package/dist/core/data.js +118 -0
  110. package/dist/core/data.js.map +1 -0
  111. package/dist/core/document.js +187 -0
  112. package/dist/core/document.js.map +1 -0
  113. package/dist/core/extension.js +60 -0
  114. package/dist/core/extension.js.map +1 -0
  115. package/dist/core/file.js +336 -0
  116. package/dist/core/file.js.map +1 -0
  117. package/dist/core/fill.js +153 -0
  118. package/dist/core/fill.js.map +1 -0
  119. package/dist/core/find.js +42 -0
  120. package/dist/core/find.js.map +1 -0
  121. package/dist/core/format.js +404 -0
  122. package/dist/core/format.js.map +1 -0
  123. package/dist/core/group.js +134 -0
  124. package/dist/core/group.js.map +1 -0
  125. package/dist/core/history.js +68 -0
  126. package/dist/core/history.js.map +1 -0
  127. package/dist/core/index.js +17 -0
  128. package/dist/core/link.js +34 -0
  129. package/dist/core/link.js.map +1 -0
  130. package/dist/core/merge.js +105 -0
  131. package/dist/core/merge.js.map +1 -0
  132. package/dist/core/model.js +48 -0
  133. package/dist/core/model.js.map +1 -0
  134. package/dist/core/nav.js +44 -0
  135. package/dist/core/nav.js.map +1 -0
  136. package/dist/core/paint.js +106 -0
  137. package/dist/core/paint.js.map +1 -0
  138. package/dist/core/plain-cell-types.js +54 -0
  139. package/dist/core/plain-cell-types.js.map +1 -0
  140. package/dist/core/protect.js +246 -0
  141. package/dist/core/protect.js.map +1 -0
  142. package/dist/core/selection.js +37 -0
  143. package/dist/core/selection.js.map +1 -0
  144. package/dist/core/transaction.js +1582 -0
  145. package/dist/core/transaction.js.map +1 -0
  146. package/dist/core/validation.js +135 -0
  147. package/dist/core/validation.js.map +1 -0
  148. package/dist/core/versions.js +161 -0
  149. package/dist/core/versions.js.map +1 -0
  150. package/dist/export/color.js +36 -0
  151. package/dist/export/color.js.map +1 -0
  152. package/dist/export/draw.js +316 -0
  153. package/dist/export/draw.js.map +1 -0
  154. package/dist/export/index.js +390 -0
  155. package/dist/export/index.js.map +1 -0
  156. package/dist/formula/edit.js +210 -0
  157. package/dist/formula/edit.js.map +1 -0
  158. package/dist/formula/engine.js +526 -0
  159. package/dist/formula/engine.js.map +1 -0
  160. package/dist/formula/error.js +39 -0
  161. package/dist/formula/error.js.map +1 -0
  162. package/dist/formula/functions.js +1493 -0
  163. package/dist/formula/functions.js.map +1 -0
  164. package/dist/formula/help.js +408 -0
  165. package/dist/formula/help.js.map +1 -0
  166. package/dist/formula/index.js +7 -0
  167. package/dist/formula/parse.js +369 -0
  168. package/dist/formula/parse.js.map +1 -0
  169. package/dist/formula/values.js +123 -0
  170. package/dist/formula/values.js.map +1 -0
  171. package/dist/formulas.js +30 -0
  172. package/dist/formulas.js.map +1 -0
  173. package/dist/index.js +39 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/locale.js +1312 -0
  176. package/dist/locale.js.map +1 -0
  177. package/dist/sheet.js +6 -0
  178. package/dist/sheet.js.map +1 -0
  179. package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
  180. package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/style.css +1 -0
  182. package/dist/style.d.ts +1 -0
  183. package/dist/style.js +17 -0
  184. package/dist/types/cell-types.d.ts +2 -0
  185. package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
  186. package/dist/types/code/index.d.ts +12 -0
  187. package/dist/types/code/language.d.ts +13 -0
  188. package/dist/types/collaboration/anchor.d.ts +65 -0
  189. package/dist/types/collaboration/index.d.ts +59 -0
  190. package/dist/types/collaboration/peers.d.ts +21 -0
  191. package/dist/types/collaboration/shared.d.ts +75 -0
  192. package/dist/types/components/cell-panel.vue.d.ts +20 -0
  193. package/dist/types/components/cell-renders.d.ts +9 -0
  194. package/dist/types/components/date-panel.vue.d.ts +9 -0
  195. package/dist/types/components/filter-panel.vue.d.ts +21 -0
  196. package/dist/types/components/format-dialog.vue.d.ts +24 -0
  197. package/dist/types/components/format-list.vue.d.ts +25 -0
  198. package/dist/types/components/format-presets.d.ts +30 -0
  199. package/dist/types/components/option-panel.vue.d.ts +13 -0
  200. package/dist/types/components/sheet-comment.vue.d.ts +29 -0
  201. package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
  202. package/dist/types/components/sheet-link.vue.d.ts +32 -0
  203. package/dist/types/components/sheet-menu.vue.d.ts +20 -0
  204. package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
  205. package/dist/types/components/sheet-note.vue.d.ts +22 -0
  206. package/dist/types/components/sheet-outline.vue.d.ts +36 -0
  207. package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
  208. package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
  209. package/dist/types/components/validation-dialog.vue.d.ts +25 -0
  210. package/dist/types/context.d.ts +189 -0
  211. package/dist/types/core/address.d.ts +109 -0
  212. package/dist/types/core/axis.d.ts +23 -0
  213. package/dist/types/core/cell-type.d.ts +157 -0
  214. package/dist/types/core/clipboard.d.ts +48 -0
  215. package/dist/types/core/commands.d.ts +76 -0
  216. package/dist/types/core/comments.d.ts +22 -0
  217. package/dist/types/core/conditional.d.ts +114 -0
  218. package/dist/types/core/csv.d.ts +93 -0
  219. package/dist/types/core/data.d.ts +26 -0
  220. package/dist/types/core/document.d.ts +49 -0
  221. package/dist/types/core/extension.d.ts +177 -0
  222. package/dist/types/core/file.d.ts +301 -0
  223. package/dist/types/core/fill.d.ts +23 -0
  224. package/dist/types/core/find.d.ts +39 -0
  225. package/dist/types/core/format.d.ts +47 -0
  226. package/dist/types/core/group.d.ts +76 -0
  227. package/dist/types/core/history.d.ts +27 -0
  228. package/dist/types/core/index.d.ts +33 -0
  229. package/dist/types/core/link.d.ts +14 -0
  230. package/dist/types/core/merge.d.ts +26 -0
  231. package/dist/types/core/model.d.ts +363 -0
  232. package/dist/types/core/nav.d.ts +15 -0
  233. package/dist/types/core/paint.d.ts +19 -0
  234. package/dist/types/core/plain-cell-types.d.ts +6 -0
  235. package/dist/types/core/protect.d.ts +34 -0
  236. package/dist/types/core/selection.d.ts +25 -0
  237. package/dist/types/core/transaction.d.ts +470 -0
  238. package/dist/types/core/validation.d.ts +55 -0
  239. package/dist/types/core/versions.d.ts +38 -0
  240. package/dist/types/export/color.d.ts +7 -0
  241. package/dist/types/export/draw.d.ts +98 -0
  242. package/dist/types/export/index.d.ts +47 -0
  243. package/dist/types/formula/edit.d.ts +62 -0
  244. package/dist/types/formula/engine.d.ts +99 -0
  245. package/dist/types/formula/error.d.ts +30 -0
  246. package/dist/types/formula/functions.d.ts +62 -0
  247. package/dist/types/formula/help.d.ts +12 -0
  248. package/dist/types/formula/index.d.ts +13 -0
  249. package/dist/types/formula/parse.d.ts +92 -0
  250. package/dist/types/formula/values.d.ts +65 -0
  251. package/dist/types/formulas.d.ts +32 -0
  252. package/dist/types/index.d.ts +225 -0
  253. package/dist/types/locale.d.ts +531 -0
  254. package/dist/types/sheet.vue.d.ts +60 -0
  255. package/dist/types/types.d.ts +498 -0
  256. package/dist/types/use-comments.d.ts +47 -0
  257. package/dist/types/use-messages.d.ts +7 -0
  258. package/dist/use-comments.js +130 -0
  259. package/dist/use-comments.js.map +1 -0
  260. package/dist/use-messages.js +29 -0
  261. package/dist/use-messages.js.map +1 -0
  262. package/package.json +93 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet-toolbar.js","names":["_sfc_main"],"sources":["../../src/components/sheet-toolbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n BaselineIcon,\n BoldIcon,\n CellsMergeIcon,\n ChevronDownIcon,\n DecimalDecreaseIcon,\n DecimalIncreaseIcon,\n DownloadIcon,\n FilterIcon,\n ItalicIcon,\n PaintBucketIcon,\n PaletteIcon,\n SortAscendingIcon,\n SortDescendingIcon,\n SortIcon,\n RedoIcon,\n StrikethroughIcon,\n TableFreezeIcon,\n TableCheckIcon,\n TableHighlightIcon,\n TextAlignCenterIcon,\n TextAlignLeftIcon,\n TextAlignRightIcon,\n TextValignBottomIcon,\n TextValignMiddleIcon,\n TextValignTopIcon,\n TypeXIcon,\n UnderlineIcon,\n UndoIcon,\n UploadIcon,\n WrapTextIcon,\n LinkIcon,\n ShieldLockIcon,\n} from '@dou-ui/icons'\nimport {\n DouButton,\n DouColorPanel,\n DouDropdown,\n DouDropdownItem,\n DouDropdownRadioGroup,\n DouDropdownSub,\n DouPopover,\n DouToggle,\n DouToggleGroup,\n DouToolbar,\n DouToolbarSeparator,\n DouTooltip,\n type ToggleValue,\n type Tone,\n} from 'dou-ui'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useTemplateRef,\n watch,\n type Component,\n} from 'vue'\nimport { useSheet } from '../context'\nimport { boundsOf, columnName, formatRange, inBounds, parseRange } from '../core/address'\nimport { changeDecimals, formatValue } from '../core/format'\nimport { rangesOf, ruleRangeText } from '../core/conditional'\nimport type {\n SheetConditionalFormat,\n SheetRuleRange,\n SheetStyle,\n SheetValidation,\n} from '../core/model'\nimport FormatDialog from './format-dialog.vue'\nimport FormatList from './format-list.vue'\nimport ValidationDialog from './validation-dialog.vue'\nimport {\n barSwatch,\n LOOKS,\n SCALES,\n scaleSwatch,\n TONES,\n toneKey,\n type SheetScaleColors,\n} from './format-presets'\nimport { format } from '../locale'\nimport type { SheetExportScope } from '../core/file'\nimport type {\n SheetFormatOption,\n SheetMenuItem,\n SheetToolbarItem,\n SheetToolbarProps,\n SheetToolbarSlots,\n SheetToolbarTool,\n} from '../types'\n\ndefineOptions({ name: 'DouSheetToolbar' })\n\nconst props = defineProps<SheetToolbarProps>()\ndefineSlots<SheetToolbarSlots>()\n\nconst sheet = useSheet()\nconst { messages: m, active } = sheet\n\nconst DEFAULT_TOOLS: SheetToolbarTool[] = [\n 'undo',\n 'redo',\n '|',\n 'format',\n 'decimals',\n '|',\n 'bold',\n 'italic',\n 'underline',\n 'strike',\n '|',\n 'color',\n 'fill',\n '|',\n 'align',\n 'valign',\n 'wrap',\n '|',\n 'merge',\n 'freeze',\n 'link',\n '|',\n 'sort',\n 'filter',\n 'conditional',\n 'validation',\n 'protect',\n '|',\n 'clear',\n]\n\nconst BUILT_IN = new Set<string>([...DEFAULT_TOOLS, 'export', 'import'])\n\n/** The protect menu: locking the selection, then protecting the sheet and the workbook. */\nconst PROTECT_COMMANDS = ['lock', 'protect.sheet', 'protect.workbook'] as const\n\n/** A tool that isn't built in, ready to draw: an extension's item, or a command by name. */\ninterface ExtraTool {\n key: string\n label: string\n icon?: Component\n shortcut?: string\n /** `undefined` for a plain button. */\n pressed?: boolean\n disabled: boolean\n run?: () => void\n children?: SheetMenuItem[]\n}\n\n/** An item filled in from its command. */\nfunction extraTool(item: SheetToolbarItem): ExtraTool {\n const name = item.command\n const info = name ? sheet.commands.get(name) : undefined\n if (name && !info)\n throw new Error(\n `[@dou-ui/sheet] the toolbar item \"${item.key}\" runs no command: \"${name}\" is unknown`,\n )\n return {\n key: item.key,\n label: item.label ?? info?.label ?? item.key,\n icon: item.icon ?? info?.icon,\n shortcut: item.shortcut ?? info?.shortcut,\n pressed: item.pressed ?? (name && info!.toggle ? sheet.commands.pressed(name) : undefined),\n disabled: item.disabled ?? (name ? !sheet.commands.can(name) : false),\n run: item.run ?? (name ? () => void sheet.commands.run(name) : undefined),\n children: item.children,\n }\n}\n/** The extensions' items, by key. */\nconst extensionItems = computed(() => {\n const map = new Map<string, SheetToolbarItem>()\n for (const item of sheet.toolbarItems.value) {\n if (BUILT_IN.has(item.key) || map.has(item.key))\n throw new Error(\n `[@dou-ui/sheet] the toolbar item \"${item.key}\" is added twice, or has the name of a built-in tool`,\n )\n warnOnce(item)\n map.set(item.key, item)\n }\n return map\n})\n\n// by key: extensions often give new item objects on every update\nconst warned = new Set<string>()\n/** Says once when an item can't show a name or does nothing when pressed. */\nfunction warnOnce(item: SheetToolbarItem) {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production' || warned.has(item.key)) return\n warned.add(item.key)\n if (!item.command && !item.label)\n console.warn(\n `[@dou-ui/sheet] the toolbar item \"${item.key}\" has no label or command, so it shows its key`,\n )\n if (!item.command && !item.run && !item.children?.length)\n console.warn(\n `[@dou-ui/sheet] the toolbar item \"${item.key}\" has no run, command or children: pressing it does nothing`,\n )\n}\n\n/**\n * Built-in tools of one button that an extension's command replaced: they\n * are drawn from the command, with its label, icon and state.\n */\nconst SINGLE = new Set([\n 'undo',\n 'redo',\n 'bold',\n 'italic',\n 'underline',\n 'strike',\n 'wrap',\n 'merge',\n 'filter',\n 'clear',\n 'link',\n])\nconst replaced = (tool: string) => SINGLE.has(tool) && sheet.commands.get(tool)?.builtin === false\n\n/** The tools to show, without separators at either end or two in a row. */\nconst tools = computed(() => {\n const list = props.tools ?? [...DEFAULT_TOOLS, '|', ...extensionItems.value.keys()]\n const out: string[] = []\n for (const tool of list)\n if (tool !== '|' || (out.length && out[out.length - 1] !== '|')) out.push(tool)\n if (out[out.length - 1] === '|') out.pop()\n return out\n})\n\n/** The tools that aren't built in, by name. */\nconst extras = computed(() => {\n const map = new Map<string, ExtraTool>()\n for (const tool of tools.value) {\n if (replaced(tool)) {\n map.set(tool, extraTool({ key: tool, command: tool }))\n continue\n }\n if (tool === '|' || BUILT_IN.has(tool)) continue\n const item = extensionItems.value.get(tool)\n if (item) map.set(tool, extraTool(item))\n else if (sheet.commands.has(tool)) map.set(tool, extraTool({ key: tool, command: tool }))\n else\n throw new Error(\n `[@dou-ui/sheet] the toolbar has no tool \"${tool}\": it is no built-in tool, item of an extension or command`,\n )\n }\n return map\n})\n\n/** Runs a command from a tool, then gives the keys back to the grid after a press. */\nfunction run(name: string, arg?: unknown) {\n sheet.commands.run(name, arg)\n done()\n}\nconst can = (name: string) => sheet.commands.can(name)\nconst on = (name: string) => sheet.commands.pressed(name)\n\nfunction runExtra(item: ExtraTool) {\n item.run?.()\n done()\n}\nfunction runChild(child: SheetMenuItem) {\n const name = child.command\n if (child.run) child.run()\n else if (name) sheet.commands.run(name)\n done()\n}\nconst childLabel = (child: SheetMenuItem) =>\n child.label ??\n (child.command ? (sheet.commands.get(child.command)?.label ?? child.key) : child.key)\nconst childIcon = (child: SheetMenuItem) =>\n child.icon ?? (child.command ? sheet.commands.get(child.command)?.icon : undefined)\nconst childShortcut = (child: SheetMenuItem) =>\n child.shortcut ?? (child.command ? sheet.commands.get(child.command)?.shortcut : undefined)\n// shortcuts mean nothing without a keyboard, as in the right-click menu\n// Read once mounted, so server-rendered markup matches the first render.\nconst coarse = ref(false)\nonMounted(() => {\n coarse.value = typeof matchMedia !== 'undefined' && matchMedia('(pointer: coarse)').matches\n})\nconst childDisabled = (child: SheetMenuItem) =>\n child.disabled ?? (child.command ? !sheet.commands.can(child.command) : false)\n\nconst disabled = computed(() => sheet.readonly.value)\nconst style = computed<SheetStyle>(() => sheet.styleAt(active.value))\nconst cellFormat = computed(() => sheet.formatAt(active.value))\n\n// After a pointer press the keys go back to the grid; from the keyboard,\n// focus stays on the toolbar.\nlet pointer = false\nfunction onPointerDown(event: PointerEvent) {\n pointer = true\n // a press on a button doesn't take the focus from the grid (or the cell being edited)\n if (event.pointerType === 'mouse' && (event.target as Element).closest('button'))\n event.preventDefault()\n}\nfunction onKeyDown() {\n pointer = false\n}\nfunction done() {\n if (pointer) void nextTick(() => sheet.focus())\n}\n\nconst menuOpen = ref<string | null>(null)\nfunction onMenu(tool: string, open: boolean) {\n if (open) menuOpen.value = tool\n else if (menuOpen.value === tool) {\n menuOpen.value = null\n done()\n }\n}\n\n// Export and import --------------------------------------------------------------------\n\n/** Whether more than one cell is selected, so exporting the selection means something. */\nconst manySelected = computed(() => {\n const { ranges } = sheet.selection.value\n if (ranges.length !== 1) return true\n const { start, end } = ranges[0]!\n return start.row !== end.row || start.col !== end.col\n})\nconst exportItems = computed(() =>\n sheet.exporters.value.map((exporter) => {\n const scopes = exporter.scopes.filter((scope) => scope !== 'selection' || manySelected.value)\n return {\n name: exporter.name,\n label: exporter.label,\n icon: exporter.icon,\n extension: exporter.extension,\n // a choice of what to write only when there is one\n scopes: scopes.length > 1 ? scopes : [],\n scope: scopes[0],\n }\n }),\n)\nfunction exportWith(name: string, scope?: SheetExportScope) {\n done()\n const exporter = sheet.exporters.value.find((e) => e.name === name)!\n const fileName =\n typeof props.fileName === 'function'\n ? props.fileName(exporter, scope ?? exporter.scopes[0]!)\n : props.fileName && `${props.fileName}.${exporter.extension}`\n // a failure shows over the grid and comes as the sheet's `file-error`\n sheet.saveFile(name, { scope, fileName }).catch(() => {})\n}\n\nconst picker = useTemplateRef<HTMLInputElement>('picker')\nfunction pickFile() {\n picker.value?.click()\n}\nfunction onPicked(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n // the same file can be picked again\n input.value = ''\n // back to the grid, so the button's tooltip doesn't stay up after the picker closes\n void nextTick(() => sheet.focus())\n // a failure shows over the grid and comes as the sheet's `file-error`\n if (file) sheet.importFile(file).catch(() => {})\n}\nfunction onPickCancel() {\n void nextTick(() => sheet.focus())\n}\n\n// Bold, italic, underline, strikethrough ------------------------------------------------\n\ntype StyleToggle = 'bold' | 'italic' | 'underline' | 'strike'\nconst STYLE: Record<StyleToggle, { icon: Component; key: string }> = {\n bold: { icon: BoldIcon, key: 'mod+b' },\n italic: { icon: ItalicIcon, key: 'mod+i' },\n underline: { icon: UnderlineIcon, key: 'mod+u' },\n strike: { icon: StrikethroughIcon, key: 'mod+shift+x' },\n}\nconst isStyleTool = (tool: string): tool is StyleToggle => tool in STYLE\n\n// Number formats --------------------------------------------------------------------------\n\nconst formats = computed<SheetFormatOption[]>(\n () =>\n props.formats ?? [\n { label: m.value.formatGeneral, format: undefined, example: 1234.5 },\n { label: m.value.formatNumber, format: '#,##0.00', example: 1234.5 },\n { label: m.value.formatPercent, format: '0.00%', example: 0.1234 },\n { label: m.value.formatCurrency, format: m.value.currencyFormat, example: 1234.5 },\n { label: m.value.formatDate, format: 'yyyy-mm-dd', example: '2026-03-05' },\n { label: m.value.formatTime, format: 'hh:mm', example: '09:30' },\n { label: m.value.formatText, format: '@' },\n ],\n)\n/**\n * The option the cell's format is, or is with other decimals or grouping:\n * `¥#,##0.000` is still currency, `0.0%` still percent and `0.0` a number.\n */\nconst formatShown = computed(() => {\n const current = cellFormat.value\n const exact = formats.value.find((option) => option.format === current)\n if (exact || !current) return exact ?? null\n const kind = withoutDecimals(current)\n return (\n formats.value.find((option) => option.format && withoutDecimals(option.format) === kind) ?? null\n )\n})\nconst formatLabel = computed(() => formatShown.value?.label ?? m.value.formatCustom)\n// the kind of a format: without decimals and grouping, so `0.00` is a number like `#,##0.00`\nconst withoutDecimals = (pattern: string) =>\n changeDecimals(pattern, null, -99)!.replace(/[#,](?=[#,0]*0)/g, '')\nconst exampleOf = (option: SheetFormatOption) =>\n option.example == null ? '' : formatValue(option.example, option.format)\n\nfunction setFormat(option: SheetFormatOption) {\n sheet.formatSelection(option.format)\n done()\n}\n\n// Colors ----------------------------------------------------------------------------------\n\nconst toneLabel = (tone: Tone) => m.value[toneKey(tone)]\n\ntype ColorTool = 'color' | 'fill'\nconst colorOpen = ref<ColorTool | null>(null)\nconst custom = ref<ColorTool | null>(null)\nconst customColor = ref<string | null>(null)\n\n/** The color a tool shows now: a tone, a CSS color, or `null` for none. */\nfunction colorOf(tool: ColorTool) {\n const s = style.value\n return tool === 'color'\n ? { tone: s.color ? undefined : s.tone, css: s.color }\n : { tone: s.background ? undefined : s.fill, css: s.background }\n}\n\n/** What the mark under the icon is drawn in. */\nfunction swatchOf(tool: ColorTool) {\n const { tone, css } = colorOf(tool)\n if (css) return css\n if (!tone) return undefined\n return tool === 'color' ? `var(--dou-color-${tone}-text)` : `var(--dou-color-${tone})`\n}\n\nfunction setColor(tool: ColorTool, value: { tone?: Tone; css?: string }, close = true) {\n sheet.styleSelection(\n tool === 'color'\n ? { tone: value.tone, color: value.css }\n : { fill: value.tone, background: value.css },\n )\n if (close) {\n colorOpen.value = null\n done()\n }\n}\n\n/** Opens the color panel, starting from the color the cell shows now. */\nfunction openCustom(tool: ColorTool, event: MouseEvent) {\n custom.value = custom.value === tool ? null : tool\n const css = colorOf(tool).css\n if (css) customColor.value = css\n else {\n const shown = (event.currentTarget as HTMLElement)\n .closest('.dou-sheet-toolbar__colors')\n ?.querySelector<HTMLElement>('[aria-pressed=\"true\"]')\n const look = shown && getComputedStyle(shown)\n customColor.value = (look && (tool === 'color' ? look.color : look.backgroundColor)) || null\n }\n}\nwatch(colorOpen, (open) => {\n if (open) return\n custom.value = null\n done()\n})\n\n// Alignment -------------------------------------------------------------------------------\n\nconst ALIGN = [\n { value: 'left', icon: TextAlignLeftIcon, label: 'alignLeft' },\n { value: 'center', icon: TextAlignCenterIcon, label: 'alignCenter' },\n { value: 'right', icon: TextAlignRightIcon, label: 'alignRight' },\n] as const\nconst VALIGN = [\n { value: 'top', icon: TextValignTopIcon, label: 'alignTop' },\n { value: 'middle', icon: TextValignMiddleIcon, label: 'alignMiddle' },\n { value: 'bottom', icon: TextValignBottomIcon, label: 'alignBottom' },\n] as const\n\n// what the cell does without a style: numbers right, text left, and the middle\nconst align = computed(() => ALIGN.find((a) => on(`align.${a.value}`))?.value ?? null)\nconst valign = computed(() => VALIGN.find((v) => on(`valign.${v.value}`))?.value ?? 'middle')\nconst valignIcon = computed(() => VALIGN.find((v) => v.value === valign.value)!.icon)\n\nfunction setAlign(value: ToggleValue | ToggleValue[] | null) {\n if (typeof value === 'string') run(`align.${value}`)\n}\n\n// Conditional formats ---------------------------------------------------------------------\n\n/** The selected ranges, as names such as `D2:D40`. */\nfunction selectedRanges() {\n const data = sheet.sheet.value\n const size = { rows: data.rowCount ?? 1000, cols: data.colCount ?? 26 }\n return sheet.selection.value.ranges.map((range) => formatRange(range, size))\n}\n/** The selection as a rule's range, which a new rule starts with: one range, or several. */\nfunction selectedRange(): SheetRuleRange {\n const ranges = selectedRanges()\n return ranges.length === 1 ? ranges[0]! : ranges\n}\n/** Whether a rule's ranges hold a cell. */\nconst ruleHolds = (range: SheetRuleRange, row: number, col: number) =>\n rangesOf(range).some((text) => {\n const r = parseRange(text)\n return !!r && inBounds(boundsOf(r), row, col)\n })\n\nfunction addFormats(list: SheetConditionalFormat[]) {\n if (!list.length) return\n sheet.transact(\n (tr) => {\n for (const f of list) tr.addConditionalFormat(f)\n },\n { action: 'format' },\n )\n sheet.announce(\n format(m.value.formats.added, { range: list.map((f) => ruleRangeText(f.range)).join(', ') }),\n )\n done()\n}\n\n/** The menu items that open the dialog, and the rule each starts with. */\nconst HIGHLIGHTS = ['gt', 'lt', 'between', 'eq', 'contains', 'duplicates', 'formula'] as const\nconst RANKS = ['top', 'bottom', 'above', 'below'] as const\ntype FormatItem = (typeof HIGHLIGHTS)[number] | (typeof RANKS)[number]\nfunction startOf(item: FormatItem, range: SheetRuleRange): SheetConditionalFormat {\n const look = { ...LOOKS.danger }\n switch (item) {\n case 'duplicates':\n return { range, duplicates: true, style: look }\n case 'formula':\n return { range, formula: '', style: look }\n case 'top':\n return { range, top: 10, style: look }\n case 'bottom':\n return { range, bottom: 10, style: look }\n case 'above':\n case 'below':\n return { range, average: item, style: look }\n default:\n return { range, when: { op: item }, style: look }\n }\n}\n\nconst dialogFormat = ref<SheetConditionalFormat>({ range: '', when: { op: 'gt' }, style: {} })\nconst dialogTitle = ref('')\nconst dialogOpen = ref(false)\n/** The rule being edited, by its place in the list; `-1` adds one. */\nconst editing = ref(-1)\nfunction openFormat(item: FormatItem) {\n dialogFormat.value = startOf(item, selectedRange())\n dialogTitle.value = m.value.formats[item].replace(/…$/, '')\n editing.value = -1\n dialogOpen.value = true\n}\nfunction editFormat(index: number) {\n dialogFormat.value = conditionals.value[index]!\n dialogTitle.value = m.value.formats.editTitle\n editing.value = index\n dialogOpen.value = true\n}\nfunction applyFormat(f: SheetConditionalFormat) {\n if (editing.value < 0) return addFormats([f])\n const list = [...conditionals.value]\n list[editing.value] = f\n setFormats(list)\n}\nfunction dialogClosed() {\n // editing from the list goes back to it, which keeps the focus\n if (!listOpen.value) sheet.focus()\n}\n// Data validation --------------------------------------------------------------------------\n\nconst validations = computed(() => sheet.sheet.value.validations ?? [])\nconst ruleOpen = ref(false)\nconst rule = ref<SheetValidation>({ range: '', list: [] })\n/** The rule being edited, by its place in the sheet's; `-1` adds one. */\nconst ruleIndex = ref(-1)\n/** Opens the rule of the active cell, or a new one for the selection. */\nfunction openValidation() {\n const { row, col } = active.value\n ruleIndex.value = validations.value.findIndex((r) => ruleHolds(r.range, row, col))\n rule.value =\n ruleIndex.value >= 0\n ? validations.value[ruleIndex.value]!\n : { range: selectedRange(), list: [] }\n ruleOpen.value = true\n}\nfunction applyValidation(next: SheetValidation) {\n const list = [...validations.value]\n if (ruleIndex.value >= 0) list[ruleIndex.value] = next\n else list.push(next)\n sheet.transact((tr) => tr.setValidations(list), { action: 'validation' })\n sheet.announce(format(m.value.validation.added, { range: ruleRangeText(next.range) }))\n}\nfunction removeValidation() {\n const gone = validations.value[ruleIndex.value]\n if (!gone) return\n sheet.transact((tr) => tr.setValidations(validations.value.filter((r) => r !== gone)), {\n action: 'validation',\n })\n sheet.announce(format(m.value.validation.removed, { range: ruleRangeText(gone.range) }))\n}\n\nconst listOpen = ref(false)\nconst conditionals = computed(() => sheet.sheet.value.conditionalFormats ?? [])\nfunction setFormats(next: SheetConditionalFormat[]) {\n sheet.transact((tr) => tr.setConditionalFormats(next), { action: 'format' })\n}\nfunction clearFormats(all: boolean) {\n const before = conditionals.value.length\n sheet.transact(\n (tr) => {\n if (all) tr.clearConditionalFormats()\n else for (const range of selectedRanges()) tr.clearConditionalFormats(range)\n },\n { action: 'format' },\n )\n const count = before - (sheet.sheet.value.conditionalFormats?.length ?? 0)\n sheet.announce(format(m.value.formats.cleared, { count }))\n done()\n}\n\nfunction addScale(colors: SheetScaleColors) {\n addFormats([{ range: selectedRange(), scale: [...colors] as SheetScaleColors }])\n}\nfunction addBar(tone: Tone) {\n addFormats([{ range: selectedRange(), bar: tone }])\n}\n\n// Merge and freeze ------------------------------------------------------------------------\n\nconst freeze = computed(() => sheet.sheet.value.freeze ?? {})\nfunction setFreeze(rows: number, cols: number) {\n sheet.transact(\n (tr) =>\n tr.updateSheet({\n freeze: rows || cols ? { rows: rows || undefined, cols: cols || undefined } : undefined,\n }),\n { action: 'freeze' },\n )\n done()\n}\nconst freezeItems = computed(() => {\n const { row, col } = active.value\n const f = freeze.value\n const items = [\n { key: 'row1', label: m.value.freezeFirstRow, rows: 1, cols: f.cols ?? 0 },\n { key: 'col1', label: m.value.freezeFirstCol, rows: f.rows ?? 0, cols: 1 },\n ]\n if (row > 0)\n items.push({\n key: 'rows',\n label: format(m.value.freezeRows, { n: row + 1 }),\n rows: row + 1,\n cols: f.cols ?? 0,\n })\n if (col > 0)\n items.push({\n key: 'cols',\n label: format(m.value.freezeCols, { col: columnName(col) }),\n rows: f.rows ?? 0,\n cols: col + 1,\n })\n return items\n})\nconst frozen = computed(() => !!(freeze.value.rows || freeze.value.cols))\n\n// Too narrow for every tool: one row that scrolls sideways, faded at the side with more\n\nconst bar = useTemplateRef<InstanceType<typeof DouToolbar>>('bar')\nconst barEl = computed(() => (bar.value?.$el as HTMLElement | undefined) ?? null)\nconst more = ref({ start: false, end: false })\nfunction measure() {\n const el = barEl.value\n if (!el) return\n const start = el.scrollLeft > 1\n const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1\n if (start !== more.value.start || end !== more.value.end) more.value = { start, end }\n}\nlet observer: ResizeObserver | undefined\nwatch(\n barEl,\n (el) => {\n observer?.disconnect()\n if (!el || typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver(measure)\n observer.observe(el)\n },\n { flush: 'post' },\n)\nonBeforeUnmount(() => observer?.disconnect())\n</script>\n\n<template>\n <div class=\"dou-sheet-toolbar\">\n <DouToolbar\n ref=\"bar\"\n size=\"sm\"\n class=\"dou-sheet-toolbar__bar\"\n :class=\"{ 'has-more-start': more.start, 'has-more-end': more.end }\"\n :aria-label=\"m.toolbar\"\n @pointerdown=\"onPointerDown\"\n @keydown=\"onKeyDown\"\n @scroll.passive=\"measure\"\n >\n <slot name=\"start\" />\n <template v-for=\"(tool, i) in tools\" :key=\"tool === '|' ? `|${i}` : tool\">\n <DouToolbarSeparator v-if=\"tool === '|'\" />\n\n <template v-else-if=\"extras.get(tool)?.children\">\n <template v-for=\"item in [extras.get(tool)!]\" :key=\"item.key\">\n <DouTooltip\n :content=\"item.label\"\n :disabled=\"menuOpen === tool || !item.icon\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"item.disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :aria-label=\"item.label\"\n >\n <component :is=\"item.icon\" v-if=\"item.icon\" />\n <span v-else class=\"dou-sheet-toolbar__text\">{{ item.label }}</span>\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <template v-for=\"child in item.children\" :key=\"child.key\">\n <DouDropdownSub\n v-if=\"child.children?.length\"\n :title=\"childLabel(child)\"\n :divided=\"child.divided\"\n >\n <DouDropdownItem\n v-for=\"grand in child.children\"\n :key=\"grand.key\"\n :disabled=\"childDisabled(grand)\"\n :divided=\"grand.divided\"\n :tone=\"grand.tone\"\n :shortcut=\"coarse ? undefined : childShortcut(grand)\"\n @click=\"runChild(grand)\"\n >\n <template v-if=\"childIcon(grand)\" #prefix>\n <component :is=\"childIcon(grand)\" />\n </template>\n {{ childLabel(grand) }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownItem\n v-else\n :disabled=\"childDisabled(child)\"\n :divided=\"child.divided\"\n :tone=\"child.tone\"\n :shortcut=\"coarse ? undefined : childShortcut(child)\"\n @click=\"runChild(child)\"\n >\n <template v-if=\"childIcon(child)\" #prefix>\n <component :is=\"childIcon(child)\" />\n </template>\n {{ childLabel(child) }}\n </DouDropdownItem>\n </template>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n </template>\n </template>\n\n <template v-else-if=\"extras.get(tool)\">\n <template v-for=\"item in [extras.get(tool)!]\" :key=\"item.key\">\n <DouTooltip\n :content=\"item.label\"\n :shortcut=\"item.shortcut\"\n :disabled=\"!item.icon && !item.shortcut\"\n placement=\"bottom\"\n >\n <DouToggle\n v-if=\"item.pressed !== undefined\"\n :pressed=\"item.pressed\"\n :aria-label=\"item.label\"\n :aria-keyshortcuts=\"item.shortcut\"\n :disabled=\"item.disabled\"\n @update:pressed=\"runExtra(item)\"\n >\n <component :is=\"item.icon\" v-if=\"item.icon\" />\n <span v-else class=\"dou-sheet-toolbar__text\">{{ item.label }}</span>\n </DouToggle>\n <DouButton\n v-else\n variant=\"ghost\"\n :aria-label=\"item.label\"\n :aria-keyshortcuts=\"item.shortcut\"\n :disabled=\"item.disabled\"\n @click=\"runExtra(item)\"\n >\n <component :is=\"item.icon\" v-if=\"item.icon\" />\n <span v-else class=\"dou-sheet-toolbar__text\">{{ item.label }}</span>\n </DouButton>\n </DouTooltip>\n </template>\n </template>\n\n <template v-else-if=\"tool === 'undo' || tool === 'redo'\">\n <DouTooltip\n :content=\"m[tool]\"\n :shortcut=\"tool === 'undo' ? 'mod+z' : 'mod+shift+z'\"\n placement=\"bottom\"\n >\n <DouButton\n variant=\"ghost\"\n :aria-label=\"m[tool]\"\n :disabled=\"!can(tool)\"\n @click=\"run(tool)\"\n >\n <UndoIcon v-if=\"tool === 'undo'\" />\n <RedoIcon v-else />\n </DouButton>\n </DouTooltip>\n </template>\n\n <DouDropdown\n v-else-if=\"tool === 'format'\"\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__format\"\n :aria-label=\"`${m.numberFormat}:${formatLabel}`\"\n >\n <span class=\"dou-sheet-toolbar__format-label\">{{ formatLabel }}</span>\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownRadioGroup :model-value=\"formatShown ? formats.indexOf(formatShown) : -1\">\n <DouDropdownItem\n v-for=\"(option, i) in formats\"\n :key=\"option.label\"\n :value=\"i\"\n class=\"dou-sheet-toolbar__format-item\"\n @click=\"setFormat(option)\"\n >\n {{ option.label }}\n <template #suffix>\n <span class=\"dou-sheet-toolbar__example\">{{ exampleOf(option) }}</span>\n </template>\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </template>\n </DouDropdown>\n\n <template v-else-if=\"tool === 'decimals'\">\n <DouTooltip\n v-for=\"by in [-1, 1]\"\n :key=\"by\"\n :content=\"by < 0 ? m.fewerDecimals : m.moreDecimals\"\n placement=\"bottom\"\n >\n <DouButton\n variant=\"ghost\"\n :aria-label=\"by < 0 ? m.fewerDecimals : m.moreDecimals\"\n :disabled=\"!can(by < 0 ? 'decimals.fewer' : 'decimals.more')\"\n @click=\"run(by < 0 ? 'decimals.fewer' : 'decimals.more')\"\n >\n <DecimalDecreaseIcon v-if=\"by < 0\" />\n <DecimalIncreaseIcon v-else />\n </DouButton>\n </DouTooltip>\n </template>\n\n <DouTooltip\n v-else-if=\"isStyleTool(tool)\"\n :content=\"m[tool]\"\n :shortcut=\"STYLE[tool].key\"\n placement=\"bottom\"\n >\n <DouToggle\n :pressed=\"on(tool)\"\n :aria-label=\"m[tool]\"\n :disabled=\"!can(tool)\"\n @update:pressed=\"run(tool, $event)\"\n >\n <component :is=\"STYLE[tool].icon\" />\n </DouToggle>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'color' || tool === 'fill'\"\n :content=\"tool === 'color' ? m.textColor : m.fillColor\"\n :disabled=\"colorOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouPopover\n :open=\"colorOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"colorOpen = $event ? tool : colorOpen === tool ? null : colorOpen\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__color\"\n :class=\"`is-${tool}`\"\n :style=\"{ '--dou-sheet-swatch': swatchOf(tool) }\"\n :aria-label=\"tool === 'color' ? m.textColor : m.fillColor\"\n :aria-expanded=\"colorOpen === tool\"\n >\n <BaselineIcon v-if=\"tool === 'color'\" />\n <PaintBucketIcon v-else />\n </DouButton>\n <template #content>\n <div\n class=\"dou-sheet-toolbar__colors\"\n :aria-label=\"tool === 'color' ? m.textColor : m.fillColor\"\n role=\"group\"\n >\n <button\n type=\"button\"\n class=\"dou-sheet-toolbar__swatch is-default\"\n :class=\"`is-${tool}`\"\n :aria-pressed=\"!colorOf(tool).tone && !colorOf(tool).css\"\n :aria-label=\"m.colorDefault\"\n :title=\"m.colorDefault\"\n @click=\"setColor(tool, {})\"\n />\n <button\n v-for=\"tone in TONES\"\n :key=\"tone\"\n type=\"button\"\n class=\"dou-sheet-toolbar__swatch\"\n :class=\"[`is-${tool}`, `is-tone-${tone}`]\"\n :aria-pressed=\"colorOf(tool).tone === tone\"\n :aria-label=\"toneLabel(tone)\"\n :title=\"toneLabel(tone)\"\n @click=\"setColor(tool, { tone })\"\n >\n <span v-if=\"tool === 'color'\" aria-hidden=\"true\">A</span>\n </button>\n <button\n type=\"button\"\n class=\"dou-sheet-toolbar__swatch is-custom\"\n :class=\"`is-${tool}`\"\n :aria-pressed=\"!!colorOf(tool).css\"\n :aria-expanded=\"custom === tool\"\n :aria-label=\"m.colorCustom\"\n :title=\"m.colorCustom\"\n :style=\"{ '--dou-sheet-swatch': colorOf(tool).css }\"\n @click=\"openCustom(tool, $event)\"\n >\n <span v-if=\"colorOf(tool).css && tool === 'color'\" aria-hidden=\"true\">A</span>\n <PaletteIcon v-else-if=\"!colorOf(tool).css\" />\n </button>\n </div>\n <DouColorPanel\n v-if=\"custom === tool\"\n v-model=\"customColor\"\n class=\"dou-sheet-toolbar__panel\"\n :bordered=\"false\"\n format=\"hex\"\n show-input\n @change=\"$event && setColor(tool, { css: $event }, false)\"\n />\n </template>\n </DouPopover>\n </span>\n </DouTooltip>\n\n <DouToggleGroup\n v-else-if=\"tool === 'align'\"\n type=\"single\"\n :model-value=\"align\"\n :disabled=\"disabled\"\n @update:model-value=\"setAlign\"\n >\n <DouTooltip v-for=\"a in ALIGN\" :key=\"a.value\" :content=\"m[a.label]\" placement=\"bottom\">\n <DouToggle :value=\"a.value\" :aria-label=\"m[a.label]\">\n <component :is=\"a.icon\" />\n </DouToggle>\n </DouTooltip>\n </DouToggleGroup>\n\n <DouTooltip\n v-else-if=\"tool === 'valign'\"\n :content=\"m.verticalAlign\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :aria-label=\"m.verticalAlign\"\n >\n <component :is=\"valignIcon\" />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownRadioGroup :model-value=\"valign\">\n <DouDropdownItem\n v-for=\"v in VALIGN\"\n :key=\"v.value\"\n :value=\"v.value\"\n :disabled=\"!can(`valign.${v.value}`)\"\n @click=\"run(`valign.${v.value}`)\"\n >\n <template #prefix><component :is=\"v.icon\" /></template>\n {{ m[v.label] }}\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip v-else-if=\"tool === 'wrap'\" :content=\"m.wrap\" placement=\"bottom\">\n <DouToggle\n :pressed=\"on('wrap')\"\n :aria-label=\"m.wrap\"\n :disabled=\"!can('wrap')\"\n @update:pressed=\"run('wrap', $event)\"\n >\n <WrapTextIcon />\n </DouToggle>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'merge'\"\n :content=\"sheet.commands.get('merge')?.label\"\n placement=\"bottom\"\n >\n <DouToggle\n :pressed=\"on('merge')\"\n :aria-label=\"m.mergeCells\"\n :disabled=\"!can('merge')\"\n @update:pressed=\"run('merge')\"\n >\n <CellsMergeIcon />\n </DouToggle>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'link'\"\n :content=\"m.link.add\"\n shortcut=\"mod+k\"\n placement=\"bottom\"\n >\n <DouButton\n variant=\"ghost\"\n :aria-label=\"m.link.add\"\n :disabled=\"!can('link')\"\n @click=\"run('link')\"\n >\n <LinkIcon />\n </DouButton>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'protect'\"\n :content=\"m.protect.menu\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :class=\"{ 'is-on': on('protect.sheet') }\"\n :aria-label=\"m.protect.menu\"\n >\n <ShieldLockIcon />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownItem\n v-for=\"name in PROTECT_COMMANDS\"\n :key=\"name\"\n :disabled=\"!can(name)\"\n :divided=\"name === 'protect.sheet'\"\n @click=\"run(name)\"\n >\n <template #prefix>\n <component :is=\"sheet.commands.get(name)?.icon\" />\n </template>\n {{ sheet.commands.get(name)?.label }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'freeze'\"\n :content=\"m.freeze\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :class=\"{ 'is-on': frozen }\"\n :aria-label=\"m.freeze\"\n >\n <TableFreezeIcon />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownItem\n v-for=\"item in freezeItems\"\n :key=\"item.key\"\n :checked=\"(freeze.rows ?? 0) === item.rows && (freeze.cols ?? 0) === item.cols\"\n @click=\"setFreeze(item.rows, item.cols)\"\n >\n {{ item.label }}\n </DouDropdownItem>\n <DouDropdownItem divided :disabled=\"!frozen\" @click=\"setFreeze(0, 0)\">\n {{ m.unfreeze }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'sort'\"\n :content=\"m.filter.sort\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :aria-label=\"m.filter.sort\"\n >\n <SortIcon />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownItem :disabled=\"!can('sort.asc')\" @click=\"run('sort.asc')\">\n <template #prefix><SortAscendingIcon /></template>\n {{ m.filter.sortAsc }}\n </DouDropdownItem>\n <DouDropdownItem :disabled=\"!can('sort.desc')\" @click=\"run('sort.desc')\">\n <template #prefix><SortDescendingIcon /></template>\n {{ m.filter.sortDesc }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'filter'\"\n :content=\"sheet.commands.get('filter')?.label\"\n placement=\"bottom\"\n >\n <DouToggle\n :pressed=\"on('filter')\"\n :aria-label=\"m.filter.turnOn\"\n :disabled=\"!can('filter')\"\n @update:pressed=\"run('filter')\"\n >\n <FilterIcon />\n </DouToggle>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'conditional'\"\n :content=\"m.formats.title\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n :disabled=\"disabled\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :aria-label=\"m.formats.title\"\n >\n <TableHighlightIcon />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <DouDropdownSub :title=\"m.formats.highlight\">\n <DouDropdownItem v-for=\"kind in HIGHLIGHTS\" :key=\"kind\" @click=\"openFormat(kind)\">\n {{ m.formats[kind] }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownSub :title=\"m.formats.ranks\">\n <DouDropdownItem v-for=\"kind in RANKS\" :key=\"kind\" @click=\"openFormat(kind)\">\n {{ m.formats[kind] }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownSub :title=\"m.formats.bar\">\n <DouDropdownItem v-for=\"tone in TONES\" :key=\"tone\" @click=\"addBar(tone)\">\n <template #prefix>\n <span\n class=\"dou-sheet-toolbar__gradient\"\n :style=\"{ backgroundImage: barSwatch(tone) }\"\n />\n </template>\n {{ toneLabel(tone) }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownSub :title=\"m.formats.scale\">\n <DouDropdownItem\n v-for=\"(colors, key) in SCALES\"\n :key=\"key\"\n @click=\"addScale(colors)\"\n >\n <template #prefix>\n <span\n class=\"dou-sheet-toolbar__gradient\"\n :style=\"{ backgroundImage: scaleSwatch(colors) }\"\n />\n </template>\n {{ m.formats.scales[key] }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownItem divided @click=\"clearFormats(false)\">\n {{ m.formats.clearSelection }}\n </DouDropdownItem>\n <DouDropdownItem :disabled=\"!conditionals.length\" @click=\"clearFormats(true)\">\n {{ m.formats.clearSheet }}\n </DouDropdownItem>\n <DouDropdownItem divided @click=\"listOpen = true\">\n {{ m.formats.manage }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'export'\"\n :content=\"m.file.export\"\n :disabled=\"menuOpen === tool\"\n placement=\"bottom\"\n >\n <span class=\"dou-sheet-toolbar__trigger\">\n <DouDropdown\n :open=\"menuOpen === tool\"\n placement=\"bottom-start\"\n @update:open=\"onMenu(tool, $event)\"\n >\n <DouButton\n variant=\"ghost\"\n class=\"dou-sheet-toolbar__menu\"\n :aria-label=\"m.file.export\"\n >\n <DownloadIcon />\n <template #trailing><ChevronDownIcon /></template>\n </DouButton>\n <template #menu>\n <template v-for=\"item in exportItems\" :key=\"item.name\">\n <DouDropdownSub v-if=\"item.scopes.length\" :title=\"item.label\">\n <template v-if=\"item.icon\" #prefix><component :is=\"item.icon\" /></template>\n <DouDropdownItem\n v-for=\"scope in item.scopes\"\n :key=\"scope\"\n @click=\"exportWith(item.name, scope)\"\n >\n {{ m.file[scope] }}\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownItem v-else @click=\"exportWith(item.name, item.scope)\">\n <template v-if=\"item.icon\" #prefix><component :is=\"item.icon\" /></template>\n {{ item.label }}\n </DouDropdownItem>\n </template>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n\n <DouTooltip v-else-if=\"tool === 'import'\" :content=\"m.file.import\" placement=\"bottom\">\n <DouButton\n variant=\"ghost\"\n :aria-label=\"m.file.import\"\n aria-haspopup=\"dialog\"\n :disabled=\"disabled\"\n @click=\"pickFile\"\n >\n <UploadIcon />\n </DouButton>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'validation'\"\n :content=\"m.validation.title\"\n placement=\"bottom\"\n >\n <DouButton\n variant=\"ghost\"\n :aria-label=\"m.validation.title\"\n aria-haspopup=\"dialog\"\n :disabled=\"disabled\"\n @click=\"openValidation\"\n >\n <TableCheckIcon />\n </DouButton>\n </DouTooltip>\n\n <DouTooltip\n v-else-if=\"tool === 'clear'\"\n :content=\"m.clearFormatting\"\n shortcut=\"mod+\\\"\n placement=\"bottom\"\n >\n <DouButton\n variant=\"ghost\"\n :aria-label=\"m.clearFormatting\"\n :disabled=\"!can('clear')\"\n @click=\"run('clear')\"\n >\n <TypeXIcon />\n </DouButton>\n </DouTooltip>\n </template>\n <slot name=\"end\" />\n </DouToolbar>\n <input\n ref=\"picker\"\n type=\"file\"\n class=\"dou-sheet-toolbar__picker\"\n :accept=\"sheet.importAccept.value\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n @change=\"onPicked\"\n @cancel=\"onPickCancel\"\n />\n <FormatDialog\n v-model:open=\"dialogOpen\"\n :format=\"dialogFormat\"\n :title=\"dialogTitle\"\n :styles=\"sheet.doc.value.styles\"\n :messages=\"m\"\n @apply=\"applyFormat\"\n @closed=\"dialogClosed\"\n />\n <ValidationDialog\n v-model:open=\"ruleOpen\"\n :rule=\"rule\"\n :editing=\"ruleIndex >= 0\"\n :messages=\"m\"\n @apply=\"applyValidation\"\n @remove=\"removeValidation\"\n @closed=\"sheet.focus()\"\n />\n <FormatList\n v-model:open=\"listOpen\"\n :formats=\"conditionals\"\n :styles=\"sheet.doc.value.styles\"\n :messages=\"m\"\n @change=\"setFormats\"\n @edit=\"editFormat\"\n @closed=\"sheet.focus()\"\n />\n </div>\n</template>\n"],"mappings":""}