@veltra/desktop 1.1.14 → 1.1.18

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 (277) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js.map +1 -1
  3. package/dist/components/action/action.js.map +1 -1
  4. package/dist/components/action/action.vue.d.ts +1 -1
  5. package/dist/components/action/style2.css +0 -4
  6. package/dist/components/auto-complete/auto-complete.js.map +1 -1
  7. package/dist/components/auto-complete/style.js +1 -1
  8. package/dist/components/auto-complete/use-keyboard.js.map +1 -1
  9. package/dist/components/auto-complete/use-suggestions.js.map +1 -1
  10. package/dist/components/badge/badge.js +1 -1
  11. package/dist/components/badge/badge.js.map +1 -1
  12. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  13. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  14. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  15. package/dist/components/batch-edit/helper.js.map +1 -1
  16. package/dist/components/batch-edit/style.js +1 -1
  17. package/dist/components/batch-edit/use-edit.js.map +1 -1
  18. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  19. package/dist/components/button/button-group.js.map +1 -1
  20. package/dist/components/button/button.js.map +1 -1
  21. package/dist/components/calendar/calendar.js.map +1 -1
  22. package/dist/components/calendar/utils.js.map +1 -1
  23. package/dist/components/card/card-action.js.map +1 -1
  24. package/dist/components/card/card-content.js.map +1 -1
  25. package/dist/components/card/card-cover.js.map +1 -1
  26. package/dist/components/card/card-header.js.map +1 -1
  27. package/dist/components/card/card.js.map +1 -1
  28. package/dist/components/cascade/cascade-panel-item.js.map +1 -1
  29. package/dist/components/cascade/cascade.js.map +1 -1
  30. package/dist/components/cascade/cascade.vue.d.ts +3 -3
  31. package/dist/components/cascade/node.js.map +1 -1
  32. package/dist/components/cascade/use-check.js.map +1 -1
  33. package/dist/components/cascade/use-data-map.js.map +1 -1
  34. package/dist/components/cascade/use-select.js +1 -1
  35. package/dist/components/cascade/use-select.js.map +1 -1
  36. package/dist/components/check-tag/check-tag.js.map +1 -1
  37. package/dist/components/checkbox/checkbox-button.js.map +1 -1
  38. package/dist/components/checkbox/checkbox.js.map +1 -1
  39. package/dist/components/checkbox/style.js +1 -1
  40. package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
  41. package/dist/components/code-editor/code-editor.js.map +1 -1
  42. package/dist/components/collapse/collapse-item.js.map +1 -1
  43. package/dist/components/collapse/collapse.js.map +1 -1
  44. package/dist/components/condition-editor/components/condition-group.js +138 -103
  45. package/dist/components/condition-editor/components/condition-group.js.map +1 -1
  46. package/dist/components/condition-editor/components/condition-row.js +146 -95
  47. package/dist/components/condition-editor/components/condition-row.js.map +1 -1
  48. package/dist/components/condition-editor/condition-editor.js +18 -40
  49. package/dist/components/condition-editor/condition-editor.js.map +1 -1
  50. package/dist/components/condition-editor/condition-editor.vue.d.ts +0 -2
  51. package/dist/components/condition-editor/core/evaluator.d.ts +22 -0
  52. package/dist/components/condition-editor/core/evaluator.js +111 -50
  53. package/dist/components/condition-editor/core/evaluator.js.map +1 -1
  54. package/dist/components/condition-editor/core/operators.js.map +1 -1
  55. package/dist/components/condition-editor/index.d.ts +2 -1
  56. package/dist/components/condition-editor/style2.css +159 -82
  57. package/dist/components/context-menu/api.js.map +1 -1
  58. package/dist/components/context-menu/context-menu-item.js.map +1 -1
  59. package/dist/components/context-menu/context-menu.js.map +1 -1
  60. package/dist/components/context-menu/style.js +1 -1
  61. package/dist/components/date-panel/date-panel.js.map +1 -1
  62. package/dist/components/date-panel/headers/day.js.map +1 -1
  63. package/dist/components/date-panel/headers/month.js.map +1 -1
  64. package/dist/components/date-panel/headers/year.js.map +1 -1
  65. package/dist/components/date-panel/panels/day.js.map +1 -1
  66. package/dist/components/date-panel/panels/month.js.map +1 -1
  67. package/dist/components/date-panel/panels/year.js.map +1 -1
  68. package/dist/components/date-panel/use-date-select.js.map +1 -1
  69. package/dist/components/date-panel/use-panel-date.js.map +1 -1
  70. package/dist/components/date-panel/use-panel.js.map +1 -1
  71. package/dist/components/date-picker/date-picker.js.map +1 -1
  72. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  73. package/dist/components/dialog/dialog.js.map +1 -1
  74. package/dist/components/dialog/style.js +2 -2
  75. package/dist/components/dialog/use-maximum.js.map +1 -1
  76. package/dist/components/drawer/drawer.js.map +1 -1
  77. package/dist/components/drawer/style.js +1 -1
  78. package/dist/components/dropdown/dropdown.js +1 -0
  79. package/dist/components/dropdown/dropdown.js.map +1 -1
  80. package/dist/components/dropdown/style.js +1 -1
  81. package/dist/components/empty/empty.js.map +1 -1
  82. package/dist/components/expression-editor/components/path-preview.js.map +1 -1
  83. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  84. package/dist/components/expression-editor/core/chip.js.map +1 -1
  85. package/dist/components/expression-editor/core/editor.js.map +1 -1
  86. package/dist/components/expression-editor/core/mention.js.map +1 -1
  87. package/dist/components/expression-editor/core/model.js.map +1 -1
  88. package/dist/components/expression-editor/di.js.map +1 -1
  89. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  90. package/dist/components/file-picker/file-picker.js.map +1 -1
  91. package/dist/components/file-picker/helper.js.map +1 -1
  92. package/dist/components/file-viewer/file-viewer.js.map +1 -1
  93. package/dist/components/file-viewer/helper.js.map +1 -1
  94. package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -1
  95. package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -1
  96. package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -1
  97. package/dist/components/file-viewer/previewers/pdf-viewport-scroll.js.map +1 -1
  98. package/dist/components/file-viewer/previewers/pdf-zoom-bridge.js.map +1 -1
  99. package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -1
  100. package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -1
  101. package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -1
  102. package/dist/components/float-button/float-button.js.map +1 -1
  103. package/dist/components/form/dynamic-form-model.js.map +1 -1
  104. package/dist/components/form/form-model.js.map +1 -1
  105. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  106. package/dist/components/form/helper.js.map +1 -1
  107. package/dist/components/form/use-node-interceptor.js.map +1 -1
  108. package/dist/components/form-item/form-item.js.map +1 -1
  109. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +2 -1
  110. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  111. package/dist/components/gantt-chart/gantt-chart.js.map +1 -1
  112. package/dist/components/grid/breakpoint.js.map +1 -1
  113. package/dist/components/grid/grid-item.js.map +1 -1
  114. package/dist/components/grid/grid.js.map +1 -1
  115. package/dist/components/grid/use-responsive.js.map +1 -1
  116. package/dist/components/grid-input/grid-input.js.map +1 -1
  117. package/dist/components/group-input/group-input.js.map +1 -1
  118. package/dist/components/group-input/use-group-items.js.map +1 -1
  119. package/dist/components/icon/icon.js.map +1 -1
  120. package/dist/components/index.d.ts +1 -0
  121. package/dist/components/index.js +4 -0
  122. package/dist/components/input/input.vue.d.ts +2 -2
  123. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js.map +1 -1
  124. package/dist/components/input/style.js +1 -1
  125. package/dist/components/kbd/kbd.js.map +1 -1
  126. package/dist/components/layout/layout-resizer.js.map +1 -1
  127. package/dist/components/layout/layout.js.map +1 -1
  128. package/dist/components/list/list-item.js.map +1 -1
  129. package/dist/components/list/list.js.map +1 -1
  130. package/dist/components/loading/directive.js.map +1 -1
  131. package/dist/components/loading/loading.js.map +1 -1
  132. package/dist/components/loading/style.js +1 -1
  133. package/dist/components/menu/helper.js.map +1 -1
  134. package/dist/components/menu/menu-item-collapsed.js.map +1 -1
  135. package/dist/components/menu/menu-item.js.map +1 -1
  136. package/dist/components/menu/menu-sub-collapsed.js.map +1 -1
  137. package/dist/components/menu/menu-sub.js.map +1 -1
  138. package/dist/components/menu/menu.js.map +1 -1
  139. package/dist/components/menu/use-menu-item.js.map +1 -1
  140. package/dist/components/menu/walk-menu-path.js.map +1 -1
  141. package/dist/components/message/helper.js.map +1 -1
  142. package/dist/components/message/message-box.js.map +1 -1
  143. package/dist/components/message/message.js.map +1 -1
  144. package/dist/components/message/message2.js.map +1 -1
  145. package/dist/components/message-confirm/message-confirm.js.map +1 -1
  146. package/dist/components/message-confirm/message-confirm2.js.map +1 -1
  147. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  148. package/dist/components/multi-select/multi-select.js.map +1 -1
  149. package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
  150. package/dist/components/multi-select/style.js +1 -1
  151. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  152. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +3 -3
  153. package/dist/components/multi-tree-select/style.js +1 -1
  154. package/dist/components/node-render/node-render.js.map +1 -1
  155. package/dist/components/notification/index.js.map +1 -1
  156. package/dist/components/notification/notification.js.map +1 -1
  157. package/dist/components/notification/notification.vue.d.ts +1 -1
  158. package/dist/components/number/number.js.map +1 -1
  159. package/dist/components/number-input/number-input.js.map +1 -1
  160. package/dist/components/number-input/number-input.vue.d.ts +2 -2
  161. package/dist/components/number-range-input/number-range-input.js.map +1 -1
  162. package/dist/components/paginator/paginator.js.map +1 -1
  163. package/dist/components/palette/color-transform.js.map +1 -1
  164. package/dist/components/palette/palette-alpha.js.map +1 -1
  165. package/dist/components/palette/palette-color-switch.js.map +1 -1
  166. package/dist/components/palette/palette-hue.js.map +1 -1
  167. package/dist/components/palette/palette-sv.js.map +1 -1
  168. package/dist/components/palette/palette.js.map +1 -1
  169. package/dist/components/palette/use-hsv.js.map +1 -1
  170. package/dist/components/password-input/password-input.js.map +1 -1
  171. package/dist/components/pop-confirm/pop-confirm.js.map +1 -1
  172. package/dist/components/progress/progress.js.map +1 -1
  173. package/dist/components/progress-nodes/progress-nodes.js.map +1 -1
  174. package/dist/components/radio/radio.js.map +1 -1
  175. package/dist/components/radio/style.js +1 -1
  176. package/dist/components/radio-group/radio-group.js.map +1 -1
  177. package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -1
  178. package/dist/components/rich-text-editor/toolbar.js.map +1 -1
  179. package/dist/components/scroll/scroll-bar.js +2 -0
  180. package/dist/components/scroll/scroll-bar.js.map +1 -1
  181. package/dist/components/scroll/scroll.js.map +1 -1
  182. package/dist/components/select/select.js.map +1 -1
  183. package/dist/components/select/use-keyboard.js.map +1 -1
  184. package/dist/components/select/use-options.js.map +1 -1
  185. package/dist/components/slider/slider-thumb.js.map +1 -1
  186. package/dist/components/slider/slider.js +1 -1
  187. package/dist/components/slider/slider.js.map +1 -1
  188. package/dist/components/slider/use-slider.js.map +1 -1
  189. package/dist/components/steps/steps.js.map +1 -1
  190. package/dist/components/switch/switch.js.map +1 -1
  191. package/dist/components/table/di.js.map +1 -1
  192. package/dist/components/table/node/col.js.map +1 -1
  193. package/dist/components/table/node/row.js.map +1 -1
  194. package/dist/components/table/table-body.js.map +1 -1
  195. package/dist/components/table/table-cell.js.map +1 -1
  196. package/dist/components/table/table-foot.js.map +1 -1
  197. package/dist/components/table/table-head.js.map +1 -1
  198. package/dist/components/table/table-row.js.map +1 -1
  199. package/dist/components/table/table.js.map +1 -1
  200. package/dist/components/table/use-check.js.map +1 -1
  201. package/dist/components/table/use-col-resize.js.map +1 -1
  202. package/dist/components/table/use-columns.js.map +1 -1
  203. package/dist/components/table/use-fixed-columns.js.map +1 -1
  204. package/dist/components/table/use-rows.js.map +1 -1
  205. package/dist/components/table/use-table.js.map +1 -1
  206. package/dist/components/table-editor/table-editor.js.map +1 -1
  207. package/dist/components/tabs/style.js +1 -1
  208. package/dist/components/tabs/tabs-horizontal.js.map +1 -1
  209. package/dist/components/tabs/tabs-horizontal.vue.d.ts +3 -3
  210. package/dist/components/tabs/tabs-vertical.js.map +1 -1
  211. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  212. package/dist/components/tabs/tabs.js.map +1 -1
  213. package/dist/components/tabs/tabs.vue.d.ts +3 -3
  214. package/dist/components/tabs/use-tabs-bar.js.map +1 -1
  215. package/dist/components/tag/tag.js.map +1 -1
  216. package/dist/components/text/text.js.map +1 -1
  217. package/dist/components/textarea/style.js +1 -1
  218. package/dist/components/textarea/textarea.js.map +1 -1
  219. package/dist/components/textarea/textarea.vue.d.ts +3 -3
  220. package/dist/components/textarea/utils.js.map +1 -1
  221. package/dist/components/theme/schema.js.map +1 -1
  222. package/dist/components/theme/theme.js.map +1 -1
  223. package/dist/components/tip/tip.js.map +1 -1
  224. package/dist/components/tip/tip.vue.d.ts +1 -1
  225. package/dist/components/tip/use-nest.js.map +1 -1
  226. package/dist/components/tree/tree-node.js.map +1 -1
  227. package/dist/components/tree/tree-node2.js.map +1 -1
  228. package/dist/components/tree/tree.js.map +1 -1
  229. package/dist/components/tree/use-check.js.map +1 -1
  230. package/dist/components/tree/use-filter.js.map +1 -1
  231. package/dist/components/tree/use-select.js.map +1 -1
  232. package/dist/components/tree/use-tree-nodes.js.map +1 -1
  233. package/dist/components/tree-select/tree-select.js.map +1 -1
  234. package/dist/components/tree-select/tree-select.vue.d.ts +3 -3
  235. package/dist/components/watermark/watermark.js.map +1 -1
  236. package/dist/index.d.ts +4 -3
  237. package/dist/index.js +2 -1
  238. package/dist/install.js.map +1 -1
  239. package/dist/types/batch-edit.d.ts +2 -2
  240. package/dist/types/condition-editor.d.ts +21 -9
  241. package/dist/types/index.d.ts +2 -2
  242. package/package.json +13 -16
  243. package/src/components/action/style.scss +0 -6
  244. package/src/components/auto-complete/style.scss +0 -1
  245. package/src/components/auto-complete/style.ts +1 -1
  246. package/src/components/badge/badge.vue +1 -1
  247. package/src/components/batch-edit/style.ts +1 -1
  248. package/src/components/button/style.scss +1 -1
  249. package/src/components/cascade/use-select.ts +8 -6
  250. package/src/components/checkbox/style.ts +1 -1
  251. package/src/components/collapse/__test__/collapse.test.ts +3 -3
  252. package/src/components/condition-editor/components/condition-group.vue +118 -87
  253. package/src/components/condition-editor/components/condition-row.vue +137 -89
  254. package/src/components/condition-editor/condition-editor.vue +19 -48
  255. package/src/components/condition-editor/core/evaluator.ts +143 -57
  256. package/src/components/condition-editor/index.ts +6 -0
  257. package/src/components/condition-editor/style.scss +189 -102
  258. package/src/components/context-menu/style.ts +1 -1
  259. package/src/components/dialog/style.ts +2 -2
  260. package/src/components/drawer/style.ts +1 -1
  261. package/src/components/dropdown/style.ts +1 -1
  262. package/src/components/expression-editor/style.scss +1 -1
  263. package/src/components/form-item/form-item.vue +4 -9
  264. package/src/components/input/style.ts +1 -1
  265. package/src/components/loading/style.ts +1 -1
  266. package/src/components/menu/style.scss +1 -1
  267. package/src/components/message/style.scss +1 -1
  268. package/src/components/multi-select/style.ts +1 -1
  269. package/src/components/multi-tree-select/style.ts +1 -1
  270. package/src/components/number-input/__test__/number-input.test.ts +1 -1
  271. package/src/components/radio/style.ts +1 -1
  272. package/src/components/slider/slider.vue +1 -1
  273. package/src/components/table/di.ts +1 -1
  274. package/src/components/tabs/style.ts +1 -1
  275. package/src/components/textarea/style.ts +1 -1
  276. package/src/types/batch-edit.ts +2 -2
  277. package/src/types/condition-editor.ts +22 -8
