@veltra/desktop 1.8.2 → 1.8.4

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 (207) hide show
  1. package/dist/components/action/action-group.js.map +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/auto-complete/auto-complete.js.map +1 -1
  5. package/dist/components/badge/badge.js.map +1 -1
  6. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  7. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  8. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  9. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  10. package/dist/components/button/button-group.js.map +1 -1
  11. package/dist/components/button/button.js.map +1 -1
  12. package/dist/components/calendar/calendar.js.map +1 -1
  13. package/dist/components/card/card-action.js.map +1 -1
  14. package/dist/components/card/card-content.js.map +1 -1
  15. package/dist/components/card/card-cover.js.map +1 -1
  16. package/dist/components/card/card-header.js.map +1 -1
  17. package/dist/components/card/card.js.map +1 -1
  18. package/dist/components/cascade/cascade-panel-item.js.map +1 -1
  19. package/dist/components/cascade/cascade.js +1 -0
  20. package/dist/components/cascade/cascade.js.map +1 -1
  21. package/dist/components/cascade/use-select.js +6 -2
  22. package/dist/components/cascade/use-select.js.map +1 -1
  23. package/dist/components/check-tag/check-tag.js.map +1 -1
  24. package/dist/components/checkbox/checkbox-button.js.map +1 -1
  25. package/dist/components/checkbox/checkbox.js.map +1 -1
  26. package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
  27. package/dist/components/code-editor/code-editor.js.map +1 -1
  28. package/dist/components/collapse/collapse-item.js.map +1 -1
  29. package/dist/components/collapse/collapse.js.map +1 -1
  30. package/dist/components/condition-editor/components/condition-group.js.map +1 -1
  31. package/dist/components/condition-editor/components/condition-row.js.map +1 -1
  32. package/dist/components/condition-editor/condition-editor.js.map +1 -1
  33. package/dist/components/contextmenu/contextmenu-item.js.map +1 -1
  34. package/dist/components/contextmenu/contextmenu-panel.js.map +1 -1
  35. package/dist/components/contextmenu/contextmenu-sub.js.map +1 -1
  36. package/dist/components/contextmenu/contextmenu.js.map +1 -1
  37. package/dist/components/date-panel/date-panel.js.map +1 -1
  38. package/dist/components/date-panel/headers/day.js.map +1 -1
  39. package/dist/components/date-panel/headers/month.js.map +1 -1
  40. package/dist/components/date-panel/headers/year.js.map +1 -1
  41. package/dist/components/date-panel/panels/day.js.map +1 -1
  42. package/dist/components/date-panel/panels/month.js.map +1 -1
  43. package/dist/components/date-panel/panels/year.js.map +1 -1
  44. package/dist/components/date-picker/date-picker.js.map +1 -1
  45. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  46. package/dist/components/dialog/dialog.js.map +1 -1
  47. package/dist/components/drawer/drawer.js.map +1 -1
  48. package/dist/components/dropdown/dropdown.js.map +1 -1
  49. package/dist/components/dual-nav/dual-nav-app.js.map +1 -1
  50. package/dist/components/dual-nav/dual-nav.js.map +1 -1
  51. package/dist/components/empty/empty.js.map +1 -1
  52. package/dist/components/expression-editor/components/path-preview.js.map +1 -1
  53. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  54. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  55. package/dist/components/file-picker/file-picker.js.map +1 -1
  56. package/dist/components/file-viewer/file-viewer.js.map +1 -1
  57. package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -1
  58. package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -1
  59. package/dist/components/file-viewer/previewers/ofd-previewer.js.map +1 -1
  60. package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -1
  61. package/dist/components/file-viewer/previewers/pdf-viewport-scroll.js.map +1 -1
  62. package/dist/components/file-viewer/previewers/pdf-zoom-bridge.js.map +1 -1
  63. package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -1
  64. package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -1
  65. package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -1
  66. package/dist/components/float-button/float-button.js.map +1 -1
  67. package/dist/components/form/form.js.map +1 -1
  68. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  69. package/dist/components/form-item/form-item.js.map +1 -1
  70. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  71. package/dist/components/gantt-chart/gantt-chart.js.map +1 -1
  72. package/dist/components/grid/grid-item.js.map +1 -1
  73. package/dist/components/grid/grid.js.map +1 -1
  74. package/dist/components/grid-input/grid-input.js.map +1 -1
  75. package/dist/components/group-input/group-input.js.map +1 -1
  76. package/dist/components/group-nav/group-nav-item.js.map +1 -1
  77. package/dist/components/group-nav/group-nav.js.map +1 -1
  78. package/dist/components/icon/icon.js.map +1 -1
  79. package/dist/components/image-cropper/image-cropper.js.map +1 -1
  80. package/dist/components/input/input.js.map +1 -1
  81. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js.map +1 -1
  82. package/dist/components/kanban/kanban-column.js.map +1 -1
  83. package/dist/components/kanban/kanban.js.map +1 -1
  84. package/dist/components/kbd/kbd.js.map +1 -1
  85. package/dist/components/layout/layout-resizer.js.map +1 -1
  86. package/dist/components/layout/layout.js.map +1 -1
  87. package/dist/components/list/list-item.js.map +1 -1
  88. package/dist/components/list/list.js.map +1 -1
  89. package/dist/components/loading/loading.js.map +1 -1
  90. package/dist/components/message/message-box.js.map +1 -1
  91. package/dist/components/message/message.js.map +1 -1
  92. package/dist/components/message-confirm/message-confirm-box.js.map +1 -1
  93. package/dist/components/message-confirm/message-confirm.js.map +1 -1
  94. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  95. package/dist/components/multi-select/multi-select.js.map +1 -1
  96. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  97. package/dist/components/nav/nav-icon.js.map +1 -1
  98. package/dist/components/nav/nav-item-collapsed.js.map +1 -1
  99. package/dist/components/nav/nav-item.js.map +1 -1
  100. package/dist/components/nav/nav-sub-collapsed.js.map +1 -1
  101. package/dist/components/nav/nav-sub.js.map +1 -1
  102. package/dist/components/nav/nav.js.map +1 -1
  103. package/dist/components/notification/notification-box.js.map +1 -1
  104. package/dist/components/notification/notification.js.map +1 -1
  105. package/dist/components/number/number.js.map +1 -1
  106. package/dist/components/number-input/number-input.js.map +1 -1
  107. package/dist/components/number-range-input/number-range-input.js.map +1 -1
  108. package/dist/components/paginator/paginator.js.map +1 -1
  109. package/dist/components/palette/palette-alpha.js.map +1 -1
  110. package/dist/components/palette/palette-color-switch.js.map +1 -1
  111. package/dist/components/palette/palette-hue.js.map +1 -1
  112. package/dist/components/palette/palette-sv.js.map +1 -1
  113. package/dist/components/palette/palette.js.map +1 -1
  114. package/dist/components/password-input/password-input.js.map +1 -1
  115. package/dist/components/pop-confirm/pop-confirm.js.map +1 -1
  116. package/dist/components/progress/progress.js.map +1 -1
  117. package/dist/components/progress-nodes/progress-nodes.js.map +1 -1
  118. package/dist/components/radio/radio.js.map +1 -1
  119. package/dist/components/radio-group/radio-group.js.map +1 -1
  120. package/dist/components/rich-text-editor/image-node.js +53 -14
  121. package/dist/components/rich-text-editor/image-node.js.map +1 -1
  122. package/dist/components/rich-text-editor/rich-text-editor.js +9 -3
  123. package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -1
  124. package/dist/components/rich-text-editor/style2.css +31 -0
  125. package/dist/components/rich-text-editor/toolbar.js.map +1 -1
  126. package/dist/components/rich-text-editor/use-image-resize.js +118 -0
  127. package/dist/components/rich-text-editor/use-image-resize.js.map +1 -0
  128. package/dist/components/scroll/scroll-bar.js.map +1 -1
  129. package/dist/components/scroll/scroll.js.map +1 -1
  130. package/dist/components/segment/segment.js.map +1 -1
  131. package/dist/components/select/select.js +9 -5
  132. package/dist/components/select/select.js.map +1 -1
  133. package/dist/components/slider/slider-thumb.js.map +1 -1
  134. package/dist/components/slider/slider.js.map +1 -1
  135. package/dist/components/steps/steps.js.map +1 -1
  136. package/dist/components/switch/switch.js.map +1 -1
  137. package/dist/components/table/table-body.js.map +1 -1
  138. package/dist/components/table/table-cell.js.map +1 -1
  139. package/dist/components/table/table-foot.js.map +1 -1
  140. package/dist/components/table/table-head.js.map +1 -1
  141. package/dist/components/table/table-row.js.map +1 -1
  142. package/dist/components/table/table.js.map +1 -1
  143. package/dist/components/table-editor/table-editor.js.map +1 -1
  144. package/dist/components/tabs/tabs-horizontal.js.map +1 -1
  145. package/dist/components/tabs/tabs-vertical.js.map +1 -1
  146. package/dist/components/tabs/tabs.js.map +1 -1
  147. package/dist/components/tag/tag.js.map +1 -1
  148. package/dist/components/text/text.js.map +1 -1
  149. package/dist/components/textarea/textarea.js.map +1 -1
  150. package/dist/components/theme/theme.js.map +1 -1
  151. package/dist/components/tip/tip.js.map +1 -1
  152. package/dist/components/tree/tree-node.js.map +1 -1
  153. package/dist/components/tree/tree.js.map +1 -1
  154. package/dist/components/tree-select/tree-select.js +19 -10
  155. package/dist/components/tree-select/tree-select.js.map +1 -1
  156. package/dist/components/watermark/watermark.js.map +1 -1
  157. package/dist/types/cascade.d.ts +5 -0
  158. package/dist/types/select.d.ts +8 -2
  159. package/dist/types/tree-select.d.ts +8 -2
  160. package/package.json +18 -18
  161. package/src/components/batch-edit/__test__/batch-edit.test.ts +1 -1
  162. package/src/components/button/__test__/button.test.ts +1 -1
  163. package/src/components/cascade/__test__/cascade.test.ts +26 -1
  164. package/src/components/cascade/use-select.ts +5 -5
  165. package/src/components/collapse/__test__/collapse.test.ts +1 -1
  166. package/src/components/condition-editor/__test__/condition-editor.test.ts +1 -1
  167. package/src/components/contextmenu/__test__/contextmenu.test.ts +1 -1
  168. package/src/components/date-picker/__test__/date-picker.test.ts +1 -1
  169. package/src/components/date-range-picker/__test__/date-range-picker.test.ts +1 -1
  170. package/src/components/drawer/__test__/drawer.test.ts +1 -1
  171. package/src/components/expression-editor/__test__/chip.test.ts +1 -1
  172. package/src/components/expression-editor/__test__/editor.test.ts +1 -1
  173. package/src/components/expression-editor/__test__/expression-editor.test.ts +1 -1
  174. package/src/components/expression-editor/__test__/mention.test.ts +1 -1
  175. package/src/components/expression-editor/__test__/model.test.ts +1 -1
  176. package/src/components/file-viewer/__test__/helper.test.ts +1 -1
  177. package/src/components/form/__test__/form.test.ts +1 -1
  178. package/src/components/form/__test__/is-field-modified.test.ts +1 -1
  179. package/src/components/form-item/__test__/form-item.test.ts +1 -1
  180. package/src/components/image-cropper/__test__/draw-crop.test.ts +1 -1
  181. package/src/components/image-cropper/__test__/image-cropper.test.ts +1 -1
  182. package/src/components/image-cropper/__test__/use-transform.test.ts +1 -1
  183. package/src/components/kanban/__test__/kanban.test.ts +1 -1
  184. package/src/components/message-confirm/__test__/message-confirm.test.ts +1 -1
  185. package/src/components/multi-tree-select/__test__/multi-tree-select.test.ts +1 -1
  186. package/src/components/number-input/__test__/number-input.test.ts +1 -1
  187. package/src/components/number-range-input/__test__/number-range-input.test.ts +1 -1
  188. package/src/components/palette/__test__/color-transform.test.ts +1 -1
  189. package/src/components/palette/__test__/palette.test.ts +1 -1
  190. package/src/components/password-input/__test__/password-input.test.ts +1 -1
  191. package/src/components/radio-group/__test__/radio-group.test.ts +1 -1
  192. package/src/components/rich-text-editor/__test__/image-node.test.ts +140 -0
  193. package/src/components/rich-text-editor/image-node.ts +84 -15
  194. package/src/components/rich-text-editor/rich-text-editor.vue +15 -4
  195. package/src/components/rich-text-editor/style.scss +35 -0
  196. package/src/components/rich-text-editor/use-image-resize.ts +168 -0
  197. package/src/components/segment/__test__/segment.test.ts +1 -1
  198. package/src/components/select/__test__/select.test.ts +68 -1
  199. package/src/components/select/select.vue +15 -5
  200. package/src/components/table/__test__/col-width.test.ts +1 -1
  201. package/src/components/table/__test__/column-style.test.ts +1 -1
  202. package/src/components/table-editor/__test__/table-editor.test.ts +1 -1
  203. package/src/components/tree-select/__test__/tree-select.test.ts +50 -1
  204. package/src/components/tree-select/tree-select.vue +28 -11
  205. package/src/types/cascade.ts +5 -0
  206. package/src/types/select.ts +8 -2
  207. package/src/types/tree-select.ts +9 -2