@@ -1 +1 @@
1
- {"version":3,"file":"variable-picker.js","names":[],"sources":["../../../../src/components/expression-editor/components/variable-picker.vue"],"sourcesContent":["<template>\n <u-tip\n hide-arrow\n alignment=\"start\"\n direction=\"bottom\"\n trigger=\"click\"\n :trigger-dom=\"triggerDom\"\n :visible=\"visible\"\n @update:visible=\"handleVisibleChange($event)\"\n >\n <template #content>\n <div :class=\"cls.e('picker')\" @mousedown.prevent>\n <!-- 面包屑:仅逐级模式且非根级显示 -->\n <div v-if=\"!isFlat && navPath.length > 0\" :class=\"cls.e('breadcrumbs')\">\n <span :class=\"cls.e('breadcrumb-item')\">\n <span :class=\"cls.e('breadcrumb-link')\" @click=\"navigateTo(0)\">全部变量</span>\n <u-icon :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n <span\n v-for=\"(crumb, i) in navPath\"\n :key=\"`${i}-${crumb.value}`\"\n :class=\"cls.e('breadcrumb-item')\"\n >\n <span\n :class=\"[\n cls.e('breadcrumb-link'),\n { [cls.em('breadcrumb-link', 'active')]: i === navPath.length - 1 }\n ]\"\n @click=\"navigateTo(i + 1)\"\n >{{ crumb.label }}</span\n >\n <u-icon v-if=\"i < navPath.length - 1\" :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n </div>\n\n <div :class=\"cls.e('picker-body')\">\n <div :class=\"cls.e('panel')\" v-if=\"currentList.length > 0\">\n <u-scroll tag=\"ul\" :class=\"cls.e('panel-list')\" :content-class=\"cls.e('panel-content')\">\n <li\n v-for=\"(item, idx) in currentList\"\n :key=\"`${idx}-${item.value}`\"\n :data-idx=\"idx\"\n :class=\"getItemCls(item, idx)\"\n @click=\"handleItemClick(item, idx)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <span :class=\"cls.e('item-label')\">{{ item.label }}</span>\n <u-icon v-if=\"!isFlat && hasChildren(item)\" :class=\"cls.e('item-expand')\">\n <ArrowRight />\n </u-icon>\n </li>\n </u-scroll>\n </div>\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty description=\"暂无可用变量\" />\n </div>\n\n <PathPreview v-if=\"isFlat && activePath.length > 0\" :path=\"activePath\" />\n </div>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport type { VariableItem } from '../../../types'\nimport { UEmpty } from '../../empty'\nimport { UIcon } from '../../icon'\nimport { UScroll } from '../../scroll'\nimport { UTip } from '../../tip'\nimport PathPreview from './path-preview.vue'\n\ndefineOptions({\n name: 'VariablePicker'\n})\n\ninterface FlatItem extends VariableItem {\n /** 仅扁平模式使用:从根到当前节点的路径 */\n __path: VariableItem[]\n}\n\nconst props = defineProps<{\n visible: boolean\n triggerDom?: HTMLElement\n /** 顶层变量树 */\n variables?: VariableItem[]\n /** 来自编辑器 mention 状态的过滤字符串。空 = 逐级模式;非空 = 扁平模式。 */\n filter: string\n /** 是否允许选中分支节点 */\n selectableLevels: 'leaf' | 'any'\n}>()\n\nconst emit = defineEmits<{\n (e: 'select', item: VariableItem): void\n (e: 'update:visible', visible: boolean): void\n /** 用户主动取消(按 Esc)。编辑器据此把 mention 状态置为 dismissed。 */\n (e: 'dismiss'): void\n}>()\n\nconst cls = bem('expression-editor')\n\nconst navPath = ref<VariableItem[]>([])\nconst activeIndex = ref(0)\n\nconst isFlat = computed(() => props.filter !== '')\n\nfunction hasChildren(item: VariableItem): boolean {\n return !!item.children && item.children.length > 0\n}\n\nfunction isLeaf(item: VariableItem): boolean {\n return !hasChildren(item)\n}\n\n/** 当前层级展示的列表(来源依模式而定) */\nconst currentList = computed<VariableItem[]>(() => {\n const variables = props.variables ?? []\n if (isFlat.value) return flatList.value\n let level: VariableItem[] = variables\n for (const node of navPath.value) {\n const found = level.find((it) => it.value === node.value)\n if (!found || !found.children) break\n level = found.children\n }\n return level\n})\n\n/** 扁平模式下:根据 selectableLevels 拍平 + 过滤 */\nconst flatList = computed<FlatItem[]>(() => {\n const variables = props.variables ?? []\n const out: FlatItem[] = []\n const includeBranches = props.selectableLevels === 'any'\n function walk(items: VariableItem[], path: VariableItem[]) {\n for (const it of items) {\n const nextPath = [...path, it]\n if (isLeaf(it)) {\n out.push({ ...it, __path: nextPath })\n } else {\n if (includeBranches) out.push({ ...it, __path: nextPath })\n walk(it.children!, nextPath)\n }\n }\n }\n walk(variables, [])\n const q = props.filter.toLowerCase()\n return out.filter((it) => it.label.toLowerCase().includes(q))\n})\n\n/** 当前 active 项;扁平模式下用于驱动路径预览 */\nconst activePath = computed<VariableItem[]>(() => {\n if (!isFlat.value) return []\n const item = flatList.value[activeIndex.value] as FlatItem | undefined\n return item ? item.__path : []\n})\n\nfunction getItemCls(_item: VariableItem, idx: number) {\n return [cls.e('item'), bem.is('active', idx === activeIndex.value)]\n}\n\nfunction navigateTo(i: number) {\n // 0 = 全部变量;i 之后表示进入到 navPath[i-1]\n navPath.value = navPath.value.slice(0, i)\n activeIndex.value = 0\n}\n\nfunction handleItemClick(item: VariableItem, idx: number) {\n activeIndex.value = idx\n selectActive()\n}\n\nfunction selectActive() {\n const item = currentList.value[activeIndex.value]\n if (!item) return\n\n if (isFlat.value) {\n // 扁平模式始终是「选中并替换」\n emit('select', flattenSourceFor(item))\n return\n }\n\n // 逐级模式\n if (hasChildren(item)) {\n if (props.selectableLevels === 'any') {\n // Enter 在分支项上:选中分支本身\n emit('select', item)\n return\n }\n // 默认 'leaf':进入下一级\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return\n }\n emit('select', item)\n}\n\nfunction flattenSourceFor(it: VariableItem): VariableItem {\n const flat = it as FlatItem\n // 还原为「不带 __path 的纯 VariableItem」,避免泄漏内部字段\n const out: VariableItem = { value: flat.value, label: flat.label }\n if (flat.type) out.type = flat.type\n if (flat.children) out.children = flat.children\n return out\n}\n\nfunction moveActive(delta: -1 | 1) {\n const max = currentList.value.length - 1\n if (max < 0) return\n let next = activeIndex.value + delta\n if (next < 0) next = 0\n if (next > max) next = max\n activeIndex.value = next\n scrollActiveIntoView()\n}\n\nfunction scrollActiveIntoView() {\n void nextTick(() => {\n const list = document.querySelector(`.${cls.e('panel-list')}`)\n if (!list) return\n const item = list.querySelector(`[data-idx=\"${activeIndex.value}\"]`)\n if (item && 'scrollIntoView' in item) {\n ;(item as HTMLElement).scrollIntoView({ block: 'nearest' })\n }\n })\n}\n\nfunction handleVisibleChange(v: boolean) {\n emit('update:visible', v)\n if (!v) reset()\n}\n\nfunction reset() {\n navPath.value = []\n activeIndex.value = 0\n}\n\nwatch(\n () => props.visible,\n (v) => {\n if (!v) reset()\n else activeIndex.value = 0\n }\n)\n\nwatch(\n () => props.filter,\n () => {\n // 过滤词变化:重置 active;逐级 → 扁平时清空 navPath\n activeIndex.value = 0\n if (isFlat.value) navPath.value = []\n }\n)\n\n/**\n * 由父级 forward 进来的键盘事件。\n * 返回 true 表示已处理(外部应 preventDefault)。\n */\nfunction handleKeydown(e: KeyboardEvent): boolean {\n if (!props.visible) return false\n\n switch (e.key) {\n case 'Escape':\n emit('dismiss')\n emit('update:visible', false)\n reset()\n return true\n case 'ArrowDown':\n moveActive(1)\n return true\n case 'ArrowUp':\n moveActive(-1)\n return true\n case 'ArrowLeft':\n // 仅逐级模式:返回上一级;扁平模式下不拦截,让光标正常移动并触发 mention dismiss\n if (!isFlat.value && navPath.value.length > 0) {\n navPath.value = navPath.value.slice(0, -1)\n activeIndex.value = 0\n return true\n }\n return false\n case 'ArrowRight': {\n if (isFlat.value) return false\n const item = currentList.value[activeIndex.value]\n if (item && hasChildren(item)) {\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return true\n }\n return false\n }\n case 'Enter':\n selectActive()\n return true\n default:\n return false\n }\n}\n\ndefineExpose({ handleKeydown })\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"variable-picker.js","names":[],"sources":["../../../../src/components/expression-editor/components/variable-picker.vue"],"sourcesContent":["<template>\n <u-tip\n hide-arrow\n alignment=\"start\"\n direction=\"bottom\"\n trigger=\"click\"\n :trigger-dom=\"triggerDom\"\n :visible=\"visible\"\n @update:visible=\"handleVisibleChange($event)\"\n >\n <template #content>\n <div :class=\"cls.e('picker')\" @mousedown.prevent>\n <!-- 面包屑:仅逐级模式且非根级显示 -->\n <div v-if=\"!isFlat && navPath.length > 0\" :class=\"cls.e('breadcrumbs')\">\n <span :class=\"cls.e('breadcrumb-item')\">\n <span :class=\"cls.e('breadcrumb-link')\" @click=\"navigateTo(0)\">全部变量</span>\n <u-icon :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n <span\n v-for=\"(crumb, i) in navPath\"\n :key=\"`${i}-${crumb.value}`\"\n :class=\"cls.e('breadcrumb-item')\"\n >\n <span\n :class=\"[\n cls.e('breadcrumb-link'),\n { [cls.em('breadcrumb-link', 'active')]: i === navPath.length - 1 }\n ]\"\n @click=\"navigateTo(i + 1)\"\n >{{ crumb.label }}</span\n >\n <u-icon v-if=\"i < navPath.length - 1\" :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n </div>\n\n <div :class=\"cls.e('picker-body')\">\n <div :class=\"cls.e('panel')\" v-if=\"currentList.length > 0\">\n <u-scroll tag=\"ul\" :class=\"cls.e('panel-list')\" :content-class=\"cls.e('panel-content')\">\n <li\n v-for=\"(item, idx) in currentList\"\n :key=\"`${idx}-${item.value}`\"\n :data-idx=\"idx\"\n :class=\"getItemCls(item, idx)\"\n @click=\"handleItemClick(item, idx)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <span :class=\"cls.e('item-label')\">{{ item.label }}</span>\n <u-icon v-if=\"!isFlat && hasChildren(item)\" :class=\"cls.e('item-expand')\">\n <ArrowRight />\n </u-icon>\n </li>\n </u-scroll>\n </div>\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty description=\"暂无可用变量\" />\n </div>\n\n <PathPreview v-if=\"isFlat && activePath.length > 0\" :path=\"activePath\" />\n </div>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport type { VariableItem } from '../../../types'\nimport { UEmpty } from '../../empty'\nimport { UIcon } from '../../icon'\nimport { UScroll } from '../../scroll'\nimport { UTip } from '../../tip'\nimport PathPreview from './path-preview.vue'\n\ndefineOptions({\n name: 'VariablePicker'\n})\n\ninterface FlatItem extends VariableItem {\n /** 仅扁平模式使用:从根到当前节点的路径 */\n __path: VariableItem[]\n}\n\nconst props = defineProps<{\n visible: boolean\n triggerDom?: HTMLElement\n /** 顶层变量树 */\n variables?: VariableItem[]\n /** 来自编辑器 mention 状态的过滤字符串。空 = 逐级模式;非空 = 扁平模式。 */\n filter: string\n /** 是否允许选中分支节点 */\n selectableLevels: 'leaf' | 'any'\n}>()\n\nconst emit = defineEmits<{\n (e: 'select', item: VariableItem): void\n (e: 'update:visible', visible: boolean): void\n /** 用户主动取消(按 Esc)。编辑器据此把 mention 状态置为 dismissed。 */\n (e: 'dismiss'): void\n}>()\n\nconst cls = bem('expression-editor')\n\nconst navPath = ref<VariableItem[]>([])\nconst activeIndex = ref(0)\n\nconst isFlat = computed(() => props.filter !== '')\n\nfunction hasChildren(item: VariableItem): boolean {\n return !!item.children && item.children.length > 0\n}\n\nfunction isLeaf(item: VariableItem): boolean {\n return !hasChildren(item)\n}\n\n/** 当前层级展示的列表(来源依模式而定) */\nconst currentList = computed<VariableItem[]>(() => {\n const variables = props.variables ?? []\n if (isFlat.value) return flatList.value\n let level: VariableItem[] = variables\n for (const node of navPath.value) {\n const found = level.find((it) => it.value === node.value)\n if (!found || !found.children) break\n level = found.children\n }\n return level\n})\n\n/** 扁平模式下:根据 selectableLevels 拍平 + 过滤 */\nconst flatList = computed<FlatItem[]>(() => {\n const variables = props.variables ?? []\n const out: FlatItem[] = []\n const includeBranches = props.selectableLevels === 'any'\n function walk(items: VariableItem[], path: VariableItem[]) {\n for (const it of items) {\n const nextPath = [...path, it]\n if (isLeaf(it)) {\n out.push({ ...it, __path: nextPath })\n } else {\n if (includeBranches) out.push({ ...it, __path: nextPath })\n walk(it.children!, nextPath)\n }\n }\n }\n walk(variables, [])\n const q = props.filter.toLowerCase()\n return out.filter((it) => it.label.toLowerCase().includes(q))\n})\n\n/** 当前 active 项;扁平模式下用于驱动路径预览 */\nconst activePath = computed<VariableItem[]>(() => {\n if (!isFlat.value) return []\n const item = flatList.value[activeIndex.value] as FlatItem | undefined\n return item ? item.__path : []\n})\n\nfunction getItemCls(_item: VariableItem, idx: number) {\n return [cls.e('item'), bem.is('active', idx === activeIndex.value)]\n}\n\nfunction navigateTo(i: number) {\n // 0 = 全部变量;i 之后表示进入到 navPath[i-1]\n navPath.value = navPath.value.slice(0, i)\n activeIndex.value = 0\n}\n\nfunction handleItemClick(item: VariableItem, idx: number) {\n activeIndex.value = idx\n selectActive()\n}\n\nfunction selectActive() {\n const item = currentList.value[activeIndex.value]\n if (!item) return\n\n if (isFlat.value) {\n // 扁平模式始终是「选中并替换」\n emit('select', flattenSourceFor(item))\n return\n }\n\n // 逐级模式\n if (hasChildren(item)) {\n if (props.selectableLevels === 'any') {\n // Enter 在分支项上:选中分支本身\n emit('select', item)\n return\n }\n // 默认 'leaf':进入下一级\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return\n }\n emit('select', item)\n}\n\nfunction flattenSourceFor(it: VariableItem): VariableItem {\n const flat = it as FlatItem\n // 还原为「不带 __path 的纯 VariableItem」,避免泄漏内部字段\n const out: VariableItem = { value: flat.value, label: flat.label }\n if (flat.type) out.type = flat.type\n if (flat.children) out.children = flat.children\n return out\n}\n\nfunction moveActive(delta: -1 | 1) {\n const max = currentList.value.length - 1\n if (max < 0) return\n let next = activeIndex.value + delta\n if (next < 0) next = 0\n if (next > max) next = max\n activeIndex.value = next\n scrollActiveIntoView()\n}\n\nfunction scrollActiveIntoView() {\n void nextTick(() => {\n const list = document.querySelector(`.${cls.e('panel-list')}`)\n if (!list) return\n const item = list.querySelector(`[data-idx=\"${activeIndex.value}\"]`)\n if (item && 'scrollIntoView' in item) {\n ;(item as HTMLElement).scrollIntoView({ block: 'nearest' })\n }\n })\n}\n\nfunction handleVisibleChange(v: boolean) {\n emit('update:visible', v)\n if (!v) reset()\n}\n\nfunction reset() {\n navPath.value = []\n activeIndex.value = 0\n}\n\nwatch(\n () => props.visible,\n (v) => {\n if (!v) reset()\n else activeIndex.value = 0\n }\n)\n\nwatch(\n () => props.filter,\n () => {\n // 过滤词变化:重置 active;逐级 → 扁平时清空 navPath\n activeIndex.value = 0\n if (isFlat.value) navPath.value = []\n }\n)\n\n/**\n * 由父级 forward 进来的键盘事件。\n * 返回 true 表示已处理(外部应 preventDefault)。\n */\nfunction handleKeydown(e: KeyboardEvent): boolean {\n if (!props.visible) return false\n\n switch (e.key) {\n case 'Escape':\n emit('dismiss')\n emit('update:visible', false)\n reset()\n return true\n case 'ArrowDown':\n moveActive(1)\n return true\n case 'ArrowUp':\n moveActive(-1)\n return true\n case 'ArrowLeft':\n // 仅逐级模式:返回上一级;扁平模式下不拦截,让光标正常移动并触发 mention dismiss\n if (!isFlat.value && navPath.value.length > 0) {\n navPath.value = navPath.value.slice(0, -1)\n activeIndex.value = 0\n return true\n }\n return false\n case 'ArrowRight': {\n if (isFlat.value) return false\n const item = currentList.value[activeIndex.value]\n if (item && hasChildren(item)) {\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return true\n }\n return false\n }\n case 'Enter':\n selectActive()\n return true\n default:\n return false\n }\n}\n\ndefineExpose({ handleKeydown })\n</script>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"chip.js","names":[],"sources":["../../../../src/components/expression-editor/core/chip.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\n\nimport type { Segment } from './model'\n\nexport interface ChipDeps {\n cls: BEM<'expression-editor'>\n}\n\nexport interface ChipFactoryOptions extends ChipDeps {\n /**\n * 当用户点击 chip 主体(不是 ×)时回调,由调用方决定如何打开重选面板。\n * 收到的 chipEl 当前在 DOM 中的位置即为重选锚点。\n */\n onReselect: (chipEl: HTMLElement) => void\n /** 当用户点击 × 时回调,调用方应将该 chip 从模型 / DOM 中移除。 */\n onRemove: (chipEl: HTMLElement) => void\n}\n\nconst CHIP_DATA_SEG = 'var'\nconst CHIP_VALUE_ATTR = 'data-value'\nconst CHIP_TYPE_ATTR = 'data-var-type'\nconst CHIP_CLOSE_ATTR = 'data-chip-close'\n\n/** 判断一个 DOM 节点是否为 chip 元素(var 段渲染体) */\nexport function isChipElement(node: Node | null): node is HTMLElement {\n return (\n !!node &&\n node.nodeType === 1 &&\n (node as HTMLElement).getAttribute('data-seg') === CHIP_DATA_SEG\n )\n}\n\n/** 判断点击目标是否落在 × 删除区。 */\nfunction isCloseTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n return !!target.closest(`[${CHIP_CLOSE_ATTR}]`)\n}\n\n/** 从给定 segment 创建一个 chip 元素(var 段渲染体)。 */\nexport function createChip(\n segment: Extract<Segment, { kind: 'var' }>,\n opts: ChipFactoryOptions\n): HTMLElement {\n const { cls } = opts\n\n const chip = document.createElement('span')\n chip.setAttribute('data-seg', CHIP_DATA_SEG)\n chip.setAttribute(CHIP_VALUE_ATTR, segment.value)\n if (segment.type) chip.setAttribute(CHIP_TYPE_ATTR, segment.type)\n chip.setAttribute('contenteditable', 'false')\n chip.className = cls.e('chip')\n chip.title = segment.label\n\n const label = document.createElement('span')\n label.className = cls.e('chip-label')\n label.textContent = segment.type ? `${segment.label} (${segment.type})` : segment.label\n\n const close = document.createElement('span')\n close.className = cls.e('chip-close')\n close.setAttribute(CHIP_CLOSE_ATTR, 'true')\n close.setAttribute('aria-label', '删除')\n close.setAttribute('role', 'button')\n close.textContent = '×'\n\n chip.appendChild(label)\n chip.appendChild(close)\n\n chip.addEventListener('mousedown', (e) => {\n e.preventDefault()\n if (isCloseTarget(e.target)) {\n opts.onRemove(chip)\n } else {\n opts.onReselect(chip)\n }\n })\n\n return chip\n}\n\n/** 读取 chip 元素绑定的变量 value。 */\nexport function readChipValue(el: HTMLElement): string {\n return el.getAttribute(CHIP_VALUE_ATTR) ?? ''\n}\n\n/** 读取 chip 元素绑定的可选 type。 */\nexport function readChipType(el: HTMLElement): string | undefined {\n return el.getAttribute(CHIP_TYPE_ATTR) ?? undefined\n}\n\n/** 在 chip 上设置 / 取消选中(焦点)样式。 */\nexport function setChipFocused(el: HTMLElement, focused: boolean): void {\n el.classList.toggle('is-focused', focused)\n}\n\n/** 测试支持:暴露用于 DOM 选择器的属性常量。 */\nexport const ChipAttrs = {\n seg: 'data-seg',\n value: CHIP_VALUE_ATTR,\n type: CHIP_TYPE_ATTR,\n close: CHIP_CLOSE_ATTR\n} as const\n"],"mappings":";AAkBA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;;AAGxB,SAAgB,cAAc,MAAwC;AACpE,QACE,CAAC,CAAC,QACF,KAAK,aAAa,KACjB,KAAqB,aAAa,WAAW,KAAK;;;AAKvD,SAAS,cAAc,QAAqC;AAC1D,KAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,QAAO,CAAC,CAAC,OAAO,QAAQ,IAAI,gBAAgB,GAAG;;;AAIjD,SAAgB,WACd,SACA,MACa;CACb,MAAM,EAAE,QAAQ;CAEhB,MAAM,OAAO,SAAS,cAAc,OAAO;AAC3C,MAAK,aAAa,YAAY,cAAc;AAC5C,MAAK,aAAa,iBAAiB,QAAQ,MAAM;AACjD,KAAI,QAAQ,KAAM,MAAK,aAAa,gBAAgB,QAAQ,KAAK;AACjE,MAAK,aAAa,mBAAmB,QAAQ;AAC7C,MAAK,YAAY,IAAI,EAAE,OAAO;AAC9B,MAAK,QAAQ,QAAQ;CAErB,MAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,OAAM,YAAY,IAAI,EAAE,aAAa;AACrC,OAAM,cAAc,QAAQ,OAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,KAAK,KAAK,QAAQ;CAElF,MAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,OAAM,YAAY,IAAI,EAAE,aAAa;AACrC,OAAM,aAAa,iBAAiB,OAAO;AAC3C,OAAM,aAAa,cAAc,KAAK;AACtC,OAAM,aAAa,QAAQ,SAAS;AACpC,OAAM,cAAc;AAEpB,MAAK,YAAY,MAAM;AACvB,MAAK,YAAY,MAAM;AAEvB,MAAK,iBAAiB,cAAc,MAAM;AACxC,IAAE,gBAAgB;AAClB,MAAI,cAAc,EAAE,OAAO,CACzB,MAAK,SAAS,KAAK;MAEnB,MAAK,WAAW,KAAK;GAEvB;AAEF,QAAO;;;AAIT,SAAgB,cAAc,IAAyB;AACrD,QAAO,GAAG,aAAa,gBAAgB,IAAI;;;AAI7C,SAAgB,aAAa,IAAqC;AAChE,QAAO,GAAG,aAAa,eAAe,IAAI,KAAA"}
1
+ {"version":3,"file":"chip.js","names":[],"sources":["../../../../src/components/expression-editor/core/chip.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\n\nimport type { Segment } from './model'\n\nexport interface ChipDeps {\n cls: BEM<'expression-editor'>\n}\n\nexport interface ChipFactoryOptions extends ChipDeps {\n /**\n * 当用户点击 chip 主体(不是 ×)时回调,由调用方决定如何打开重选面板。\n * 收到的 chipEl 当前在 DOM 中的位置即为重选锚点。\n */\n onReselect: (chipEl: HTMLElement) => void\n /** 当用户点击 × 时回调,调用方应将该 chip 从模型 / DOM 中移除。 */\n onRemove: (chipEl: HTMLElement) => void\n}\n\nconst CHIP_DATA_SEG = 'var'\nconst CHIP_VALUE_ATTR = 'data-value'\nconst CHIP_TYPE_ATTR = 'data-var-type'\nconst CHIP_CLOSE_ATTR = 'data-chip-close'\n\n/** 判断一个 DOM 节点是否为 chip 元素(var 段渲染体) */\nexport function isChipElement(node: Node | null): node is HTMLElement {\n return (\n !!node &&\n node.nodeType === 1 &&\n (node as HTMLElement).getAttribute('data-seg') === CHIP_DATA_SEG\n )\n}\n\n/** 判断点击目标是否落在 × 删除区。 */\nfunction isCloseTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n return !!target.closest(`[${CHIP_CLOSE_ATTR}]`)\n}\n\n/** 从给定 segment 创建一个 chip 元素(var 段渲染体)。 */\nexport function createChip(\n segment: Extract<Segment, { kind: 'var' }>,\n opts: ChipFactoryOptions\n): HTMLElement {\n const { cls } = opts\n\n const chip = document.createElement('span')\n chip.setAttribute('data-seg', CHIP_DATA_SEG)\n chip.setAttribute(CHIP_VALUE_ATTR, segment.value)\n if (segment.type) chip.setAttribute(CHIP_TYPE_ATTR, segment.type)\n chip.setAttribute('contenteditable', 'false')\n chip.className = cls.e('chip')\n chip.title = segment.label\n\n const label = document.createElement('span')\n label.className = cls.e('chip-label')\n label.textContent = segment.type ? `${segment.label} (${segment.type})` : segment.label\n\n const close = document.createElement('span')\n close.className = cls.e('chip-close')\n close.setAttribute(CHIP_CLOSE_ATTR, 'true')\n close.setAttribute('aria-label', '删除')\n close.setAttribute('role', 'button')\n close.textContent = '×'\n\n chip.appendChild(label)\n chip.appendChild(close)\n\n chip.addEventListener('mousedown', (e) => {\n e.preventDefault()\n if (isCloseTarget(e.target)) {\n opts.onRemove(chip)\n } else {\n opts.onReselect(chip)\n }\n })\n\n return chip\n}\n\n/** 读取 chip 元素绑定的变量 value。 */\nexport function readChipValue(el: HTMLElement): string {\n return el.getAttribute(CHIP_VALUE_ATTR) ?? ''\n}\n\n/** 读取 chip 元素绑定的可选 type。 */\nexport function readChipType(el: HTMLElement): string | undefined {\n return el.getAttribute(CHIP_TYPE_ATTR) ?? undefined\n}\n\n/** 在 chip 上设置 / 取消选中(焦点)样式。 */\nexport function setChipFocused(el: HTMLElement, focused: boolean): void {\n el.classList.toggle('is-focused', focused)\n}\n\n/** 测试支持:暴露用于 DOM 选择器的属性常量。 */\nexport const ChipAttrs = {\n seg: 'data-seg',\n value: CHIP_VALUE_ATTR,\n type: CHIP_TYPE_ATTR,\n close: CHIP_CLOSE_ATTR\n} as const\n"],"mappings":";AAkBA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;;AAGxB,SAAgB,cAAc,MAAwC;CACpE,OACE,CAAC,CAAC,QACF,KAAK,aAAa,KACjB,KAAqB,aAAa,WAAW,KAAK;;;AAKvD,SAAS,cAAc,QAAqC;CAC1D,IAAI,EAAE,kBAAkB,UAAU,OAAO;CACzC,OAAO,CAAC,CAAC,OAAO,QAAQ,IAAI,gBAAgB,GAAG;;;AAIjD,SAAgB,WACd,SACA,MACa;CACb,MAAM,EAAE,QAAQ;CAEhB,MAAM,OAAO,SAAS,cAAc,OAAO;CAC3C,KAAK,aAAa,YAAY,cAAc;CAC5C,KAAK,aAAa,iBAAiB,QAAQ,MAAM;CACjD,IAAI,QAAQ,MAAM,KAAK,aAAa,gBAAgB,QAAQ,KAAK;CACjE,KAAK,aAAa,mBAAmB,QAAQ;CAC7C,KAAK,YAAY,IAAI,EAAE,OAAO;CAC9B,KAAK,QAAQ,QAAQ;CAErB,MAAM,QAAQ,SAAS,cAAc,OAAO;CAC5C,MAAM,YAAY,IAAI,EAAE,aAAa;CACrC,MAAM,cAAc,QAAQ,OAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,KAAK,KAAK,QAAQ;CAElF,MAAM,QAAQ,SAAS,cAAc,OAAO;CAC5C,MAAM,YAAY,IAAI,EAAE,aAAa;CACrC,MAAM,aAAa,iBAAiB,OAAO;CAC3C,MAAM,aAAa,cAAc,KAAK;CACtC,MAAM,aAAa,QAAQ,SAAS;CACpC,MAAM,cAAc;CAEpB,KAAK,YAAY,MAAM;CACvB,KAAK,YAAY,MAAM;CAEvB,KAAK,iBAAiB,cAAc,MAAM;EACxC,EAAE,gBAAgB;EAClB,IAAI,cAAc,EAAE,OAAO,EACzB,KAAK,SAAS,KAAK;OAEnB,KAAK,WAAW,KAAK;GAEvB;CAEF,OAAO;;;AAIT,SAAgB,cAAc,IAAyB;CACrD,OAAO,GAAG,aAAa,gBAAgB,IAAI;;;AAI7C,SAAgB,aAAa,IAAqC;CAChE,OAAO,GAAG,aAAa,eAAe,IAAI,KAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","names":[],"sources":["../../../../src/components/expression-editor/core/editor.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\n\nimport type { VariableItem } from '../../../types'\nimport { createChip, isChipElement, readChipType, readChipValue } from './chip'\nimport { normalize, parse, serialize, type Doc, type Segment } from './model'\n\nconst TEXT_SEG_ATTR = 'data-seg'\nconst TEXT_SEG_VALUE = 'text'\n\nexport interface EditorVariableInfo {\n value: string\n label: string\n type?: string\n}\n\nexport interface EditorOptions {\n container: HTMLElement\n cls: BEM<'expression-editor'>\n initialDoc: Doc\n /**\n * 在 DOM → 模型反推、字符串 → 模型反序列化时为 chip 补齐 label / type。\n * 用 getter 形式以便外层 `variables` props 变化时拿到最新映射。\n */\n getVariableMap?: () => Map<string, VariableItem>\n /** 模型每次更新(包括用户键入文本)后回调 */\n onChange?: (doc: Doc) => void\n /** 选中区域 / 光标偏移变化时回调,offset 为序列化字符串中的位置;无选中或不在编辑器内时为 null */\n onSelectionChange?: (info: { offset: number | null }) => void\n /** 用户点击某个 chip 的主体(重选) */\n onChipReselect?: (info: { chipEl: HTMLElement; segIndex: number; chipOffset: number }) => void\n /** 用户点击某个 chip 的 ×(删除) */\n onChipRemove?: (info: { chipEl: HTMLElement; segIndex: number }) => void\n}\n\nexport interface EditorAPI {\n /** 当前模型 */\n getDoc(): Doc\n /** 完全替换模型(会重渲染 DOM) */\n setDoc(doc: Doc): void\n /** 序列化为字符串(modelValue 形态) */\n getValue(): string\n /** 反序列化字符串为模型并替换(可选传入 variableMap 以补齐 label/type) */\n setValue(value: string): void\n /** 当前光标偏移(无 selection 返回 null) */\n getCaretOffset(): number | null\n /** 把光标设置到指定偏移(不会修改模型) */\n setCaretOffset(offset: number): void\n /**\n * 用一个 var chip 替换 [start, end) 区间。\n * 调用方通常在 mention 选中时用:start = `@` 起点、end = 当前光标。\n */\n replaceRangeWithVar(start: number, end: number, variable: EditorVariableInfo): void\n /** 移除指定段下标对应的 var chip(必须是 var 段)。 */\n removeVarAt(segIndex: number): void\n /** 替换指定段下标对应的 var chip 为新变量(必须是 var 段)。 */\n replaceVarAt(segIndex: number, variable: EditorVariableInfo): void\n /** 卸载所有事件监听(不会销毁 container DOM) */\n dispose(): void\n /** 强制根据当前模型重渲染 DOM(光标位置尽量保留) */\n rerender(): void\n /** 容器引用(只读) */\n readonly container: HTMLElement\n}\n\nexport function createEditor(opts: EditorOptions): EditorAPI {\n let doc: Doc = normalize(opts.initialDoc)\n let composing = false\n\n function emitChange(next: Doc) {\n doc = next\n opts.onChange?.(doc)\n }\n\n function makeTextSpan(value: string): HTMLSpanElement {\n const span = document.createElement('span')\n span.setAttribute(TEXT_SEG_ATTR, TEXT_SEG_VALUE)\n span.textContent = value\n return span\n }\n\n function makeChipSpan(seg: Extract<Segment, { kind: 'var' }>): HTMLElement {\n return createChip(seg, {\n cls: opts.cls,\n onReselect: (chipEl) => {\n const { segIndex, chipOffset } = locateChip(chipEl)\n if (segIndex < 0) return\n opts.onChipReselect?.({ chipEl, segIndex, chipOffset })\n },\n onRemove: (chipEl) => {\n const { segIndex } = locateChip(chipEl)\n if (segIndex < 0) return\n opts.onChipRemove?.({ chipEl, segIndex })\n }\n })\n }\n\n function locateChip(chipEl: HTMLElement): { segIndex: number; chipOffset: number } {\n let chipOffset = 0\n let segIdx = -1\n let i = 0\n for (const child of Array.from(opts.container.childNodes)) {\n if (child === chipEl) {\n segIdx = i\n return { segIndex: segIdx, chipOffset }\n }\n chipOffset += childSerialLength(child)\n i++\n }\n return { segIndex: -1, chipOffset: -1 }\n }\n\n function childSerialLength(node: Node): number {\n if (isChipElement(node)) return readChipValue(node).length + 2\n return (node.textContent ?? '').length\n }\n\n /** 渲染 doc 到 container:每个 var 两侧都补一个 text span(即便空),保证光标可停靠。 */\n function render(targetCaret?: number) {\n const container = opts.container\n container.innerHTML = ''\n\n // 在边界与每两个 var 之间确保有 text span 占位\n const expanded: Segment[] = []\n let lastWasVar = false\n let first = true\n for (const seg of doc) {\n if (first) {\n if (seg.kind === 'var') expanded.push({ kind: 'text', value: '' })\n first = false\n } else if (lastWasVar && seg.kind === 'var') {\n expanded.push({ kind: 'text', value: '' })\n }\n expanded.push(seg)\n lastWasVar = seg.kind === 'var'\n }\n if (lastWasVar || expanded.length === 0) expanded.push({ kind: 'text', value: '' })\n\n for (const seg of expanded) {\n if (seg.kind === 'text') container.appendChild(makeTextSpan(seg.value))\n else container.appendChild(makeChipSpan(seg))\n }\n\n if (typeof targetCaret === 'number') setCaret(targetCaret)\n }\n\n /** 从 DOM 反推模型:宽容裸 text node、空 text span。 */\n function readDocFromDOM(): Doc {\n const segs: Segment[] = []\n for (const child of Array.from(opts.container.childNodes)) {\n if (isChipElement(child)) {\n const value = readChipValue(child)\n const domType = readChipType(child)\n const fallbackLabel =\n child.querySelector(`.${opts.cls.e('chip-label')}`)?.textContent ?? value\n // 优先从 variableMap 取最新 label / type,保持与外部数据源一致\n const fromMap = opts.getVariableMap?.().get(value)\n const finalType = fromMap?.type ?? domType\n const seg: Segment = finalType\n ? { kind: 'var', value, label: fromMap?.label ?? fallbackLabel, type: finalType }\n : { kind: 'var', value, label: fromMap?.label ?? fallbackLabel }\n segs.push(seg)\n } else {\n segs.push({ kind: 'text', value: child.textContent ?? '' })\n }\n }\n return normalize(segs)\n }\n\n /**\n * 计算光标在序列化字符串中的全局偏移。\n * 规则:\n * - 在 text span / 裸 text node 中:父级 / 节点之前的 segments 长度累加 + selection 内偏移\n * - 在 chip 边界:根据 selection 在 chip 节点之前 / 之后判断\n */\n function getCaretOffset(): number | null {\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) return null\n\n let offset = 0\n let found = false\n for (const child of Array.from(opts.container.childNodes)) {\n if (found) break\n if (isChipElement(child)) {\n if (range.startContainer === child) {\n // selection.startOffset 表示子节点索引:0 表示 chip 起始,>=1 表示 chip 之后\n if (range.startOffset === 0) return offset\n return offset + childSerialLength(child)\n }\n if (range.startContainer === opts.container && getChildIndex(child) === range.startOffset) {\n // 容器层 selection:偏移即子节点索引\n return offset\n }\n offset += childSerialLength(child)\n } else {\n // text span 或裸 text 节点\n if (\n range.startContainer === child ||\n (child.contains(range.startContainer) && child !== opts.container)\n ) {\n // 计算 selection 在该 child textContent 中的偏移\n const localOffset = computeTextOffsetWithin(\n child,\n range.startContainer,\n range.startOffset\n )\n return offset + localOffset\n }\n if (range.startContainer === opts.container && getChildIndex(child) === range.startOffset) {\n return offset\n }\n offset += childSerialLength(child)\n }\n }\n if (\n range.startContainer === opts.container &&\n range.startOffset === opts.container.childNodes.length\n ) {\n return offset\n }\n return null\n }\n\n function getChildIndex(node: Node): number {\n let i = 0\n let cur: Node | null = node.previousSibling\n while (cur) {\n i++\n cur = cur.previousSibling\n }\n return i\n }\n\n function computeTextOffsetWithin(root: Node, target: Node, targetOffset: number): number {\n if (root === target) return targetOffset\n let total = 0\n function walk(n: Node): boolean {\n if (n === target) {\n total += targetOffset\n return true\n }\n if (n.nodeType === 3) {\n total += (n as Text).data.length\n return false\n }\n for (const c of Array.from(n.childNodes)) {\n if (walk(c)) return true\n }\n return false\n }\n walk(root)\n return total\n }\n\n /** 把光标放到 offset 处,offset 大于文档长度时落到末尾。 */\n function setCaret(offset: number): void {\n const sel = document.getSelection()\n if (!sel) return\n\n let remain = offset\n const children = Array.from(opts.container.childNodes)\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!\n const len = childSerialLength(child)\n if (isChipElement(child)) {\n if (remain <= 0) {\n // 光标定到 chip 之前 → 落到容器层 child 索引\n placeAt(opts.container, i)\n return\n }\n if (remain < len) {\n // 落在 chip 内部 → 视作 chip 之后\n placeAt(opts.container, i + 1)\n return\n }\n remain -= len\n } else {\n if (remain <= len) {\n // 落在该 text span 内\n placeWithinText(child, remain)\n return\n }\n remain -= len\n }\n }\n // 落到末尾\n if (children.length === 0) {\n placeAt(opts.container, 0)\n return\n }\n const last = children[children.length - 1]!\n if (isChipElement(last)) placeAt(opts.container, children.length)\n else placeWithinText(last, (last.textContent ?? '').length)\n }\n\n function placeAt(parent: Node, index: number) {\n const sel = document.getSelection()\n if (!sel) return\n const range = document.createRange()\n range.setStart(parent, index)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n\n function placeWithinText(span: Node, offset: number): void {\n const sel = document.getSelection()\n if (!sel) return\n const target = firstTextDescendant(span)\n const range = document.createRange()\n if (target) {\n const max = target.data.length\n range.setStart(target, Math.min(offset, max))\n } else {\n // 空 span:让光标落到 span 内(child index 0)\n range.setStart(span, 0)\n }\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n\n function firstTextDescendant(root: Node): Text | null {\n if (root.nodeType === 3) return root as Text\n for (const c of Array.from(root.childNodes)) {\n const t = firstTextDescendant(c)\n if (t) return t\n }\n return null\n }\n\n /** input 后:从 DOM 反推 doc,并触发 onChange(如有变化)。 */\n function syncFromDOM(): void {\n if (composing) return\n const next = readDocFromDOM()\n if (!docEqual(next, doc)) emitChange(next)\n }\n\n function docEqual(a: Doc, b: Doc): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const x = a[i]!\n const y = b[i]!\n if (x.kind !== y.kind) return false\n if (x.kind === 'text' && y.kind === 'text' && x.value !== y.value) return false\n if (x.kind === 'var' && y.kind === 'var') {\n if (x.value !== y.value || x.label !== y.label || x.type !== y.type) return false\n }\n }\n return true\n }\n\n function onInput() {\n syncFromDOM()\n }\n function onCompositionStart() {\n composing = true\n }\n function onCompositionEnd() {\n composing = false\n syncFromDOM()\n }\n function onPaste(e: ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData?.getData('text/plain') ?? ''\n if (!text) return\n insertPlainTextAtCaret(text)\n }\n function onSelectionChange() {\n if (composing) return\n if (!opts.onSelectionChange) return\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) {\n opts.onSelectionChange({ offset: null })\n return\n }\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) {\n opts.onSelectionChange({ offset: null })\n return\n }\n opts.onSelectionChange({ offset: getCaretOffset() })\n }\n\n function insertPlainTextAtCaret(text: string) {\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) return\n range.deleteContents()\n const node = document.createTextNode(text)\n range.insertNode(node)\n range.setStartAfter(node)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n syncFromDOM()\n }\n\n // 命令:替换区间为 var chip\n function replaceRangeWithVar(start: number, end: number, variable: EditorVariableInfo) {\n const a = Math.min(start, end)\n const b = Math.max(start, end)\n const left = sliceDoc(doc, 0, a)\n const right = sliceDoc(doc, b, totalLength(doc))\n const inserted: Segment = variable.type\n ? { kind: 'var', value: variable.value, label: variable.label, type: variable.type }\n : { kind: 'var', value: variable.value, label: variable.label }\n const next = normalize([...left, inserted, ...right])\n emitChange(next)\n render(a + variable.value.length + 2) // 光标定到 chip 之后\n }\n\n function removeVarAt(segIndex: number) {\n const target = doc[segIndex]\n if (!target || target.kind !== 'var') return\n const before = doc.slice(0, segIndex)\n const after = doc.slice(segIndex + 1)\n const caretOffset = sliceLength(before)\n const next = normalize([...before, ...after])\n emitChange(next)\n render(caretOffset)\n }\n\n function replaceVarAt(segIndex: number, variable: EditorVariableInfo) {\n const target = doc[segIndex]\n if (!target || target.kind !== 'var') return\n const next = doc.slice()\n const replaced: Segment = variable.type\n ? { kind: 'var', value: variable.value, label: variable.label, type: variable.type }\n : { kind: 'var', value: variable.value, label: variable.label }\n next[segIndex] = replaced\n const caretOffset = sliceLength(doc.slice(0, segIndex)) + variable.value.length + 2\n emitChange(normalize(next))\n render(caretOffset)\n }\n\n // 工具:基于序列化偏移的切片(不会切开 var)\n function sliceDoc(d: Doc, start: number, end: number): Doc {\n const out: Segment[] = []\n let pos = 0\n for (const seg of d) {\n const len = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n const segStart = pos\n const segEnd = pos + len\n if (segEnd <= start || segStart >= end) {\n pos += len\n continue\n }\n if (seg.kind === 'text') {\n const a = Math.max(0, start - segStart)\n const b = Math.min(len, end - segStart)\n const slice = seg.value.slice(a, b)\n if (slice) out.push({ kind: 'text', value: slice })\n } else {\n // var 是原子:只有当 [start, end) 完整覆盖该 var 时保留;否则抛弃\n if (start <= segStart && end >= segEnd) out.push(seg)\n }\n pos += len\n }\n return out\n }\n\n function sliceLength(d: Doc): number {\n let n = 0\n for (const s of d) n += s.kind === 'text' ? s.value.length : s.value.length + 2\n return n\n }\n\n function totalLength(d: Doc): number {\n return sliceLength(d)\n }\n\n // 绑定监听\n const container = opts.container\n container.addEventListener('input', onInput)\n container.addEventListener('compositionstart', onCompositionStart)\n container.addEventListener('compositionend', onCompositionEnd)\n container.addEventListener('paste', onPaste)\n document.addEventListener('selectionchange', onSelectionChange)\n\n // 初次渲染\n render()\n\n return {\n getDoc: () => doc,\n setDoc: (next) => {\n doc = normalize(next)\n render()\n opts.onChange?.(doc)\n },\n getValue: () => serialize(doc),\n setValue: (value) => {\n doc = parse(value, opts.getVariableMap?.())\n render()\n opts.onChange?.(doc)\n },\n getCaretOffset,\n setCaretOffset: setCaret,\n replaceRangeWithVar,\n removeVarAt,\n replaceVarAt,\n rerender: () => render(),\n dispose: () => {\n container.removeEventListener('input', onInput)\n container.removeEventListener('compositionstart', onCompositionStart)\n container.removeEventListener('compositionend', onCompositionEnd)\n container.removeEventListener('paste', onPaste)\n document.removeEventListener('selectionchange', onSelectionChange)\n },\n container\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"editor.js","names":[],"sources":["../../../../src/components/expression-editor/core/editor.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\n\nimport type { VariableItem } from '../../../types'\nimport { createChip, isChipElement, readChipType, readChipValue } from './chip'\nimport { normalize, parse, serialize, type Doc, type Segment } from './model'\n\nconst TEXT_SEG_ATTR = 'data-seg'\nconst TEXT_SEG_VALUE = 'text'\n\nexport interface EditorVariableInfo {\n value: string\n label: string\n type?: string\n}\n\nexport interface EditorOptions {\n container: HTMLElement\n cls: BEM<'expression-editor'>\n initialDoc: Doc\n /**\n * 在 DOM → 模型反推、字符串 → 模型反序列化时为 chip 补齐 label / type。\n * 用 getter 形式以便外层 `variables` props 变化时拿到最新映射。\n */\n getVariableMap?: () => Map<string, VariableItem>\n /** 模型每次更新(包括用户键入文本)后回调 */\n onChange?: (doc: Doc) => void\n /** 选中区域 / 光标偏移变化时回调,offset 为序列化字符串中的位置;无选中或不在编辑器内时为 null */\n onSelectionChange?: (info: { offset: number | null }) => void\n /** 用户点击某个 chip 的主体(重选) */\n onChipReselect?: (info: { chipEl: HTMLElement; segIndex: number; chipOffset: number }) => void\n /** 用户点击某个 chip 的 ×(删除) */\n onChipRemove?: (info: { chipEl: HTMLElement; segIndex: number }) => void\n}\n\nexport interface EditorAPI {\n /** 当前模型 */\n getDoc(): Doc\n /** 完全替换模型(会重渲染 DOM) */\n setDoc(doc: Doc): void\n /** 序列化为字符串(modelValue 形态) */\n getValue(): string\n /** 反序列化字符串为模型并替换(可选传入 variableMap 以补齐 label/type) */\n setValue(value: string): void\n /** 当前光标偏移(无 selection 返回 null) */\n getCaretOffset(): number | null\n /** 把光标设置到指定偏移(不会修改模型) */\n setCaretOffset(offset: number): void\n /**\n * 用一个 var chip 替换 [start, end) 区间。\n * 调用方通常在 mention 选中时用:start = `@` 起点、end = 当前光标。\n */\n replaceRangeWithVar(start: number, end: number, variable: EditorVariableInfo): void\n /** 移除指定段下标对应的 var chip(必须是 var 段)。 */\n removeVarAt(segIndex: number): void\n /** 替换指定段下标对应的 var chip 为新变量(必须是 var 段)。 */\n replaceVarAt(segIndex: number, variable: EditorVariableInfo): void\n /** 卸载所有事件监听(不会销毁 container DOM) */\n dispose(): void\n /** 强制根据当前模型重渲染 DOM(光标位置尽量保留) */\n rerender(): void\n /** 容器引用(只读) */\n readonly container: HTMLElement\n}\n\nexport function createEditor(opts: EditorOptions): EditorAPI {\n let doc: Doc = normalize(opts.initialDoc)\n let composing = false\n\n function emitChange(next: Doc) {\n doc = next\n opts.onChange?.(doc)\n }\n\n function makeTextSpan(value: string): HTMLSpanElement {\n const span = document.createElement('span')\n span.setAttribute(TEXT_SEG_ATTR, TEXT_SEG_VALUE)\n span.textContent = value\n return span\n }\n\n function makeChipSpan(seg: Extract<Segment, { kind: 'var' }>): HTMLElement {\n return createChip(seg, {\n cls: opts.cls,\n onReselect: (chipEl) => {\n const { segIndex, chipOffset } = locateChip(chipEl)\n if (segIndex < 0) return\n opts.onChipReselect?.({ chipEl, segIndex, chipOffset })\n },\n onRemove: (chipEl) => {\n const { segIndex } = locateChip(chipEl)\n if (segIndex < 0) return\n opts.onChipRemove?.({ chipEl, segIndex })\n }\n })\n }\n\n function locateChip(chipEl: HTMLElement): { segIndex: number; chipOffset: number } {\n let chipOffset = 0\n let segIdx = -1\n let i = 0\n for (const child of Array.from(opts.container.childNodes)) {\n if (child === chipEl) {\n segIdx = i\n return { segIndex: segIdx, chipOffset }\n }\n chipOffset += childSerialLength(child)\n i++\n }\n return { segIndex: -1, chipOffset: -1 }\n }\n\n function childSerialLength(node: Node): number {\n if (isChipElement(node)) return readChipValue(node).length + 2\n return (node.textContent ?? '').length\n }\n\n /** 渲染 doc 到 container:每个 var 两侧都补一个 text span(即便空),保证光标可停靠。 */\n function render(targetCaret?: number) {\n const container = opts.container\n container.innerHTML = ''\n\n // 在边界与每两个 var 之间确保有 text span 占位\n const expanded: Segment[] = []\n let lastWasVar = false\n let first = true\n for (const seg of doc) {\n if (first) {\n if (seg.kind === 'var') expanded.push({ kind: 'text', value: '' })\n first = false\n } else if (lastWasVar && seg.kind === 'var') {\n expanded.push({ kind: 'text', value: '' })\n }\n expanded.push(seg)\n lastWasVar = seg.kind === 'var'\n }\n if (lastWasVar || expanded.length === 0) expanded.push({ kind: 'text', value: '' })\n\n for (const seg of expanded) {\n if (seg.kind === 'text') container.appendChild(makeTextSpan(seg.value))\n else container.appendChild(makeChipSpan(seg))\n }\n\n if (typeof targetCaret === 'number') setCaret(targetCaret)\n }\n\n /** 从 DOM 反推模型:宽容裸 text node、空 text span。 */\n function readDocFromDOM(): Doc {\n const segs: Segment[] = []\n for (const child of Array.from(opts.container.childNodes)) {\n if (isChipElement(child)) {\n const value = readChipValue(child)\n const domType = readChipType(child)\n const fallbackLabel =\n child.querySelector(`.${opts.cls.e('chip-label')}`)?.textContent ?? value\n // 优先从 variableMap 取最新 label / type,保持与外部数据源一致\n const fromMap = opts.getVariableMap?.().get(value)\n const finalType = fromMap?.type ?? domType\n const seg: Segment = finalType\n ? { kind: 'var', value, label: fromMap?.label ?? fallbackLabel, type: finalType }\n : { kind: 'var', value, label: fromMap?.label ?? fallbackLabel }\n segs.push(seg)\n } else {\n segs.push({ kind: 'text', value: child.textContent ?? '' })\n }\n }\n return normalize(segs)\n }\n\n /**\n * 计算光标在序列化字符串中的全局偏移。\n * 规则:\n * - 在 text span / 裸 text node 中:父级 / 节点之前的 segments 长度累加 + selection 内偏移\n * - 在 chip 边界:根据 selection 在 chip 节点之前 / 之后判断\n */\n function getCaretOffset(): number | null {\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) return null\n\n let offset = 0\n let found = false\n for (const child of Array.from(opts.container.childNodes)) {\n if (found) break\n if (isChipElement(child)) {\n if (range.startContainer === child) {\n // selection.startOffset 表示子节点索引:0 表示 chip 起始,>=1 表示 chip 之后\n if (range.startOffset === 0) return offset\n return offset + childSerialLength(child)\n }\n if (range.startContainer === opts.container && getChildIndex(child) === range.startOffset) {\n // 容器层 selection:偏移即子节点索引\n return offset\n }\n offset += childSerialLength(child)\n } else {\n // text span 或裸 text 节点\n if (\n range.startContainer === child ||\n (child.contains(range.startContainer) && child !== opts.container)\n ) {\n // 计算 selection 在该 child textContent 中的偏移\n const localOffset = computeTextOffsetWithin(\n child,\n range.startContainer,\n range.startOffset\n )\n return offset + localOffset\n }\n if (range.startContainer === opts.container && getChildIndex(child) === range.startOffset) {\n return offset\n }\n offset += childSerialLength(child)\n }\n }\n if (\n range.startContainer === opts.container &&\n range.startOffset === opts.container.childNodes.length\n ) {\n return offset\n }\n return null\n }\n\n function getChildIndex(node: Node): number {\n let i = 0\n let cur: Node | null = node.previousSibling\n while (cur) {\n i++\n cur = cur.previousSibling\n }\n return i\n }\n\n function computeTextOffsetWithin(root: Node, target: Node, targetOffset: number): number {\n if (root === target) return targetOffset\n let total = 0\n function walk(n: Node): boolean {\n if (n === target) {\n total += targetOffset\n return true\n }\n if (n.nodeType === 3) {\n total += (n as Text).data.length\n return false\n }\n for (const c of Array.from(n.childNodes)) {\n if (walk(c)) return true\n }\n return false\n }\n walk(root)\n return total\n }\n\n /** 把光标放到 offset 处,offset 大于文档长度时落到末尾。 */\n function setCaret(offset: number): void {\n const sel = document.getSelection()\n if (!sel) return\n\n let remain = offset\n const children = Array.from(opts.container.childNodes)\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!\n const len = childSerialLength(child)\n if (isChipElement(child)) {\n if (remain <= 0) {\n // 光标定到 chip 之前 → 落到容器层 child 索引\n placeAt(opts.container, i)\n return\n }\n if (remain < len) {\n // 落在 chip 内部 → 视作 chip 之后\n placeAt(opts.container, i + 1)\n return\n }\n remain -= len\n } else {\n if (remain <= len) {\n // 落在该 text span 内\n placeWithinText(child, remain)\n return\n }\n remain -= len\n }\n }\n // 落到末尾\n if (children.length === 0) {\n placeAt(opts.container, 0)\n return\n }\n const last = children[children.length - 1]!\n if (isChipElement(last)) placeAt(opts.container, children.length)\n else placeWithinText(last, (last.textContent ?? '').length)\n }\n\n function placeAt(parent: Node, index: number) {\n const sel = document.getSelection()\n if (!sel) return\n const range = document.createRange()\n range.setStart(parent, index)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n\n function placeWithinText(span: Node, offset: number): void {\n const sel = document.getSelection()\n if (!sel) return\n const target = firstTextDescendant(span)\n const range = document.createRange()\n if (target) {\n const max = target.data.length\n range.setStart(target, Math.min(offset, max))\n } else {\n // 空 span:让光标落到 span 内(child index 0)\n range.setStart(span, 0)\n }\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n\n function firstTextDescendant(root: Node): Text | null {\n if (root.nodeType === 3) return root as Text\n for (const c of Array.from(root.childNodes)) {\n const t = firstTextDescendant(c)\n if (t) return t\n }\n return null\n }\n\n /** input 后:从 DOM 反推 doc,并触发 onChange(如有变化)。 */\n function syncFromDOM(): void {\n if (composing) return\n const next = readDocFromDOM()\n if (!docEqual(next, doc)) emitChange(next)\n }\n\n function docEqual(a: Doc, b: Doc): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const x = a[i]!\n const y = b[i]!\n if (x.kind !== y.kind) return false\n if (x.kind === 'text' && y.kind === 'text' && x.value !== y.value) return false\n if (x.kind === 'var' && y.kind === 'var') {\n if (x.value !== y.value || x.label !== y.label || x.type !== y.type) return false\n }\n }\n return true\n }\n\n function onInput() {\n syncFromDOM()\n }\n function onCompositionStart() {\n composing = true\n }\n function onCompositionEnd() {\n composing = false\n syncFromDOM()\n }\n function onPaste(e: ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData?.getData('text/plain') ?? ''\n if (!text) return\n insertPlainTextAtCaret(text)\n }\n function onSelectionChange() {\n if (composing) return\n if (!opts.onSelectionChange) return\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) {\n opts.onSelectionChange({ offset: null })\n return\n }\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) {\n opts.onSelectionChange({ offset: null })\n return\n }\n opts.onSelectionChange({ offset: getCaretOffset() })\n }\n\n function insertPlainTextAtCaret(text: string) {\n const sel = document.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (!opts.container.contains(range.startContainer)) return\n range.deleteContents()\n const node = document.createTextNode(text)\n range.insertNode(node)\n range.setStartAfter(node)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n syncFromDOM()\n }\n\n // 命令:替换区间为 var chip\n function replaceRangeWithVar(start: number, end: number, variable: EditorVariableInfo) {\n const a = Math.min(start, end)\n const b = Math.max(start, end)\n const left = sliceDoc(doc, 0, a)\n const right = sliceDoc(doc, b, totalLength(doc))\n const inserted: Segment = variable.type\n ? { kind: 'var', value: variable.value, label: variable.label, type: variable.type }\n : { kind: 'var', value: variable.value, label: variable.label }\n const next = normalize([...left, inserted, ...right])\n emitChange(next)\n render(a + variable.value.length + 2) // 光标定到 chip 之后\n }\n\n function removeVarAt(segIndex: number) {\n const target = doc[segIndex]\n if (!target || target.kind !== 'var') return\n const before = doc.slice(0, segIndex)\n const after = doc.slice(segIndex + 1)\n const caretOffset = sliceLength(before)\n const next = normalize([...before, ...after])\n emitChange(next)\n render(caretOffset)\n }\n\n function replaceVarAt(segIndex: number, variable: EditorVariableInfo) {\n const target = doc[segIndex]\n if (!target || target.kind !== 'var') return\n const next = doc.slice()\n const replaced: Segment = variable.type\n ? { kind: 'var', value: variable.value, label: variable.label, type: variable.type }\n : { kind: 'var', value: variable.value, label: variable.label }\n next[segIndex] = replaced\n const caretOffset = sliceLength(doc.slice(0, segIndex)) + variable.value.length + 2\n emitChange(normalize(next))\n render(caretOffset)\n }\n\n // 工具:基于序列化偏移的切片(不会切开 var)\n function sliceDoc(d: Doc, start: number, end: number): Doc {\n const out: Segment[] = []\n let pos = 0\n for (const seg of d) {\n const len = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n const segStart = pos\n const segEnd = pos + len\n if (segEnd <= start || segStart >= end) {\n pos += len\n continue\n }\n if (seg.kind === 'text') {\n const a = Math.max(0, start - segStart)\n const b = Math.min(len, end - segStart)\n const slice = seg.value.slice(a, b)\n if (slice) out.push({ kind: 'text', value: slice })\n } else {\n // var 是原子:只有当 [start, end) 完整覆盖该 var 时保留;否则抛弃\n if (start <= segStart && end >= segEnd) out.push(seg)\n }\n pos += len\n }\n return out\n }\n\n function sliceLength(d: Doc): number {\n let n = 0\n for (const s of d) n += s.kind === 'text' ? s.value.length : s.value.length + 2\n return n\n }\n\n function totalLength(d: Doc): number {\n return sliceLength(d)\n }\n\n // 绑定监听\n const container = opts.container\n container.addEventListener('input', onInput)\n container.addEventListener('compositionstart', onCompositionStart)\n container.addEventListener('compositionend', onCompositionEnd)\n container.addEventListener('paste', onPaste)\n document.addEventListener('selectionchange', onSelectionChange)\n\n // 初次渲染\n render()\n\n return {\n getDoc: () => doc,\n setDoc: (next) => {\n doc = normalize(next)\n render()\n opts.onChange?.(doc)\n },\n getValue: () => serialize(doc),\n setValue: (value) => {\n doc = parse(value, opts.getVariableMap?.())\n render()\n opts.onChange?.(doc)\n },\n getCaretOffset,\n setCaretOffset: setCaret,\n replaceRangeWithVar,\n removeVarAt,\n replaceVarAt,\n rerender: () => render(),\n dispose: () => {\n container.removeEventListener('input', onInput)\n container.removeEventListener('compositionstart', onCompositionStart)\n container.removeEventListener('compositionend', onCompositionEnd)\n container.removeEventListener('paste', onPaste)\n document.removeEventListener('selectionchange', onSelectionChange)\n },\n container\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mention.js","names":[],"sources":["../../../../src/components/expression-editor/core/mention.ts"],"sourcesContent":["import type { Doc } from './model'\n\nexport interface MentionState {\n /** `@` 字符在序列化字符串中的偏移 */\n anchorOffset: number\n /** `@` 之后到当前光标之间的字符 */\n filter: string\n}\n\nexport interface MentionAPI {\n /**\n * 根据当前 doc + 光标 offset 推导 mention 状态。\n * 返回最新状态(可能为 null = 未激活)。\n */\n update(doc: Doc, caret: number | null): MentionState | null\n /**\n * 主动退出当前 mention(用户按 Esc / 空格 / ←→ / 失焦)。\n * 退出后即便光标仍在合法范围内,也不会自动重新激活,\n * 直到光标离开当前 anchor 区域(如:@ 被删除或光标跨段)后再次出现新的 `@`。\n */\n dismiss(): void\n /** 选中后由调用方调用,清理状态(含 dismissedAnchor),下一个 `@` 可重新激活。 */\n commit(): void\n /** 当前状态(只读) */\n getState(): MentionState | null\n}\n\nexport function createMention(): MentionAPI {\n let state: MentionState | null = null\n let dismissedAnchor: number | null = null\n\n function derive(doc: Doc, caret: number | null): MentionState | null {\n if (caret === null) return null\n\n // 找到 caret 落在哪个 text segment 以及段内偏移\n let pos = 0\n let textValue: string | null = null\n let textStart = 0\n let inTextOffset = 0\n for (const seg of doc) {\n const len = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n if (caret <= pos + len) {\n if (seg.kind === 'text') {\n textValue = seg.value\n textStart = pos\n inTextOffset = caret - pos\n }\n break\n }\n pos += len\n }\n // 边界:caret 位于结尾且没有命中段,allow caret == 文档末尾且最后段是 text\n if (textValue === null) {\n const lastSeg = doc[doc.length - 1]\n if (lastSeg && lastSeg.kind === 'text') {\n const lastStart = totalLengthBefore(doc, doc.length - 1)\n if (caret === lastStart + lastSeg.value.length) {\n textValue = lastSeg.value\n textStart = lastStart\n inTextOffset = lastSeg.value.length\n }\n }\n }\n if (textValue === null) return null\n\n // 从 inTextOffset 向前扫描,遇到空白前的最后一个 @ 即为 anchor\n let i = inTextOffset - 1\n while (i >= 0) {\n const ch = textValue[i]\n if (ch === '@') {\n const filter = textValue.slice(i + 1, inTextOffset)\n if (/\\s/.test(filter)) return null\n return { anchorOffset: textStart + i, filter }\n }\n if (ch && /\\s/.test(ch)) return null\n i--\n }\n return null\n }\n\n function totalLengthBefore(doc: Doc, segIndex: number): number {\n let n = 0\n for (let i = 0; i < segIndex; i++) {\n const s = doc[i]!\n n += s.kind === 'text' ? s.value.length : s.value.length + 2\n }\n return n\n }\n\n return {\n update(doc, caret) {\n const candidate = derive(doc, caret)\n if (!candidate) {\n // 派生不到合法 mention,dismissedAnchor 失去意义\n dismissedAnchor = null\n state = null\n return state\n }\n if (dismissedAnchor !== null && candidate.anchorOffset === dismissedAnchor) {\n // 仍处于上次主动退出的同一个 anchor 范围内,不重新激活\n state = null\n return state\n }\n // 走到这里说明:(1)首次激活;或(2)anchor 与 dismissed 不同(例如又输入了一个 @)\n dismissedAnchor = null\n state = candidate\n return state\n },\n dismiss() {\n if (state) dismissedAnchor = state.anchorOffset\n state = null\n },\n commit() {\n state = null\n dismissedAnchor = null\n },\n getState() {\n return state\n }\n }\n}\n"],"mappings":";AA2BA,SAAgB,gBAA4B;CAC1C,IAAI,QAA6B;CACjC,IAAI,kBAAiC;CAErC,SAAS,OAAO,KAAU,OAA2C;AACnE,MAAI,UAAU,KAAM,QAAO;EAG3B,IAAI,MAAM;EACV,IAAI,YAA2B;EAC/B,IAAI,YAAY;EAChB,IAAI,eAAe;AACnB,OAAK,MAAM,OAAO,KAAK;GACrB,MAAM,MAAM,IAAI,SAAS,SAAS,IAAI,MAAM,SAAS,IAAI,MAAM,SAAS;AACxE,OAAI,SAAS,MAAM,KAAK;AACtB,QAAI,IAAI,SAAS,QAAQ;AACvB,iBAAY,IAAI;AAChB,iBAAY;AACZ,oBAAe,QAAQ;;AAEzB;;AAEF,UAAO;;AAGT,MAAI,cAAc,MAAM;GACtB,MAAM,UAAU,IAAI,IAAI,SAAS;AACjC,OAAI,WAAW,QAAQ,SAAS,QAAQ;IACtC,MAAM,YAAY,kBAAkB,KAAK,IAAI,SAAS,EAAE;AACxD,QAAI,UAAU,YAAY,QAAQ,MAAM,QAAQ;AAC9C,iBAAY,QAAQ;AACpB,iBAAY;AACZ,oBAAe,QAAQ,MAAM;;;;AAInC,MAAI,cAAc,KAAM,QAAO;EAG/B,IAAI,IAAI,eAAe;AACvB,SAAO,KAAK,GAAG;GACb,MAAM,KAAK,UAAU;AACrB,OAAI,OAAO,KAAK;IACd,MAAM,SAAS,UAAU,MAAM,IAAI,GAAG,aAAa;AACnD,QAAI,KAAK,KAAK,OAAO,CAAE,QAAO;AAC9B,WAAO;KAAE,cAAc,YAAY;KAAG;KAAQ;;AAEhD,OAAI,MAAM,KAAK,KAAK,GAAG,CAAE,QAAO;AAChC;;AAEF,SAAO;;CAGT,SAAS,kBAAkB,KAAU,UAA0B;EAC7D,IAAI,IAAI;AACR,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;GACjC,MAAM,IAAI,IAAI;AACd,QAAK,EAAE,SAAS,SAAS,EAAE,MAAM,SAAS,EAAE,MAAM,SAAS;;AAE7D,SAAO;;AAGT,QAAO;EACL,OAAO,KAAK,OAAO;GACjB,MAAM,YAAY,OAAO,KAAK,MAAM;AACpC,OAAI,CAAC,WAAW;AAEd,sBAAkB;AAClB,YAAQ;AACR,WAAO;;AAET,OAAI,oBAAoB,QAAQ,UAAU,iBAAiB,iBAAiB;AAE1E,YAAQ;AACR,WAAO;;AAGT,qBAAkB;AAClB,WAAQ;AACR,UAAO;;EAET,UAAU;AACR,OAAI,MAAO,mBAAkB,MAAM;AACnC,WAAQ;;EAEV,SAAS;AACP,WAAQ;AACR,qBAAkB;;EAEpB,WAAW;AACT,UAAO;;EAEV"}
1
+ {"version":3,"file":"mention.js","names":[],"sources":["../../../../src/components/expression-editor/core/mention.ts"],"sourcesContent":["import type { Doc } from './model'\n\nexport interface MentionState {\n /** `@` 字符在序列化字符串中的偏移 */\n anchorOffset: number\n /** `@` 之后到当前光标之间的字符 */\n filter: string\n}\n\nexport interface MentionAPI {\n /**\n * 根据当前 doc + 光标 offset 推导 mention 状态。\n * 返回最新状态(可能为 null = 未激活)。\n */\n update(doc: Doc, caret: number | null): MentionState | null\n /**\n * 主动退出当前 mention(用户按 Esc / 空格 / ←→ / 失焦)。\n * 退出后即便光标仍在合法范围内,也不会自动重新激活,\n * 直到光标离开当前 anchor 区域(如:@ 被删除或光标跨段)后再次出现新的 `@`。\n */\n dismiss(): void\n /** 选中后由调用方调用,清理状态(含 dismissedAnchor),下一个 `@` 可重新激活。 */\n commit(): void\n /** 当前状态(只读) */\n getState(): MentionState | null\n}\n\nexport function createMention(): MentionAPI {\n let state: MentionState | null = null\n let dismissedAnchor: number | null = null\n\n function derive(doc: Doc, caret: number | null): MentionState | null {\n if (caret === null) return null\n\n // 找到 caret 落在哪个 text segment 以及段内偏移\n let pos = 0\n let textValue: string | null = null\n let textStart = 0\n let inTextOffset = 0\n for (const seg of doc) {\n const len = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n if (caret <= pos + len) {\n if (seg.kind === 'text') {\n textValue = seg.value\n textStart = pos\n inTextOffset = caret - pos\n }\n break\n }\n pos += len\n }\n // 边界:caret 位于结尾且没有命中段,allow caret == 文档末尾且最后段是 text\n if (textValue === null) {\n const lastSeg = doc[doc.length - 1]\n if (lastSeg && lastSeg.kind === 'text') {\n const lastStart = totalLengthBefore(doc, doc.length - 1)\n if (caret === lastStart + lastSeg.value.length) {\n textValue = lastSeg.value\n textStart = lastStart\n inTextOffset = lastSeg.value.length\n }\n }\n }\n if (textValue === null) return null\n\n // 从 inTextOffset 向前扫描,遇到空白前的最后一个 @ 即为 anchor\n let i = inTextOffset - 1\n while (i >= 0) {\n const ch = textValue[i]\n if (ch === '@') {\n const filter = textValue.slice(i + 1, inTextOffset)\n if (/\\s/.test(filter)) return null\n return { anchorOffset: textStart + i, filter }\n }\n if (ch && /\\s/.test(ch)) return null\n i--\n }\n return null\n }\n\n function totalLengthBefore(doc: Doc, segIndex: number): number {\n let n = 0\n for (let i = 0; i < segIndex; i++) {\n const s = doc[i]!\n n += s.kind === 'text' ? s.value.length : s.value.length + 2\n }\n return n\n }\n\n return {\n update(doc, caret) {\n const candidate = derive(doc, caret)\n if (!candidate) {\n // 派生不到合法 mention,dismissedAnchor 失去意义\n dismissedAnchor = null\n state = null\n return state\n }\n if (dismissedAnchor !== null && candidate.anchorOffset === dismissedAnchor) {\n // 仍处于上次主动退出的同一个 anchor 范围内,不重新激活\n state = null\n return state\n }\n // 走到这里说明:(1)首次激活;或(2)anchor 与 dismissed 不同(例如又输入了一个 @)\n dismissedAnchor = null\n state = candidate\n return state\n },\n dismiss() {\n if (state) dismissedAnchor = state.anchorOffset\n state = null\n },\n commit() {\n state = null\n dismissedAnchor = null\n },\n getState() {\n return state\n }\n }\n}\n"],"mappings":";AA2BA,SAAgB,gBAA4B;CAC1C,IAAI,QAA6B;CACjC,IAAI,kBAAiC;CAErC,SAAS,OAAO,KAAU,OAA2C;EACnE,IAAI,UAAU,MAAM,OAAO;EAG3B,IAAI,MAAM;EACV,IAAI,YAA2B;EAC/B,IAAI,YAAY;EAChB,IAAI,eAAe;EACnB,KAAK,MAAM,OAAO,KAAK;GACrB,MAAM,MAAM,IAAI,SAAS,SAAS,IAAI,MAAM,SAAS,IAAI,MAAM,SAAS;GACxE,IAAI,SAAS,MAAM,KAAK;IACtB,IAAI,IAAI,SAAS,QAAQ;KACvB,YAAY,IAAI;KAChB,YAAY;KACZ,eAAe,QAAQ;;IAEzB;;GAEF,OAAO;;EAGT,IAAI,cAAc,MAAM;GACtB,MAAM,UAAU,IAAI,IAAI,SAAS;GACjC,IAAI,WAAW,QAAQ,SAAS,QAAQ;IACtC,MAAM,YAAY,kBAAkB,KAAK,IAAI,SAAS,EAAE;IACxD,IAAI,UAAU,YAAY,QAAQ,MAAM,QAAQ;KAC9C,YAAY,QAAQ;KACpB,YAAY;KACZ,eAAe,QAAQ,MAAM;;;;EAInC,IAAI,cAAc,MAAM,OAAO;EAG/B,IAAI,IAAI,eAAe;EACvB,OAAO,KAAK,GAAG;GACb,MAAM,KAAK,UAAU;GACrB,IAAI,OAAO,KAAK;IACd,MAAM,SAAS,UAAU,MAAM,IAAI,GAAG,aAAa;IACnD,IAAI,KAAK,KAAK,OAAO,EAAE,OAAO;IAC9B,OAAO;KAAE,cAAc,YAAY;KAAG;KAAQ;;GAEhD,IAAI,MAAM,KAAK,KAAK,GAAG,EAAE,OAAO;GAChC;;EAEF,OAAO;;CAGT,SAAS,kBAAkB,KAAU,UAA0B;EAC7D,IAAI,IAAI;EACR,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;GACjC,MAAM,IAAI,IAAI;GACd,KAAK,EAAE,SAAS,SAAS,EAAE,MAAM,SAAS,EAAE,MAAM,SAAS;;EAE7D,OAAO;;CAGT,OAAO;EACL,OAAO,KAAK,OAAO;GACjB,MAAM,YAAY,OAAO,KAAK,MAAM;GACpC,IAAI,CAAC,WAAW;IAEd,kBAAkB;IAClB,QAAQ;IACR,OAAO;;GAET,IAAI,oBAAoB,QAAQ,UAAU,iBAAiB,iBAAiB;IAE1E,QAAQ;IACR,OAAO;;GAGT,kBAAkB;GAClB,QAAQ;GACR,OAAO;;EAET,UAAU;GACR,IAAI,OAAO,kBAAkB,MAAM;GACnC,QAAQ;;EAEV,SAAS;GACP,QAAQ;GACR,kBAAkB;;EAEpB,WAAW;GACT,OAAO;;EAEV"}
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../../../../src/components/expression-editor/core/model.ts"],"sourcesContent":["import type { VariableItem } from '../../../types'\n\n/** 单个文档段:纯文本或变量 chip */\nexport type Segment =\n | { kind: 'text'; value: string }\n | { kind: 'var'; value: string; label: string; type?: string }\n\n/** 归一化的文档形态:相邻 text 段会合并、空 text 段不保留 */\nexport type Doc = Segment[]\n\nconst VARIABLE_PATTERN = /\\{([^}]+)\\}/g\n\n/**\n * 反序列化:将 modelValue 字符串解析为 Segment[]。\n * 沿用旧 parser.ts 的正则切分逻辑,保证 round-trip 与历史一致。\n */\nexport function parse(content: string, variableMap?: Map<string, VariableItem>): Doc {\n const segments: Segment[] = []\n if (!content) return segments\n\n let cursor = 0\n for (const match of content.matchAll(VARIABLE_PATTERN)) {\n const value = match[1]!\n const start = match.index!\n if (start > cursor) {\n segments.push({ kind: 'text', value: content.slice(cursor, start) })\n }\n const found = variableMap?.get(value)\n segments.push({\n kind: 'var',\n value,\n label: found?.label ?? value,\n ...(found?.type ? { type: found.type } : {})\n })\n cursor = start + match[0].length\n }\n if (cursor < content.length) {\n segments.push({ kind: 'text', value: content.slice(cursor) })\n }\n\n return normalize(segments)\n}\n\n/** 序列化:var 段输出 `{value}`,text 段直接拼接。 */\nexport function serialize(doc: Doc): string {\n let out = ''\n for (const seg of doc) {\n out += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n }\n return out\n}\n\n/**\n * 归一化:合并相邻 text 段、丢弃空 text 段。\n * 注意:DOM 层另需保证 var 两侧有 text 占位(即便空),由 editor 渲染时补齐。\n */\nexport function normalize(doc: Doc): Doc {\n const out: Segment[] = []\n for (const seg of doc) {\n if (seg.kind === 'text') {\n if (!seg.value) continue\n const last = out[out.length - 1]\n if (last && last.kind === 'text') {\n out[out.length - 1] = { kind: 'text', value: last.value + seg.value }\n continue\n }\n }\n out.push(seg)\n }\n return out\n}\n\ninterface SplitResult {\n before: Doc\n after: Doc\n}\n\n/**\n * 在序列化后的字符串偏移上切分(不会切开 var)。\n * 若偏移落在 var 中间,向前对齐到 var 起点。\n */\nexport function splitAt(doc: Doc, globalOffset: number): SplitResult {\n const before: Doc = []\n const after: Doc = []\n let pos = 0\n let consumed = false\n\n for (const seg of doc) {\n const segLen = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n if (consumed) {\n after.push(seg)\n continue\n }\n if (globalOffset >= pos + segLen) {\n before.push(seg)\n pos += segLen\n continue\n }\n if (seg.kind === 'text') {\n const offset = globalOffset - pos\n if (offset > 0) before.push({ kind: 'text', value: seg.value.slice(0, offset) })\n if (offset < seg.value.length) after.push({ kind: 'text', value: seg.value.slice(offset) })\n } else {\n after.push(seg)\n }\n consumed = true\n }\n return { before, after }\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;;;;;AAMzB,SAAgB,MAAM,SAAiB,aAA8C;CACnF,MAAM,WAAsB,EAAE;AAC9B,KAAI,CAAC,QAAS,QAAO;CAErB,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ,SAAS,iBAAiB,EAAE;EACtD,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,MAAM;AACpB,MAAI,QAAQ,OACV,UAAS,KAAK;GAAE,MAAM;GAAQ,OAAO,QAAQ,MAAM,QAAQ,MAAM;GAAE,CAAC;EAEtE,MAAM,QAAQ,aAAa,IAAI,MAAM;AACrC,WAAS,KAAK;GACZ,MAAM;GACN;GACA,OAAO,OAAO,SAAS;GACvB,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GAC5C,CAAC;AACF,WAAS,QAAQ,MAAM,GAAG;;AAE5B,KAAI,SAAS,QAAQ,OACnB,UAAS,KAAK;EAAE,MAAM;EAAQ,OAAO,QAAQ,MAAM,OAAO;EAAE,CAAC;AAG/D,QAAO,UAAU,SAAS;;;AAI5B,SAAgB,UAAU,KAAkB;CAC1C,IAAI,MAAM;AACV,MAAK,MAAM,OAAO,IAChB,QAAO,IAAI,SAAS,SAAS,IAAI,QAAQ,IAAI,IAAI,MAAM;AAEzD,QAAO;;;;;;AAOT,SAAgB,UAAU,KAAe;CACvC,MAAM,MAAiB,EAAE;AACzB,MAAK,MAAM,OAAO,KAAK;AACrB,MAAI,IAAI,SAAS,QAAQ;AACvB,OAAI,CAAC,IAAI,MAAO;GAChB,MAAM,OAAO,IAAI,IAAI,SAAS;AAC9B,OAAI,QAAQ,KAAK,SAAS,QAAQ;AAChC,QAAI,IAAI,SAAS,KAAK;KAAE,MAAM;KAAQ,OAAO,KAAK,QAAQ,IAAI;KAAO;AACrE;;;AAGJ,MAAI,KAAK,IAAI;;AAEf,QAAO"}
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../../../src/components/expression-editor/core/model.ts"],"sourcesContent":["import type { VariableItem } from '../../../types'\n\n/** 单个文档段:纯文本或变量 chip */\nexport type Segment =\n | { kind: 'text'; value: string }\n | { kind: 'var'; value: string; label: string; type?: string }\n\n/** 归一化的文档形态:相邻 text 段会合并、空 text 段不保留 */\nexport type Doc = Segment[]\n\nconst VARIABLE_PATTERN = /\\{([^}]+)\\}/g\n\n/**\n * 反序列化:将 modelValue 字符串解析为 Segment[]。\n * 沿用旧 parser.ts 的正则切分逻辑,保证 round-trip 与历史一致。\n */\nexport function parse(content: string, variableMap?: Map<string, VariableItem>): Doc {\n const segments: Segment[] = []\n if (!content) return segments\n\n let cursor = 0\n for (const match of content.matchAll(VARIABLE_PATTERN)) {\n const value = match[1]!\n const start = match.index!\n if (start > cursor) {\n segments.push({ kind: 'text', value: content.slice(cursor, start) })\n }\n const found = variableMap?.get(value)\n segments.push({\n kind: 'var',\n value,\n label: found?.label ?? value,\n ...(found?.type ? { type: found.type } : {})\n })\n cursor = start + match[0].length\n }\n if (cursor < content.length) {\n segments.push({ kind: 'text', value: content.slice(cursor) })\n }\n\n return normalize(segments)\n}\n\n/** 序列化:var 段输出 `{value}`,text 段直接拼接。 */\nexport function serialize(doc: Doc): string {\n let out = ''\n for (const seg of doc) {\n out += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n }\n return out\n}\n\n/**\n * 归一化:合并相邻 text 段、丢弃空 text 段。\n * 注意:DOM 层另需保证 var 两侧有 text 占位(即便空),由 editor 渲染时补齐。\n */\nexport function normalize(doc: Doc): Doc {\n const out: Segment[] = []\n for (const seg of doc) {\n if (seg.kind === 'text') {\n if (!seg.value) continue\n const last = out[out.length - 1]\n if (last && last.kind === 'text') {\n out[out.length - 1] = { kind: 'text', value: last.value + seg.value }\n continue\n }\n }\n out.push(seg)\n }\n return out\n}\n\ninterface SplitResult {\n before: Doc\n after: Doc\n}\n\n/**\n * 在序列化后的字符串偏移上切分(不会切开 var)。\n * 若偏移落在 var 中间,向前对齐到 var 起点。\n */\nexport function splitAt(doc: Doc, globalOffset: number): SplitResult {\n const before: Doc = []\n const after: Doc = []\n let pos = 0\n let consumed = false\n\n for (const seg of doc) {\n const segLen = seg.kind === 'text' ? seg.value.length : seg.value.length + 2\n if (consumed) {\n after.push(seg)\n continue\n }\n if (globalOffset >= pos + segLen) {\n before.push(seg)\n pos += segLen\n continue\n }\n if (seg.kind === 'text') {\n const offset = globalOffset - pos\n if (offset > 0) before.push({ kind: 'text', value: seg.value.slice(0, offset) })\n if (offset < seg.value.length) after.push({ kind: 'text', value: seg.value.slice(offset) })\n } else {\n after.push(seg)\n }\n consumed = true\n }\n return { before, after }\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;;;;;AAMzB,SAAgB,MAAM,SAAiB,aAA8C;CACnF,MAAM,WAAsB,EAAE;CAC9B,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ,SAAS,iBAAiB,EAAE;EACtD,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,MAAM;EACpB,IAAI,QAAQ,QACV,SAAS,KAAK;GAAE,MAAM;GAAQ,OAAO,QAAQ,MAAM,QAAQ,MAAM;GAAE,CAAC;EAEtE,MAAM,QAAQ,aAAa,IAAI,MAAM;EACrC,SAAS,KAAK;GACZ,MAAM;GACN;GACA,OAAO,OAAO,SAAS;GACvB,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;GAC5C,CAAC;EACF,SAAS,QAAQ,MAAM,GAAG;;CAE5B,IAAI,SAAS,QAAQ,QACnB,SAAS,KAAK;EAAE,MAAM;EAAQ,OAAO,QAAQ,MAAM,OAAO;EAAE,CAAC;CAG/D,OAAO,UAAU,SAAS;;;AAI5B,SAAgB,UAAU,KAAkB;CAC1C,IAAI,MAAM;CACV,KAAK,MAAM,OAAO,KAChB,OAAO,IAAI,SAAS,SAAS,IAAI,QAAQ,IAAI,IAAI,MAAM;CAEzD,OAAO;;;;;;AAOT,SAAgB,UAAU,KAAe;CACvC,MAAM,MAAiB,EAAE;CACzB,KAAK,MAAM,OAAO,KAAK;EACrB,IAAI,IAAI,SAAS,QAAQ;GACvB,IAAI,CAAC,IAAI,OAAO;GAChB,MAAM,OAAO,IAAI,IAAI,SAAS;GAC9B,IAAI,QAAQ,KAAK,SAAS,QAAQ;IAChC,IAAI,IAAI,SAAS,KAAK;KAAE,MAAM;KAAQ,OAAO,KAAK,QAAQ,IAAI;KAAO;IACrE;;;EAGJ,IAAI,KAAK,IAAI;;CAEf,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"di.js","names":[],"sources":["../../../src/components/expression-editor/di.ts"],"sourcesContent":["import type { VariableItem } from '../../types'\n\n/** 扁平化变量树,创建 value -> VariableItem 的映射,用于 DOM → 模型反推时补齐 label/type。 */\nexport function createVariableMap(variables?: VariableItem[]): Map<string, VariableItem> {\n const map = new Map<string, VariableItem>()\n function traverse(items: VariableItem[] | undefined) {\n if (!items) return\n for (const item of items) {\n map.set(item.value, item)\n if (item.children) traverse(item.children)\n }\n }\n traverse(variables)\n return map\n}\n"],"mappings":";;AAGA,SAAgB,kBAAkB,WAAuD;CACvF,MAAM,sBAAM,IAAI,KAA2B;CAC3C,SAAS,SAAS,OAAmC;AACnD,MAAI,CAAC,MAAO;AACZ,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,IAAI,KAAK,OAAO,KAAK;AACzB,OAAI,KAAK,SAAU,UAAS,KAAK,SAAS;;;AAG9C,UAAS,UAAU;AACnB,QAAO"}
1
+ {"version":3,"file":"di.js","names":[],"sources":["../../../src/components/expression-editor/di.ts"],"sourcesContent":["import type { VariableItem } from '../../types'\n\n/** 扁平化变量树,创建 value -> VariableItem 的映射,用于 DOM → 模型反推时补齐 label/type。 */\nexport function createVariableMap(variables?: VariableItem[]): Map<string, VariableItem> {\n const map = new Map<string, VariableItem>()\n function traverse(items: VariableItem[] | undefined) {\n if (!items) return\n for (const item of items) {\n map.set(item.value, item)\n if (item.children) traverse(item.children)\n }\n }\n traverse(variables)\n return map\n}\n"],"mappings":";;AAGA,SAAgB,kBAAkB,WAAuD;CACvF,MAAM,sBAAM,IAAI,KAA2B;CAC3C,SAAS,SAAS,OAAmC;EACnD,IAAI,CAAC,OAAO;EACZ,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,IAAI,KAAK,OAAO,KAAK;GACzB,IAAI,KAAK,UAAU,SAAS,KAAK,SAAS;;;CAG9C,SAAS,UAAU;CACnB,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"expression-editor.js","names":[],"sources":["../../../src/components/expression-editor/expression-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <div\n ref=\"container\"\n :class=\"cls.e('container')\"\n :contenteditable=\"!readonly && !disabled\"\n :data-empty=\"isEmpty || undefined\"\n :data-placeholder=\"props.placeholder\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n ></div>\n\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ props.placeholder }}\n </div>\n\n <VariablePicker\n ref=\"pickerRef\"\n :visible=\"pickerVisible\"\n :trigger-dom=\"pickerTriggerDom\"\n :variables=\"props.variables\"\n :filter=\"pickerFilter\"\n :selectable-levels=\"props.selectableLevels ?? 'leaf'\"\n @select=\"onPickerSelect\"\n @dismiss=\"onPickerDismiss\"\n @update:visible=\"onPickerVisibleChange\"\n />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch\n} from 'vue'\n\nimport type { ExpressionEditorProps, VariableItem } from '../../types'\nimport VariablePicker from './components/variable-picker.vue'\nimport { createEditor, type EditorAPI } from './core/editor'\nimport { createMention, type MentionAPI } from './core/mention'\nimport { parse, type Doc } from './core/model'\nimport { createVariableMap } from './di'\n\ndefineOptions({\n name: 'ExpressionEditor'\n})\n\nconst props = withDefaults(defineProps<ExpressionEditorProps>(), {\n placeholder: '请输入表达式,输入 @ 可插入变量',\n selectableLevels: 'leaf'\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst cls = bem('expression-editor')\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst containerRef = useTemplateRef<HTMLDivElement>('container')\nconst pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')\n\nconst className = computed(() => [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('readonly', readonly.value)\n])\n\nconst variableMap = computed(() => createVariableMap(props.variables))\n\nconst editorRef = shallowRef<EditorAPI | null>(null)\nconst mention: MentionAPI = createMention()\n\nconst currentDoc = shallowRef<Doc>([])\nconst isEmpty = computed(() => currentDoc.value.length === 0)\nconst showPlaceholder = computed(() => isEmpty.value && !disabled.value)\n\nconst pickerMode = shallowRef<'mention' | 'reselect' | null>(null)\nconst pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)\nconst reselectingSegIdx = shallowRef<number | null>(null)\nconst pickerFilter = shallowRef('')\n\nconst pickerVisible = computed(() => pickerMode.value !== null)\n\nfunction syncMention() {\n const editor = editorRef.value\n if (!editor) return\n if (pickerMode.value === 'reselect') return\n const caret = editor.getCaretOffset()\n const state = mention.update(currentDoc.value, caret)\n if (state) {\n pickerMode.value = 'mention'\n pickerFilter.value = state.filter\n pickerTriggerDom.value = containerRef.value ?? undefined\n } else if (pickerMode.value === 'mention') {\n closePicker()\n }\n}\n\nfunction closePicker() {\n pickerMode.value = null\n reselectingSegIdx.value = null\n pickerFilter.value = ''\n}\n\nonMounted(() => {\n if (!containerRef.value) return\n const initialDoc = parse(props.modelValue ?? '', variableMap.value)\n currentDoc.value = initialDoc\n\n const editor = createEditor({\n container: containerRef.value,\n cls,\n initialDoc,\n getVariableMap: () => variableMap.value,\n onChange: (doc) => {\n currentDoc.value = doc\n const value = serializeFromDoc(doc)\n if (value !== (props.modelValue ?? '')) emit('update:modelValue', value)\n syncMention()\n },\n onSelectionChange: () => {\n syncMention()\n },\n onChipReselect: ({ chipEl, segIndex }) => {\n if (disabled.value || readonly.value) return\n mention.commit()\n pickerMode.value = 'reselect'\n pickerFilter.value = ''\n pickerTriggerDom.value = chipEl\n reselectingSegIdx.value = segIndex\n },\n onChipRemove: ({ segIndex }) => {\n if (disabled.value || readonly.value) return\n editor.removeVarAt(segIndex)\n }\n })\n editorRef.value = editor\n})\n\nonBeforeUnmount(() => {\n editorRef.value?.dispose()\n})\n\nfunction serializeFromDoc(doc: Doc): string {\n let s = ''\n for (const seg of doc) s += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n return s\n}\n\nwatch(\n () => props.modelValue,\n (v) => {\n const editor = editorRef.value\n if (!editor) return\n if (v === editor.getValue()) return\n editor.setValue(v ?? '')\n }\n)\n\nwatch(variableMap, () => {\n // variables 变化:重渲染以刷新 chip label / type\n const editor = editorRef.value\n if (!editor) return\n editor.setValue(editor.getValue())\n})\n\nfunction onKeydown(e: KeyboardEvent) {\n if (disabled.value || readonly.value) return\n // mention / reselect 激活时,优先把箭头键 / Enter / Esc 转给 picker\n if (pickerMode.value !== null) {\n const handledByPicker = pickerRef.value?.handleKeydown(e)\n if (handledByPicker) {\n e.preventDefault()\n return\n }\n }\n\n if (pickerMode.value === 'mention') {\n // 这些键导致 mention 退出(保留 @filter 文本,光标继续移动)\n if (e.key === ' ' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n mention.dismiss()\n closePicker()\n }\n }\n}\n\nfunction onBlur() {\n if (pickerMode.value === 'mention') {\n mention.dismiss()\n // 下一帧再关闭,避免和 picker 内 click 冲突\n void nextTick(() => closePicker())\n }\n}\n\nfunction onPickerSelect(item: VariableItem) {\n const editor = editorRef.value\n if (!editor) return\n\n if (pickerMode.value === 'mention') {\n const state = mention.getState()\n if (!state) return\n const anchor = state.anchorOffset\n const caret = editor.getCaretOffset() ?? anchor + state.filter.length + 1\n editor.replaceRangeWithVar(anchor, caret, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n mention.commit()\n } else if (pickerMode.value === 'reselect') {\n const idx = reselectingSegIdx.value\n if (idx !== null) {\n editor.replaceVarAt(idx, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n }\n }\n closePicker()\n}\n\nfunction onPickerDismiss() {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n}\n\nfunction onPickerVisibleChange(v: boolean) {\n if (!v && pickerMode.value !== null) {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n }\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"expression-editor.js","names":[],"sources":["../../../src/components/expression-editor/expression-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <div\n ref=\"container\"\n :class=\"cls.e('container')\"\n :contenteditable=\"!readonly && !disabled\"\n :data-empty=\"isEmpty || undefined\"\n :data-placeholder=\"props.placeholder\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n ></div>\n\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ props.placeholder }}\n </div>\n\n <VariablePicker\n ref=\"pickerRef\"\n :visible=\"pickerVisible\"\n :trigger-dom=\"pickerTriggerDom\"\n :variables=\"props.variables\"\n :filter=\"pickerFilter\"\n :selectable-levels=\"props.selectableLevels ?? 'leaf'\"\n @select=\"onPickerSelect\"\n @dismiss=\"onPickerDismiss\"\n @update:visible=\"onPickerVisibleChange\"\n />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch\n} from 'vue'\n\nimport type { ExpressionEditorProps, VariableItem } from '../../types'\nimport VariablePicker from './components/variable-picker.vue'\nimport { createEditor, type EditorAPI } from './core/editor'\nimport { createMention, type MentionAPI } from './core/mention'\nimport { parse, type Doc } from './core/model'\nimport { createVariableMap } from './di'\n\ndefineOptions({\n name: 'ExpressionEditor'\n})\n\nconst props = withDefaults(defineProps<ExpressionEditorProps>(), {\n placeholder: '请输入表达式,输入 @ 可插入变量',\n selectableLevels: 'leaf'\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst cls = bem('expression-editor')\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst containerRef = useTemplateRef<HTMLDivElement>('container')\nconst pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')\n\nconst className = computed(() => [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('readonly', readonly.value)\n])\n\nconst variableMap = computed(() => createVariableMap(props.variables))\n\nconst editorRef = shallowRef<EditorAPI | null>(null)\nconst mention: MentionAPI = createMention()\n\nconst currentDoc = shallowRef<Doc>([])\nconst isEmpty = computed(() => currentDoc.value.length === 0)\nconst showPlaceholder = computed(() => isEmpty.value && !disabled.value)\n\nconst pickerMode = shallowRef<'mention' | 'reselect' | null>(null)\nconst pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)\nconst reselectingSegIdx = shallowRef<number | null>(null)\nconst pickerFilter = shallowRef('')\n\nconst pickerVisible = computed(() => pickerMode.value !== null)\n\nfunction syncMention() {\n const editor = editorRef.value\n if (!editor) return\n if (pickerMode.value === 'reselect') return\n const caret = editor.getCaretOffset()\n const state = mention.update(currentDoc.value, caret)\n if (state) {\n pickerMode.value = 'mention'\n pickerFilter.value = state.filter\n pickerTriggerDom.value = containerRef.value ?? undefined\n } else if (pickerMode.value === 'mention') {\n closePicker()\n }\n}\n\nfunction closePicker() {\n pickerMode.value = null\n reselectingSegIdx.value = null\n pickerFilter.value = ''\n}\n\nonMounted(() => {\n if (!containerRef.value) return\n const initialDoc = parse(props.modelValue ?? '', variableMap.value)\n currentDoc.value = initialDoc\n\n const editor = createEditor({\n container: containerRef.value,\n cls,\n initialDoc,\n getVariableMap: () => variableMap.value,\n onChange: (doc) => {\n currentDoc.value = doc\n const value = serializeFromDoc(doc)\n if (value !== (props.modelValue ?? '')) emit('update:modelValue', value)\n syncMention()\n },\n onSelectionChange: () => {\n syncMention()\n },\n onChipReselect: ({ chipEl, segIndex }) => {\n if (disabled.value || readonly.value) return\n mention.commit()\n pickerMode.value = 'reselect'\n pickerFilter.value = ''\n pickerTriggerDom.value = chipEl\n reselectingSegIdx.value = segIndex\n },\n onChipRemove: ({ segIndex }) => {\n if (disabled.value || readonly.value) return\n editor.removeVarAt(segIndex)\n }\n })\n editorRef.value = editor\n})\n\nonBeforeUnmount(() => {\n editorRef.value?.dispose()\n})\n\nfunction serializeFromDoc(doc: Doc): string {\n let s = ''\n for (const seg of doc) s += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n return s\n}\n\nwatch(\n () => props.modelValue,\n (v) => {\n const editor = editorRef.value\n if (!editor) return\n if (v === editor.getValue()) return\n editor.setValue(v ?? '')\n }\n)\n\nwatch(variableMap, () => {\n // variables 变化:重渲染以刷新 chip label / type\n const editor = editorRef.value\n if (!editor) return\n editor.setValue(editor.getValue())\n})\n\nfunction onKeydown(e: KeyboardEvent) {\n if (disabled.value || readonly.value) return\n // mention / reselect 激活时,优先把箭头键 / Enter / Esc 转给 picker\n if (pickerMode.value !== null) {\n const handledByPicker = pickerRef.value?.handleKeydown(e)\n if (handledByPicker) {\n e.preventDefault()\n return\n }\n }\n\n if (pickerMode.value === 'mention') {\n // 这些键导致 mention 退出(保留 @filter 文本,光标继续移动)\n if (e.key === ' ' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n mention.dismiss()\n closePicker()\n }\n }\n}\n\nfunction onBlur() {\n if (pickerMode.value === 'mention') {\n mention.dismiss()\n // 下一帧再关闭,避免和 picker 内 click 冲突\n void nextTick(() => closePicker())\n }\n}\n\nfunction onPickerSelect(item: VariableItem) {\n const editor = editorRef.value\n if (!editor) return\n\n if (pickerMode.value === 'mention') {\n const state = mention.getState()\n if (!state) return\n const anchor = state.anchorOffset\n const caret = editor.getCaretOffset() ?? anchor + state.filter.length + 1\n editor.replaceRangeWithVar(anchor, caret, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n mention.commit()\n } else if (pickerMode.value === 'reselect') {\n const idx = reselectingSegIdx.value\n if (idx !== null) {\n editor.replaceVarAt(idx, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n }\n }\n closePicker()\n}\n\nfunction onPickerDismiss() {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n}\n\nfunction onPickerVisibleChange(v: boolean) {\n if (!v && pickerMode.value !== null) {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n }\n}\n</script>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-picker.js","names":[],"sources":["../../../src/components/file-picker/file-picker.vue"],"sourcesContent":["<template>\n <component\n :is=\"tag\"\n :class=\"cls.b\"\n @click.stop=\"handleClickPicker\"\n @dragover.prevent=\"handleDragover\"\n @dragleave=\"handleDragLeave\"\n @drop.prevent=\"handleDrop\"\n >\n <input\n :multiple=\"multiple\"\n :class=\"cls.e('native')\"\n type=\"file\"\n :accept=\"accept\"\n hidden\n placeholder=\"选择文件\"\n ref=\"fileRef\"\n @change=\"handleChange\"\n capture=\"environment\"\n />\n <slot :is-dragover=\"isDragover\" />\n </component>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { shallowRef, ref } from 'vue'\n\nimport type { UploaderProps, UploaderEmits } from '../../types'\nimport { matchAccept } from './helper'\n\ndefineOptions({\n name: 'FilePicker'\n})\n\nconst props = withDefaults(defineProps<UploaderProps>(), {\n tag: 'div',\n multiple: false\n})\n\nconst emit = defineEmits<UploaderEmits>()\n\nconst cls = bem('file-picker')\n\nconst fileRef = shallowRef<HTMLInputElement>()\nconst isDragover = ref(false)\n\nconst processFiles = (files: File[]) => {\n const filteredFiles = files.filter((file) => matchAccept(file, props.accept))\n emit('pick', filteredFiles)\n}\n\nconst handleChange = (e: Event) => {\n const target = e.target as HTMLInputElement\n const files = Array.from(target.files || [])\n processFiles(files)\n target.value = ''\n}\n\nconst handleDragover = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = true\n}\n\nconst handleDragLeave = (e: DragEvent) => {\n if (props.disabled) return\n const currentTarget = e.currentTarget as HTMLElement\n const relatedTarget = e.relatedTarget as HTMLElement | null\n\n if (currentTarget.contains(relatedTarget)) return\n isDragover.value = false\n}\n\nconst handleDrop = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = false\n const files = Array.from(e.dataTransfer?.files || [])\n processFiles(files)\n}\n\nfunction handleClickPicker() {\n if (props.disabled) return\n fileRef.value?.click()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EAmCA,MAAM,QAAQ;EAKd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,cAAa;EAE7B,MAAM,UAAU,YAA6B;EAC7C,MAAM,aAAa,IAAI,MAAK;EAE5B,MAAM,gBAAgB,UAAkB;AAEtC,QAAK,QADiB,MAAM,QAAQ,SAAS,YAAY,MAAM,MAAM,OAAO,CAC/D,CAAa;;EAG5B,MAAM,gBAAgB,MAAa;GACjC,MAAM,SAAS,EAAE;AAEjB,gBADc,MAAM,KAAK,OAAO,SAAS,EAAE,CAC9B,CAAK;AAClB,UAAO,QAAQ;;EAGjB,MAAM,kBAAkB,MAAiB;AACvC,OAAI,MAAM,SAAU;AACpB,cAAW,QAAQ;;EAGrB,MAAM,mBAAmB,MAAiB;AACxC,OAAI,MAAM,SAAU;GACpB,MAAM,gBAAgB,EAAE;GACxB,MAAM,gBAAgB,EAAE;AAExB,OAAI,cAAc,SAAS,cAAc,CAAE;AAC3C,cAAW,QAAQ;;EAGrB,MAAM,cAAc,MAAiB;AACnC,OAAI,MAAM,SAAU;AACpB,cAAW,QAAQ;AAEnB,gBADc,MAAM,KAAK,EAAE,cAAc,SAAS,EAAE,CACvC,CAAK;;EAGpB,SAAS,oBAAoB;AAC3B,OAAI,MAAM,SAAU;AACpB,WAAQ,OAAO,OAAM;;;uBAjFrB,YAoBY,wBAnBL,QAAA,IAAG,EAAA;IACP,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC;IACZ,SAAK,cAAO,mBAAiB,CAAA,OAAA,CAAA;IAC7B,YAAQ,cAAU,gBAAc,CAAA,UAAA,CAAA;IAChC,aAAW;IACX,QAAI,cAAU,YAAU,CAAA,UAAA,CAAA;;2BAYvB,CAVF,mBAUE,SAAA;KATC,UAAU,QAAA;KACV,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,SAAA,CAAA;KACb,MAAK;KACJ,QAAQ,QAAA;KACT,QAAA;KACA,aAAY;cACR;KAAJ,KAAI;KACH,UAAQ;KACT,SAAQ;8BAEV,WAAkC,KAAA,QAAA,WAAA,EAA3B,YAAa,WAAA,OAAU,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"file-picker.js","names":[],"sources":["../../../src/components/file-picker/file-picker.vue"],"sourcesContent":["<template>\n <component\n :is=\"tag\"\n :class=\"cls.b\"\n @click.stop=\"handleClickPicker\"\n @dragover.prevent=\"handleDragover\"\n @dragleave=\"handleDragLeave\"\n @drop.prevent=\"handleDrop\"\n >\n <input\n :multiple=\"multiple\"\n :class=\"cls.e('native')\"\n type=\"file\"\n :accept=\"accept\"\n hidden\n placeholder=\"选择文件\"\n ref=\"fileRef\"\n @change=\"handleChange\"\n capture=\"environment\"\n />\n <slot :is-dragover=\"isDragover\" />\n </component>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { shallowRef, ref } from 'vue'\n\nimport type { UploaderProps, UploaderEmits } from '../../types'\nimport { matchAccept } from './helper'\n\ndefineOptions({\n name: 'FilePicker'\n})\n\nconst props = withDefaults(defineProps<UploaderProps>(), {\n tag: 'div',\n multiple: false\n})\n\nconst emit = defineEmits<UploaderEmits>()\n\nconst cls = bem('file-picker')\n\nconst fileRef = shallowRef<HTMLInputElement>()\nconst isDragover = ref(false)\n\nconst processFiles = (files: File[]) => {\n const filteredFiles = files.filter((file) => matchAccept(file, props.accept))\n emit('pick', filteredFiles)\n}\n\nconst handleChange = (e: Event) => {\n const target = e.target as HTMLInputElement\n const files = Array.from(target.files || [])\n processFiles(files)\n target.value = ''\n}\n\nconst handleDragover = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = true\n}\n\nconst handleDragLeave = (e: DragEvent) => {\n if (props.disabled) return\n const currentTarget = e.currentTarget as HTMLElement\n const relatedTarget = e.relatedTarget as HTMLElement | null\n\n if (currentTarget.contains(relatedTarget)) return\n isDragover.value = false\n}\n\nconst handleDrop = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = false\n const files = Array.from(e.dataTransfer?.files || [])\n processFiles(files)\n}\n\nfunction handleClickPicker() {\n if (props.disabled) return\n fileRef.value?.click()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EAmCA,MAAM,QAAQ;EAKd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,cAAa;EAE7B,MAAM,UAAU,YAA6B;EAC7C,MAAM,aAAa,IAAI,MAAK;EAE5B,MAAM,gBAAgB,UAAkB;GAEtC,KAAK,QADiB,MAAM,QAAQ,SAAS,YAAY,MAAM,MAAM,OAAO,CAClD,CAAA;;EAG5B,MAAM,gBAAgB,MAAa;GACjC,MAAM,SAAS,EAAE;GAEjB,aADc,MAAM,KAAK,OAAO,SAAS,EAAE,CACzB,CAAA;GAClB,OAAO,QAAQ;;EAGjB,MAAM,kBAAkB,MAAiB;GACvC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;;EAGrB,MAAM,mBAAmB,MAAiB;GACxC,IAAI,MAAM,UAAU;GACpB,MAAM,gBAAgB,EAAE;GACxB,MAAM,gBAAgB,EAAE;GAExB,IAAI,cAAc,SAAS,cAAa,EAAG;GAC3C,WAAW,QAAQ;;EAGrB,MAAM,cAAc,MAAiB;GACnC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;GAEnB,aADc,MAAM,KAAK,EAAE,cAAc,SAAS,EAAE,CAClC,CAAA;;EAGpB,SAAS,oBAAoB;GAC3B,IAAI,MAAM,UAAU;GACpB,QAAQ,OAAO,OAAM;;;uBAjFrB,YAoBY,wBAnBL,QAAA,IAAG,EAAA;IACP,OAAK,eAAE,MAAA,IAAA,CAAI,EAAC;IACZ,SAAK,cAAO,mBAAiB,CAAA,OAAA,CAAA;IAC7B,YAAQ,cAAU,gBAAc,CAAA,UAAA,CAAA;IAChC,aAAW;IACX,QAAI,cAAU,YAAU,CAAA,UAAA,CAAA;;2BAYvB,CAVF,mBAUE,SAAA;KATC,UAAU,QAAA;KACV,OAAK,eAAE,MAAA,IAAA,CAAI,EAAC,SAAA,CAAA;KACb,MAAK;KACJ,QAAQ,QAAA;KACT,QAAA;KACA,aAAY;cACR;KAAJ,KAAI;KACH,UAAQ;KACT,SAAQ;8BAEV,WAAkC,KAAA,QAAA,WAAA,EAA3B,YAAa,WAAA,OAAU,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/file-picker/helper.ts"],"sourcesContent":["/**\n * 校验文件是否匹配 accept 字符串(支持 MIME 类型与通配符)。\n *\n * 支持的形式:\n * - `*` / `*\\/*`:接受所有文件\n * - `image/*`:MIME 类型组匹配\n * - `application/pdf`:精确 MIME 类型\n * - `.pdf` / `.docx`:扩展名匹配(不区分大小写)\n *\n * 多个值以逗号分隔,命中任一即视为匹配。\n */\nexport function matchAccept(file: File, accept?: string): boolean {\n if (!accept) return true\n\n const fileType = file.type.toLowerCase()\n const fileName = file.name.toLowerCase()\n const acceptList = accept\n .split(',')\n .map((item) => item.trim().toLowerCase())\n .filter(Boolean)\n\n if (!acceptList.length) return true\n\n return acceptList.some((item) => {\n if (item === '*' || item === '*/*') return true\n if (item.startsWith('.')) return fileName.endsWith(item)\n if (item.endsWith('/*')) {\n const group = item.slice(0, -2)\n return fileType.startsWith(`${group}/`)\n }\n return fileType === item\n })\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,YAAY,MAAY,QAA0B;AAChE,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,WAAW,KAAK,KAAK,aAAa;CACxC,MAAM,WAAW,KAAK,KAAK,aAAa;CACxC,MAAM,aAAa,OAChB,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,MAAM,CAAC,aAAa,CAAC,CACxC,OAAO,QAAQ;AAElB,KAAI,CAAC,WAAW,OAAQ,QAAO;AAE/B,QAAO,WAAW,MAAM,SAAS;AAC/B,MAAI,SAAS,OAAO,SAAS,MAAO,QAAO;AAC3C,MAAI,KAAK,WAAW,IAAI,CAAE,QAAO,SAAS,SAAS,KAAK;AACxD,MAAI,KAAK,SAAS,KAAK,EAAE;GACvB,MAAM,QAAQ,KAAK,MAAM,GAAG,GAAG;AAC/B,UAAO,SAAS,WAAW,GAAG,MAAM,GAAG;;AAEzC,SAAO,aAAa;GACpB"}
1
+ {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/file-picker/helper.ts"],"sourcesContent":["/**\n * 校验文件是否匹配 accept 字符串(支持 MIME 类型与通配符)。\n *\n * 支持的形式:\n * - `*` / `*\\/*`:接受所有文件\n * - `image/*`:MIME 类型组匹配\n * - `application/pdf`:精确 MIME 类型\n * - `.pdf` / `.docx`:扩展名匹配(不区分大小写)\n *\n * 多个值以逗号分隔,命中任一即视为匹配。\n */\nexport function matchAccept(file: File, accept?: string): boolean {\n if (!accept) return true\n\n const fileType = file.type.toLowerCase()\n const fileName = file.name.toLowerCase()\n const acceptList = accept\n .split(',')\n .map((item) => item.trim().toLowerCase())\n .filter(Boolean)\n\n if (!acceptList.length) return true\n\n return acceptList.some((item) => {\n if (item === '*' || item === '*/*') return true\n if (item.startsWith('.')) return fileName.endsWith(item)\n if (item.endsWith('/*')) {\n const group = item.slice(0, -2)\n return fileType.startsWith(`${group}/`)\n }\n return fileType === item\n })\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,YAAY,MAAY,QAA0B;CAChE,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,WAAW,KAAK,KAAK,aAAa;CACxC,MAAM,WAAW,KAAK,KAAK,aAAa;CACxC,MAAM,aAAa,OAChB,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,MAAM,CAAC,aAAa,CAAC,CACxC,OAAO,QAAQ;CAElB,IAAI,CAAC,WAAW,QAAQ,OAAO;CAE/B,OAAO,WAAW,MAAM,SAAS;EAC/B,IAAI,SAAS,OAAO,SAAS,OAAO,OAAO;EAC3C,IAAI,KAAK,WAAW,IAAI,EAAE,OAAO,SAAS,SAAS,KAAK;EACxD,IAAI,KAAK,SAAS,KAAK,EAAE;GACvB,MAAM,QAAQ,KAAK,MAAM,GAAG,GAAG;GAC/B,OAAO,SAAS,WAAW,GAAG,MAAM,GAAG;;EAEzC,OAAO,aAAa;GACpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-viewer.js","names":[],"sources":["../../../src/components/file-viewer/file-viewer.vue"],"sourcesContent":["<template>\n <Teleport to=\"body\" :disabled=\"!isModal\">\n <transition :name=\"isModal ? 'u-file-viewer-modal' : ''\">\n <div\n v-if=\"!isModal || openModel\"\n :class=\"[cls.b, bem.is('modal', isModal)]\"\n :tabindex=\"isModal ? -1 : undefined\"\n ref=\"rootRef\"\n @mousedown.self=\"handleBackdropMousedown\"\n >\n <div v-if=\"isModal\" :class=\"cls.e('backdrop')\" @mousedown.self=\"handleBackdropMousedown\" />\n <div :class=\"cls.e('inner')\">\n <aside v-if=\"showSidebar\" :class=\"cls.e('sidebar')\" :style=\"{ width: sidebarWidthCss }\">\n <header :class=\"cls.e('sidebar-head')\">\n <span :class=\"cls.e('sidebar-title')\">文件</span>\n <span :class=\"cls.e('sidebar-count')\">{{ normalizedFiles.length }}</span>\n </header>\n <u-scroll tag=\"ul\" :class=\"cls.e('list')\">\n <li\n v-for=\"f in normalizedFiles\"\n :key=\"f.id\"\n :class=\"[cls.e('item'), bem.is('active', f.id === activeId)]\"\n :title=\"f.name\"\n @click=\"activate(f.id)\"\n >\n <span :class=\"cls.e('item-meta')\">\n <span :class=\"cls.e('item-name')\" :title=\"f.name\">{{ f.name }}</span>\n <span :class=\"cls.e('item-size')\">{{ formatBytes(f.size) }}</span>\n </span>\n <span :class=\"cls.e('item-kind')\">{{ label(f.kind) }}</span>\n </li>\n <li v-if=\"!normalizedFiles.length\" :class=\"cls.e('list-empty')\">\n <u-empty text=\"暂无文件\" :size=\"32\" />\n </li>\n </u-scroll>\n </aside>\n\n <section :class=\"cls.e('stage')\">\n <header :class=\"cls.e('stage-head')\">\n <div :class=\"cls.e('stage-title')\">\n <span v-if=\"activeFile\" :class=\"[cls.e('badge'), cls.em('badge', activeFile.kind)]\">\n {{ label(activeFile.kind) }}\n </span>\n <span :class=\"cls.e('stage-copy')\">\n <span :class=\"cls.e('stage-name')\" :title=\"activeFile?.name\">\n {{ activeFile?.name ?? '—' }}\n </span>\n <span v-if=\"activeFile\" :class=\"cls.e('stage-sub')\">\n <span>{{ activeIndexLabel }}</span>\n <span v-if=\"activeFile.size\">{{ formatBytes(activeFile.size) }}</span>\n </span>\n </span>\n </div>\n <div :class=\"cls.e('stage-actions')\">\n <span :class=\"cls.e('action-group')\">\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"!hasPrev\"\n type=\"button\"\n aria-label=\"上一个\"\n title=\"上一个\"\n @click=\"prev\"\n >\n <u-icon :size=\"15\">\n <ArrowLeft />\n </u-icon>\n </button>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"!hasNext\"\n type=\"button\"\n aria-label=\"下一个\"\n title=\"下一个\"\n @click=\"next\"\n >\n <u-icon :size=\"15\">\n <ArrowRight />\n </u-icon>\n </button>\n </span>\n <span v-if=\"isZoomable\" :class=\"cls.e('action-group')\">\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"zoomOutDisabled\"\n type=\"button\"\n aria-label=\"缩小\"\n title=\"缩小\"\n @click=\"zoomOut\"\n >\n <u-icon :size=\"15\">\n <ZoomOut />\n </u-icon>\n </button>\n <span :class=\"cls.e('zoom-value')\">{{ zoomPercent }}</span>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"zoomInDisabled\"\n type=\"button\"\n aria-label=\"放大\"\n title=\"放大\"\n @click=\"zoomIn\"\n >\n <u-icon :size=\"15\">\n <ZoomIn />\n </u-icon>\n </button>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"isTransformReset\"\n type=\"button\"\n aria-label=\"重置视图\"\n title=\"重置视图\"\n @click=\"resetTransform\"\n >\n <u-icon :size=\"15\">\n <Refresh />\n </u-icon>\n </button>\n </span>\n <button\n v-if=\"downloadable && activeFile\"\n :class=\"[cls.e('action'), cls.em('action', 'primary'), cls.em('action', 'icon')]\"\n type=\"button\"\n aria-label=\"下载\"\n title=\"下载\"\n @click=\"download\"\n >\n <u-icon :size=\"15\">\n <Download />\n </u-icon>\n </button>\n <button\n v-if=\"isModal\"\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n type=\"button\"\n aria-label=\"关闭预览\"\n title=\"关闭\"\n @click=\"handleClose\"\n >\n <u-icon :size=\"16\">\n <Close />\n </u-icon>\n </button>\n </div>\n </header>\n <div :class=\"cls.e('body')\">\n <transition name=\"u-file-viewer-fade\" mode=\"out-in\">\n <div\n v-if=\"activeFile\"\n :key=\"activeFile.id\"\n :class=\"[\n cls.e('viewport'),\n bem.is('transformable', isTransformable),\n bem.is('pannable', canPan),\n bem.is('dragging', isDragging)\n ]\"\n @pointerdown.capture=\"handleViewportPointerDown\"\n @pointermove.capture=\"handleViewportPointerMove\"\n @pointerup.capture=\"handleViewportPointerEnd\"\n @pointercancel.capture=\"handleViewportPointerEnd\"\n @wheel=\"handleViewportWheel\"\n @dblclick=\"handleViewportDblclick\"\n >\n <component\n :is=\"PreviewerMap[activeFile.kind]\"\n ref=\"previewerRef\"\n :file=\"activeFile\"\n :max-rows=\"sheetMaxRows\"\n :class=\"isTransformable ? cls.e('previewer') : undefined\"\n :style=\"previewerStyle\"\n @error=\"handleChildError\"\n @zoom-change=\"handlePdfZoomChange\"\n />\n </div>\n <div v-else :class=\"cls.e('empty')\">\n <u-empty text=\"暂无可预览文件\" />\n </div>\n </transition>\n </div>\n </section>\n </div>\n </div>\n </transition>\n </Teleport>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n ArrowLeft,\n ArrowRight,\n Close,\n Download,\n Refresh,\n ZoomIn,\n ZoomOut\n} from '@veltra/icons/normal'\nimport { bem, withUnit } from '@veltra/utils'\nimport {\n computed,\n defineAsyncComponent,\n nextTick,\n onBeforeUnmount,\n ref,\n shallowRef,\n watch\n} from 'vue'\nimport type { CSSProperties } from 'vue'\n\nimport type {\n _FileViewerExposed,\n FileViewerEmits,\n FileViewerItem,\n FileViewerKind,\n FileViewerProps\n} from '../../types/file-viewer'\nimport { UEmpty } from '../empty'\nimport { UIcon } from '../icon'\nimport { UScroll } from '../scroll'\nimport { FILE_VIEWER_KIND_LABEL, downloadFile, formatBytes, inferKind } from './helper'\n\ndefineOptions({\n name: 'FileViewer',\n inheritAttrs: false\n})\n\nconst props = withDefaults(defineProps<FileViewerProps>(), {\n modelValue: undefined,\n sidebarWidth: '280px',\n sheetMaxRows: 50_000,\n downloadable: true,\n open: undefined,\n closeOnClickBackdrop: true,\n closeOnEsc: true\n})\n\nconst emit = defineEmits<FileViewerEmits>()\n\nconst cls = bem('file-viewer')\n\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 3\n/** 每次缩放固定增减 10% */\nconst SCALE_STEP = 0.1\n/** 使用 CSS transform 缩放的类型(PDF 由 EmbedPDF zoom 插件处理) */\nconst TRANSFORMABLE_KINDS = new Set<FileViewerKind>(['image'])\n/** 工具栏显示缩放控件的类型 */\nconst ZOOMABLE_KINDS = new Set<FileViewerKind>(['image', 'pdf'])\n\nconst activeId = defineModel<string | undefined>('modelValue', { default: undefined })\nconst openModel = defineModel<boolean | undefined>('open', { default: undefined })\n\nconst rootRef = shallowRef<HTMLDivElement>()\nconst previewerRef = shallowRef<{\n zoomIn?: () => void\n zoomOut?: () => void\n resetZoom?: () => void\n}>()\nconst scale = ref(1)\nconst pdfZoomLevel = ref(1)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\nconst isDragging = ref(false)\n\nlet dragState:\n | {\n pointerId: number\n startX: number\n startY: number\n originX: number\n originY: number\n }\n | undefined\n\nconst PreviewerMap: Record<FileViewerKind, ReturnType<typeof defineAsyncComponent>> = {\n image: defineAsyncComponent(() => import('./previewers/image-previewer.vue')),\n video: defineAsyncComponent(() => import('./previewers/video-previewer.vue')),\n pdf: defineAsyncComponent(() => import('./previewers/pdf-previewer.vue')),\n sheet: defineAsyncComponent(() => import('./previewers/sheet-previewer.vue')),\n docx: defineAsyncComponent(() => import('./previewers/docx-previewer.vue')),\n text: defineAsyncComponent(() => import('./previewers/text-previewer.vue'))\n}\n\ninterface NormalizedFile extends FileViewerItem {\n id: string\n kind: FileViewerKind\n}\n\nconst normalizedFiles = computed<NormalizedFile[]>(() =>\n props.files.map((f, i) => ({\n ...f,\n id: f.id ?? `file-${i}`,\n kind: inferKind(f.name, f.kind)\n }))\n)\n\nconst activeFile = computed<NormalizedFile | undefined>(() =>\n normalizedFiles.value.find((f) => f.id === activeId.value)\n)\n\nconst activeIndex = computed(() => normalizedFiles.value.findIndex((f) => f.id === activeId.value))\n\nconst hasPrev = computed(() => activeIndex.value > 0)\nconst hasNext = computed(\n () => activeIndex.value >= 0 && activeIndex.value < normalizedFiles.value.length - 1\n)\n\nconst showSidebar = computed(() => props.sidebarWidth !== false && props.sidebarWidth !== 0)\n\nconst sidebarWidthCss = computed(() => {\n const w = props.sidebarWidth\n if (w === false || w === 0) return undefined\n return withUnit(w ?? '280px', 'px')\n})\n\n/** 是否启用模态模式:只要父组件显式传入 open(含 v-model:open),即进入模态 */\nconst isModal = computed(() => openModel.value !== undefined)\n\nconst activeIndexLabel = computed(() =>\n activeIndex.value >= 0 ? `${activeIndex.value + 1} / ${normalizedFiles.value.length}` : ''\n)\n\nconst isTransformable = computed(\n () => !!activeFile.value && TRANSFORMABLE_KINDS.has(activeFile.value.kind)\n)\n\nconst isZoomable = computed(() => !!activeFile.value && ZOOMABLE_KINDS.has(activeFile.value.kind))\n\nconst isPdfActive = computed(() => activeFile.value?.kind === 'pdf')\n\nconst canPan = computed(() => isTransformable.value && scale.value > 1)\n\nconst displayZoomLevel = computed(() => (isPdfActive.value ? pdfZoomLevel.value : scale.value))\n\nconst zoomPercent = computed(() => `${Math.round(displayZoomLevel.value * 100)}%`)\n\nconst zoomInDisabled = computed(() => !isZoomable.value || displayZoomLevel.value >= MAX_SCALE)\n\nconst zoomOutDisabled = computed(() => !isZoomable.value || displayZoomLevel.value <= MIN_SCALE)\n\nconst isTransformReset = computed(() => {\n if (isPdfActive.value) {\n return Math.abs(pdfZoomLevel.value - 1) < 0.02\n }\n return scale.value === 1 && offsetX.value === 0 && offsetY.value === 0\n})\n\nconst previewerStyle = computed<CSSProperties | undefined>(() => {\n if (!isTransformable.value) return undefined\n return {\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value})`\n }\n})\n\nfunction label(kind: FileViewerKind): string {\n return FILE_VIEWER_KIND_LABEL[kind]\n}\n\nfunction activate(id: string) {\n if (activeId.value === id) return\n activeId.value = id\n const f = normalizedFiles.value.find((x) => x.id === id)\n if (f) emit('change', f)\n}\n\nfunction prev() {\n const i = activeIndex.value\n if (i <= 0) return\n const target = normalizedFiles.value[i - 1]\n if (target) activate(target.id)\n}\n\nfunction next() {\n const i = activeIndex.value\n if (i < 0 || i >= normalizedFiles.value.length - 1) return\n const target = normalizedFiles.value[i + 1]\n if (target) activate(target.id)\n}\n\nfunction normalizeScale(value: number): number {\n const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, value))\n return Math.round(clamped * 100) / 100\n}\n\nfunction setScale(value: number) {\n scale.value = normalizeScale(value)\n if (scale.value <= 1) {\n offsetX.value = 0\n offsetY.value = 0\n }\n}\n\nfunction zoomIn() {\n if (!isZoomable.value) return\n if (isPdfActive.value) {\n previewerRef.value?.zoomIn?.()\n return\n }\n setScale(scale.value + SCALE_STEP)\n}\n\nfunction zoomOut() {\n if (!isZoomable.value) return\n if (isPdfActive.value) {\n previewerRef.value?.zoomOut?.()\n return\n }\n setScale(scale.value - SCALE_STEP)\n}\n\nfunction resetTransform() {\n if (isPdfActive.value) {\n previewerRef.value?.resetZoom?.()\n pdfZoomLevel.value = 1\n return\n }\n scale.value = 1\n offsetX.value = 0\n offsetY.value = 0\n dragState = undefined\n isDragging.value = false\n}\n\nfunction handlePdfZoomChange(level: number) {\n pdfZoomLevel.value = level\n}\n\nfunction isZoomWheel(e: WheelEvent): boolean {\n return e.ctrlKey || e.metaKey\n}\n\nfunction handleViewportWheel(e: WheelEvent) {\n if (!activeFile.value || activeFile.value.kind !== 'image') return\n if (!isZoomWheel(e)) return\n if (isInteractiveTarget(e.target)) return\n e.preventDefault()\n const delta = e.deltaY > 0 ? -SCALE_STEP : SCALE_STEP\n setScale(scale.value + delta)\n}\n\nfunction isInteractiveTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false\n return !!target.closest('button, a, input, textarea, select, video, [contenteditable=\"true\"]')\n}\n\nfunction handleViewportPointerDown(e: PointerEvent) {\n if (!canPan.value || e.button !== 0 || isInteractiveTarget(e.target)) return\n\n dragState = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n originX: offsetX.value,\n originY: offsetY.value\n }\n isDragging.value = true\n const el = e.currentTarget as HTMLElement\n el.setPointerCapture(e.pointerId)\n e.preventDefault()\n}\n\nfunction handleViewportPointerMove(e: PointerEvent) {\n if (!dragState || dragState.pointerId !== e.pointerId) return\n offsetX.value = dragState.originX + e.clientX - dragState.startX\n offsetY.value = dragState.originY + e.clientY - dragState.startY\n}\n\nfunction handleViewportPointerEnd(e: PointerEvent) {\n if (!dragState || dragState.pointerId !== e.pointerId) return\n const el = e.currentTarget as HTMLElement\n if (el.hasPointerCapture(e.pointerId)) {\n el.releasePointerCapture(e.pointerId)\n }\n dragState = undefined\n isDragging.value = false\n}\n\nfunction handleViewportDblclick() {\n if (isPdfActive.value) return\n if (!isTransformable.value) return\n if (scale.value === 1) {\n setScale(2)\n } else {\n resetTransform()\n }\n}\n\nasync function download() {\n if (!activeFile.value) return\n try {\n await downloadFile(activeFile.value)\n } catch (err) {\n emit('error', { file: activeFile.value, error: err })\n }\n}\n\nfunction handleChildError(err: unknown) {\n if (!activeFile.value) return\n emit('error', { file: activeFile.value, error: err })\n}\n\nfunction handleClose() {\n if (!isModal.value) return\n openModel.value = false\n}\n\nfunction handleWindowKeydown(e: KeyboardEvent) {\n if (e.key !== 'Escape') return\n if (!isModal.value || !openModel.value) return\n if (props.closeOnEsc === false) return\n e.stopPropagation()\n handleClose()\n}\n\nfunction handleBackdropMousedown() {\n if (!isModal.value) return\n if (props.closeOnClickBackdrop === false) return\n handleClose()\n}\n\nwatch(\n normalizedFiles,\n (files) => {\n if (!files.length) {\n activeId.value = undefined\n return\n }\n if (!activeId.value || !files.some((f) => f.id === activeId.value)) {\n activeId.value = files[0]!.id\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => activeFile.value?.id,\n async () => {\n pdfZoomLevel.value = 1\n await nextTick()\n resetTransform()\n }\n)\n\n// 模态打开时:锁定 body 滚动 + 让容器获得焦点以便接收 ESC\nlet previousBodyOverflow = ''\nlet isBodyLocked = false\n\nfunction lockBody() {\n if (isBodyLocked || typeof document === 'undefined') return\n previousBodyOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n isBodyLocked = true\n}\n\nfunction unlockBody() {\n if (!isBodyLocked || typeof document === 'undefined') return\n document.body.style.overflow = previousBodyOverflow\n isBodyLocked = false\n}\n\nwatch(\n [isModal, openModel],\n ([modal, open]) => {\n if (modal && open) {\n lockBody()\n nextTick(() => {\n rootRef.value?.focus()\n })\n if (typeof window !== 'undefined') {\n window.addEventListener('keydown', handleWindowKeydown, true)\n }\n } else {\n unlockBody()\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', handleWindowKeydown, true)\n }\n }\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n unlockBody()\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', handleWindowKeydown, true)\n }\n})\n\ndefineExpose<_FileViewerExposed>({\n activeId,\n activate,\n next,\n prev\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+OA,MAAM,YAAY;AAClB,MAAM,YAAY;;AAElB,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAjBnB,MAAM,QAAQ;EAUd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,cAAa;EAO7B,MAAM,sBAAsB,IAAI,IAAoB,CAAC,QAAQ,CAAA;;EAE7D,MAAM,iBAAiB,IAAI,IAAoB,CAAC,SAAS,MAAM,CAAA;EAE/D,MAAM,WAAW,SAA+B,SAAC,aAAoC;EACrF,MAAM,YAAY,SAAgC,SAAC,OAA8B;EAEjF,MAAM,UAAU,YAA2B;EAC3C,MAAM,eAAe,YAIlB;EACH,MAAM,QAAQ,IAAI,EAAC;EACnB,MAAM,eAAe,IAAI,EAAC;EAC1B,MAAM,UAAU,IAAI,EAAC;EACrB,MAAM,UAAU,IAAI,EAAC;EACrB,MAAM,aAAa,IAAI,MAAK;EAE5B,IAAI;EAUJ,MAAM,eAAgF;GACpF,OAAO,2BAA2B,OAAO,mCAAoC;GAC7E,OAAO,2BAA2B,OAAO,mCAAoC;GAC7E,KAAK,2BAA2B,OAAO,iCAAkC;GACzE,OAAO,2BAA2B,OAAO,mCAAoC;GAC7E,MAAM,2BAA2B,OAAO,kCAAmC;GAC3E,MAAM,2BAA2B,OAAO,kCAAkC;GAC5E;EAOA,MAAM,kBAAkB,eACtB,MAAM,MAAM,KAAK,GAAG,OAAO;GACzB,GAAG;GACH,IAAI,EAAE,MAAM,QAAQ;GACpB,MAAM,UAAU,EAAE,MAAM,EAAE,KAAI;GAC/B,EAAC,CACJ;EAEA,MAAM,aAAa,eACjB,gBAAgB,MAAM,MAAM,MAAM,EAAE,OAAO,SAAS,MAAK,CAC3D;EAEA,MAAM,cAAc,eAAe,gBAAgB,MAAM,WAAW,MAAM,EAAE,OAAO,SAAS,MAAM,CAAA;EAElG,MAAM,UAAU,eAAe,YAAY,QAAQ,EAAC;EACpD,MAAM,UAAU,eACR,YAAY,SAAS,KAAK,YAAY,QAAQ,gBAAgB,MAAM,SAAS,EACrF;EAEA,MAAM,cAAc,eAAe,MAAM,iBAAiB,SAAS,MAAM,iBAAiB,EAAC;EAE3F,MAAM,kBAAkB,eAAe;GACrC,MAAM,IAAI,MAAM;AAChB,OAAI,MAAM,SAAS,MAAM,EAAG,QAAO,KAAA;AACnC,UAAO,SAAS,KAAK,SAAS,KAAI;IACnC;;EAGD,MAAM,UAAU,eAAe,UAAU,UAAU,KAAA,EAAS;EAE5D,MAAM,mBAAmB,eACvB,YAAY,SAAS,IAAI,GAAG,YAAY,QAAQ,EAAE,KAAK,gBAAgB,MAAM,WAAW,GAC1F;EAEA,MAAM,kBAAkB,eAChB,CAAC,CAAC,WAAW,SAAS,oBAAoB,IAAI,WAAW,MAAM,KAAI,CAC3E;EAEA,MAAM,aAAa,eAAe,CAAC,CAAC,WAAW,SAAS,eAAe,IAAI,WAAW,MAAM,KAAK,CAAA;EAEjG,MAAM,cAAc,eAAe,WAAW,OAAO,SAAS,MAAK;EAEnE,MAAM,SAAS,eAAe,gBAAgB,SAAS,MAAM,QAAQ,EAAC;EAEtE,MAAM,mBAAmB,eAAgB,YAAY,QAAQ,aAAa,QAAQ,MAAM,MAAM;EAE9F,MAAM,cAAc,eAAe,GAAG,KAAK,MAAM,iBAAiB,QAAQ,IAAI,CAAC,GAAE;EAEjF,MAAM,iBAAiB,eAAe,CAAC,WAAW,SAAS,iBAAiB,SAAS,UAAS;EAE9F,MAAM,kBAAkB,eAAe,CAAC,WAAW,SAAS,iBAAiB,SAAS,UAAS;EAE/F,MAAM,mBAAmB,eAAe;AACtC,OAAI,YAAY,MACd,QAAO,KAAK,IAAI,aAAa,QAAQ,EAAE,GAAG;AAE5C,UAAO,MAAM,UAAU,KAAK,QAAQ,UAAU,KAAK,QAAQ,UAAU;IACtE;EAED,MAAM,iBAAiB,eAA0C;AAC/D,OAAI,CAAC,gBAAgB,MAAO,QAAO,KAAA;AACnC,UAAO,EACL,WAAW,eAAe,QAAQ,MAAM,MAAM,QAAQ,MAAM,eAAe,MAAM,MAAM,IACzF;IACD;EAED,SAAS,MAAM,MAA8B;AAC3C,UAAO,uBAAuB;;EAGhC,SAAS,SAAS,IAAY;AAC5B,OAAI,SAAS,UAAU,GAAI;AAC3B,YAAS,QAAQ;GACjB,MAAM,IAAI,gBAAgB,MAAM,MAAM,MAAM,EAAE,OAAO,GAAE;AACvD,OAAI,EAAG,MAAK,UAAU,EAAC;;EAGzB,SAAS,OAAO;GACd,MAAM,IAAI,YAAY;AACtB,OAAI,KAAK,EAAG;GACZ,MAAM,SAAS,gBAAgB,MAAM,IAAI;AACzC,OAAI,OAAQ,UAAS,OAAO,GAAE;;EAGhC,SAAS,OAAO;GACd,MAAM,IAAI,YAAY;AACtB,OAAI,IAAI,KAAK,KAAK,gBAAgB,MAAM,SAAS,EAAG;GACpD,MAAM,SAAS,gBAAgB,MAAM,IAAI;AACzC,OAAI,OAAQ,UAAS,OAAO,GAAE;;EAGhC,SAAS,eAAe,OAAuB;AAE7C,UAAO,KAAK,MADI,KAAK,IAAI,WAAW,KAAK,IAAI,WAAW,MAAM,CAC5C,GAAU,IAAI,GAAG;;EAGrC,SAAS,SAAS,OAAe;AAC/B,SAAM,QAAQ,eAAe,MAAK;AAClC,OAAI,MAAM,SAAS,GAAG;AACpB,YAAQ,QAAQ;AAChB,YAAQ,QAAQ;;;EAIpB,SAAS,SAAS;AAChB,OAAI,CAAC,WAAW,MAAO;AACvB,OAAI,YAAY,OAAO;AACrB,iBAAa,OAAO,UAAS;AAC7B;;AAEF,YAAS,MAAM,QAAQ,WAAU;;EAGnC,SAAS,UAAU;AACjB,OAAI,CAAC,WAAW,MAAO;AACvB,OAAI,YAAY,OAAO;AACrB,iBAAa,OAAO,WAAU;AAC9B;;AAEF,YAAS,MAAM,QAAQ,WAAU;;EAGnC,SAAS,iBAAiB;AACxB,OAAI,YAAY,OAAO;AACrB,iBAAa,OAAO,aAAY;AAChC,iBAAa,QAAQ;AACrB;;AAEF,SAAM,QAAQ;AACd,WAAQ,QAAQ;AAChB,WAAQ,QAAQ;AAChB,eAAY,KAAA;AACZ,cAAW,QAAQ;;EAGrB,SAAS,oBAAoB,OAAe;AAC1C,gBAAa,QAAQ;;EAGvB,SAAS,YAAY,GAAwB;AAC3C,UAAO,EAAE,WAAW,EAAE;;EAGxB,SAAS,oBAAoB,GAAe;AAC1C,OAAI,CAAC,WAAW,SAAS,WAAW,MAAM,SAAS,QAAS;AAC5D,OAAI,CAAC,YAAY,EAAE,CAAE;AACrB,OAAI,oBAAoB,EAAE,OAAO,CAAE;AACnC,KAAE,gBAAe;GACjB,MAAM,QAAQ,EAAE,SAAS,IAAI,CAAC,aAAa;AAC3C,YAAS,MAAM,QAAQ,MAAK;;EAG9B,SAAS,oBAAoB,QAAqC;AAChE,OAAI,EAAE,kBAAkB,aAAc,QAAO;AAC7C,UAAO,CAAC,CAAC,OAAO,QAAQ,wEAAqE;;EAG/F,SAAS,0BAA0B,GAAiB;AAClD,OAAI,CAAC,OAAO,SAAS,EAAE,WAAW,KAAK,oBAAoB,EAAE,OAAO,CAAE;AAEtE,eAAY;IACV,WAAW,EAAE;IACb,QAAQ,EAAE;IACV,QAAQ,EAAE;IACV,SAAS,QAAQ;IACjB,SAAS,QAAQ;IACnB;AACA,cAAW,QAAQ;AACR,KAAE,cACV,kBAAkB,EAAE,UAAS;AAChC,KAAE,gBAAe;;EAGnB,SAAS,0BAA0B,GAAiB;AAClD,OAAI,CAAC,aAAa,UAAU,cAAc,EAAE,UAAW;AACvD,WAAQ,QAAQ,UAAU,UAAU,EAAE,UAAU,UAAU;AAC1D,WAAQ,QAAQ,UAAU,UAAU,EAAE,UAAU,UAAU;;EAG5D,SAAS,yBAAyB,GAAiB;AACjD,OAAI,CAAC,aAAa,UAAU,cAAc,EAAE,UAAW;GACvD,MAAM,KAAK,EAAE;AACb,OAAI,GAAG,kBAAkB,EAAE,UAAU,CACnC,IAAG,sBAAsB,EAAE,UAAS;AAEtC,eAAY,KAAA;AACZ,cAAW,QAAQ;;EAGrB,SAAS,yBAAyB;AAChC,OAAI,YAAY,MAAO;AACvB,OAAI,CAAC,gBAAgB,MAAO;AAC5B,OAAI,MAAM,UAAU,EAClB,UAAS,EAAC;OAEV,iBAAe;;EAInB,eAAe,WAAW;AACxB,OAAI,CAAC,WAAW,MAAO;AACvB,OAAI;AACF,UAAM,aAAa,WAAW,MAAK;YAC5B,KAAK;AACZ,SAAK,SAAS;KAAE,MAAM,WAAW;KAAO,OAAO;KAAK,CAAA;;;EAIxD,SAAS,iBAAiB,KAAc;AACtC,OAAI,CAAC,WAAW,MAAO;AACvB,QAAK,SAAS;IAAE,MAAM,WAAW;IAAO,OAAO;IAAK,CAAA;;EAGtD,SAAS,cAAc;AACrB,OAAI,CAAC,QAAQ,MAAO;AACpB,aAAU,QAAQ;;EAGpB,SAAS,oBAAoB,GAAkB;AAC7C,OAAI,EAAE,QAAQ,SAAU;AACxB,OAAI,CAAC,QAAQ,SAAS,CAAC,UAAU,MAAO;AACxC,OAAI,MAAM,eAAe,MAAO;AAChC,KAAE,iBAAgB;AAClB,gBAAY;;EAGd,SAAS,0BAA0B;AACjC,OAAI,CAAC,QAAQ,MAAO;AACpB,OAAI,MAAM,yBAAyB,MAAO;AAC1C,gBAAY;;AAGd,QACE,kBACC,UAAU;AACT,OAAI,CAAC,MAAM,QAAQ;AACjB,aAAS,QAAQ,KAAA;AACjB;;AAEF,OAAI,CAAC,SAAS,SAAS,CAAC,MAAM,MAAM,MAAM,EAAE,OAAO,SAAS,MAAM,CAChE,UAAS,QAAQ,MAAM,GAAI;KAG/B,EAAE,WAAW,MAAK,CACpB;AAEA,cACQ,WAAW,OAAO,IACxB,YAAY;AACV,gBAAa,QAAQ;AACrB,SAAM,UAAS;AACf,mBAAe;IAEnB;EAGA,IAAI,uBAAuB;EAC3B,IAAI,eAAe;EAEnB,SAAS,WAAW;AAClB,OAAI,gBAAgB,OAAO,aAAa,YAAa;AACrD,0BAAuB,SAAS,KAAK,MAAM;AAC3C,YAAS,KAAK,MAAM,WAAW;AAC/B,kBAAe;;EAGjB,SAAS,aAAa;AACpB,OAAI,CAAC,gBAAgB,OAAO,aAAa,YAAa;AACtD,YAAS,KAAK,MAAM,WAAW;AAC/B,kBAAe;;AAGjB,QACE,CAAC,SAAS,UAAU,GACnB,CAAC,OAAO,UAAU;AACjB,OAAI,SAAS,MAAM;AACjB,cAAS;AACT,mBAAe;AACb,aAAQ,OAAO,OAAM;MACtB;AACD,QAAI,OAAO,WAAW,YACpB,QAAO,iBAAiB,WAAW,qBAAqB,KAAI;UAEzD;AACL,gBAAW;AACX,QAAI,OAAO,WAAW,YACpB,QAAO,oBAAoB,WAAW,qBAAqB,KAAI;;KAIrE,EAAE,WAAW,MAAK,CACpB;AAEA,wBAAsB;AACpB,eAAW;AACX,OAAI,OAAO,WAAW,YACpB,QAAO,oBAAoB,WAAW,qBAAqB,KAAI;IAElE;AAED,WAAiC;GAC/B;GACA;GACA;GACA;GACD,CAAA;;uBA/kBC,YAsLW,UAAA;IAtLD,IAAG;IAAQ,UAAQ,CAAG,QAAA;OAC9B,YAoLa,YAAA,EApLA,MAAM,QAAA,QAAO,wBAAA,IAAA,EAAA;2BAmLlB,CAAA,CAjLG,QAAA,SAAW,UAAA,SAAA,WAAA,EADpB,mBAkLM,OAAA;;KAhLH,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,GAAG,MAAA,IAAG,CAAC,GAAE,SAAU,QAAA,MAAO,CAAA,CAAA;KACtC,UAAU,QAAA,QAAO,KAAQ,KAAA;cACtB;KAAJ,KAAI;KACH,aAAS,cAAO,yBAAuB,CAAA,OAAA,CAAA;QAE7B,QAAA,SAAA,WAAA,EAAX,mBAA2F,OAAA;;KAAtE,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,WAAA,CAAA;KAAe,aAAS,cAAO,yBAAuB,CAAA,OAAA,CAAA;sDACvF,mBAyKM,OAAA,EAzKA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,QAAA,CAAA,EAAA,EAAA,CACH,YAAA,SAAA,WAAA,EAAb,mBAuBQ,SAAA;;KAvBmB,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,UAAA,CAAA;KAAc,OAAK,eAAA,EAAA,OAAW,gBAAA,OAAe,CAAA;QAClF,mBAGS,UAAA,EAHA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,eAAA,CAAA,EAAA,EAAA,CACnB,mBAA+C,QAAA,EAAxC,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,EAAmB,MAAE,EAAA,EACxC,mBAAyE,QAAA,EAAlE,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,EAAA,gBAAsB,gBAAA,MAAgB,OAAM,EAAA,EAAA,CAAA,EAAA,EAAA,EAEjE,YAiBW,MAAA,YAAA,EAAA;KAjBD,KAAI;KAAM,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,OAAA,CAAA;;4BAEA,EAAA,UAAA,KAAA,EAD9B,mBAYK,UAAA,MAAA,WAXS,gBAAA,QAAL,MAAC;0BADV,mBAYK,MAAA;OAVF,KAAK,EAAE;OACP,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,OAAA,EAAU,MAAA,IAAG,CAAC,GAAE,UAAW,EAAE,OAAO,SAAA,MAAQ,CAAA,CAAA;OACzD,OAAO,EAAE;OACT,UAAK,WAAE,SAAS,EAAE,GAAE;UAErB,mBAGO,QAAA,EAHA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,YAAA,CAAA,EAAA,EAAA,CACjB,mBAAqE,QAAA;OAA9D,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,YAAA,CAAA;OAAgB,OAAO,EAAE;yBAAS,EAAE,KAAI,EAAA,IAAA,WAAA,EAC3D,mBAAkE,QAAA,EAA3D,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,YAAA,CAAA,EAAA,EAAA,gBAAkB,MAAA,YAAW,CAAC,EAAE,KAAI,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA,EAEzD,mBAA4D,QAAA,EAArD,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,YAAA,CAAA,EAAA,EAAA,gBAAkB,MAAM,EAAE,KAAI,CAAA,EAAA,EAAA,CAAA,EAAA,IAAA,WAAA;iBAExC,gBAAA,MAAgB,UAAA,WAAA,EAA3B,mBAEK,MAAA;;MAF+B,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,aAAA,CAAA;SAC9C,YAAkC,MAAA,YAAA,EAAA;MAAzB,MAAK;MAAQ,MAAM;;;+DAKlC,mBA8IU,WAAA,EA9IA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,QAAA,CAAA,EAAA,EAAA,CACpB,mBA0GS,UAAA,EA1GA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,aAAA,CAAA,EAAA,EAAA,CACnB,mBAaM,OAAA,EAbA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,cAAA,CAAA,EAAA,EAAA,CACJ,WAAA,SAAA,WAAA,EAAZ,mBAEO,QAAA;;KAFkB,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,QAAA,EAAW,MAAA,IAAG,CAAC,GAAE,SAAU,WAAA,MAAW,KAAI,CAAA,CAAA;uBAC3E,MAAM,WAAA,MAAW,KAAI,CAAA,EAAA,EAAA,IAAA,mBAAA,QAAA,KAAA,EAE1B,mBAQO,QAAA,EARA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,aAAA,CAAA,EAAA,EAAA,CACjB,mBAEO,QAAA;KAFA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,aAAA,CAAA;KAAiB,OAAO,WAAA,OAAY;uBAClD,WAAA,OAAY,QAAI,IAAA,EAAA,IAAA,WAAA,EAET,WAAA,SAAA,WAAA,EAAZ,mBAGO,QAAA;;KAHkB,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,YAAA,CAAA;QACnC,mBAAmC,QAAA,MAAA,gBAA1B,iBAAA,MAAgB,EAAA,EAAA,EACb,WAAA,MAAW,QAAA,WAAA,EAAvB,mBAAsE,QAAA,YAAA,gBAAtC,MAAA,YAAW,CAAC,WAAA,MAAW,KAAI,CAAA,EAAA,EAAA,IAAA,mBAAA,QAAA,KAAA,CAAA,EAAA,EAAA,IAAA,mBAAA,QAAA,KAAA,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA,EAIjE,mBA0FM,OAAA,EA1FA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,EAAA;KAChB,mBAyBO,QAAA,EAzBA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,eAAA,CAAA,EAAA,EAAA,CACjB,mBAWS,UAAA;MAVN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;MAC/B,UAAQ,CAAG,QAAA;MACZ,MAAK;MACL,cAAW;MACX,OAAM;MACL,SAAO;SAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;6BACF,CAAb,YAAa,MAAA,UAAA,CAAA,CAAA,CAAA;;2BAGjB,mBAWS,UAAA;MAVN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;MAC/B,UAAQ,CAAG,QAAA;MACZ,MAAK;MACL,cAAW;MACX,OAAM;MACL,SAAO;SAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;6BACD,CAAd,YAAc,MAAA,WAAA,CAAA,CAAA,CAAA;;;KAIR,WAAA,SAAA,WAAA,EAAZ,mBAsCO,QAAA;;MAtCkB,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,eAAA,CAAA;;MACnC,mBAWS,UAAA;OAVN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;OAC/B,UAAU,gBAAA;OACX,MAAK;OACL,cAAW;OACX,OAAM;OACL,SAAO;UAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;8BACJ,CAAX,YAAW,MAAA,QAAA,CAAA,CAAA,CAAA;;;MAGf,mBAA2D,QAAA,EAApD,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,aAAA,CAAA,EAAA,EAAA,gBAAmB,YAAA,MAAW,EAAA,EAAA;MACjD,mBAWS,UAAA;OAVN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;OAC/B,UAAU,eAAA;OACX,MAAK;OACL,cAAW;OACX,OAAM;OACL,SAAO;UAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;8BACL,CAAV,YAAU,MAAA,OAAA,CAAA,CAAA,CAAA;;;MAGd,mBAWS,UAAA;OAVN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;OAC/B,UAAU,iBAAA;OACX,MAAK;OACL,cAAW;OACX,OAAM;OACL,SAAO;UAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;8BACJ,CAAX,YAAW,MAAA,QAAA,CAAA,CAAA,CAAA;;;;KAKT,QAAA,gBAAgB,WAAA,SAAA,WAAA,EADxB,mBAWS,UAAA;;MATN,OAAK,eAAA;OAAG,MAAA,IAAG,CAAC,EAAC,SAAA;OAAY,MAAA,IAAG,CAAC,GAAE,UAAA,UAAA;OAAuB,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA;OAAA,CAAA;MAC7D,MAAK;MACL,cAAW;MACX,OAAM;MACL,SAAO;SAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;6BACH,CAAZ,YAAY,MAAA,SAAA,CAAA,CAAA,CAAA;;;KAIR,QAAA,SAAA,WAAA,EADR,mBAWS,UAAA;;MATN,OAAK,eAAA,CAAG,MAAA,IAAG,CAAC,EAAC,SAAA,EAAY,MAAA,IAAG,CAAC,GAAE,UAAA,OAAA,CAAA,CAAA;MAChC,MAAK;MACL,cAAW;MACX,OAAM;MACL,SAAO;SAER,YAES,MAAA,YAAA,EAAA,EAFA,MAAM,IAAE,EAAA;6BACN,CAAT,YAAS,MAAA,MAAA,CAAA,CAAA,CAAA;;;gBAKjB,mBAiCM,OAAA,EAjCA,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,OAAA,CAAA,EAAA,EAAA,CAChB,YA+Ba,YAAA;KA/BD,MAAK;KAAqB,MAAK;;4BA2BnC,CAzBE,WAAA,SAAA,WAAA,EADR,mBA0BM,OAAA;MAxBH,KAAK,WAAA,MAAW;MAChB,OAAK,eAAA;OAAwB,MAAA,IAAG,CAAC,EAAC,WAAA;OAAkC,MAAA,IAAG,CAAC,GAAE,iBAAkB,gBAAA,MAAe;OAAuB,MAAA,IAAG,CAAC,GAAE,YAAa,OAAA,MAAM;OAAuB,MAAA,IAAG,CAAC,GAAE,YAAa,WAAA,MAAU;;4BAM1L;4BACA;0BACF;8BACI;MACvB,SAAO;MACP,YAAU;uBAEX,YASE,wBARK,aAAa,WAAA,MAAW,MAAI,EAAA;eAC7B;MAAJ,KAAI;MACH,MAAM,WAAA;MACN,YAAU,QAAA;MACV,OAAK,eAAE,gBAAA,QAAkB,MAAA,IAAG,CAAC,EAAC,YAAA,GAAgB,KAAA,EAAS;MACvD,OAAK,eAAE,eAAA,MAAc;MACrB,SAAO;MACP,cAAa;;;;;;gCAGlB,mBAEM,OAAA;;MAFO,OAAK,eAAE,MAAA,IAAG,CAAC,EAAC,QAAA,CAAA;SACvB,YAA0B,MAAA,YAAA,EAAA,EAAjB,MAAK,WAAS,CAAA,CAAA,EAAA,EAAA,EAAA,CAAA"}
1
+ {"version":3,"file":"file-viewer.js","names":[],"sources":["../../../src/components/file-viewer/file-viewer.vue"],"sourcesContent":["<template>\n <Teleport to=\"body\" :disabled=\"!isModal\">\n <transition :name=\"isModal ? 'u-file-viewer-modal' : ''\">\n <div\n v-if=\"!isModal || openModel\"\n :class=\"[cls.b, bem.is('modal', isModal)]\"\n :tabindex=\"isModal ? -1 : undefined\"\n ref=\"rootRef\"\n @mousedown.self=\"handleBackdropMousedown\"\n >\n <div v-if=\"isModal\" :class=\"cls.e('backdrop')\" @mousedown.self=\"handleBackdropMousedown\" />\n <div :class=\"cls.e('inner')\">\n <aside v-if=\"showSidebar\" :class=\"cls.e('sidebar')\" :style=\"{ width: sidebarWidthCss }\">\n <header :class=\"cls.e('sidebar-head')\">\n <span :class=\"cls.e('sidebar-title')\">文件</span>\n <span :class=\"cls.e('sidebar-count')\">{{ normalizedFiles.length }}</span>\n </header>\n <u-scroll tag=\"ul\" :class=\"cls.e('list')\">\n <li\n v-for=\"f in normalizedFiles\"\n :key=\"f.id\"\n :class=\"[cls.e('item'), bem.is('active', f.id === activeId)]\"\n :title=\"f.name\"\n @click=\"activate(f.id)\"\n >\n <span :class=\"cls.e('item-meta')\">\n <span :class=\"cls.e('item-name')\" :title=\"f.name\">{{ f.name }}</span>\n <span :class=\"cls.e('item-size')\">{{ formatBytes(f.size) }}</span>\n </span>\n <span :class=\"cls.e('item-kind')\">{{ label(f.kind) }}</span>\n </li>\n <li v-if=\"!normalizedFiles.length\" :class=\"cls.e('list-empty')\">\n <u-empty text=\"暂无文件\" :size=\"32\" />\n </li>\n </u-scroll>\n </aside>\n\n <section :class=\"cls.e('stage')\">\n <header :class=\"cls.e('stage-head')\">\n <div :class=\"cls.e('stage-title')\">\n <span v-if=\"activeFile\" :class=\"[cls.e('badge'), cls.em('badge', activeFile.kind)]\">\n {{ label(activeFile.kind) }}\n </span>\n <span :class=\"cls.e('stage-copy')\">\n <span :class=\"cls.e('stage-name')\" :title=\"activeFile?.name\">\n {{ activeFile?.name ?? '—' }}\n </span>\n <span v-if=\"activeFile\" :class=\"cls.e('stage-sub')\">\n <span>{{ activeIndexLabel }}</span>\n <span v-if=\"activeFile.size\">{{ formatBytes(activeFile.size) }}</span>\n </span>\n </span>\n </div>\n <div :class=\"cls.e('stage-actions')\">\n <span :class=\"cls.e('action-group')\">\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"!hasPrev\"\n type=\"button\"\n aria-label=\"上一个\"\n title=\"上一个\"\n @click=\"prev\"\n >\n <u-icon :size=\"15\">\n <ArrowLeft />\n </u-icon>\n </button>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"!hasNext\"\n type=\"button\"\n aria-label=\"下一个\"\n title=\"下一个\"\n @click=\"next\"\n >\n <u-icon :size=\"15\">\n <ArrowRight />\n </u-icon>\n </button>\n </span>\n <span v-if=\"isZoomable\" :class=\"cls.e('action-group')\">\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"zoomOutDisabled\"\n type=\"button\"\n aria-label=\"缩小\"\n title=\"缩小\"\n @click=\"zoomOut\"\n >\n <u-icon :size=\"15\">\n <ZoomOut />\n </u-icon>\n </button>\n <span :class=\"cls.e('zoom-value')\">{{ zoomPercent }}</span>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"zoomInDisabled\"\n type=\"button\"\n aria-label=\"放大\"\n title=\"放大\"\n @click=\"zoomIn\"\n >\n <u-icon :size=\"15\">\n <ZoomIn />\n </u-icon>\n </button>\n <button\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n :disabled=\"isTransformReset\"\n type=\"button\"\n aria-label=\"重置视图\"\n title=\"重置视图\"\n @click=\"resetTransform\"\n >\n <u-icon :size=\"15\">\n <Refresh />\n </u-icon>\n </button>\n </span>\n <button\n v-if=\"downloadable && activeFile\"\n :class=\"[cls.e('action'), cls.em('action', 'primary'), cls.em('action', 'icon')]\"\n type=\"button\"\n aria-label=\"下载\"\n title=\"下载\"\n @click=\"download\"\n >\n <u-icon :size=\"15\">\n <Download />\n </u-icon>\n </button>\n <button\n v-if=\"isModal\"\n :class=\"[cls.e('action'), cls.em('action', 'icon')]\"\n type=\"button\"\n aria-label=\"关闭预览\"\n title=\"关闭\"\n @click=\"handleClose\"\n >\n <u-icon :size=\"16\">\n <Close />\n </u-icon>\n </button>\n </div>\n </header>\n <div :class=\"cls.e('body')\">\n <transition name=\"u-file-viewer-fade\" mode=\"out-in\">\n <div\n v-if=\"activeFile\"\n :key=\"activeFile.id\"\n :class=\"[\n cls.e('viewport'),\n bem.is('transformable', isTransformable),\n bem.is('pannable', canPan),\n bem.is('dragging', isDragging)\n ]\"\n @pointerdown.capture=\"handleViewportPointerDown\"\n @pointermove.capture=\"handleViewportPointerMove\"\n @pointerup.capture=\"handleViewportPointerEnd\"\n @pointercancel.capture=\"handleViewportPointerEnd\"\n @wheel=\"handleViewportWheel\"\n @dblclick=\"handleViewportDblclick\"\n >\n <component\n :is=\"PreviewerMap[activeFile.kind]\"\n ref=\"previewerRef\"\n :file=\"activeFile\"\n :max-rows=\"sheetMaxRows\"\n :class=\"isTransformable ? cls.e('previewer') : undefined\"\n :style=\"previewerStyle\"\n @error=\"handleChildError\"\n @zoom-change=\"handlePdfZoomChange\"\n />\n </div>\n <div v-else :class=\"cls.e('empty')\">\n <u-empty text=\"暂无可预览文件\" />\n </div>\n </transition>\n </div>\n </section>\n </div>\n </div>\n </transition>\n </Teleport>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n ArrowLeft,\n ArrowRight,\n Close,\n Download,\n Refresh,\n ZoomIn,\n ZoomOut\n} from '@veltra/icons/normal'\nimport { bem, withUnit } from '@veltra/utils'\nimport {\n computed,\n defineAsyncComponent,\n nextTick,\n onBeforeUnmount,\n ref,\n shallowRef,\n watch\n} from 'vue'\nimport type { CSSProperties } from 'vue'\n\nimport type {\n _FileViewerExposed,\n FileViewerEmits,\n FileViewerItem,\n FileViewerKind,\n FileViewerProps\n} from '../../types/file-viewer'\nimport { UEmpty } from '../empty'\nimport { UIcon } from '../icon'\nimport { UScroll } from '../scroll'\nimport { FILE_VIEWER_KIND_LABEL, downloadFile, formatBytes, inferKind } from './helper'\n\ndefineOptions({\n name: 'FileViewer',\n inheritAttrs: false\n})\n\nconst props = withDefaults(defineProps<FileViewerProps>(), {\n modelValue: undefined,\n sidebarWidth: '280px',\n sheetMaxRows: 50_000,\n downloadable: true,\n open: undefined,\n closeOnClickBackdrop: true,\n closeOnEsc: true\n})\n\nconst emit = defineEmits<FileViewerEmits>()\n\nconst cls = bem('file-viewer')\n\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 3\n/** 每次缩放固定增减 10% */\nconst SCALE_STEP = 0.1\n/** 使用 CSS transform 缩放的类型(PDF 由 EmbedPDF zoom 插件处理) */\nconst TRANSFORMABLE_KINDS = new Set<FileViewerKind>(['image'])\n/** 工具栏显示缩放控件的类型 */\nconst ZOOMABLE_KINDS = new Set<FileViewerKind>(['image', 'pdf'])\n\nconst activeId = defineModel<string | undefined>('modelValue', { default: undefined })\nconst openModel = defineModel<boolean | undefined>('open', { default: undefined })\n\nconst rootRef = shallowRef<HTMLDivElement>()\nconst previewerRef = shallowRef<{\n zoomIn?: () => void\n zoomOut?: () => void\n resetZoom?: () => void\n}>()\nconst scale = ref(1)\nconst pdfZoomLevel = ref(1)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\nconst isDragging = ref(false)\n\nlet dragState:\n | {\n pointerId: number\n startX: number\n startY: number\n originX: number\n originY: number\n }\n | undefined\n\nconst PreviewerMap: Record<FileViewerKind, ReturnType<typeof defineAsyncComponent>> = {\n image: defineAsyncComponent(() => import('./previewers/image-previewer.vue')),\n video: defineAsyncComponent(() => import('./previewers/video-previewer.vue')),\n pdf: defineAsyncComponent(() => import('./previewers/pdf-previewer.vue')),\n sheet: defineAsyncComponent(() => import('./previewers/sheet-previewer.vue')),\n docx: defineAsyncComponent(() => import('./previewers/docx-previewer.vue')),\n text: defineAsyncComponent(() => import('./previewers/text-previewer.vue'))\n}\n\ninterface NormalizedFile extends FileViewerItem {\n id: string\n kind: FileViewerKind\n}\n\nconst normalizedFiles = computed<NormalizedFile[]>(() =>\n props.files.map((f, i) => ({\n ...f,\n id: f.id ?? `file-${i}`,\n kind: inferKind(f.name, f.kind)\n }))\n)\n\nconst activeFile = computed<NormalizedFile | undefined>(() =>\n normalizedFiles.value.find((f) => f.id === activeId.value)\n)\n\nconst activeIndex = computed(() => normalizedFiles.value.findIndex((f) => f.id === activeId.value))\n\nconst hasPrev = computed(() => activeIndex.value > 0)\nconst hasNext = computed(\n () => activeIndex.value >= 0 && activeIndex.value < normalizedFiles.value.length - 1\n)\n\nconst showSidebar = computed(() => props.sidebarWidth !== false && props.sidebarWidth !== 0)\n\nconst sidebarWidthCss = computed(() => {\n const w = props.sidebarWidth\n if (w === false || w === 0) return undefined\n return withUnit(w ?? '280px', 'px')\n})\n\n/** 是否启用模态模式:只要父组件显式传入 open(含 v-model:open),即进入模态 */\nconst isModal = computed(() => openModel.value !== undefined)\n\nconst activeIndexLabel = computed(() =>\n activeIndex.value >= 0 ? `${activeIndex.value + 1} / ${normalizedFiles.value.length}` : ''\n)\n\nconst isTransformable = computed(\n () => !!activeFile.value && TRANSFORMABLE_KINDS.has(activeFile.value.kind)\n)\n\nconst isZoomable = computed(() => !!activeFile.value && ZOOMABLE_KINDS.has(activeFile.value.kind))\n\nconst isPdfActive = computed(() => activeFile.value?.kind === 'pdf')\n\nconst canPan = computed(() => isTransformable.value && scale.value > 1)\n\nconst displayZoomLevel = computed(() => (isPdfActive.value ? pdfZoomLevel.value : scale.value))\n\nconst zoomPercent = computed(() => `${Math.round(displayZoomLevel.value * 100)}%`)\n\nconst zoomInDisabled = computed(() => !isZoomable.value || displayZoomLevel.value >= MAX_SCALE)\n\nconst zoomOutDisabled = computed(() => !isZoomable.value || displayZoomLevel.value <= MIN_SCALE)\n\nconst isTransformReset = computed(() => {\n if (isPdfActive.value) {\n return Math.abs(pdfZoomLevel.value - 1) < 0.02\n }\n return scale.value === 1 && offsetX.value === 0 && offsetY.value === 0\n})\n\nconst previewerStyle = computed<CSSProperties | undefined>(() => {\n if (!isTransformable.value) return undefined\n return {\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value})`\n }\n})\n\nfunction label(kind: FileViewerKind): string {\n return FILE_VIEWER_KIND_LABEL[kind]\n}\n\nfunction activate(id: string) {\n if (activeId.value === id) return\n activeId.value = id\n const f = normalizedFiles.value.find((x) => x.id === id)\n if (f) emit('change', f)\n}\n\nfunction prev() {\n const i = activeIndex.value\n if (i <= 0) return\n const target = normalizedFiles.value[i - 1]\n if (target) activate(target.id)\n}\n\nfunction next() {\n const i = activeIndex.value\n if (i < 0 || i >= normalizedFiles.value.length - 1) return\n const target = normalizedFiles.value[i + 1]\n if (target) activate(target.id)\n}\n\nfunction normalizeScale(value: number): number {\n const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, value))\n return Math.round(clamped * 100) / 100\n}\n\nfunction setScale(value: number) {\n scale.value = normalizeScale(value)\n if (scale.value <= 1) {\n offsetX.value = 0\n offsetY.value = 0\n }\n}\n\nfunction zoomIn() {\n if (!isZoomable.value) return\n if (isPdfActive.value) {\n previewerRef.value?.zoomIn?.()\n return\n }\n setScale(scale.value + SCALE_STEP)\n}\n\nfunction zoomOut() {\n if (!isZoomable.value) return\n if (isPdfActive.value) {\n previewerRef.value?.zoomOut?.()\n return\n }\n setScale(scale.value - SCALE_STEP)\n}\n\nfunction resetTransform() {\n if (isPdfActive.value) {\n previewerRef.value?.resetZoom?.()\n pdfZoomLevel.value = 1\n return\n }\n scale.value = 1\n offsetX.value = 0\n offsetY.value = 0\n dragState = undefined\n isDragging.value = false\n}\n\nfunction handlePdfZoomChange(level: number) {\n pdfZoomLevel.value = level\n}\n\nfunction isZoomWheel(e: WheelEvent): boolean {\n return e.ctrlKey || e.metaKey\n}\n\nfunction handleViewportWheel(e: WheelEvent) {\n if (!activeFile.value || activeFile.value.kind !== 'image') return\n if (!isZoomWheel(e)) return\n if (isInteractiveTarget(e.target)) return\n e.preventDefault()\n const delta = e.deltaY > 0 ? -SCALE_STEP : SCALE_STEP\n setScale(scale.value + delta)\n}\n\nfunction isInteractiveTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false\n return !!target.closest('button, a, input, textarea, select, video, [contenteditable=\"true\"]')\n}\n\nfunction handleViewportPointerDown(e: PointerEvent) {\n if (!canPan.value || e.button !== 0 || isInteractiveTarget(e.target)) return\n\n dragState = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n originX: offsetX.value,\n originY: offsetY.value\n }\n isDragging.value = true\n const el = e.currentTarget as HTMLElement\n el.setPointerCapture(e.pointerId)\n e.preventDefault()\n}\n\nfunction handleViewportPointerMove(e: PointerEvent) {\n if (!dragState || dragState.pointerId !== e.pointerId) return\n offsetX.value = dragState.originX + e.clientX - dragState.startX\n offsetY.value = dragState.originY + e.clientY - dragState.startY\n}\n\nfunction handleViewportPointerEnd(e: PointerEvent) {\n if (!dragState || dragState.pointerId !== e.pointerId) return\n const el = e.currentTarget as HTMLElement\n if (el.hasPointerCapture(e.pointerId)) {\n el.releasePointerCapture(e.pointerId)\n }\n dragState = undefined\n isDragging.value = false\n}\n\nfunction handleViewportDblclick() {\n if (isPdfActive.value) return\n if (!isTransformable.value) return\n if (scale.value === 1) {\n setScale(2)\n } else {\n resetTransform()\n }\n}\n\nasync function download() {\n if (!activeFile.value) return\n try {\n await downloadFile(activeFile.value)\n } catch (err) {\n emit('error', { file: activeFile.value, error: err })\n }\n}\n\nfunction handleChildError(err: unknown) {\n if (!activeFile.value) return\n emit('error', { file: activeFile.value, error: err })\n}\n\nfunction handleClose() {\n if (!isModal.value) return\n openModel.value = false\n}\n\nfunction handleWindowKeydown(e: KeyboardEvent) {\n if (e.key !== 'Escape') return\n if (!isModal.value || !openModel.value) return\n if (props.closeOnEsc === false) return\n e.stopPropagation()\n handleClose()\n}\n\nfunction handleBackdropMousedown() {\n if (!isModal.value) return\n if (props.closeOnClickBackdrop === false) return\n handleClose()\n}\n\nwatch(\n normalizedFiles,\n (files) => {\n if (!files.length) {\n activeId.value = undefined\n return\n }\n if (!activeId.value || !files.some((f) => f.id === activeId.value)) {\n activeId.value = files[0]!.id\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => activeFile.value?.id,\n async () => {\n pdfZoomLevel.value = 1\n await nextTick()\n resetTransform()\n }\n)\n\n// 模态打开时:锁定 body 滚动 + 让容器获得焦点以便接收 ESC\nlet previousBodyOverflow = ''\nlet isBodyLocked = false\n\nfunction lockBody() {\n if (isBodyLocked || typeof document === 'undefined') return\n previousBodyOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n isBodyLocked = true\n}\n\nfunction unlockBody() {\n if (!isBodyLocked || typeof document === 'undefined') return\n document.body.style.overflow = previousBodyOverflow\n isBodyLocked = false\n}\n\nwatch(\n [isModal, openModel],\n ([modal, open]) => {\n if (modal && open) {\n lockBody()\n nextTick(() => {\n rootRef.value?.focus()\n })\n if (typeof window !== 'undefined') {\n window.addEventListener('keydown', handleWindowKeydown, true)\n }\n } else {\n unlockBody()\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', handleWindowKeydown, true)\n }\n }\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n unlockBody()\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', handleWindowKeydown, true)\n }\n})\n\ndefineExpose<_FileViewerExposed>({\n activeId,\n activate,\n next,\n prev\n})\n</script>\n"],"mappings":"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"}