@@ -1 +1 @@
1
- {"version":3,"file":"pop-confirm.js","names":[],"sources":["../../../src/components/pop-confirm/pop-confirm.vue"],"sourcesContent":["<template>\n <u-tip\n :class=\"cls.b\"\n :trigger=\"trigger\"\n :direction=\"direction\"\n :alignment=\"alignment\"\n :content-tag=\"contentTag\"\n v-model:visible=\"visible\"\n >\n <slot name=\"reference\" />\n\n <template #content>\n <div :class=\"[cls.m('main'), cls.m(size)]\">\n <u-icon :size=\"16\" :class=\"cls.m('icon')\" :style=\"{ color: iconColor }\">\n <component :is=\"icon\" />\n </u-icon>\n <span>\n {{ title }}\n </span>\n </div>\n <div :class=\"cls.m('action')\">\n <u-button size=\"small\" @click=\"cancel\" type=\"primary\" text>\n {{ cancelText }}\n </u-button>\n &nbsp;\n <u-button size=\"small\" type=\"primary\" @click=\"confirm\">\n {{ confirmText }}\n </u-button>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { QuestionFilled } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { ref } from 'vue'\n\nimport type { PopConfirmProps, PopConfirmEmits } from '../../types'\nimport { UButton } from '../button'\nimport { UIcon } from '../icon'\nimport { UTip } from '../tip'\n\ndefineOptions({ name: 'UPopConfirm' })\n\nconst emit = defineEmits<PopConfirmEmits>()\n\nwithDefaults(defineProps<PopConfirmProps>(), {\n trigger: 'click',\n icon: QuestionFilled,\n direction: 'bottom',\n iconColor: '#ffc107',\n confirmText: '确认',\n cancelText: '取消'\n})\n\nconst cls = bem('pop-confirm')\n\nconst visible = ref(false)\n\nconst { formProps } = injectFormContext()\n\nconst { size } = useFormFallbackProps([formProps ?? {}], { size: 'default' })\n\nconst confirm = () => {\n emit('confirm')\n visible.value = false\n}\n\nconst cancel = () => {\n emit('cancel')\n visible.value = false\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA+CA,MAAM,OAAO;EAWb,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,UAAU,IAAI,KAAK;EAEzB,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,SAAS,qBAAqB,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,MAAM,UAAU,CAAC;EAE5E,MAAM,gBAAgB;GACpB,KAAK,SAAS;GACd,QAAQ,QAAQ;EAClB;EAEA,MAAM,eAAe;GACnB,KAAK,QAAQ;GACb,QAAQ,QAAQ;EAClB;;GAzEE,OAAA,UAAA,GAAA,YA6BQ,MAAA,WAAA,GAAA;IA5BL,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,SAAS,QAAA;IACT,WAAW,QAAA;IACX,WAAW,QAAA;IACX,eAAa,QAAA;IACN,SAAS,QAAA;IAAA,oBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,QAAO,QAAA;;IAIb,SAAO,cAQV,CAPN,mBAOM,OAAA,EAPA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,GAAU,MAAA,GAAA,CAAG,CAAC,EAAE,MAAA,IAAA,CAAI,CAAA,CAAA,EAAA,GACrC,CAAA,YAES,MAAA,WAAA,GAAA;KAFA,MAAM;KAAK,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;KAAW,OAAK,eAAA,EAAA,OAAW,QAAA,UAAS,CAAA;;KAClE,SAAA,cAAwB,EAAxB,UAAA,GAAA,YAAwB,wBAAR,QAAA,IAAI,CAAA,EAAA,CAAA;;IAEtB,GAAA,GAAA,CAAA,SAAA,OAAA,CAAA,GAAA,mBAEO,QAAA,MAAA,gBADF,QAAA,KAAK,GAAA,CAAA,CAAA,GAAA,CAGZ,GAAA,mBAQM,OAAA,EARA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA,EAAA,GAAA;KAChB,YAEW,MAAA,WAAA,GAAA;MAFD,MAAK;MAAS,SAAO;MAAQ,MAAK;MAAU,MAAA;;MACpD,SAAA,cAAgB,CAAb,gBAAA,gBAAA,QAAA,UAAU,GAAA,CAAA,CAAA,CAAA;;;KACJ,OAAA,OAAA,OAAA,KAAA,gBAAA,UAEX,EAAA;KAAA,YAEW,MAAA,WAAA,GAAA;MAFD,MAAK;MAAQ,MAAK;MAAW,SAAO;;MAC5C,SAAA,cAAiB,CAAd,gBAAA,gBAAA,QAAA,WAAW,GAAA,CAAA,CAAA,CAAA;;;;IAjBpB,SAAA,cAAyB,CAAzB,WAAyB,KAAA,QAAA,WAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"pop-confirm.js","names":["_openBlock","_createBlock","_unref","UTip","_normalizeClass","_withCtx","_createElementVNode","_createVNode","UIcon","_normalizeStyle","_resolveDynamicComponent","_toDisplayString","UButton","_createTextVNode","_renderSlot"],"sources":["../../../src/components/pop-confirm/pop-confirm.vue"],"sourcesContent":["<template>\n <u-tip\n :class=\"cls.b\"\n :trigger=\"trigger\"\n :direction=\"direction\"\n :alignment=\"alignment\"\n :content-tag=\"contentTag\"\n v-model:visible=\"visible\"\n >\n <slot name=\"reference\" />\n\n <template #content>\n <div :class=\"[cls.m('main'), cls.m(size)]\">\n <u-icon :size=\"16\" :class=\"cls.m('icon')\" :style=\"{ color: iconColor }\">\n <component :is=\"icon\" />\n </u-icon>\n <span>\n {{ title }}\n </span>\n </div>\n <div :class=\"cls.m('action')\">\n <u-button size=\"small\" @click=\"cancel\" type=\"primary\" text>\n {{ cancelText }}\n </u-button>\n &nbsp;\n <u-button size=\"small\" type=\"primary\" @click=\"confirm\">\n {{ confirmText }}\n </u-button>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { QuestionFilled } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { ref } from 'vue'\n\nimport type { PopConfirmProps, PopConfirmEmits } from '../../types'\nimport { UButton } from '../button'\nimport { UIcon } from '../icon'\nimport { UTip } from '../tip'\n\ndefineOptions({ name: 'UPopConfirm' })\n\nconst emit = defineEmits<PopConfirmEmits>()\n\nwithDefaults(defineProps<PopConfirmProps>(), {\n trigger: 'click',\n icon: QuestionFilled,\n direction: 'bottom',\n iconColor: '#ffc107',\n confirmText: '确认',\n cancelText: '取消'\n})\n\nconst cls = bem('pop-confirm')\n\nconst visible = ref(false)\n\nconst { formProps } = injectFormContext()\n\nconst { size } = useFormFallbackProps([formProps ?? {}], { size: 'default' })\n\nconst confirm = () => {\n emit('confirm')\n visible.value = false\n}\n\nconst cancel = () => {\n emit('cancel')\n visible.value = false\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA+CA,MAAM,OAAO;EAWb,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,UAAU,IAAI,KAAK;EAEzB,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,SAAS,qBAAqB,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,MAAM,UAAU,CAAC;EAE5E,MAAM,gBAAgB;GACpB,KAAK,SAAS;GACd,QAAQ,QAAQ;EAClB;EAEA,MAAM,eAAe;GACnB,KAAK,QAAQ;GACb,QAAQ,QAAQ;EAClB;;GAzEE,OAAAA,UAAA,GAAAC,YA6BQC,MAAAC,WAAA,GAAA;IA5BL,OAAKC,eAAEF,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,SAAS,QAAA;IACT,WAAW,QAAA;IACX,WAAW,QAAA;IACX,eAAa,QAAA;IACN,SAAS,QAAA;IAAA,oBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,QAAO,QAAA;;IAIb,SAAOG,cAQV,CAPNC,mBAOM,OAAA,EAPA,OAAKF,eAAA,CAAGF,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,GAAUA,MAAA,GAAA,CAAG,CAAC,EAAEA,MAAA,IAAA,CAAI,CAAA,CAAA,EAAA,GACrC,CAAAK,YAESL,MAAAM,WAAA,GAAA;KAFA,MAAM;KAAK,OAAKJ,eAAEF,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;KAAW,OAAKO,eAAA,EAAA,OAAW,QAAA,UAAS,CAAA;;KAClE,SAAAJ,cAAwB,EAAxBL,UAAA,GAAAC,YAAwBS,wBAAR,QAAA,IAAI,CAAA,EAAA,CAAA;;IAEtB,GAAA,GAAA,CAAA,SAAA,OAAA,CAAA,GAAAJ,mBAEO,QAAA,MAAAK,gBADF,QAAA,KAAK,GAAA,CAAA,CAAA,GAAA,CAGZ,GAAAL,mBAQM,OAAA,EARA,OAAKF,eAAEF,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA,EAAA,GAAA;KAChBK,YAEWL,MAAAU,WAAA,GAAA;MAFD,MAAK;MAAS,SAAO;MAAQ,MAAK;MAAU,MAAA;;MACpD,SAAAP,cAAgB,CAAbQ,gBAAAF,gBAAA,QAAA,UAAU,GAAA,CAAA,CAAA,CAAA;;;KACJ,OAAA,OAAA,OAAA,KAAAE,gBAAA,UAEX,EAAA;KAAAN,YAEWL,MAAAU,WAAA,GAAA;MAFD,MAAK;MAAQ,MAAK;MAAW,SAAO;;MAC5C,SAAAP,cAAiB,CAAdQ,gBAAAF,gBAAA,QAAA,WAAW,GAAA,CAAA,CAAA,CAAA;;;;IAjBpB,SAAAN,cAAyB,CAAzBS,WAAyB,KAAA,QAAA,WAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","names":[],"sources":["../../../src/components/progress/progress.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <!-- 条形进度条 -->\n <div :class=\"cls.e('bar-wrap')\" v-if=\"!circle\">\n <div :class=\"cls.e('bar')\" :style=\"{ width: percentage + '%' }\">\n <slot v-bind=\"{ percentage, type }\">{{ percentage }}%</slot>\n </div>\n </div>\n\n <!-- 环形进度条 -->\n <div\n v-else\n :class=\"cls.e('circle')\"\n :style=\"{ height: withUnit(size, 'px'), width: withUnit(size, 'px') }\"\n >\n <svg viewBox=\"0 0 100 100\">\n <circle cx=\"50\" cy=\"50\" :r=\"r\" fill=\"none\" stroke=\"#f5f8fa\" stroke-width=\"8\" />\n <circle\n :class=\"cls.e('circle-ring')\"\n cx=\"50\"\n cy=\"50\"\n :r=\"r\"\n fill=\"none\"\n :stroke=\"stroke\"\n stroke-width=\"8\"\n :stroke-dasharray=\"dashArray\"\n :stroke-dashoffset=\"offset\"\n :stroke-linecap=\"percentage === 0 ? undefined : 'round'\"\n />\n </svg>\n\n <div :class=\"cls.e('circle-content')\">\n <slot v-bind=\"{ percentage, type }\"> {{ percentage }}% </slot>\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { n } from '@cat-kit/core'\nimport { bem, withUnit } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { ProgressProps, ColorType } from '../../types'\n\ndefineOptions({ name: 'UProgress' })\n\nconst props = withDefaults(defineProps<ProgressProps>(), { type: 'primary', circle: false })\n\ndefineSlots<{\n default: (props: {\n /** 进度百分比 */\n percentage: number\n /** 样式类型 */\n type: ColorType\n }) => any\n}>()\n\nconst cls = bem('progress')\n\nconst percentage = computed(() => {\n return n(props.percentage ?? 0).range(0, 100)\n})\n\nconst type = computed<ColorType>(() => {\n const { type } = props\n return typeof type === 'function' ? type(percentage.value) : type\n})\n\nconst className = computed(() => {\n return [cls.b, bem.is('circle', props.circle), cls.m(type.value)]\n})\n\n/** 描边 */\nconst stroke = computed(() => {\n return `var(--u-color-${type.value})`\n})\n\nconst r = 44\nconst c = 2 * Math.PI * r\nconst offset = c / 4\n\n/** 圆周 */\nconst dashArray = computed(() => {\n const dashLen = (c * percentage.value) / 100\n return `${dashLen}, ${c - dashLen}`\n})\n</script>\n"],"mappings":";;;;;;;;;;AA8EA,MAAM,IAAI;;;;;;;;;;;;;;;;;EA/BV,MAAM,QAAQ;EAWd,MAAM,MAAM,IAAI,UAAU;EAE1B,MAAM,aAAa,eAAe;GAChC,OAAO,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG;EAC9C,CAAC;EAED,MAAM,OAAO,eAA0B;GACrC,MAAM,EAAE,SAAS;GACjB,OAAO,OAAO,SAAS,aAAa,KAAK,WAAW,KAAK,IAAI;EAC/D,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,OAAO;IAAC,IAAI;IAAG,IAAI,GAAG,UAAU,MAAM,MAAM;IAAG,IAAI,EAAE,KAAK,KAAK;GAAC;EAClE,CAAC;;EAGD,MAAM,SAAS,eAAe;GAC5B,OAAO,iBAAiB,KAAK,MAAM;EACrC,CAAC;EAGD,MAAM,IAAI,IAAI,KAAK,KAAK;EACxB,MAAM,SAAS,IAAI;;EAGnB,MAAM,YAAY,eAAe;GAC/B,MAAM,UAAW,IAAI,WAAW,QAAS;GACzC,OAAO,GAAG,QAAQ,IAAI,IAAI;EAC5B,CAAC;;GArFC,OAAA,UAAA,GAAA,mBAkCM,OAAA,EAlCA,OAAK,eAAE,UAAA,KAAS,EAAA,GACpB,CAAA,mBAAA,SAAA,GACuC,CAAA,QAAA,UAAvC,UAAA,GAAA,mBAIM,OAAA;;IAJA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,UAAA,CAAA;GAChB,GAAA,CAAA,mBAEM,OAAA;IAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,KAAA,CAAA;IAAU,OAAK,eAAA,EAAA,OAAW,WAAA,QAAU,IAAA,CAAA;GACpD,GAAA,CAAA,WAA4D,KAAA,QAAA,WAAA,eAAA,mBAAA;IAAA,YAA5C,WAAA;IAAU,MAAE,KAAA;GAAI,CAAA,CAAA,SAA4B,CAArB,gBAAA,gBAAA,WAAA,KAAU,IAAG,KAAC,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,GAAA,CAKzD,MAAA,UAAA,GAAA,mBAwBM,UAAA,EAAA,KAAA,EAAA,GAAA,CAzBN,mBAAA,SAAA,GACA,mBAwBM,OAAA;IAtBH,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA;IACZ,OAAK,eAAA;KAAA,QAAY,MAAA,QAAA,CAAQ,CAAC,QAAA,MAAI,IAAA;KAAA,OAAgB,MAAA,QAAA,CAAQ,CAAC,QAAA,MAAI,IAAA;IAAA,CAAA;GAE5D,GAAA,EAAA,UAAA,GAAA,mBAcM,OAdN,YAcM,CAbJ,mBAA+E,UAAA;IAAvE,IAAG;IAAK,IAAG;IAAS;IAAG,MAAK;IAAO,QAAO;IAAU,gBAAa;GACzE,CAAA,GAAA,mBAWE,UAAA;IAVC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;IACb,IAAG;IACH,IAAG;IACC;IACJ,MAAK;IACJ,QAAQ,OAAA;IACT,gBAAa;IACZ,oBAAkB,UAAA;IAClB,qBAAmB;IACnB,kBAAgB,WAAA,UAAU,IAAS,KAAA,IAAS;GAIjD,GAAA,MAAA,IAAA,UAAA,CAAA,CAAA,IAAA,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,GAChB,CAAA,WAA8D,KAAA,QAAA,WAAA,eAAA,mBAAA;IAAA,YAA9C,WAAA;IAAU,MAAE,KAAA;GAAI,CAAA,CAAA,SAA8B,CAAtB,gBAAA,gBAAA,WAAA,KAAU,IAAG,MAAE,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,EAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"progress.js","names":["_openBlock","_createElementBlock","_normalizeClass","_createCommentVNode","_unref","_createElementVNode","_normalizeStyle","_renderSlot","_normalizeProps","_guardReactiveProps","_createTextVNode","_toDisplayString","_Fragment"],"sources":["../../../src/components/progress/progress.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <!-- 条形进度条 -->\n <div :class=\"cls.e('bar-wrap')\" v-if=\"!circle\">\n <div :class=\"cls.e('bar')\" :style=\"{ width: percentage + '%' }\">\n <slot v-bind=\"{ percentage, type }\">{{ percentage }}%</slot>\n </div>\n </div>\n\n <!-- 环形进度条 -->\n <div\n v-else\n :class=\"cls.e('circle')\"\n :style=\"{ height: withUnit(size, 'px'), width: withUnit(size, 'px') }\"\n >\n <svg viewBox=\"0 0 100 100\">\n <circle cx=\"50\" cy=\"50\" :r=\"r\" fill=\"none\" stroke=\"#f5f8fa\" stroke-width=\"8\" />\n <circle\n :class=\"cls.e('circle-ring')\"\n cx=\"50\"\n cy=\"50\"\n :r=\"r\"\n fill=\"none\"\n :stroke=\"stroke\"\n stroke-width=\"8\"\n :stroke-dasharray=\"dashArray\"\n :stroke-dashoffset=\"offset\"\n :stroke-linecap=\"percentage === 0 ? undefined : 'round'\"\n />\n </svg>\n\n <div :class=\"cls.e('circle-content')\">\n <slot v-bind=\"{ percentage, type }\"> {{ percentage }}% </slot>\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { n } from '@cat-kit/core'\nimport { bem, withUnit } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { ProgressProps, ColorType } from '../../types'\n\ndefineOptions({ name: 'UProgress' })\n\nconst props = withDefaults(defineProps<ProgressProps>(), { type: 'primary', circle: false })\n\ndefineSlots<{\n default: (props: {\n /** 进度百分比 */\n percentage: number\n /** 样式类型 */\n type: ColorType\n }) => any\n}>()\n\nconst cls = bem('progress')\n\nconst percentage = computed(() => {\n return n(props.percentage ?? 0).range(0, 100)\n})\n\nconst type = computed<ColorType>(() => {\n const { type } = props\n return typeof type === 'function' ? type(percentage.value) : type\n})\n\nconst className = computed(() => {\n return [cls.b, bem.is('circle', props.circle), cls.m(type.value)]\n})\n\n/** 描边 */\nconst stroke = computed(() => {\n return `var(--u-color-${type.value})`\n})\n\nconst r = 44\nconst c = 2 * Math.PI * r\nconst offset = c / 4\n\n/** 圆周 */\nconst dashArray = computed(() => {\n const dashLen = (c * percentage.value) / 100\n return `${dashLen}, ${c - dashLen}`\n})\n</script>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"progress-nodes.js","names":[],"sources":["../../../src/components/progress-nodes/progress-nodes.vue"],"sourcesContent":["<template>\n <div ref=\"container\" :class=\"className\" :style=\"containerStyle\" @scroll=\"checkScroll\">\n <ul :class=\"cls.e('list')\">\n <li\n v-for=\"(node, index) in nodes\"\n :key=\"node.key ?? index\"\n :class=\"[\n cls.e('item'),\n bem.is('checked', isChecked(node, index)),\n bem.is('active', isActive(node))\n ]\"\n @click=\"handleClick(node, index)\"\n >\n <div :class=\"cls.e('node')\">\n <!-- 虚线连接 -->\n <div v-if=\"index !== nodes.length - 1\" :class=\"cls.e('link')\"></div>\n\n <!-- 节点圆点 -->\n <span :class=\"cls.e('dot')\">\n <slot name=\"icon\" :node=\"node\" :index=\"index\" />\n </span>\n </div>\n\n <div :class=\"cls.e('label')\">\n <slot :node=\"node\" :index=\"index\">\n {{ getLabel(node) }}\n </slot>\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { useDrag, useResizeObserver } from '@veltra/compositions'\nimport { bem, fieldKey } from '@veltra/utils'\nimport { computed, shallowRef, toRefs, useTemplateRef, watch, nextTick } from 'vue'\n\nimport type { ProgressNodesProps, ProgressNodesEmits } from '../../types'\n\ndefineOptions({ name: 'UProgressNodes' })\n\nconst props = withDefaults(defineProps<ProgressNodesProps>(), {\n colorType: 'primary',\n labelKey: 'label',\n valueKey: 'value'\n})\n\nconst emit = defineEmits<ProgressNodesEmits>()\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst { nodes } = toRefs(props)\n\nconst cls = bem('progress-nodes')\nconst containerRef = useTemplateRef('container')\nconst isDragging = shallowRef(false)\nconst isScrollable = shallowRef(false)\nconst showMaskStart = shallowRef(false)\nconst showMaskEnd = shallowRef(false)\n\nconst className = computed(() => [\n cls.b,\n cls.m(props.colorType),\n bem.is('scrollable', isScrollable.value),\n bem.is('dragging', isDragging.value),\n bem.is('mask-start', showMaskStart.value),\n bem.is('mask-end', showMaskEnd.value)\n])\n\nconst containerStyle = computed(() => {\n const style: Record<string, string> = {}\n const maxWidth = toCssSize(props.maxWidth)\n if (maxWidth) style.maxWidth = maxWidth\n return style\n})\n\nlet startScrollLeft = 0\n\nuseDrag({\n target: containerRef,\n onDragStart() {\n if (!isScrollable.value) return\n const container = containerRef.value\n if (!container) return\n isDragging.value = true\n startScrollLeft = container.scrollLeft\n },\n onDrag({ x }) {\n if (!isScrollable.value) return\n const container = containerRef.value\n if (!container) return\n container.scrollLeft = startScrollLeft - x\n },\n onDragEnd() {\n isDragging.value = false\n }\n})\n\nuseResizeObserver({ targets: containerRef, onResize: checkScroll })\n\nwatch(\n nodes,\n () => {\n nextTick(checkScroll)\n },\n { deep: true }\n)\n\nfunction checkScroll() {\n const container = containerRef.value\n if (!container) return\n\n const { scrollLeft, scrollWidth, clientWidth } = container\n isScrollable.value = scrollWidth > clientWidth + 1\n showMaskStart.value = scrollLeft > 0\n showMaskEnd.value = scrollLeft + clientWidth < scrollWidth - 1\n}\n\nfunction isChecked(node: Record<string, any>, index: number) {\n return props.check?.(node, index) ?? false\n}\n\nfunction isActive(node: Record<string, any>) {\n const value = o(node).get(valueKey.value)\n return props.modelValue === value\n}\n\nfunction getLabel(node: Record<string, any>) {\n return o(node).get(labelKey.value)\n}\n\nfunction handleClick(node: Record<string, any>, index: number) {\n const value = o(node).get(valueKey.value)\n emit('update:modelValue', value)\n emit('click', node, index)\n}\n\nfunction toCssSize(value?: number | string) {\n if (value === undefined) return undefined\n return typeof value === 'number' ? `${value}px` : value\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"progress-nodes.js","names":["_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_unref","_Fragment","_renderList","_createCommentVNode","_renderSlot","_createTextVNode","_toDisplayString"],"sources":["../../../src/components/progress-nodes/progress-nodes.vue"],"sourcesContent":["<template>\n <div ref=\"container\" :class=\"className\" :style=\"containerStyle\" @scroll=\"checkScroll\">\n <ul :class=\"cls.e('list')\">\n <li\n v-for=\"(node, index) in nodes\"\n :key=\"node.key ?? index\"\n :class=\"[\n cls.e('item'),\n bem.is('checked', isChecked(node, index)),\n bem.is('active', isActive(node))\n ]\"\n @click=\"handleClick(node, index)\"\n >\n <div :class=\"cls.e('node')\">\n <!-- 虚线连接 -->\n <div v-if=\"index !== nodes.length - 1\" :class=\"cls.e('link')\"></div>\n\n <!-- 节点圆点 -->\n <span :class=\"cls.e('dot')\">\n <slot name=\"icon\" :node=\"node\" :index=\"index\" />\n </span>\n </div>\n\n <div :class=\"cls.e('label')\">\n <slot :node=\"node\" :index=\"index\">\n {{ getLabel(node) }}\n </slot>\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { useDrag, useResizeObserver } from '@veltra/compositions'\nimport { bem, fieldKey } from '@veltra/utils'\nimport { computed, shallowRef, toRefs, useTemplateRef, watch, nextTick } from 'vue'\n\nimport type { ProgressNodesProps, ProgressNodesEmits } from '../../types'\n\ndefineOptions({ name: 'UProgressNodes' })\n\nconst props = withDefaults(defineProps<ProgressNodesProps>(), {\n colorType: 'primary',\n labelKey: 'label',\n valueKey: 'value'\n})\n\nconst emit = defineEmits<ProgressNodesEmits>()\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst { nodes } = toRefs(props)\n\nconst cls = bem('progress-nodes')\nconst containerRef = useTemplateRef('container')\nconst isDragging = shallowRef(false)\nconst isScrollable = shallowRef(false)\nconst showMaskStart = shallowRef(false)\nconst showMaskEnd = shallowRef(false)\n\nconst className = computed(() => [\n cls.b,\n cls.m(props.colorType),\n bem.is('scrollable', isScrollable.value),\n bem.is('dragging', isDragging.value),\n bem.is('mask-start', showMaskStart.value),\n bem.is('mask-end', showMaskEnd.value)\n])\n\nconst containerStyle = computed(() => {\n const style: Record<string, string> = {}\n const maxWidth = toCssSize(props.maxWidth)\n if (maxWidth) style.maxWidth = maxWidth\n return style\n})\n\nlet startScrollLeft = 0\n\nuseDrag({\n target: containerRef,\n onDragStart() {\n if (!isScrollable.value) return\n const container = containerRef.value\n if (!container) return\n isDragging.value = true\n startScrollLeft = container.scrollLeft\n },\n onDrag({ x }) {\n if (!isScrollable.value) return\n const container = containerRef.value\n if (!container) return\n container.scrollLeft = startScrollLeft - x\n },\n onDragEnd() {\n isDragging.value = false\n }\n})\n\nuseResizeObserver({ targets: containerRef, onResize: checkScroll })\n\nwatch(\n nodes,\n () => {\n nextTick(checkScroll)\n },\n { deep: true }\n)\n\nfunction checkScroll() {\n const container = containerRef.value\n if (!container) return\n\n const { scrollLeft, scrollWidth, clientWidth } = container\n isScrollable.value = scrollWidth > clientWidth + 1\n showMaskStart.value = scrollLeft > 0\n showMaskEnd.value = scrollLeft + clientWidth < scrollWidth - 1\n}\n\nfunction isChecked(node: Record<string, any>, index: number) {\n return props.check?.(node, index) ?? false\n}\n\nfunction isActive(node: Record<string, any>) {\n const value = o(node).get(valueKey.value)\n return props.modelValue === value\n}\n\nfunction getLabel(node: Record<string, any>) {\n return o(node).get(labelKey.value)\n}\n\nfunction handleClick(node: Record<string, any>, index: number) {\n const value = o(node).get(valueKey.value)\n emit('update:modelValue', value)\n emit('click', node, index)\n}\n\nfunction toCssSize(value?: number | string) {\n if (value === undefined) return undefined\n return typeof value === 'number' ? `${value}px` : value\n}\n</script>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"radio.js","names":[],"sources":["../../../src/components/radio/radio.vue"],"sourcesContent":["<template>\n <label :class=\"classList\">\n <input\n type=\"radio\"\n :class=\"cls.e('native')\"\n :value=\"value\"\n v-model=\"model\"\n :disabled=\"disabled\"\n />\n\n <!-- radio框 -->\n <section :class=\"cls.e('button')\">\n <transition name=\"zoom-in\">\n <span :class=\"cls.e('inner')\" v-if=\"radioChecked\"></span>\n </transition>\n </section>\n\n <span :class=\"cls.e('label')\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { RadioProps, RadioEmits } from '../../types'\n\ndefineOptions({ name: 'URadio' })\n\nconst model = defineModel<any>()\n\nconst props = withDefaults(defineProps<RadioProps>(), { disabled: undefined })\n\ndefineEmits<RadioEmits>()\n\nconst cls = bem('radio')\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false\n})\n\nconst radioChecked = computed(() => model.value === props.value)\n\nconst classList = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('checked', radioChecked.value)\n ]\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,MAAM,QAAQ,SAAgB,SAAA,YAAC;EAE/B,MAAM,QAAQ;EAId,MAAM,MAAM,IAAI,OAAO;EAEvB,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,MAAM,aAAa,qBAAqB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GACxE,MAAM;GACN,UAAU;EACZ,CAAC;EAED,MAAM,eAAe,eAAe,MAAM,UAAU,MAAM,KAAK;EAE/D,MAAM,YAAY,eAAe;GAC/B,OAAO;IACL,IAAI;IACJ,IAAI,EAAE,KAAK,KAAK;IAChB,IAAI,GAAG,YAAY,SAAS,KAAK;IACjC,IAAI,GAAG,WAAW,aAAa,KAAK;GACtC;EACF,CAAC;;GAxDC,OAAA,UAAA,GAAA,mBAmBQ,SAAA,EAnBA,OAAK,eAAE,UAAA,KAAS,EAAA,GAAA;IACtB,eAAA,mBAME,SAAA;KALA,MAAK;KACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA;KACZ,OAAO,QAAA;KACC,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,MAAK,QAAA;KACb,UAAU,MAAA,QAAA;IADF,GAAA,MAAA,IAAA,UAAA,GAAA,CAAA,CAAA,aAAA,MAAA,KAAK,CAAA,CAAA;IAIhB,mBAAA,UAAA;IACA,mBAIU,WAAA,EAJA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA,EAAA,GACpB,CAAA,YAEa,YAAA,EAFD,MAAK,UAAS,GAAA;KACxB,SAAA,cAAyD,CAArB,aAAA,SAApC,UAAA,GAAA,mBAAyD,QAAA;;MAAlD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA;;;;IAIvB,mBAEO,QAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA,EAAA,GACjB,CAAA,WAAwB,KAAA,QAAA,WAAA,CAAA,SAAA,CAAf,gBAAA,gBAAA,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"radio.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_withDirectives","_createElementVNode","_unref","_vModelRadio","_createCommentVNode","_createVNode","_Transition","_withCtx","_renderSlot","_createTextVNode","_toDisplayString"],"sources":["../../../src/components/radio/radio.vue"],"sourcesContent":["<template>\n <label :class=\"classList\">\n <input\n type=\"radio\"\n :class=\"cls.e('native')\"\n :value=\"value\"\n v-model=\"model\"\n :disabled=\"disabled\"\n />\n\n <!-- radio框 -->\n <section :class=\"cls.e('button')\">\n <transition name=\"zoom-in\">\n <span :class=\"cls.e('inner')\" v-if=\"radioChecked\"></span>\n </transition>\n </section>\n\n <span :class=\"cls.e('label')\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { RadioProps, RadioEmits } from '../../types'\n\ndefineOptions({ name: 'URadio' })\n\nconst model = defineModel<any>()\n\nconst props = withDefaults(defineProps<RadioProps>(), { disabled: undefined })\n\ndefineEmits<RadioEmits>()\n\nconst cls = bem('radio')\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false\n})\n\nconst radioChecked = computed(() => model.value === props.value)\n\nconst classList = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('checked', radioChecked.value)\n ]\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,MAAM,QAAQA,SAAgB,SAAA,YAAC;EAE/B,MAAM,QAAQ;EAId,MAAM,MAAM,IAAI,OAAO;EAEvB,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,MAAM,aAAa,qBAAqB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GACxE,MAAM;GACN,UAAU;EACZ,CAAC;EAED,MAAM,eAAe,eAAe,MAAM,UAAU,MAAM,KAAK;EAE/D,MAAM,YAAY,eAAe;GAC/B,OAAO;IACL,IAAI;IACJ,IAAI,EAAE,KAAK,KAAK;IAChB,IAAI,GAAG,YAAY,SAAS,KAAK;IACjC,IAAI,GAAG,WAAW,aAAa,KAAK;GACtC;EACF,CAAC;;GAxDC,OAAAC,UAAA,GAAAC,mBAmBQ,SAAA,EAnBA,OAAKC,eAAE,UAAA,KAAS,EAAA,GAAA;IACtBC,eAAAC,mBAME,SAAA;KALA,MAAK;KACJ,OAAKF,eAAEG,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA;KACZ,OAAO,QAAA;KACC,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,MAAK,QAAA;KACb,UAAUA,MAAA,QAAA;IADF,GAAA,MAAA,IAAA,UAAA,GAAA,CAAA,CAAAC,aAAA,MAAA,KAAK,CAAA,CAAA;IAIhBC,mBAAA,UAAA;IACAH,mBAIU,WAAA,EAJA,OAAKF,eAAEG,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA,EAAA,GACpB,CAAAG,YAEaC,YAAA,EAFD,MAAK,UAAS,GAAA;KACxB,SAAAC,cAAyD,CAArB,aAAA,SAApCV,UAAA,GAAAC,mBAAyD,QAAA;;MAAlD,OAAKC,eAAEG,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA;;;;IAIvBD,mBAEO,QAAA,EAFA,OAAKF,eAAEG,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA,EAAA,GACjB,CAAAM,WAAwB,KAAA,QAAA,WAAA,CAAA,SAAA,CAAfC,gBAAAC,gBAAA,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":["$attrs"],"sources":["../../../src/components/radio-group/radio-group.vue"],"sourcesContent":["<template>\n <div v-if=\"!readonly\" v-bind=\"$attrs\" :class=\"[cls.b, bem.is('block', block), cls.m(size)]\">\n <u-radio\n v-for=\"item of items\"\n :key=\"item[valueKey]\"\n :value=\"item[valueKey]\"\n :model-value=\"model\"\n @update:model-value=\"handleUpdate($event, item)\"\n :disabled=\"disabledItem?.(item) || disabled\"\n :size=\"size\"\n >\n {{ item[labelKey] }}\n </u-radio>\n </div>\n\n <template v-else>\n {{ items.find((item) => item[valueKey] === model)?.[labelKey] || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { RadioGroupProps, RadioGroupEmits } from '../../types'\nimport URadio from '../radio/radio.vue'\n\ndefineOptions({ name: 'URadioGroup', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<RadioGroupProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n disabled: undefined,\n readonly: undefined\n})\n\nconst model = defineModel<any>()\n\nconst emit = defineEmits<RadioGroupEmits>()\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false,\n readonly: false\n})\n\nconst cls = bem('radio-group')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst handleUpdate = (value: any, item: Record<string, any>) => {\n model.value = value\n emit('change', item)\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BA,MAAM,QAAQ;EAOd,MAAM,QAAQ,SAAgB,SAAA,YAAC;EAE/B,MAAM,OAAO;EAEb,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,MAAM,UAAU,aAAa,qBAAqB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GAClF,MAAM;GACN,UAAU;GACV,UAAU;EACZ,CAAC;EAED,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,WAAW,eAAe,SAAS,MAAM,UAAU,OAAO,CAAC;EACjE,MAAM,WAAW,eAAe,SAAS,MAAM,UAAU,OAAO,CAAC;EAEjE,MAAM,gBAAgB,OAAY,SAA8B;GAC9D,MAAM,QAAQ;GACd,KAAK,UAAU,IAAI;EACrB;;GAzDc,OAAA,CAAA,MAAA,QAAA,KAAZ,UAAA,GAAA,mBAYM,OAZN,WAYM,EAAA,KAAA,EAAA,GAZwBA,KAAAA,QAAM,EAAG,OAAK;IAAG,MAAA,GAAA,CAAG,CAAC;IAAG,MAAA,GAAA,CAAG,CAAC,GAAE,SAAU,QAAA,KAAK;IAAG,MAAA,GAAA,CAAG,CAAC,EAAE,MAAA,IAAA,CAAI;GAAA,EAAA,CAAA,GACtF,EAAA,UAAA,IAAA,GAAA,mBAUU,UAAA,MAAA,WATO,QAAA,QAAR,SAAI;IADb,OAAA,UAAA,GAAA,YAUU,aAAA;KARP,KAAK,KAAK,SAAA;KACV,OAAO,KAAK,SAAA;KACZ,eAAa,MAAA;KACb,wBAAkB,WAAE,aAAa,QAAQ,IAAI;KAC7C,UAAU,QAAA,eAAe,IAAI,KAAK,MAAA,QAAA;KAClC,MAAM,MAAA,IAAA;;KAEP,SAAA,cAAoB,CAAjB,gBAAA,gBAAA,KAAK,SAAA,MAAQ,GAAA,CAAA,CAAA,CAAA;;;;;;;;;GAIpB,CAAA,GAAA,GAAA,EAAA,GAAA,EAAA,MAAA,UAAA,GAAA,mBAEW,UAAA,EAAA,KAAA,EAAA,GAAA,CADN,gBAAA,gBAAA,QAAA,MAAM,MAAM,SAAS,KAAK,SAAA,WAAc,MAAA,KAAK,CAAA,GAAI,SAAA,UAAa,MAAA,kBAAA,CAAkB,GAAA,CAAA,CAAA,GAAA,EAAA"}
1
+ {"version":3,"file":"radio-group.js","names":["$attrs","_useModel","_unref","_openBlock","_createElementBlock","_mergeProps","_Fragment","_renderList","_createBlock","URadio","_withCtx","_createTextVNode","_toDisplayString"],"sources":["../../../src/components/radio-group/radio-group.vue"],"sourcesContent":["<template>\n <div v-if=\"!readonly\" v-bind=\"$attrs\" :class=\"[cls.b, bem.is('block', block), cls.m(size)]\">\n <u-radio\n v-for=\"item of items\"\n :key=\"item[valueKey]\"\n :value=\"item[valueKey]\"\n :model-value=\"model\"\n @update:model-value=\"handleUpdate($event, item)\"\n :disabled=\"disabledItem?.(item) || disabled\"\n :size=\"size\"\n >\n {{ item[labelKey] }}\n </u-radio>\n </div>\n\n <template v-else>\n {{ items.find((item) => item[valueKey] === model)?.[labelKey] || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { RadioGroupProps, RadioGroupEmits } from '../../types'\nimport URadio from '../radio/radio.vue'\n\ndefineOptions({ name: 'URadioGroup', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<RadioGroupProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n disabled: undefined,\n readonly: undefined\n})\n\nconst model = defineModel<any>()\n\nconst emit = defineEmits<RadioGroupEmits>()\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false,\n readonly: false\n})\n\nconst cls = bem('radio-group')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst handleUpdate = (value: any, item: Record<string, any>) => {\n model.value = value\n emit('change', item)\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BA,MAAM,QAAQ;EAOd,MAAM,QAAQC,SAAgB,SAAA,YAAC;EAE/B,MAAM,OAAO;EAEb,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,MAAM,UAAU,aAAa,qBAAqB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GAClF,MAAM;GACN,UAAU;GACV,UAAU;EACZ,CAAC;EAED,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,WAAW,eAAe,SAAS,MAAM,UAAU,OAAO,CAAC;EACjE,MAAM,WAAW,eAAe,SAAS,MAAM,UAAU,OAAO,CAAC;EAEjE,MAAM,gBAAgB,OAAY,SAA8B;GAC9D,MAAM,QAAQ;GACd,KAAK,UAAU,IAAI;EACrB;;GAzDc,OAAA,CAAAC,MAAA,QAAA,KAAZC,UAAA,GAAAC,mBAYM,OAZNC,WAYM,EAAA,KAAA,EAAA,GAZwBL,KAAAA,QAAM,EAAG,OAAK;IAAGE,MAAA,GAAA,CAAG,CAAC;IAAGA,MAAA,GAAA,CAAG,CAAC,GAAE,SAAU,QAAA,KAAK;IAAGA,MAAA,GAAA,CAAG,CAAC,EAAEA,MAAA,IAAA,CAAI;GAAA,EAAA,CAAA,GACtF,EAAAC,UAAA,IAAA,GAAAC,mBAUUE,UAAA,MAAAC,WATO,QAAA,QAAR,SAAI;IADb,OAAAJ,UAAA,GAAAK,YAUUC,aAAA;KARP,KAAK,KAAK,SAAA;KACV,OAAO,KAAK,SAAA;KACZ,eAAa,MAAA;KACb,wBAAkB,WAAE,aAAa,QAAQ,IAAI;KAC7C,UAAU,QAAA,eAAe,IAAI,KAAKP,MAAA,QAAA;KAClC,MAAMA,MAAA,IAAA;;KAEP,SAAAQ,cAAoB,CAAjBC,gBAAAC,gBAAA,KAAK,SAAA,MAAQ,GAAA,CAAA,CAAA,CAAA;;;;;;;;;GAIpB,CAAA,GAAA,GAAA,EAAA,GAAA,EAAA,MAAAT,UAAA,GAAAC,mBAEWE,UAAA,EAAA,KAAA,EAAA,GAAA,CADNK,gBAAAC,gBAAA,QAAA,MAAM,MAAM,SAAS,KAAK,SAAA,WAAc,MAAA,KAAK,CAAA,GAAI,SAAA,UAAaV,MAAA,kBAAA,CAAkB,GAAA,CAAA,CAAA,GAAA,EAAA"}
@@ -7,33 +7,41 @@ function nextImageId() {
7
7
  /**
8
8
  * 图片节点:插入时 src 为本地 objectURL、__file 持有原始文件,提交前由宿主调用
9
9
  * uploadImages 换成服务器地址。__file 不参与序列化,仅会话内有效。
10
+ * width / height 为显示尺寸(px),0 表示未设置、按图片自然尺寸(受 max-width 约束)。
10
11
  * 本仓库为手写 Vue 绑定(无 React 适配层),不走 decorate 渲染管道,
11
- * createDOM 直接返回 <img> 元素。
12
+ * createDOM 返回 wrapper:`<span><img><span 手柄/></span>`,选中与拖拽缩放
13
+ * 由组件层(use-image-resize)基于 data-rte-image-key 驱动。
12
14
  */
13
15
  var ImageNode = class ImageNode extends DecoratorNode {
14
16
  __src;
15
17
  __alt;
16
18
  __id;
17
19
  __file;
20
+ __width;
21
+ __height;
18
22
  static getType() {
19
23
  return "u-image";
20
24
  }
21
25
  static clone(node) {
22
- return new ImageNode(node.__src, node.__alt, node.__id, node.__file, node.__key);
26
+ return new ImageNode(node.__src, node.__alt, node.__id, node.__file, node.__width, node.__height, node.__key);
23
27
  }
24
28
  static importJSON(serializedNode) {
25
29
  return $createImageNode({
26
30
  src: serializedNode.src,
27
31
  alt: serializedNode.alt,
28
- id: serializedNode.id
32
+ id: serializedNode.id,
33
+ width: serializedNode.width,
34
+ height: serializedNode.height
29
35
  });
30
36
  }
31
- constructor(src, alt = "", id = nextImageId(), file = null, key) {
37
+ constructor(src, alt = "", id = nextImageId(), file = null, width = 0, height = 0, key) {
32
38
  super(key);
33
39
  this.__src = src;
34
40
  this.__alt = alt;
35
41
  this.__id = id;
36
42
  this.__file = file;
43
+ this.__width = width;
44
+ this.__height = height;
37
45
  }
38
46
  static importDOM() {
39
47
  return { img: () => ({
@@ -47,6 +55,8 @@ var ImageNode = class ImageNode extends DecoratorNode {
47
55
  element.setAttribute("class", "u-rte-image");
48
56
  element.setAttribute("alt", this.__alt);
49
57
  element.setAttribute("data-rte-image-id", this.__id);
58
+ if (this.__width) element.setAttribute("width", String(this.__width));
59
+ if (this.__height) element.setAttribute("height", String(this.__height));
50
60
  return { element };
51
61
  }
52
62
  exportJSON() {
@@ -55,21 +65,42 @@ var ImageNode = class ImageNode extends DecoratorNode {
55
65
  version: 1,
56
66
  src: this.__src,
57
67
  alt: this.__alt,
58
- id: this.__id
68
+ id: this.__id,
69
+ width: this.__width,
70
+ height: this.__height
59
71
  };
60
72
  }
61
73
  createDOM() {
62
- const element = document.createElement("img");
63
- element.className = "u-rte-image";
64
- element.src = this.__src;
65
- element.alt = this.__alt;
66
- element.draggable = false;
67
- return element;
74
+ const wrapper = document.createElement("span");
75
+ wrapper.className = "u-rte-image-wrap";
76
+ wrapper.contentEditable = "false";
77
+ wrapper.setAttribute("data-rte-image-key", this.__key);
78
+ const img = document.createElement("img");
79
+ img.className = "u-rte-image";
80
+ img.src = this.__src;
81
+ img.alt = this.__alt;
82
+ img.draggable = false;
83
+ if (this.__width) img.style.width = `${this.__width}px`;
84
+ if (this.__height) img.style.height = `${this.__height}px`;
85
+ const handle = document.createElement("span");
86
+ handle.className = "u-rte-image-resizer";
87
+ handle.setAttribute("aria-hidden", "true");
88
+ wrapper.append(img, handle);
89
+ return wrapper;
68
90
  }
69
91
  updateDOM(prevNode, element) {
70
- const img = element;
92
+ const img = element.querySelector("img");
93
+ if (!img) return false;
71
94
  if (prevNode.__src !== this.__src) img.src = this.__src;
72
95
  if (prevNode.__alt !== this.__alt) img.alt = this.__alt;
96
+ if (prevNode.__width !== this.__width) {
97
+ if (this.__width) img.style.width = `${this.__width}px`;
98
+ else img.style.removeProperty("width");
99
+ }
100
+ if (prevNode.__height !== this.__height) {
101
+ if (this.__height) img.style.height = `${this.__height}px`;
102
+ else img.style.removeProperty("height");
103
+ }
73
104
  return false;
74
105
  }
75
106
  /** Vue 环境无 React 渲染管道,装饰内容即 createDOM 返回的元素本身 */
@@ -88,6 +119,12 @@ var ImageNode = class ImageNode extends DecoratorNode {
88
119
  self.__src = src;
89
120
  self.__file = null;
90
121
  }
122
+ /** 写入显示尺寸(拖拽缩放结束时调用),px,0 表示恢复自然尺寸 */
123
+ setWidthAndHeight(width, height) {
124
+ const self = this.getWritable();
125
+ self.__width = width;
126
+ self.__height = height;
127
+ }
91
128
  };
92
129
  function convertImageElement(domNode) {
93
130
  const src = domNode.getAttribute("src");
@@ -95,11 +132,13 @@ function convertImageElement(domNode) {
95
132
  return { node: $createImageNode({
96
133
  src,
97
134
  alt: domNode.getAttribute("alt") ?? "",
98
- id: domNode.getAttribute("data-rte-image-id") ?? void 0
135
+ id: domNode.getAttribute("data-rte-image-id") ?? void 0,
136
+ width: parseInt(domNode.getAttribute("width") ?? "") || 0,
137
+ height: parseInt(domNode.getAttribute("height") ?? "") || 0
99
138
  }) };
100
139
  }
101
140
  function $createImageNode(options) {
102
- return new ImageNode(options.src, options.alt ?? "", options.id ?? nextImageId(), options.file ?? null);
141
+ return new ImageNode(options.src, options.alt ?? "", options.id ?? nextImageId(), options.file ?? null, options.width ?? 0, options.height ?? 0);
103
142
  }
104
143
  function $isImageNode(node) {
105
144
  return node instanceof ImageNode;
@@ -1 +1 @@
1
- {"version":3,"file":"image-node.js","names":[],"sources":["../../../src/components/rich-text-editor/image-node.ts"],"sourcesContent":["import {\n $insertNodes,\n DecoratorNode,\n type DOMConversionMap,\n type DOMConversionOutput,\n type DOMExportOutput,\n type LexicalEditor,\n type LexicalNode,\n type NodeKey,\n type SerializedLexicalNode,\n type Spread\n} from 'lexical'\n\nlet imageUid = 0\n\nfunction nextImageId(): string {\n return `rte-img-${++imageUid}`\n}\n\nexport interface SerializedImageNode extends Spread<\n { src: string; alt: string; id: string },\n SerializedLexicalNode\n> {}\n\n/**\n * 图片节点:插入时 src 为本地 objectURL、__file 持有原始文件,提交前由宿主调用\n * uploadImages 换成服务器地址。__file 不参与序列化,仅会话内有效。\n * 本仓库为手写 Vue 绑定(无 React 适配层),不走 decorate 渲染管道,\n * createDOM 直接返回 <img> 元素。\n */\nexport class ImageNode extends DecoratorNode<void> {\n __src: string\n __alt: string\n __id: string\n __file: File | null\n\n static override getType(): string {\n return 'u-image'\n }\n\n static override clone(node: ImageNode): ImageNode {\n return new ImageNode(node.__src, node.__alt, node.__id, node.__file, node.__key)\n }\n\n static override importJSON(serializedNode: SerializedImageNode): ImageNode {\n return $createImageNode({\n src: serializedNode.src,\n alt: serializedNode.alt,\n id: serializedNode.id\n })\n }\n\n constructor(src: string, alt = '', id = nextImageId(), file: File | null = null, key?: NodeKey) {\n super(key)\n this.__src = src\n this.__alt = alt\n this.__id = id\n this.__file = file\n }\n\n static override importDOM(): DOMConversionMap | null {\n return { img: () => ({ conversion: convertImageElement, priority: 0 }) }\n }\n\n override exportDOM(): DOMExportOutput {\n const element = document.createElement('img')\n element.setAttribute('src', this.__src)\n element.setAttribute('class', 'u-rte-image')\n element.setAttribute('alt', this.__alt)\n element.setAttribute('data-rte-image-id', this.__id)\n return { element }\n }\n\n override exportJSON(): SerializedImageNode {\n return { type: 'u-image', version: 1, src: this.__src, alt: this.__alt, id: this.__id }\n }\n\n override createDOM(): HTMLElement {\n const element = document.createElement('img')\n element.className = 'u-rte-image'\n element.src = this.__src\n element.alt = this.__alt\n element.draggable = false\n return element\n }\n\n override updateDOM(prevNode: ImageNode, element: HTMLElement): boolean {\n const img = element as HTMLImageElement\n if (prevNode.__src !== this.__src) img.src = this.__src\n if (prevNode.__alt !== this.__alt) img.alt = this.__alt\n return false\n }\n\n /** Vue 环境无 React 渲染管道,装饰内容即 createDOM 返回的元素本身 */\n override decorate(): null {\n return null\n }\n\n getSrc(): string {\n return this.__src\n }\n\n getFile(): File | null {\n return this.__file\n }\n\n /** 替换为服务器地址,同时清除待上传标记(已上传的节点不再重复上传) */\n setSrc(src: string): void {\n const self = this.getWritable()\n self.__src = src\n self.__file = null\n }\n}\n\nfunction convertImageElement(domNode: HTMLElement): DOMConversionOutput {\n const src = domNode.getAttribute('src')\n if (!src) return { node: null }\n return {\n node: $createImageNode({\n src,\n alt: domNode.getAttribute('alt') ?? '',\n id: domNode.getAttribute('data-rte-image-id') ?? undefined\n })\n }\n}\n\nexport function $createImageNode(options: {\n src: string\n alt?: string\n id?: string\n file?: File | null\n}): ImageNode {\n return new ImageNode(\n options.src,\n options.alt ?? '',\n options.id ?? nextImageId(),\n options.file ?? null\n )\n}\n\nexport function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode {\n return node instanceof ImageNode\n}\n\n// 各编辑器实例创建的图片 objectURL,用于节点删除 / 组件卸载时回收\nconst objectUrls = new WeakMap<LexicalEditor, Set<string>>()\n\nfunction getImageObjectUrls(editor: LexicalEditor): Set<string> {\n let urls = objectUrls.get(editor)\n if (!urls) {\n urls = new Set()\n objectUrls.set(editor, urls)\n }\n return urls\n}\n\n/** 插入图片文件:objectURL 即时预览,File 挂在节点上等待提交时上传。返回插入的图片数量 */\nexport function insertImageFiles(editor: LexicalEditor, files: File[] | FileList): number {\n const images = Array.from(files).filter((file) => file.type.startsWith('image/'))\n if (!images.length) return 0\n\n const urls = getImageObjectUrls(editor)\n\n editor.update(() => {\n const nodes = images.map((file) => {\n const url = URL.createObjectURL(file)\n urls.add(url)\n return $createImageNode({ src: url, alt: file.name, file })\n })\n $insertNodes(nodes)\n })\n\n return images.length\n}\n\n/** 回收已从内容中移除的图片 objectURL,aliveUrls 为当前内容中仍存活的图片地址 */\nexport function gcImageObjectUrls(editor: LexicalEditor, aliveUrls: Set<string>): void {\n const urls = objectUrls.get(editor)\n if (!urls?.size) return\n urls.forEach((url) => {\n if (!aliveUrls.has(url)) {\n URL.revokeObjectURL(url)\n urls.delete(url)\n }\n })\n}\n\n/** 回收该编辑器创建的全部图片 objectURL(组件卸载时调用) */\nexport function revokeImageObjectUrls(editor: LexicalEditor): void {\n const urls = objectUrls.get(editor)\n urls?.forEach((url) => URL.revokeObjectURL(url))\n objectUrls.delete(editor)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-node.js","names":[],"sources":["../../../src/components/rich-text-editor/image-node.ts"],"sourcesContent":["import {\n $insertNodes,\n DecoratorNode,\n type DOMConversionMap,\n type DOMConversionOutput,\n type DOMExportOutput,\n type LexicalEditor,\n type LexicalNode,\n type NodeKey,\n type SerializedLexicalNode,\n type Spread\n} from 'lexical'\n\nlet imageUid = 0\n\nfunction nextImageId(): string {\n return `rte-img-${++imageUid}`\n}\n\nexport interface SerializedImageNode extends Spread<\n { src: string; alt: string; id: string; width: number; height: number },\n SerializedLexicalNode\n> {}\n\n/**\n * 图片节点:插入时 src 为本地 objectURL、__file 持有原始文件,提交前由宿主调用\n * uploadImages 换成服务器地址。__file 不参与序列化,仅会话内有效。\n * width / height 为显示尺寸(px),0 表示未设置、按图片自然尺寸(受 max-width 约束)。\n * 本仓库为手写 Vue 绑定(无 React 适配层),不走 decorate 渲染管道,\n * createDOM 返回 wrapper:`<span><img><span 手柄/></span>`,选中与拖拽缩放\n * 由组件层(use-image-resize)基于 data-rte-image-key 驱动。\n */\nexport class ImageNode extends DecoratorNode<void> {\n __src: string\n __alt: string\n __id: string\n __file: File | null\n __width: number\n __height: number\n\n static override getType(): string {\n return 'u-image'\n }\n\n static override clone(node: ImageNode): ImageNode {\n return new ImageNode(\n node.__src,\n node.__alt,\n node.__id,\n node.__file,\n node.__width,\n node.__height,\n node.__key\n )\n }\n\n static override importJSON(serializedNode: SerializedImageNode): ImageNode {\n return $createImageNode({\n src: serializedNode.src,\n alt: serializedNode.alt,\n id: serializedNode.id,\n width: serializedNode.width,\n height: serializedNode.height\n })\n }\n\n constructor(\n src: string,\n alt = '',\n id = nextImageId(),\n file: File | null = null,\n width = 0,\n height = 0,\n key?: NodeKey\n ) {\n super(key)\n this.__src = src\n this.__alt = alt\n this.__id = id\n this.__file = file\n this.__width = width\n this.__height = height\n }\n\n static override importDOM(): DOMConversionMap | null {\n return { img: () => ({ conversion: convertImageElement, priority: 0 }) }\n }\n\n override exportDOM(): DOMExportOutput {\n const element = document.createElement('img')\n element.setAttribute('src', this.__src)\n element.setAttribute('class', 'u-rte-image')\n element.setAttribute('alt', this.__alt)\n element.setAttribute('data-rte-image-id', this.__id)\n if (this.__width) element.setAttribute('width', String(this.__width))\n if (this.__height) element.setAttribute('height', String(this.__height))\n return { element }\n }\n\n override exportJSON(): SerializedImageNode {\n return {\n type: 'u-image',\n version: 1,\n src: this.__src,\n alt: this.__alt,\n id: this.__id,\n width: this.__width,\n height: this.__height\n }\n }\n\n override createDOM(): HTMLElement {\n const wrapper = document.createElement('span')\n wrapper.className = 'u-rte-image-wrap'\n wrapper.contentEditable = 'false'\n wrapper.setAttribute('data-rte-image-key', this.__key)\n\n const img = document.createElement('img')\n img.className = 'u-rte-image'\n img.src = this.__src\n img.alt = this.__alt\n img.draggable = false\n if (this.__width) img.style.width = `${this.__width}px`\n if (this.__height) img.style.height = `${this.__height}px`\n\n const handle = document.createElement('span')\n handle.className = 'u-rte-image-resizer'\n handle.setAttribute('aria-hidden', 'true')\n\n wrapper.append(img, handle)\n return wrapper\n }\n\n override updateDOM(prevNode: ImageNode, element: HTMLElement): boolean {\n const img = element.querySelector('img')\n if (!img) return false\n if (prevNode.__src !== this.__src) img.src = this.__src\n if (prevNode.__alt !== this.__alt) img.alt = this.__alt\n if (prevNode.__width !== this.__width) {\n if (this.__width) img.style.width = `${this.__width}px`\n else img.style.removeProperty('width')\n }\n if (prevNode.__height !== this.__height) {\n if (this.__height) img.style.height = `${this.__height}px`\n else img.style.removeProperty('height')\n }\n return false\n }\n\n /** Vue 环境无 React 渲染管道,装饰内容即 createDOM 返回的元素本身 */\n override decorate(): null {\n return null\n }\n\n getSrc(): string {\n return this.__src\n }\n\n getFile(): File | null {\n return this.__file\n }\n\n /** 替换为服务器地址,同时清除待上传标记(已上传的节点不再重复上传) */\n setSrc(src: string): void {\n const self = this.getWritable()\n self.__src = src\n self.__file = null\n }\n\n /** 写入显示尺寸(拖拽缩放结束时调用),px,0 表示恢复自然尺寸 */\n setWidthAndHeight(width: number, height: number): void {\n const self = this.getWritable()\n self.__width = width\n self.__height = height\n }\n}\n\nfunction convertImageElement(domNode: HTMLElement): DOMConversionOutput {\n const src = domNode.getAttribute('src')\n if (!src) return { node: null }\n return {\n node: $createImageNode({\n src,\n alt: domNode.getAttribute('alt') ?? '',\n id: domNode.getAttribute('data-rte-image-id') ?? undefined,\n width: parseInt(domNode.getAttribute('width') ?? '') || 0,\n height: parseInt(domNode.getAttribute('height') ?? '') || 0\n })\n }\n}\n\nexport function $createImageNode(options: {\n src: string\n alt?: string\n id?: string\n file?: File | null\n width?: number\n height?: number\n}): ImageNode {\n return new ImageNode(\n options.src,\n options.alt ?? '',\n options.id ?? nextImageId(),\n options.file ?? null,\n options.width ?? 0,\n options.height ?? 0\n )\n}\n\nexport function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode {\n return node instanceof ImageNode\n}\n\n// 各编辑器实例创建的图片 objectURL,用于节点删除 / 组件卸载时回收\nconst objectUrls = new WeakMap<LexicalEditor, Set<string>>()\n\nfunction getImageObjectUrls(editor: LexicalEditor): Set<string> {\n let urls = objectUrls.get(editor)\n if (!urls) {\n urls = new Set()\n objectUrls.set(editor, urls)\n }\n return urls\n}\n\n/** 插入图片文件:objectURL 即时预览,File 挂在节点上等待提交时上传。返回插入的图片数量 */\nexport function insertImageFiles(editor: LexicalEditor, files: File[] | FileList): number {\n const images = Array.from(files).filter((file) => file.type.startsWith('image/'))\n if (!images.length) return 0\n\n const urls = getImageObjectUrls(editor)\n\n editor.update(() => {\n const nodes = images.map((file) => {\n const url = URL.createObjectURL(file)\n urls.add(url)\n return $createImageNode({ src: url, alt: file.name, file })\n })\n $insertNodes(nodes)\n })\n\n return images.length\n}\n\n/** 回收已从内容中移除的图片 objectURL,aliveUrls 为当前内容中仍存活的图片地址 */\nexport function gcImageObjectUrls(editor: LexicalEditor, aliveUrls: Set<string>): void {\n const urls = objectUrls.get(editor)\n if (!urls?.size) return\n urls.forEach((url) => {\n if (!aliveUrls.has(url)) {\n URL.revokeObjectURL(url)\n urls.delete(url)\n }\n })\n}\n\n/** 回收该编辑器创建的全部图片 objectURL(组件卸载时调用) */\nexport function revokeImageObjectUrls(editor: LexicalEditor): void {\n const urls = objectUrls.get(editor)\n urls?.forEach((url) => URL.revokeObjectURL(url))\n objectUrls.delete(editor)\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { $isImageNode, ImageNode, gcImageObjectUrls, insertImageFiles, revokeImageObjectUrls } from "./image-node.js";
2
2
  import _sfc_main$1 from "./toolbar.js";
3
+ import { useImageResize } from "./use-image-resize.js";
3
4
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeModels, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, shallowRef, toDisplayString, unref, useModel, useTemplateRef, watch } from "vue";
4
5
  import { useFormFallbackProps } from "@veltra/compositions";
5
6
  import { bem, injectFormContext } from "@veltra/utils";
@@ -8,7 +9,7 @@ import { $generateHtmlFromNodes, $generateNodesFromDOM } from "@lexical/html";
8
9
  import { AutoLinkNode, LinkNode } from "@lexical/link";
9
10
  import { ListItemNode, ListNode, registerList } from "@lexical/list";
10
11
  import { HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
11
- import { $createParagraphNode, $createRangeSelection, $getNodeByKey, $getRoot, $nodesOfType, $setSelection, COMMAND_PRIORITY_HIGH, DROP_COMMAND, PASTE_COMMAND, createEditor } from "lexical";
12
+ import { $createParagraphNode, $createRangeSelection, $getNodeByKey, $getRoot, $getSelection, $isNodeSelection, $nodesOfType, $setSelection, COMMAND_PRIORITY_HIGH, DROP_COMMAND, PASTE_COMMAND, createEditor } from "lexical";
12
13
  //#region src/components/rich-text-editor/rich-text-editor.vue
13
14
  const _hoisted_1 = ["contenteditable"];
14
15
  const LEXICAL_THEME = {
@@ -112,6 +113,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
112
113
  const showPlaceholder = ref(true);
113
114
  let isComposing = false;
114
115
  let cleanupFns = [];
116
+ const acceptsImage = () => props.image && !isDisabled.value && !isReadonly.value;
117
+ useImageResize(editor, acceptsImage);
115
118
  function initEditor() {
116
119
  if (!editorContainer.value) return;
117
120
  const editorInstance = createEditor({
@@ -135,7 +138,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
135
138
  cleanupFns.push(registerRichText(editorInstance));
136
139
  cleanupFns.push(registerList(editorInstance));
137
140
  cleanupFns.push(registerHistory(editorInstance, createEmptyHistoryState(), 300));
138
- const acceptsImage = () => props.image && !isDisabled.value && !isReadonly.value;
139
141
  cleanupFns.push(editorInstance.registerCommand(PASTE_COMMAND, (event) => {
140
142
  if (!acceptsImage()) return false;
141
143
  if (insertImageFiles(editorInstance, event.clipboardData?.files ?? []) > 0) {
@@ -252,7 +254,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
252
254
  }
253
255
  });
254
256
  watch([isDisabled, isReadonly], () => {
255
- if (editor.value) editor.value.setEditable(!isDisabled.value && !isReadonly.value);
257
+ if (!editor.value) return;
258
+ editor.value.setEditable(!isDisabled.value && !isReadonly.value);
259
+ if (isDisabled.value || isReadonly.value) editor.value.update(() => {
260
+ if ($isNodeSelection($getSelection())) $setSelection(null);
261
+ });
256
262
  });
257
263
  onMounted(() => {
258
264
  initEditor();
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text-editor.js","names":[],"sources":["../../../src/components/rich-text-editor/rich-text-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <toolbar\n v-if=\"!isReadonly && toolbarItems.length > 0\"\n :editor=\"editor\"\n :toolbar=\"toolbarItems\"\n :disabled=\"isDisabled\"\n />\n <div :class=\"cls.e('content')\">\n <div\n ref=\"editorContainer\"\n :class=\"cls.e('editable')\"\n :contenteditable=\"!isDisabled && !isReadonly\"\n ></div>\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ placeholder }}\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport type { ToolbarItem } from '../../types'\n\nconst LEXICAL_THEME = {\n paragraph: 'u-rte-paragraph',\n heading: {\n h1: 'u-rte-h1',\n h2: 'u-rte-h2',\n h3: 'u-rte-h3',\n h4: 'u-rte-h4',\n h5: 'u-rte-h5',\n h6: 'u-rte-h6'\n },\n text: {\n bold: 'u-rte-bold',\n italic: 'u-rte-italic',\n underline: 'u-rte-underline',\n strikethrough: 'u-rte-strikethrough'\n },\n list: {\n ul: 'u-rte-ul',\n ol: 'u-rte-ol',\n listitem: 'u-rte-li',\n nested: { listitem: 'u-rte-li--nested' }\n },\n quote: 'u-rte-blockquote',\n\n link: 'u-rte-link',\n image: 'u-rte-image'\n}\n\nconst DEFAULT_TOOLBAR: ToolbarItem[] = [\n 'undo',\n 'redo',\n '|',\n 'heading',\n '|',\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n '|',\n 'bullet-list',\n 'ordered-list',\n '|',\n 'blockquote',\n '|',\n 'link',\n '|',\n 'image'\n]\n</script>\n\n<script lang=\"ts\" setup>\nimport { registerHistory, createEmptyHistoryState } from '@lexical/history'\nimport { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html'\nimport { LinkNode, AutoLinkNode } from '@lexical/link'\nimport { ListNode, ListItemNode, registerList } from '@lexical/list'\nimport { registerRichText } from '@lexical/rich-text'\nimport { HeadingNode, QuoteNode } from '@lexical/rich-text'\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n $createRangeSelection,\n $getNodeByKey,\n $getRoot,\n $createParagraphNode,\n $nodesOfType,\n $setSelection,\n COMMAND_PRIORITY_HIGH,\n DROP_COMMAND,\n PASTE_COMMAND,\n createEditor,\n type LexicalEditor\n} from 'lexical'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n RichTextEditorProps,\n RichTextImageUploader,\n _RichTextEditorExposed\n} from '../../types'\nimport {\n ImageNode,\n $isImageNode,\n gcImageObjectUrls,\n insertImageFiles,\n revokeImageObjectUrls\n} from './image-node'\nimport Toolbar from './toolbar.vue'\n\nconst props = withDefaults(defineProps<RichTextEditorProps>(), {\n disabled: undefined,\n readonly: undefined,\n format: 'html',\n toolbar: () => DEFAULT_TOOLBAR,\n placeholder: '',\n image: true\n})\n\nconst model = defineModel<string>()\n\nconst cls = bem('rich-text-editor')\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst isDisabled = computed(() => !!disabled.value)\nconst isReadonly = computed(() => !!readonly.value)\n\nconst className = computed<string[]>(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', isDisabled.value),\n bem.is('readonly', isReadonly.value)\n ]\n})\n\nconst toolbarItems = computed<ToolbarItem[]>(() => {\n const items = props.toolbar ?? DEFAULT_TOOLBAR\n return props.image ? items : items.filter((item) => item !== 'image')\n})\n\nconst editorContainer = useTemplateRef('editorContainer')\nconst editor = shallowRef<LexicalEditor | null>(null)\nconst showPlaceholder = ref(true)\nlet isComposing = false\nlet cleanupFns: (() => void)[] = []\n\nfunction initEditor() {\n if (!editorContainer.value) return\n\n const editorInstance = createEditor({\n namespace: 'URichTextEditor',\n theme: LEXICAL_THEME,\n nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, AutoLinkNode, ImageNode],\n editable: !isDisabled.value && !isReadonly.value,\n onError: (error: Error) => {\n console.error('Lexical error:', error)\n }\n })\n\n editorInstance.setRootElement(editorContainer.value)\n\n // Register plugins\n cleanupFns.push(registerRichText(editorInstance))\n cleanupFns.push(registerList(editorInstance))\n cleanupFns.push(registerHistory(editorInstance, createEmptyHistoryState(), 300))\n\n // Image input: paste & drop\n const acceptsImage = () => props.image && !isDisabled.value && !isReadonly.value\n\n cleanupFns.push(\n editorInstance.registerCommand(\n PASTE_COMMAND,\n (event) => {\n if (!acceptsImage()) return false\n if (insertImageFiles(editorInstance, event.clipboardData?.files ?? []) > 0) {\n event.preventDefault()\n return true\n }\n return false\n },\n COMMAND_PRIORITY_HIGH\n ),\n editorInstance.registerCommand(\n DROP_COMMAND,\n (event) => {\n if (!acceptsImage()) return false\n const files = event.dataTransfer?.files\n if (!files?.length) return false\n // 先把光标落到落点,再插入\n const range = document.caretRangeFromPoint(event.clientX, event.clientY)\n if (range) {\n editorInstance.update(() => {\n const selection = $createRangeSelection()\n selection.applyDOMRange(range)\n $setSelection(selection)\n })\n }\n if (insertImageFiles(editorInstance, files) > 0) {\n event.preventDefault()\n return true\n }\n return false\n },\n COMMAND_PRIORITY_HIGH\n )\n )\n\n // Set initial value\n if (model.value) {\n setEditorContent(editorInstance, model.value)\n }\n\n // Listen for content changes\n cleanupFns.push(\n editorInstance.registerUpdateListener(({ editorState }) => {\n editorState.read(\n () => {\n const root = $getRoot()\n const imageNodes = $nodesOfType(ImageNode)\n showPlaceholder.value =\n root.getTextContent().trim().length === 0 && imageNodes.length === 0\n\n // 回收已从内容中移除的图片 objectURL\n gcImageObjectUrls(editorInstance, new Set(imageNodes.map((node) => node.getSrc())))\n\n if (isComposing) return\n\n const value = serializeContent(editorInstance)\n\n // Avoid circular updates: only emit if content really changed\n if (value !== model.value) {\n isComposing = true\n model.value = value\n // Use microtask to reset after Vue processes the update\n queueMicrotask(() => {\n isComposing = false\n })\n }\n },\n { editor: editorInstance }\n )\n })\n )\n\n // Composing event listeners for IME\n const rootElement = editorInstance.getRootElement()\n if (rootElement) {\n const onCompositionStart = () => {\n isComposing = true\n }\n const onCompositionEnd = () => {\n isComposing = false\n }\n rootElement.addEventListener('compositionstart', onCompositionStart)\n rootElement.addEventListener('compositionend', onCompositionEnd)\n cleanupFns.push(() => {\n rootElement.removeEventListener('compositionstart', onCompositionStart)\n rootElement.removeEventListener('compositionend', onCompositionEnd)\n })\n }\n\n editor.value = editorInstance\n}\n\nfunction setEditorContent(editorInstance: LexicalEditor, value: string) {\n editorInstance.update(() => {\n const root = $getRoot()\n root.clear()\n\n if (props.format === 'json') {\n try {\n const parsedState = editorInstance.parseEditorState(value)\n editorInstance.setEditorState(parsedState)\n } catch {\n // If JSON parse fails, treat as empty\n root.append($createParagraphNode())\n }\n } else {\n // HTML format\n const parser = new DOMParser()\n const dom = parser.parseFromString(value, 'text/html')\n const nodes = $generateNodesFromDOM(editorInstance, dom)\n root.append(...nodes)\n }\n })\n}\n\nfunction serializeContent(editorInstance: LexicalEditor): string {\n return editorInstance.read(() =>\n props.format === 'json'\n ? JSON.stringify(editorInstance.getEditorState().toJSON())\n : $generateHtmlFromNodes(editorInstance)\n )\n}\n\nlet isUploadingImages = false\n\nasync function uploadImages(upload: RichTextImageUploader): Promise<string> {\n const editorInstance = editor.value\n if (!editorInstance) return model.value ?? ''\n if (isUploadingImages) throw new Error('图片正在上传中')\n\n const pending = editorInstance.getEditorState().read(() =>\n $nodesOfType(ImageNode)\n .filter((node) => node.getFile())\n .map((node) => ({ key: node.getKey(), file: node.getFile() as File }))\n )\n\n if (pending.length) {\n isUploadingImages = true\n try {\n const urls = await Promise.all(pending.map(({ file }) => upload(file)))\n editorInstance.update(() => {\n pending.forEach(({ key }, index) => {\n const node = $getNodeByKey(key)\n if ($isImageNode(node)) node.setSrc(urls[index])\n })\n })\n } finally {\n isUploadingImages = false\n }\n }\n\n return serializeContent(editorInstance)\n}\n\ndefineExpose<_RichTextEditorExposed>({ uploadImages })\n\n// Watch external model changes\nwatch(model, (newVal) => {\n if (!editor.value || isComposing) return\n\n const editorInstance = editor.value\n\n // Compare current content to avoid circular updates\n const currentValue = serializeContent(editorInstance)\n\n if (currentValue !== newVal) {\n isComposing = true\n setEditorContent(editorInstance, newVal ?? '')\n queueMicrotask(() => {\n isComposing = false\n })\n }\n})\n\n// Watch disabled/readonly changes\nwatch([isDisabled, isReadonly], () => {\n if (editor.value) {\n editor.value.setEditable(!isDisabled.value && !isReadonly.value)\n }\n})\n\nonMounted(() => {\n initEditor()\n})\n\nonBeforeUnmount(() => {\n cleanupFns.forEach((fn) => fn())\n cleanupFns = []\n if (editor.value) revokeImageObjectUrls(editor.value)\n editor.value = null\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"rich-text-editor.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_createBlock","Toolbar","_createCommentVNode","_createElementVNode","_unref","_toDisplayString"],"sources":["../../../src/components/rich-text-editor/rich-text-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <toolbar\n v-if=\"!isReadonly && toolbarItems.length > 0\"\n :editor=\"editor\"\n :toolbar=\"toolbarItems\"\n :disabled=\"isDisabled\"\n />\n <div :class=\"cls.e('content')\">\n <div\n ref=\"editorContainer\"\n :class=\"cls.e('editable')\"\n :contenteditable=\"!isDisabled && !isReadonly\"\n ></div>\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ placeholder }}\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport type { ToolbarItem } from '../../types'\n\nconst LEXICAL_THEME = {\n paragraph: 'u-rte-paragraph',\n heading: {\n h1: 'u-rte-h1',\n h2: 'u-rte-h2',\n h3: 'u-rte-h3',\n h4: 'u-rte-h4',\n h5: 'u-rte-h5',\n h6: 'u-rte-h6'\n },\n text: {\n bold: 'u-rte-bold',\n italic: 'u-rte-italic',\n underline: 'u-rte-underline',\n strikethrough: 'u-rte-strikethrough'\n },\n list: {\n ul: 'u-rte-ul',\n ol: 'u-rte-ol',\n listitem: 'u-rte-li',\n nested: { listitem: 'u-rte-li--nested' }\n },\n quote: 'u-rte-blockquote',\n\n link: 'u-rte-link',\n image: 'u-rte-image'\n}\n\nconst DEFAULT_TOOLBAR: ToolbarItem[] = [\n 'undo',\n 'redo',\n '|',\n 'heading',\n '|',\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n '|',\n 'bullet-list',\n 'ordered-list',\n '|',\n 'blockquote',\n '|',\n 'link',\n '|',\n 'image'\n]\n</script>\n\n<script lang=\"ts\" setup>\nimport { registerHistory, createEmptyHistoryState } from '@lexical/history'\nimport { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html'\nimport { LinkNode, AutoLinkNode } from '@lexical/link'\nimport { ListNode, ListItemNode, registerList } from '@lexical/list'\nimport { registerRichText } from '@lexical/rich-text'\nimport { HeadingNode, QuoteNode } from '@lexical/rich-text'\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n $createRangeSelection,\n $getNodeByKey,\n $getRoot,\n $createParagraphNode,\n $nodesOfType,\n $getSelection,\n $isNodeSelection,\n $setSelection,\n COMMAND_PRIORITY_HIGH,\n DROP_COMMAND,\n PASTE_COMMAND,\n createEditor,\n type LexicalEditor\n} from 'lexical'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n RichTextEditorProps,\n RichTextImageUploader,\n _RichTextEditorExposed\n} from '../../types'\nimport {\n ImageNode,\n $isImageNode,\n gcImageObjectUrls,\n insertImageFiles,\n revokeImageObjectUrls\n} from './image-node'\nimport Toolbar from './toolbar.vue'\nimport { useImageResize } from './use-image-resize'\n\nconst props = withDefaults(defineProps<RichTextEditorProps>(), {\n disabled: undefined,\n readonly: undefined,\n format: 'html',\n toolbar: () => DEFAULT_TOOLBAR,\n placeholder: '',\n image: true\n})\n\nconst model = defineModel<string>()\n\nconst cls = bem('rich-text-editor')\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst isDisabled = computed(() => !!disabled.value)\nconst isReadonly = computed(() => !!readonly.value)\n\nconst className = computed<string[]>(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', isDisabled.value),\n bem.is('readonly', isReadonly.value)\n ]\n})\n\nconst toolbarItems = computed<ToolbarItem[]>(() => {\n const items = props.toolbar ?? DEFAULT_TOOLBAR\n return props.image ? items : items.filter((item) => item !== 'image')\n})\n\nconst editorContainer = useTemplateRef('editorContainer')\nconst editor = shallowRef<LexicalEditor | null>(null)\nconst showPlaceholder = ref(true)\nlet isComposing = false\nlet cleanupFns: (() => void)[] = []\n\nconst acceptsImage = () => props.image && !isDisabled.value && !isReadonly.value\n\n// 图片选中 / 拖拽缩放交互\nuseImageResize(editor, acceptsImage)\n\nfunction initEditor() {\n if (!editorContainer.value) return\n\n const editorInstance = createEditor({\n namespace: 'URichTextEditor',\n theme: LEXICAL_THEME,\n nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, AutoLinkNode, ImageNode],\n editable: !isDisabled.value && !isReadonly.value,\n onError: (error: Error) => {\n console.error('Lexical error:', error)\n }\n })\n\n editorInstance.setRootElement(editorContainer.value)\n\n // Register plugins\n cleanupFns.push(registerRichText(editorInstance))\n cleanupFns.push(registerList(editorInstance))\n cleanupFns.push(registerHistory(editorInstance, createEmptyHistoryState(), 300))\n\n // Image input: paste & drop\n cleanupFns.push(\n editorInstance.registerCommand(\n PASTE_COMMAND,\n (event) => {\n if (!acceptsImage()) return false\n if (insertImageFiles(editorInstance, event.clipboardData?.files ?? []) > 0) {\n event.preventDefault()\n return true\n }\n return false\n },\n COMMAND_PRIORITY_HIGH\n ),\n editorInstance.registerCommand(\n DROP_COMMAND,\n (event) => {\n if (!acceptsImage()) return false\n const files = event.dataTransfer?.files\n if (!files?.length) return false\n // 先把光标落到落点,再插入\n const range = document.caretRangeFromPoint(event.clientX, event.clientY)\n if (range) {\n editorInstance.update(() => {\n const selection = $createRangeSelection()\n selection.applyDOMRange(range)\n $setSelection(selection)\n })\n }\n if (insertImageFiles(editorInstance, files) > 0) {\n event.preventDefault()\n return true\n }\n return false\n },\n COMMAND_PRIORITY_HIGH\n )\n )\n\n // Set initial value\n if (model.value) {\n setEditorContent(editorInstance, model.value)\n }\n\n // Listen for content changes\n cleanupFns.push(\n editorInstance.registerUpdateListener(({ editorState }) => {\n editorState.read(\n () => {\n const root = $getRoot()\n const imageNodes = $nodesOfType(ImageNode)\n showPlaceholder.value =\n root.getTextContent().trim().length === 0 && imageNodes.length === 0\n\n // 回收已从内容中移除的图片 objectURL\n gcImageObjectUrls(editorInstance, new Set(imageNodes.map((node) => node.getSrc())))\n\n if (isComposing) return\n\n const value = serializeContent(editorInstance)\n\n // Avoid circular updates: only emit if content really changed\n if (value !== model.value) {\n isComposing = true\n model.value = value\n // Use microtask to reset after Vue processes the update\n queueMicrotask(() => {\n isComposing = false\n })\n }\n },\n { editor: editorInstance }\n )\n })\n )\n\n // Composing event listeners for IME\n const rootElement = editorInstance.getRootElement()\n if (rootElement) {\n const onCompositionStart = () => {\n isComposing = true\n }\n const onCompositionEnd = () => {\n isComposing = false\n }\n rootElement.addEventListener('compositionstart', onCompositionStart)\n rootElement.addEventListener('compositionend', onCompositionEnd)\n cleanupFns.push(() => {\n rootElement.removeEventListener('compositionstart', onCompositionStart)\n rootElement.removeEventListener('compositionend', onCompositionEnd)\n })\n }\n\n editor.value = editorInstance\n}\n\nfunction setEditorContent(editorInstance: LexicalEditor, value: string) {\n editorInstance.update(() => {\n const root = $getRoot()\n root.clear()\n\n if (props.format === 'json') {\n try {\n const parsedState = editorInstance.parseEditorState(value)\n editorInstance.setEditorState(parsedState)\n } catch {\n // If JSON parse fails, treat as empty\n root.append($createParagraphNode())\n }\n } else {\n // HTML format\n const parser = new DOMParser()\n const dom = parser.parseFromString(value, 'text/html')\n const nodes = $generateNodesFromDOM(editorInstance, dom)\n root.append(...nodes)\n }\n })\n}\n\nfunction serializeContent(editorInstance: LexicalEditor): string {\n return editorInstance.read(() =>\n props.format === 'json'\n ? JSON.stringify(editorInstance.getEditorState().toJSON())\n : $generateHtmlFromNodes(editorInstance)\n )\n}\n\nlet isUploadingImages = false\n\nasync function uploadImages(upload: RichTextImageUploader): Promise<string> {\n const editorInstance = editor.value\n if (!editorInstance) return model.value ?? ''\n if (isUploadingImages) throw new Error('图片正在上传中')\n\n const pending = editorInstance.getEditorState().read(() =>\n $nodesOfType(ImageNode)\n .filter((node) => node.getFile())\n .map((node) => ({ key: node.getKey(), file: node.getFile() as File }))\n )\n\n if (pending.length) {\n isUploadingImages = true\n try {\n const urls = await Promise.all(pending.map(({ file }) => upload(file)))\n editorInstance.update(() => {\n pending.forEach(({ key }, index) => {\n const node = $getNodeByKey(key)\n if ($isImageNode(node)) node.setSrc(urls[index])\n })\n })\n } finally {\n isUploadingImages = false\n }\n }\n\n return serializeContent(editorInstance)\n}\n\ndefineExpose<_RichTextEditorExposed>({ uploadImages })\n\n// Watch external model changes\nwatch(model, (newVal) => {\n if (!editor.value || isComposing) return\n\n const editorInstance = editor.value\n\n // Compare current content to avoid circular updates\n const currentValue = serializeContent(editorInstance)\n\n if (currentValue !== newVal) {\n isComposing = true\n setEditorContent(editorInstance, newVal ?? '')\n queueMicrotask(() => {\n isComposing = false\n })\n }\n})\n\n// Watch disabled/readonly changes\nwatch([isDisabled, isReadonly], () => {\n if (!editor.value) return\n editor.value.setEditable(!isDisabled.value && !isReadonly.value)\n if (isDisabled.value || isReadonly.value) {\n // 切出可编辑态时取消可能残留的图片选中\n editor.value.update(() => {\n if ($isNodeSelection($getSelection())) $setSelection(null)\n })\n }\n})\n\nonMounted(() => {\n initEditor()\n})\n\nonBeforeUnmount(() => {\n cleanupFns.forEach((fn) => fn())\n cleanupFns = []\n if (editor.value) revokeImageObjectUrls(editor.value)\n editor.value = null\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;AAwBA,MAAM,gBAAgB;CACpB,WAAW;CACX,SAAS;EACP,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN;CACA,MAAM;EACJ,MAAM;EACN,QAAQ;EACR,WAAW;EACX,eAAe;CACjB;CACA,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,UAAU;EACV,QAAQ,EAAE,UAAU,mBAAmB;CACzC;CACA,OAAO;CAEP,MAAM;CACN,OAAO;AACT;AAEA,MAAM,kBAAiC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CA,MAAM,QAAQ;EASd,MAAM,QAAQA,SAAmB,SAAA,YAAC;EAElC,MAAM,MAAM,IAAI,kBAAkB;EAElC,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,MAAM,UAAU,aAAa,qBAAqB,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC;EAElF,MAAM,aAAa,eAAe,CAAC,CAAC,SAAS,KAAK;EAClD,MAAM,aAAa,eAAe,CAAC,CAAC,SAAS,KAAK;EAElD,MAAM,YAAY,eAAyB;GACzC,OAAO;IACL,IAAI;IACJ,IAAI,EAAE,KAAK,KAAK;IAChB,IAAI,GAAG,YAAY,WAAW,KAAK;IACnC,IAAI,GAAG,YAAY,WAAW,KAAK;GACrC;EACF,CAAC;EAED,MAAM,eAAe,eAA8B;GACjD,MAAM,QAAQ,MAAM,WAAW;GAC/B,OAAO,MAAM,QAAQ,QAAQ,MAAM,QAAQ,SAAS,SAAS,OAAO;EACtE,CAAC;EAED,MAAM,kBAAkB,eAAe,iBAAiB;EACxD,MAAM,SAAS,WAAiC,IAAI;EACpD,MAAM,kBAAkB,IAAI,IAAI;EAChC,IAAI,cAAc;EAClB,IAAI,aAA6B,CAAC;EAElC,MAAM,qBAAqB,MAAM,SAAS,CAAC,WAAW,SAAS,CAAC,WAAW;EAG3E,eAAe,QAAQ,YAAY;EAEnC,SAAS,aAAa;GACpB,IAAI,CAAC,gBAAgB,OAAO;GAE5B,MAAM,iBAAiB,aAAa;IAClC,WAAW;IACX,OAAO;IACP,OAAO;KAAC;KAAa;KAAW;KAAU;KAAc;KAAU;KAAc;IAAS;IACzF,UAAU,CAAC,WAAW,SAAS,CAAC,WAAW;IAC3C,UAAU,UAAiB;KACzB,QAAQ,MAAM,kBAAkB,KAAK;IACvC;GACF,CAAC;GAED,eAAe,eAAe,gBAAgB,KAAK;GAGnD,WAAW,KAAK,iBAAiB,cAAc,CAAC;GAChD,WAAW,KAAK,aAAa,cAAc,CAAC;GAC5C,WAAW,KAAK,gBAAgB,gBAAgB,wBAAwB,GAAG,GAAG,CAAC;GAG/E,WAAW,KACT,eAAe,gBACb,gBACC,UAAU;IACT,IAAI,CAAC,aAAa,GAAG,OAAO;IAC5B,IAAI,iBAAiB,gBAAgB,MAAM,eAAe,SAAS,CAAC,CAAC,IAAI,GAAG;KAC1E,MAAM,eAAe;KACrB,OAAO;IACT;IACA,OAAO;GACT,GACA,qBACF,GACA,eAAe,gBACb,eACC,UAAU;IACT,IAAI,CAAC,aAAa,GAAG,OAAO;IAC5B,MAAM,QAAQ,MAAM,cAAc;IAClC,IAAI,CAAC,OAAO,QAAQ,OAAO;IAE3B,MAAM,QAAQ,SAAS,oBAAoB,MAAM,SAAS,MAAM,OAAO;IACvE,IAAI,OACF,eAAe,aAAa;KAC1B,MAAM,YAAY,sBAAsB;KACxC,UAAU,cAAc,KAAK;KAC7B,cAAc,SAAS;IACzB,CAAC;IAEH,IAAI,iBAAiB,gBAAgB,KAAK,IAAI,GAAG;KAC/C,MAAM,eAAe;KACrB,OAAO;IACT;IACA,OAAO;GACT,GACA,qBACF,CACF;GAGA,IAAI,MAAM,OACR,iBAAiB,gBAAgB,MAAM,KAAK;GAI9C,WAAW,KACT,eAAe,wBAAwB,EAAE,kBAAkB;IACzD,YAAY,WACJ;KACJ,MAAM,OAAO,SAAS;KACtB,MAAM,aAAa,aAAa,SAAS;KACzC,gBAAgB,QACd,KAAK,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,KAAK,WAAW,WAAW;KAGrE,kBAAkB,gBAAgB,IAAI,IAAI,WAAW,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC;KAElF,IAAI,aAAa;KAEjB,MAAM,QAAQ,iBAAiB,cAAc;KAG7C,IAAI,UAAU,MAAM,OAAO;MACzB,cAAc;MACd,MAAM,QAAQ;MAEd,qBAAqB;OACnB,cAAc;MAChB,CAAC;KACH;IACF,GACA,EAAE,QAAQ,eAAe,CAC3B;GACF,CAAC,CACH;GAGA,MAAM,cAAc,eAAe,eAAe;GAClD,IAAI,aAAa;IACf,MAAM,2BAA2B;KAC/B,cAAc;IAChB;IACA,MAAM,yBAAyB;KAC7B,cAAc;IAChB;IACA,YAAY,iBAAiB,oBAAoB,kBAAkB;IACnE,YAAY,iBAAiB,kBAAkB,gBAAgB;IAC/D,WAAW,WAAW;KACpB,YAAY,oBAAoB,oBAAoB,kBAAkB;KACtE,YAAY,oBAAoB,kBAAkB,gBAAgB;IACpE,CAAC;GACH;GAEA,OAAO,QAAQ;EACjB;EAEA,SAAS,iBAAiB,gBAA+B,OAAe;GACtE,eAAe,aAAa;IAC1B,MAAM,OAAO,SAAS;IACtB,KAAK,MAAM;IAEX,IAAI,MAAM,WAAW,QACnB,IAAI;KACF,MAAM,cAAc,eAAe,iBAAiB,KAAK;KACzD,eAAe,eAAe,WAAW;IAC3C,QAAQ;KAEN,KAAK,OAAO,qBAAqB,CAAC;IACpC;SACK;KAGL,MAAM,MAAM,IADO,UACP,CAAA,CAAO,gBAAgB,OAAO,WAAW;KACrD,MAAM,QAAQ,sBAAsB,gBAAgB,GAAG;KACvD,KAAK,OAAO,GAAG,KAAK;IACtB;GACF,CAAC;EACH;EAEA,SAAS,iBAAiB,gBAAuC;GAC/D,OAAO,eAAe,WACpB,MAAM,WAAW,SACb,KAAK,UAAU,eAAe,eAAe,CAAC,CAAC,OAAO,CAAC,IACvD,uBAAuB,cAAc,CAC3C;EACF;EAEA,IAAI,oBAAoB;EAExB,eAAe,aAAa,QAAgD;GAC1E,MAAM,iBAAiB,OAAO;GAC9B,IAAI,CAAC,gBAAgB,OAAO,MAAM,SAAS;GAC3C,IAAI,mBAAmB,MAAM,IAAI,MAAM,SAAS;GAEhD,MAAM,UAAU,eAAe,eAAe,CAAC,CAAC,WAC9C,aAAa,SAAS,CAAA,CACnB,QAAQ,SAAS,KAAK,QAAQ,CAAC,CAAA,CAC/B,KAAK,UAAU;IAAE,KAAK,KAAK,OAAO;IAAG,MAAM,KAAK,QAAQ;GAAU,EAAE,CACzE;GAEA,IAAI,QAAQ,QAAQ;IAClB,oBAAoB;IACpB,IAAI;KACF,MAAM,OAAO,MAAM,QAAQ,IAAI,QAAQ,KAAK,EAAE,WAAW,OAAO,IAAI,CAAC,CAAC;KACtE,eAAe,aAAa;MAC1B,QAAQ,SAAS,EAAE,OAAO,UAAU;OAClC,MAAM,OAAO,cAAc,GAAG;OAC9B,IAAI,aAAa,IAAI,GAAG,KAAK,OAAO,KAAK,MAAM;MACjD,CAAC;KACH,CAAC;IACH,UAAU;KACR,oBAAoB;IACtB;GACF;GAEA,OAAO,iBAAiB,cAAc;EACxC;EAEA,SAAqC,EAAE,aAAa,CAAC;EAGrD,MAAM,QAAQ,WAAW;GACvB,IAAI,CAAC,OAAO,SAAS,aAAa;GAElC,MAAM,iBAAiB,OAAO;GAK9B,IAFqB,iBAAiB,cAElC,MAAiB,QAAQ;IAC3B,cAAc;IACd,iBAAiB,gBAAgB,UAAU,EAAE;IAC7C,qBAAqB;KACnB,cAAc;IAChB,CAAC;GACH;EACF,CAAC;EAGD,MAAM,CAAC,YAAY,UAAU,SAAS;GACpC,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO,MAAM,YAAY,CAAC,WAAW,SAAS,CAAC,WAAW,KAAK;GAC/D,IAAI,WAAW,SAAS,WAAW,OAEjC,OAAO,MAAM,aAAa;IACxB,IAAI,iBAAiB,cAAc,CAAC,GAAG,cAAc,IAAI;GAC3D,CAAC;EAEL,CAAC;EAED,gBAAgB;GACd,WAAW;EACb,CAAC;EAED,sBAAsB;GACpB,WAAW,SAAS,OAAO,GAAG,CAAC;GAC/B,aAAa,CAAC;GACd,IAAI,OAAO,OAAO,sBAAsB,OAAO,KAAK;GACpD,OAAO,QAAQ;EACjB,CAAC;;GA3XC,OAAAC,UAAA,GAAAC,mBAiBM,OAAA,EAjBA,OAAKC,eAAE,UAAA,KAAS,EAAA,GAEX,CAAA,CAAA,WAAA,SAAc,aAAA,MAAa,SAAM,KAD1CF,UAAA,GAAAG,YAKEC,aAAA;;IAHC,QAAQ,OAAA;IACR,SAAS,aAAA;IACT,UAAU,WAAA;;;;;GAEb,CAAA,KAAAC,mBAAA,QAAA,IAAA,GAAAC,mBASM,OAAA,EATA,OAAKJ,eAAEK,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA,EAAA,GAChB,CAAAD,mBAIO,OAAA;IAHD,SAAA;IAAJ,KAAI;IACH,OAAKJ,eAAEK,MAAA,GAAA,CAAG,CAAC,EAAC,UAAA,CAAA;IACZ,iBAAe,CAAG,WAAA,SAAU,CAAK,WAAA;GAEzB,GAAA,MAAA,IAAA,UAAA,GAAA,gBAAA,SAAXP,UAAA,GAAAC,mBAEM,OAAA;;IAFuB,OAAKC,eAAEK,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;GACpC,GAAAC,gBAAA,QAAA,WAAW,GAAA,CAAA,KAAAH,mBAAA,QAAA,IAAA,CAAA,GAAA,CAAA,CAAA,GAAA,CAAA"}
@@ -342,3 +342,34 @@
342
342
  height: auto;
343
343
  vertical-align: bottom;
344
344
  }
345
+
346
+ .u-rte-image-wrap {
347
+ position: relative;
348
+ display: inline-block;
349
+ max-width: 100%;
350
+ vertical-align: bottom;
351
+ line-height: 0;
352
+ }
353
+ .u-rte-image-wrap .u-rte-image {
354
+ display: block;
355
+ }
356
+ .u-rte-image-wrap.is-selected .u-rte-image {
357
+ outline: 2px solid var(--u-color-primary);
358
+ border-radius: 2px;
359
+ }
360
+
361
+ .u-rte-image-resizer {
362
+ position: absolute;
363
+ right: -5px;
364
+ bottom: -5px;
365
+ display: none;
366
+ width: 10px;
367
+ height: 10px;
368
+ border-radius: 50%;
369
+ background-color: var(--u-bg-color-top);
370
+ border: 2px solid var(--u-color-primary);
371
+ cursor: nwse-resize;
372
+ }
373
+ .u-rte-image-wrap.is-selected .u-rte-image-resizer {
374
+ display: block;
375
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"toolbar.js","names":[],"sources":["../../../src/components/rich-text-editor/toolbar.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('toolbar')\">\n <template v-for=\"(item, index) in toolbar\" :key=\"index\">\n <div v-if=\"item === '|'\" :class=\"cls.e('separator')\"></div>\n\n <div v-else-if=\"item === 'heading'\" :class=\"[cls.e('toolbar-dropdown')]\">\n <select\n :class=\"[cls.e('heading-select'), { 'is-disabled': disabled }]\"\n :value=\"activeHeading\"\n :disabled=\"disabled\"\n @change=\"onHeadingChange\"\n >\n <option value=\"paragraph\">正文</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"h5\">Heading 5</option>\n <option value=\"h6\">Heading 6</option>\n </select>\n </div>\n\n <button\n v-else\n type=\"button\"\n :class=\"[\n cls.e('toolbar-btn'),\n { 'is-active': isActive(item) },\n { 'is-disabled': disabled }\n ]\"\n :disabled=\"disabled\"\n :title=\"getTitle(item)\"\n @click=\"onToolbarAction(item)\"\n >\n <span :class=\"cls.e('toolbar-icon')\" v-html=\"getIcon(item)\"></span>\n </button>\n </template>\n\n <input ref=\"imageInput\" type=\"file\" accept=\"image/*\" multiple hidden @change=\"onImageChange\" />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'\nimport { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode } from '@lexical/list'\nimport { $createHeadingNode, $isHeadingNode, type HeadingTagType } from '@lexical/rich-text'\nimport { $isQuoteNode, $createQuoteNode } from '@lexical/rich-text'\nimport { $setBlocksType } from '@lexical/selection'\nimport { $getNearestNodeOfType } from '@lexical/utils'\nimport { bem } from '@veltra/utils'\nimport {\n $getSelection,\n $isRangeSelection,\n FORMAT_TEXT_COMMAND,\n UNDO_COMMAND,\n REDO_COMMAND,\n type LexicalEditor,\n $createParagraphNode,\n type RangeSelection\n} from 'lexical'\nimport { ref, onBeforeUnmount, useTemplateRef, watch } from 'vue'\n\nimport type { ToolbarItem } from '../../types'\nimport { insertImageFiles } from './image-node'\n\nconst props = defineProps<{\n editor: LexicalEditor | null\n toolbar: ToolbarItem[]\n disabled: boolean\n}>()\n\nconst cls = bem('rich-text-editor')\n\nconst imageInput = useTemplateRef('imageInput')\n\n// Active states\nconst isBold = ref(false)\nconst isItalic = ref(false)\nconst isUnderline = ref(false)\nconst isStrikethrough = ref(false)\nconst isLink = ref(false)\nconst isBulletList = ref(false)\nconst isOrderedList = ref(false)\nconst isBlockquote = ref(false)\nconst activeHeading = ref('paragraph')\n\nlet cleanupListener: (() => void) | null = null\n\nfunction setupListener(editorInstance: LexicalEditor) {\n cleanupListener?.()\n cleanupListener = editorInstance.registerUpdateListener(({ editorState }) => {\n editorState.read(\n () => {\n const selection = $getSelection()\n if (!$isRangeSelection(selection)) return\n\n updateToolbarState(selection)\n },\n { editor: editorInstance }\n )\n })\n}\n\nfunction updateToolbarState(selection: RangeSelection) {\n isBold.value = selection.hasFormat('bold')\n isItalic.value = selection.hasFormat('italic')\n isUnderline.value = selection.hasFormat('underline')\n isStrikethrough.value = selection.hasFormat('strikethrough')\n\n // Check block type\n const anchorNode = selection.anchor.getNode()\n let element = anchorNode.getKey() === 'root' ? anchorNode : anchorNode.getTopLevelElementOrThrow()\n\n if ($isHeadingNode(element)) {\n activeHeading.value = (element as any).getTag()\n } else {\n activeHeading.value = 'paragraph'\n }\n\n // Check list\n const parentList = $getNearestNodeOfType(anchorNode, ListNode)\n if (parentList) {\n const listType = (parentList as any).getListType()\n isBulletList.value = listType === 'bullet'\n isOrderedList.value = listType === 'number'\n } else {\n isBulletList.value = false\n isOrderedList.value = false\n }\n\n // Check blockquote\n isBlockquote.value = $isQuoteNode(element)\n\n // Check link\n const parent = anchorNode.getParent()\n isLink.value = $isLinkNode(parent)\n}\n\nwatch(\n () => props.editor,\n (editorInstance) => {\n if (editorInstance) {\n setupListener(editorInstance)\n }\n },\n { immediate: true }\n)\n\nfunction isActive(item: ToolbarItem): boolean {\n switch (item) {\n case 'bold':\n return isBold.value\n case 'italic':\n return isItalic.value\n case 'underline':\n return isUnderline.value\n case 'strikethrough':\n return isStrikethrough.value\n case 'bullet-list':\n return isBulletList.value\n case 'ordered-list':\n return isOrderedList.value\n case 'blockquote':\n return isBlockquote.value\n case 'link':\n return isLink.value\n default:\n return false\n }\n}\n\nfunction onToolbarAction(item: ToolbarItem) {\n const editorInstance = props.editor\n if (!editorInstance || props.disabled) return\n\n switch (item) {\n case 'bold':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')\n break\n case 'italic':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')\n break\n case 'underline':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')\n break\n case 'strikethrough':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')\n break\n case 'code':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')\n break\n case 'bullet-list':\n editorInstance.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)\n break\n case 'ordered-list':\n editorInstance.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)\n break\n case 'blockquote':\n editorInstance.update(() => {\n const selection = $getSelection()\n if ($isRangeSelection(selection)) {\n if (isBlockquote.value) {\n $setBlocksType(selection, () => $createParagraphNode())\n } else {\n $setBlocksType(selection, () => $createQuoteNode())\n }\n }\n })\n break\n case 'link':\n toggleLink(editorInstance)\n break\n case 'image':\n imageInput.value?.click()\n break\n case 'undo':\n editorInstance.dispatchCommand(UNDO_COMMAND, undefined)\n break\n case 'redo':\n editorInstance.dispatchCommand(REDO_COMMAND, undefined)\n break\n }\n\n // Keep focus on editor\n editorInstance.focus()\n}\n\nfunction onImageChange(event: Event) {\n const input = event.target as HTMLInputElement\n if (input.files?.length && props.editor && !props.disabled) {\n insertImageFiles(props.editor, input.files)\n }\n // 允许重复选择同一文件\n input.value = ''\n}\n\nfunction onHeadingChange(event: Event) {\n const editorInstance = props.editor\n if (!editorInstance || props.disabled) return\n\n const value = (event.target as HTMLSelectElement).value\n\n editorInstance.update(() => {\n const selection = $getSelection()\n if ($isRangeSelection(selection)) {\n if (value === 'paragraph') {\n $setBlocksType(selection, () => $createParagraphNode())\n } else {\n $setBlocksType(selection, () => $createHeadingNode(value as HeadingTagType))\n }\n }\n })\n\n editorInstance.focus()\n}\n\nfunction toggleLink(editorInstance: LexicalEditor) {\n let shouldRemoveLink = false\n\n editorInstance.read(() => {\n const selection = $getSelection()\n if (!$isRangeSelection(selection)) return\n\n const parent = selection.anchor.getNode().getParent()\n shouldRemoveLink = $isLinkNode(parent)\n })\n\n if (shouldRemoveLink) {\n editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, null)\n return\n }\n\n const url = prompt('请输入链接地址:', 'https://')\n if (url && url !== 'https://') {\n editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, url)\n }\n}\n\nfunction getTitle(item: ToolbarItem): string {\n const titles: Record<string, string> = {\n bold: '加粗',\n italic: '斜体',\n underline: '下划线',\n strikethrough: '删除线',\n code: '行内代码',\n 'bullet-list': '无序列表',\n 'ordered-list': '有序列表',\n blockquote: '引用',\n 'code-block': '代码块',\n link: '链接',\n image: '图片',\n undo: '撤销',\n redo: '重做'\n }\n return titles[item] ?? item\n}\n\nfunction getIcon(item: ToolbarItem): string {\n const icons: Record<string, string> = {\n bold: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/></svg>',\n italic:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/></svg>',\n underline:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3\"/><line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"/></svg>',\n strikethrough:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/></svg>',\n code: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n 'bullet-list':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>',\n 'ordered-list':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/></svg>',\n blockquote:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z\"/><path d=\"M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z\"/></svg>',\n 'code-block':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"18\" rx=\"2\"/><path d=\"m8 10-3 2 3 2\"/><path d=\"m16 10 3 2-3 2\"/><path d=\"m13 7-2 10\"/></svg>',\n link: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>',\n image:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>',\n undo: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>',\n redo: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>'\n }\n return icons[item] ?? ''\n}\n\nonBeforeUnmount(() => {\n cleanupListener?.()\n cleanupListener = null\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"toolbar.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_createStaticVNode"],"sources":["../../../src/components/rich-text-editor/toolbar.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('toolbar')\">\n <template v-for=\"(item, index) in toolbar\" :key=\"index\">\n <div v-if=\"item === '|'\" :class=\"cls.e('separator')\"></div>\n\n <div v-else-if=\"item === 'heading'\" :class=\"[cls.e('toolbar-dropdown')]\">\n <select\n :class=\"[cls.e('heading-select'), { 'is-disabled': disabled }]\"\n :value=\"activeHeading\"\n :disabled=\"disabled\"\n @change=\"onHeadingChange\"\n >\n <option value=\"paragraph\">正文</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"h5\">Heading 5</option>\n <option value=\"h6\">Heading 6</option>\n </select>\n </div>\n\n <button\n v-else\n type=\"button\"\n :class=\"[\n cls.e('toolbar-btn'),\n { 'is-active': isActive(item) },\n { 'is-disabled': disabled }\n ]\"\n :disabled=\"disabled\"\n :title=\"getTitle(item)\"\n @click=\"onToolbarAction(item)\"\n >\n <span :class=\"cls.e('toolbar-icon')\" v-html=\"getIcon(item)\"></span>\n </button>\n </template>\n\n <input ref=\"imageInput\" type=\"file\" accept=\"image/*\" multiple hidden @change=\"onImageChange\" />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'\nimport { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode } from '@lexical/list'\nimport { $createHeadingNode, $isHeadingNode, type HeadingTagType } from '@lexical/rich-text'\nimport { $isQuoteNode, $createQuoteNode } from '@lexical/rich-text'\nimport { $setBlocksType } from '@lexical/selection'\nimport { $getNearestNodeOfType } from '@lexical/utils'\nimport { bem } from '@veltra/utils'\nimport {\n $getSelection,\n $isRangeSelection,\n FORMAT_TEXT_COMMAND,\n UNDO_COMMAND,\n REDO_COMMAND,\n type LexicalEditor,\n $createParagraphNode,\n type RangeSelection\n} from 'lexical'\nimport { ref, onBeforeUnmount, useTemplateRef, watch } from 'vue'\n\nimport type { ToolbarItem } from '../../types'\nimport { insertImageFiles } from './image-node'\n\nconst props = defineProps<{\n editor: LexicalEditor | null\n toolbar: ToolbarItem[]\n disabled: boolean\n}>()\n\nconst cls = bem('rich-text-editor')\n\nconst imageInput = useTemplateRef('imageInput')\n\n// Active states\nconst isBold = ref(false)\nconst isItalic = ref(false)\nconst isUnderline = ref(false)\nconst isStrikethrough = ref(false)\nconst isLink = ref(false)\nconst isBulletList = ref(false)\nconst isOrderedList = ref(false)\nconst isBlockquote = ref(false)\nconst activeHeading = ref('paragraph')\n\nlet cleanupListener: (() => void) | null = null\n\nfunction setupListener(editorInstance: LexicalEditor) {\n cleanupListener?.()\n cleanupListener = editorInstance.registerUpdateListener(({ editorState }) => {\n editorState.read(\n () => {\n const selection = $getSelection()\n if (!$isRangeSelection(selection)) return\n\n updateToolbarState(selection)\n },\n { editor: editorInstance }\n )\n })\n}\n\nfunction updateToolbarState(selection: RangeSelection) {\n isBold.value = selection.hasFormat('bold')\n isItalic.value = selection.hasFormat('italic')\n isUnderline.value = selection.hasFormat('underline')\n isStrikethrough.value = selection.hasFormat('strikethrough')\n\n // Check block type\n const anchorNode = selection.anchor.getNode()\n let element = anchorNode.getKey() === 'root' ? anchorNode : anchorNode.getTopLevelElementOrThrow()\n\n if ($isHeadingNode(element)) {\n activeHeading.value = (element as any).getTag()\n } else {\n activeHeading.value = 'paragraph'\n }\n\n // Check list\n const parentList = $getNearestNodeOfType(anchorNode, ListNode)\n if (parentList) {\n const listType = (parentList as any).getListType()\n isBulletList.value = listType === 'bullet'\n isOrderedList.value = listType === 'number'\n } else {\n isBulletList.value = false\n isOrderedList.value = false\n }\n\n // Check blockquote\n isBlockquote.value = $isQuoteNode(element)\n\n // Check link\n const parent = anchorNode.getParent()\n isLink.value = $isLinkNode(parent)\n}\n\nwatch(\n () => props.editor,\n (editorInstance) => {\n if (editorInstance) {\n setupListener(editorInstance)\n }\n },\n { immediate: true }\n)\n\nfunction isActive(item: ToolbarItem): boolean {\n switch (item) {\n case 'bold':\n return isBold.value\n case 'italic':\n return isItalic.value\n case 'underline':\n return isUnderline.value\n case 'strikethrough':\n return isStrikethrough.value\n case 'bullet-list':\n return isBulletList.value\n case 'ordered-list':\n return isOrderedList.value\n case 'blockquote':\n return isBlockquote.value\n case 'link':\n return isLink.value\n default:\n return false\n }\n}\n\nfunction onToolbarAction(item: ToolbarItem) {\n const editorInstance = props.editor\n if (!editorInstance || props.disabled) return\n\n switch (item) {\n case 'bold':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')\n break\n case 'italic':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')\n break\n case 'underline':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')\n break\n case 'strikethrough':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')\n break\n case 'code':\n editorInstance.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')\n break\n case 'bullet-list':\n editorInstance.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)\n break\n case 'ordered-list':\n editorInstance.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)\n break\n case 'blockquote':\n editorInstance.update(() => {\n const selection = $getSelection()\n if ($isRangeSelection(selection)) {\n if (isBlockquote.value) {\n $setBlocksType(selection, () => $createParagraphNode())\n } else {\n $setBlocksType(selection, () => $createQuoteNode())\n }\n }\n })\n break\n case 'link':\n toggleLink(editorInstance)\n break\n case 'image':\n imageInput.value?.click()\n break\n case 'undo':\n editorInstance.dispatchCommand(UNDO_COMMAND, undefined)\n break\n case 'redo':\n editorInstance.dispatchCommand(REDO_COMMAND, undefined)\n break\n }\n\n // Keep focus on editor\n editorInstance.focus()\n}\n\nfunction onImageChange(event: Event) {\n const input = event.target as HTMLInputElement\n if (input.files?.length && props.editor && !props.disabled) {\n insertImageFiles(props.editor, input.files)\n }\n // 允许重复选择同一文件\n input.value = ''\n}\n\nfunction onHeadingChange(event: Event) {\n const editorInstance = props.editor\n if (!editorInstance || props.disabled) return\n\n const value = (event.target as HTMLSelectElement).value\n\n editorInstance.update(() => {\n const selection = $getSelection()\n if ($isRangeSelection(selection)) {\n if (value === 'paragraph') {\n $setBlocksType(selection, () => $createParagraphNode())\n } else {\n $setBlocksType(selection, () => $createHeadingNode(value as HeadingTagType))\n }\n }\n })\n\n editorInstance.focus()\n}\n\nfunction toggleLink(editorInstance: LexicalEditor) {\n let shouldRemoveLink = false\n\n editorInstance.read(() => {\n const selection = $getSelection()\n if (!$isRangeSelection(selection)) return\n\n const parent = selection.anchor.getNode().getParent()\n shouldRemoveLink = $isLinkNode(parent)\n })\n\n if (shouldRemoveLink) {\n editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, null)\n return\n }\n\n const url = prompt('请输入链接地址:', 'https://')\n if (url && url !== 'https://') {\n editorInstance.dispatchCommand(TOGGLE_LINK_COMMAND, url)\n }\n}\n\nfunction getTitle(item: ToolbarItem): string {\n const titles: Record<string, string> = {\n bold: '加粗',\n italic: '斜体',\n underline: '下划线',\n strikethrough: '删除线',\n code: '行内代码',\n 'bullet-list': '无序列表',\n 'ordered-list': '有序列表',\n blockquote: '引用',\n 'code-block': '代码块',\n link: '链接',\n image: '图片',\n undo: '撤销',\n redo: '重做'\n }\n return titles[item] ?? item\n}\n\nfunction getIcon(item: ToolbarItem): string {\n const icons: Record<string, string> = {\n bold: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/></svg>',\n italic:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/></svg>',\n underline:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3\"/><line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"/></svg>',\n strikethrough:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/></svg>',\n code: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n 'bullet-list':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>',\n 'ordered-list':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/></svg>',\n blockquote:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z\"/><path d=\"M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z\"/></svg>',\n 'code-block':\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"18\" rx=\"2\"/><path d=\"m8 10-3 2 3 2\"/><path d=\"m16 10 3 2-3 2\"/><path d=\"m13 7-2 10\"/></svg>',\n link: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>',\n image:\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>',\n undo: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>',\n redo: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>'\n }\n return icons[item] ?? ''\n}\n\nonBeforeUnmount(() => {\n cleanupListener?.()\n cleanupListener = null\n})\n</script>\n"],"mappings":"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"}