@wzyjs/uis 0.3.28 → 0.3.30

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 (230) hide show
  1. package/dist/advanced/Com2Canvas/index.d.ts +8 -0
  2. package/dist/advanced/Com2Canvas/index.js +39 -0
  3. package/dist/advanced/Crud/components/CardList/index.d.ts +2 -0
  4. package/dist/advanced/Crud/components/CardList/index.js +90 -0
  5. package/dist/advanced/Crud/components/CreateUpdate/index.d.ts +2 -0
  6. package/dist/advanced/Crud/components/CreateUpdate/index.js +78 -0
  7. package/dist/advanced/Crud/components/ListTabs/index.d.ts +8 -0
  8. package/dist/advanced/Crud/components/ListTabs/index.js +7 -0
  9. package/dist/advanced/Crud/components/Provider/index.d.ts +7 -0
  10. package/dist/advanced/Crud/components/Provider/index.js +42 -0
  11. package/dist/advanced/Crud/components/QuickFilters/index.d.ts +8 -0
  12. package/dist/advanced/Crud/components/QuickFilters/index.js +20 -0
  13. package/dist/advanced/Crud/components/Remove/index.d.ts +2 -0
  14. package/dist/advanced/Crud/components/Remove/index.js +18 -0
  15. package/dist/advanced/Crud/components/index.d.ts +6 -0
  16. package/dist/advanced/Crud/components/index.js +6 -0
  17. package/dist/advanced/Crud/hooks/index.d.ts +5 -0
  18. package/dist/advanced/Crud/hooks/index.js +5 -0
  19. package/dist/advanced/Crud/hooks/useColumns.d.ts +11 -0
  20. package/dist/advanced/Crud/hooks/useColumns.js +111 -0
  21. package/dist/advanced/Crud/hooks/useList.d.ts +12 -0
  22. package/dist/advanced/Crud/hooks/useList.js +53 -0
  23. package/dist/advanced/Crud/hooks/useListFilters.d.ts +11 -0
  24. package/dist/advanced/Crud/hooks/useListFilters.js +159 -0
  25. package/dist/advanced/Crud/hooks/useOrderable.d.ts +15 -0
  26. package/dist/advanced/Crud/hooks/useOrderable.js +75 -0
  27. package/dist/advanced/Crud/hooks/useRequest.d.ts +13 -0
  28. package/dist/advanced/Crud/hooks/useRequest.js +27 -0
  29. package/dist/advanced/Crud/index.d.ts +3 -0
  30. package/dist/advanced/Crud/index.js +46 -0
  31. package/dist/advanced/Crud/types/index.d.ts +176 -0
  32. package/dist/advanced/Crud/types/index.js +1 -0
  33. package/dist/advanced/Crud/utils/index.d.ts +7 -0
  34. package/dist/advanced/Crud/utils/index.js +80 -0
  35. package/dist/advanced/Crud/utils/query.d.ts +3 -0
  36. package/dist/advanced/Crud/utils/query.js +34 -0
  37. package/dist/advanced/MindMap/context.d.ts +12 -0
  38. package/dist/advanced/MindMap/context.js +12 -0
  39. package/dist/advanced/MindMap/hooks/useAlignmentSnap.d.ts +15 -0
  40. package/dist/advanced/MindMap/hooks/useAlignmentSnap.js +164 -0
  41. package/dist/advanced/MindMap/hooks/useCopyPaste.d.ts +11 -0
  42. package/dist/advanced/MindMap/hooks/useCopyPaste.js +209 -0
  43. package/dist/advanced/MindMap/hooks/useDropToReparent.d.ts +21 -0
  44. package/dist/advanced/MindMap/hooks/useDropToReparent.js +216 -0
  45. package/dist/advanced/MindMap/hooks/useExpandCollapse.d.ts +18 -0
  46. package/dist/advanced/MindMap/hooks/useExpandCollapse.js +108 -0
  47. package/dist/advanced/MindMap/hooks/useMoveDescendants.d.ts +12 -0
  48. package/dist/advanced/MindMap/hooks/useMoveDescendants.js +98 -0
  49. package/dist/advanced/MindMap/hooks/useUndoRedo.d.ts +14 -0
  50. package/dist/advanced/MindMap/hooks/useUndoRedo.js +181 -0
  51. package/dist/advanced/MindMap/index.d.ts +29 -0
  52. package/dist/advanced/MindMap/index.js +52 -0
  53. package/dist/advanced/index.d.ts +5 -0
  54. package/dist/advanced/index.js +5 -0
  55. package/dist/antd/index.d.ts +6 -0
  56. package/dist/antd/index.js +5 -0
  57. package/dist/buttons/ButtonGroup/index.d.ts +8 -0
  58. package/dist/buttons/ButtonGroup/index.js +13 -0
  59. package/dist/buttons/ConfirmButton/index.d.ts +5 -0
  60. package/dist/buttons/ConfirmButton/index.js +9 -0
  61. package/dist/buttons/CopyButton/index.d.ts +6 -0
  62. package/dist/buttons/CopyButton/index.js +26 -0
  63. package/dist/buttons/DrawerButton/index.d.ts +6 -0
  64. package/dist/buttons/DrawerButton/index.js +13 -0
  65. package/dist/buttons/ProgressButton/index.css +63 -0
  66. package/dist/buttons/ProgressButton/index.d.ts +17 -0
  67. package/dist/buttons/ProgressButton/index.js +31 -0
  68. package/dist/buttons/SectorButton/index.d.ts +20 -0
  69. package/dist/buttons/SectorButton/index.js +130 -0
  70. package/dist/buttons/index.d.ts +6 -0
  71. package/dist/buttons/index.js +6 -0
  72. package/dist/display/CodeView/index.d.ts +26 -0
  73. package/dist/display/CodeView/index.js +60 -0
  74. package/dist/display/EnumTag/index.d.ts +12 -0
  75. package/dist/display/EnumTag/index.js +10 -0
  76. package/dist/display/HtmlDataRenderer/index.d.ts +6 -0
  77. package/dist/display/HtmlDataRenderer/index.js +15 -0
  78. package/dist/display/HtmlView/index.d.ts +6 -0
  79. package/dist/display/HtmlView/index.js +6 -0
  80. package/dist/display/IframePro/index.d.ts +8 -0
  81. package/dist/display/IframePro/index.js +24 -0
  82. package/dist/display/JsonSchemaRenderer/index.d.ts +11 -0
  83. package/dist/display/JsonSchemaRenderer/index.js +62 -0
  84. package/dist/display/JsonView/index.d.ts +3 -0
  85. package/dist/display/JsonView/index.js +7 -0
  86. package/dist/display/MarkdownView/index.d.ts +7 -0
  87. package/dist/display/MarkdownView/index.js +80 -0
  88. package/dist/display/MarkdownView/style.d.ts +1 -0
  89. package/{src/components/Markdown/style.ts → dist/display/MarkdownView/style.js} +1 -1
  90. package/dist/display/VideoPro/index.d.ts +9 -0
  91. package/dist/display/VideoPro/index.js +15 -0
  92. package/dist/display/index.d.ts +9 -0
  93. package/dist/display/index.js +9 -0
  94. package/dist/inputs/CheckboxButton/index.css +22 -0
  95. package/dist/inputs/CheckboxButton/index.d.ts +12 -0
  96. package/dist/inputs/CheckboxButton/index.js +9 -0
  97. package/dist/inputs/DateSwitcher/index.css +10 -0
  98. package/dist/inputs/DateSwitcher/index.d.ts +8 -0
  99. package/dist/inputs/DateSwitcher/index.js +29 -0
  100. package/dist/inputs/FetchSelect/index.d.ts +3 -0
  101. package/dist/inputs/FetchSelect/index.js +121 -0
  102. package/dist/inputs/FetchSelect/types.d.ts +33 -0
  103. package/dist/inputs/FetchSelect/types.js +1 -0
  104. package/dist/inputs/FetchSelect/utils.d.ts +21 -0
  105. package/dist/inputs/FetchSelect/utils.js +67 -0
  106. package/dist/inputs/FileUploader/index.d.ts +22 -0
  107. package/dist/inputs/FileUploader/index.js +79 -0
  108. package/dist/inputs/IconSelect/index.d.ts +89 -0
  109. package/dist/inputs/IconSelect/index.js +54 -0
  110. package/dist/inputs/ImageUploader/index.d.ts +12 -0
  111. package/dist/inputs/ImageUploader/index.js +192 -0
  112. package/dist/inputs/RadioButton/index.d.ts +15 -0
  113. package/dist/inputs/RadioButton/index.js +11 -0
  114. package/dist/inputs/RangeInput/index.d.ts +8 -0
  115. package/dist/inputs/RangeInput/index.js +17 -0
  116. package/dist/inputs/TextInput/index.d.ts +6 -0
  117. package/dist/inputs/TextInput/index.js +30 -0
  118. package/dist/inputs/index.d.ts +9 -0
  119. package/dist/inputs/index.js +9 -0
  120. package/dist/layout/DragSort/index.d.ts +16 -0
  121. package/dist/layout/DragSort/index.js +12 -0
  122. package/dist/layout/FoldCard/index.d.ts +9 -0
  123. package/dist/layout/FoldCard/index.js +69 -0
  124. package/dist/layout/PageBase/index.d.ts +6 -0
  125. package/dist/layout/PageBase/index.js +6 -0
  126. package/dist/layout/ResizableGridLayout/index.d.ts +11 -0
  127. package/dist/layout/ResizableGridLayout/index.js +13 -0
  128. package/dist/layout/SideMenu/index.d.ts +27 -0
  129. package/dist/layout/SideMenu/index.js +40 -0
  130. package/dist/layout/TabsPro/index.d.ts +9 -0
  131. package/dist/layout/TabsPro/index.js +87 -0
  132. package/dist/layout/index.d.ts +6 -0
  133. package/dist/layout/index.js +6 -0
  134. package/dist/web.d.ts +6 -0
  135. package/dist/web.js +6 -0
  136. package/package.json +28 -12
  137. package/src/antd/form/CheckboxButton/index.module.scss +0 -24
  138. package/src/antd/form/CheckboxButton/index.tsx +0 -31
  139. package/src/antd/form/FileUploader/index.tsx +0 -163
  140. package/src/antd/form/RadioButton/index.tsx +0 -32
  141. package/src/antd/form/Upload/index.tsx +0 -65
  142. package/src/antd/form/UploadImage/index.tsx +0 -338
  143. package/src/antd/form/index.ts +0 -6
  144. package/src/antd/index.ts +0 -46
  145. package/src/antd/pro/Alert/index.tsx +0 -24
  146. package/src/antd/pro/Button/components/Confirm.tsx +0 -24
  147. package/src/antd/pro/Button/components/Copy.tsx +0 -47
  148. package/src/antd/pro/Button/components/Drawer.tsx +0 -37
  149. package/src/antd/pro/Button/components/Group.tsx +0 -26
  150. package/src/antd/pro/Button/index.tsx +0 -11
  151. package/src/antd/pro/Card/index.tsx +0 -92
  152. package/src/antd/pro/Collapse/components/Item.tsx +0 -30
  153. package/src/antd/pro/Collapse/index.tsx +0 -27
  154. package/src/antd/pro/Image/index.tsx +0 -17
  155. package/src/antd/pro/Input/components/Range.tsx +0 -46
  156. package/src/antd/pro/Input/index.tsx +0 -61
  157. package/src/antd/pro/Popconfirm/index.tsx +0 -16
  158. package/src/antd/pro/Radio/components/Cancel.tsx +0 -30
  159. package/src/antd/pro/Radio/index.tsx +0 -7
  160. package/src/antd/pro/Space/index.tsx +0 -15
  161. package/src/antd/pro/Tabs/index.tsx +0 -135
  162. package/src/antd/pro/Typography/components/String.tsx +0 -72
  163. package/src/antd/pro/Typography/index.tsx +0 -9
  164. package/src/antd/pro/index.ts +0 -11
  165. package/src/components/BottomBar/index.tsx +0 -28
  166. package/src/components/CodeView/index.tsx +0 -85
  167. package/src/components/Collapse/index.tsx +0 -26
  168. package/src/components/Com2Canvas/index.tsx +0 -60
  169. package/src/components/CompileHtml/index.tsx +0 -26
  170. package/src/components/Crud/components/CardList/index.tsx +0 -174
  171. package/src/components/Crud/components/CreateUpdate/index.tsx +0 -179
  172. package/src/components/Crud/components/Provider/index.tsx +0 -83
  173. package/src/components/Crud/components/Remove/index.tsx +0 -56
  174. package/src/components/Crud/components/index.ts +0 -4
  175. package/src/components/Crud/hooks/index.ts +0 -4
  176. package/src/components/Crud/hooks/useColumns.tsx +0 -169
  177. package/src/components/Crud/hooks/useList.ts +0 -65
  178. package/src/components/Crud/hooks/useOrderable.tsx +0 -107
  179. package/src/components/Crud/hooks/useRequest.ts +0 -41
  180. package/src/components/Crud/index.tsx +0 -91
  181. package/src/components/Crud/types/index.ts +0 -188
  182. package/src/components/Crud/utils/index.ts +0 -87
  183. package/src/components/DateSwitcher/index.module.scss +0 -10
  184. package/src/components/DateSwitcher/index.tsx +0 -75
  185. package/src/components/DownloadLink/index.tsx +0 -36
  186. package/src/components/DragSort/index.tsx +0 -77
  187. package/src/components/DynamicSelect/index.tsx +0 -74
  188. package/src/components/DynamicSelect/utils.ts +0 -45
  189. package/src/components/EnumTag/index.tsx +0 -24
  190. package/src/components/FetchSelect/index.tsx +0 -57
  191. package/src/components/Fold/index.tsx +0 -52
  192. package/src/components/FormPro/index.tsx +0 -28
  193. package/src/components/GroupLayout/index.tsx +0 -45
  194. package/src/components/HtmlPro/index.tsx +0 -18
  195. package/src/components/IframePro/index.tsx +0 -52
  196. package/src/components/JsonRenderer/index.tsx +0 -114
  197. package/src/components/JsonView/index.tsx +0 -21
  198. package/src/components/Markdown/index.tsx +0 -152
  199. package/src/components/MindMap/context.tsx +0 -29
  200. package/src/components/MindMap/hooks/useAlignmentSnap.ts +0 -220
  201. package/src/components/MindMap/hooks/useCopyPaste.ts +0 -272
  202. package/src/components/MindMap/hooks/useDropToReparent.ts +0 -288
  203. package/src/components/MindMap/hooks/useExpandCollapse.ts +0 -146
  204. package/src/components/MindMap/hooks/useMoveDescendants.ts +0 -136
  205. package/src/components/MindMap/hooks/useUndoRedo.ts +0 -232
  206. package/src/components/MindMap/index.tsx +0 -117
  207. package/src/components/MultiImageDisplay/index.tsx +0 -63
  208. package/src/components/ProgressButton/index.module.scss +0 -65
  209. package/src/components/ProgressButton/index.tsx +0 -96
  210. package/src/components/SectorButton/index.tsx +0 -247
  211. package/src/components/TextInput/index.tsx +0 -61
  212. package/src/components/TimelineBar/components/CurrentWeekHighlight/index.tsx +0 -64
  213. package/src/components/TimelineBar/components/Guides/index.tsx +0 -61
  214. package/src/components/TimelineBar/components/Ticks/index.tsx +0 -56
  215. package/src/components/TimelineBar/components/TodayIndicator/index.tsx +0 -54
  216. package/src/components/TimelineBar/components/index.ts +0 -4
  217. package/src/components/TimelineBar/const.ts +0 -3
  218. package/src/components/TimelineBar/hooks/index.ts +0 -5
  219. package/src/components/TimelineBar/hooks/useHighlightRange.ts +0 -21
  220. package/src/components/TimelineBar/hooks/useMonthGuides.ts +0 -40
  221. package/src/components/TimelineBar/hooks/useTickValues.ts +0 -18
  222. package/src/components/TimelineBar/hooks/useVisibleRange.ts +0 -43
  223. package/src/components/TimelineBar/hooks/useWeekGuides.ts +0 -39
  224. package/src/components/TimelineBar/index.tsx +0 -63
  225. package/src/components/TimelineBar/utils.ts +0 -27
  226. package/src/components/Video/index.tsx +0 -37
  227. package/src/components/index.ts +0 -27
  228. package/src/rn.ts +0 -1
  229. package/src/rns/index.ts +0 -0
  230. package/src/web.ts +0 -2
@@ -1,152 +0,0 @@
1
- 'use client'
2
-
3
- import { useMemo } from 'react'
4
- import ReactMarkdown from 'react-markdown'
5
- import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
6
- import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
7
- import remarkGfm from 'remark-gfm'
8
- import rehypeSlug from 'rehype-slug'
9
- import rehypeAutolinkHeadings from 'rehype-autolink-headings'
10
-
11
- import 'github-markdown-css/github-markdown-light.css'
12
- import { style } from './style'
13
-
14
- interface MarkdownProps {
15
- content: string
16
- showToc?: boolean // 是否显示目录
17
- }
18
-
19
- interface TocItem {
20
- id: string
21
- text: string
22
- level: number
23
- }
24
-
25
- export const Markdown = (props: MarkdownProps) => {
26
- const { content = '', showToc = true } = props
27
-
28
- // 提取标题生成目录
29
- const tocItems = useMemo(() => {
30
- if (!showToc) {
31
- return []
32
- }
33
-
34
- const lines = content.split('\n')
35
- const items: TocItem[] = []
36
-
37
- lines.forEach((line) => {
38
- const match = line.match(/^(#{2,4})\s+(.+)$/)
39
- if (match && match[1] && match[2]) {
40
- const level = match[1].length
41
- const text = match[2].trim()
42
- const id = text
43
- .toLowerCase()
44
- .replace(/[^\w\u4e00-\u9fa5\s-]/g, '') // 保留中文、英文、数字、空格和连字符
45
- .replace(/\s+/g, '-') // 空格替换为连字符
46
-
47
- items.push({ id, text, level })
48
- }
49
- })
50
-
51
- return items
52
- }, [content, showToc])
53
-
54
- // 渲染目录
55
- const renderToc = () => {
56
- if (!showToc || tocItems.length === 0) {
57
- return null
58
- }
59
-
60
- return (
61
- <div
62
- className='markdown-toc-container'
63
- style={{
64
- padding: '10px 20px 10px 0',
65
- backgroundColor: '#f8f9fa',
66
- borderLeft: '4px solid #1890ff',
67
- borderRadius: '4px',
68
- overflowY: 'auto',
69
- height: '100%'
70
- }}
71
- >
72
- <ul style={{ margin: 0, padding: 0 }}>
73
- {tocItems.map((item, index) => (
74
- <li
75
- key={index}
76
- style={{
77
- marginBottom: '4px',
78
- marginLeft: `${(item.level - 1) * 16}px`,
79
- listStyle: 'none',
80
- position: 'relative',
81
- }}
82
- >
83
- <a
84
- href={`#${item.id}`}
85
- style={{
86
- textDecoration: 'none',
87
- color: '#1890ff',
88
- fontSize: item.level <= 2 ? '14px' : '13px',
89
- fontWeight: item.level === 1 ? 600 : 400,
90
- }}
91
- onClick={(e) => {
92
- e.preventDefault()
93
- const element = document.getElementById(item.id)
94
- if (element) {
95
- element.scrollIntoView({ behavior: 'smooth' })
96
- }
97
- }}
98
- >
99
- {item.text}
100
- </a>
101
- </li>
102
- ))}
103
- </ul>
104
- </div>
105
- )
106
- }
107
-
108
- return (
109
- <div style={{ height: '100%' }}>
110
- <style>{style}</style>
111
- <div className='markdown-body markdown-toc' style={{ height: '100%', display: 'flex' }}>
112
- <div style={{ marginRight: 14 }}>
113
- {renderToc()}
114
- </div>
115
- <div style={{ overflow: 'auto', flex: 1 }}>
116
- <ReactMarkdown
117
- remarkPlugins={[remarkGfm]}
118
- rehypePlugins={[
119
- rehypeSlug,
120
- [rehypeAutolinkHeadings, {
121
- behavior: 'wrap',
122
- properties: {
123
- className: ['anchor'],
124
- },
125
- }],
126
- ]}
127
- components={{
128
- code(props) {
129
- const { children, className } = props
130
- const match = /language-(\w+)/.exec(className || '')
131
- return match ? (
132
- <SyntaxHighlighter
133
- PreTag='div'
134
- children={String(children).replace(/\n$/, '')}
135
- language={match[1]}
136
- style={oneLight}
137
- />
138
- ) : (
139
- <code className={className}>
140
- {children}
141
- </code>
142
- )
143
- },
144
- }}
145
- >
146
- {content}
147
- </ReactMarkdown>
148
- </div>
149
- </div>
150
- </div>
151
- )
152
- }
@@ -1,29 +0,0 @@
1
- 'use client'
2
-
3
- import { createContext, useContext, type ReactNode } from 'react'
4
-
5
- export interface MindMapDndState {
6
- draggingNodeId: string | null
7
- dropTargetNodeId: string | null
8
- }
9
-
10
- const MindMapDndContext = createContext<MindMapDndState | null>(null)
11
-
12
- interface MindMapDndProviderProps {
13
- value: MindMapDndState
14
- children: ReactNode
15
- }
16
-
17
- export const MindMapDndProvider = (props: MindMapDndProviderProps) => {
18
- const { value, children } = props
19
- return (
20
- <MindMapDndContext.Provider value={value}>
21
- {children}
22
- </MindMapDndContext.Provider>
23
- )
24
- }
25
-
26
- export const useMindMapDndState = (): MindMapDndState => {
27
- const value = useContext(MindMapDndContext)
28
- return value ?? { draggingNodeId: null, dropTargetNodeId: null }
29
- }
@@ -1,220 +0,0 @@
1
- 'use client'
2
-
3
- import { Fragment, createElement, useCallback, useMemo, useState, type ReactElement } from 'react'
4
-
5
- import type { NodeChange, Node, Viewport } from '@xyflow/react'
6
-
7
- interface HelperLines {
8
- x: number | null
9
- y: number | null
10
- }
11
-
12
- interface UseAlignmentSnapParams<TNodeData extends Record<string, unknown>> {
13
- // 开关:关闭时只透传回调,不做任何吸附与对齐线计算
14
- enabled?: boolean
15
- // 当前节点列表(用于计算对齐点)
16
- nodes: Array<Node<TNodeData>>
17
- // 透传给 ReactFlow 的 onNodesChange(通常来自 useNodesState)
18
- onNodesChange?: (changes: Array<NodeChange<Node<TNodeData>>>) => void
19
- // 吸附阈值(单位:画布坐标 px)
20
- snapThreshold?: number
21
- }
22
-
23
- interface UseAlignmentSnapResult<TNodeData extends Record<string, unknown>> {
24
- // hook 直接产出的对齐线元素,主组件可直接渲染
25
- helperLines: ReactElement | null
26
- // 用于同步 viewport,保证对齐线在屏幕坐标下位置正确
27
- onMove: (_event: unknown, viewport: Viewport) => void
28
- // 包装后的 onNodesChange:在 position change 上做吸附与对齐线更新
29
- onNodesChange: (changes: Array<NodeChange<Node<TNodeData>>>) => void
30
- }
31
-
32
- const getNodeSize = <TNodeData extends Record<string, unknown>,>(node: Node<TNodeData>): { width: number, height: number } => {
33
- const width = node.measured?.width ?? node.width ?? 0
34
- const height = node.measured?.height ?? node.height ?? 0
35
- return { width, height }
36
- }
37
-
38
- export const useAlignmentSnap = <TNodeData extends Record<string, unknown>,>(params: UseAlignmentSnapParams<TNodeData>): UseAlignmentSnapResult<TNodeData> => {
39
- const { enabled = false, nodes, onNodesChange: onNodesChangeExternal, snapThreshold = 6 } = params
40
-
41
- const [viewport, setViewport] = useState<Viewport>({ x: 0, y: 0, zoom: 1 })
42
- // 保存对齐线在画布坐标下的位置(未做 viewport 变换)
43
- const [helperLines, setHelperLines] = useState<HelperLines>({ x: null, y: null })
44
-
45
- const onMove = useCallback((_event: unknown, nextViewport: Viewport): void => {
46
- setViewport(nextViewport)
47
- }, [])
48
-
49
- const onNodesChange = useCallback((changes: Array<NodeChange<Node<TNodeData>>>): void => {
50
- if (!onNodesChangeExternal) {
51
- return
52
- }
53
-
54
- if (!enabled) {
55
- setHelperLines({ x: null, y: null })
56
- onNodesChangeExternal(changes)
57
- return
58
- }
59
-
60
- // 只在 position change 上参与吸附,避免对 selection 等变更产生副作用
61
- const positionChange = changes.find(change => change.type === 'position')
62
- if (positionChange?.type !== 'position' || !positionChange.position) {
63
- onNodesChangeExternal(changes)
64
- return
65
- }
66
-
67
- const dragging = positionChange.dragging === true
68
- if (!dragging) {
69
- setHelperLines({ x: null, y: null })
70
- }
71
-
72
- const baseNode = nodes.find(item => item.id === positionChange.id)
73
- if (!baseNode) {
74
- onNodesChangeExternal(changes)
75
- return
76
- }
77
-
78
- const selfSize = getNodeSize(baseNode)
79
- if (!selfSize.width || !selfSize.height) {
80
- onNodesChangeExternal(changes)
81
- return
82
- }
83
-
84
- // 以“边/中/边”作为候选锚点,计算与其他节点锚点的最小距离
85
- const selfX = positionChange.position.x
86
- const selfY = positionChange.position.y
87
- const selfXAnchors: Array<{ point: number, shift: number }> = [
88
- { point: selfX, shift: 0 },
89
- { point: selfX + selfSize.width / 2, shift: selfSize.width / 2 },
90
- { point: selfX + selfSize.width, shift: selfSize.width },
91
- ]
92
- const selfYAnchors: Array<{ point: number, shift: number }> = [
93
- { point: selfY, shift: 0 },
94
- { point: selfY + selfSize.height / 2, shift: selfSize.height / 2 },
95
- { point: selfY + selfSize.height, shift: selfSize.height },
96
- ]
97
-
98
- let bestXDiff: number = Number.POSITIVE_INFINITY
99
- let bestYDiff: number = Number.POSITIVE_INFINITY
100
- let bestXLine: number | null = null
101
- let bestYLine: number | null = null
102
- let bestXOffset: number = 0
103
- let bestYOffset: number = 0
104
-
105
- nodes.forEach(other => {
106
- if (other.id === positionChange.id) {
107
- return
108
- }
109
- const otherSize = getNodeSize(other)
110
- if (!otherSize.width || !otherSize.height) {
111
- return
112
- }
113
-
114
- const otherX = other.position.x
115
- const otherY = other.position.y
116
- const otherXAnchors: Array<number> = [otherX, otherX + otherSize.width / 2, otherX + otherSize.width]
117
- const otherYAnchors: Array<number> = [otherY, otherY + otherSize.height / 2, otherY + otherSize.height]
118
-
119
- for (const selfAnchor of selfXAnchors) {
120
- for (const otherAnchor of otherXAnchors) {
121
- const diffX = Math.abs(selfAnchor.point - otherAnchor)
122
- if (diffX <= snapThreshold && diffX < bestXDiff) {
123
- bestXDiff = diffX
124
- bestXLine = otherAnchor
125
- bestXOffset = otherAnchor - selfAnchor.point
126
- }
127
- }
128
- }
129
-
130
- for (const selfAnchor of selfYAnchors) {
131
- for (const otherAnchor of otherYAnchors) {
132
- const diffY = Math.abs(selfAnchor.point - otherAnchor)
133
- if (diffY <= snapThreshold && diffY < bestYDiff) {
134
- bestYDiff = diffY
135
- bestYLine = otherAnchor
136
- bestYOffset = otherAnchor - selfAnchor.point
137
- }
138
- }
139
- }
140
- })
141
-
142
- if (dragging) {
143
- setHelperLines({ x: bestXLine, y: bestYLine })
144
- }
145
-
146
- // 没命中阈值则完全透传,避免抖动
147
- if (bestXLine === null && bestYLine === null) {
148
- onNodesChangeExternal(changes)
149
- return
150
- }
151
-
152
- const nextX = bestXLine === null ? selfX : selfX + bestXOffset
153
- const nextY = bestYLine === null ? selfY : selfY + bestYOffset
154
-
155
- // 通过“改写 position change”的方式实现吸附,避免与外部受控状态冲突
156
- const nextChanges: Array<NodeChange<Node<TNodeData>>> = changes.map(change => {
157
- if (change.type !== 'position' || change.id !== positionChange.id || !change.position) {
158
- return change
159
- }
160
- if (change.position.x === nextX && change.position.y === nextY) {
161
- return change
162
- }
163
- return {
164
- ...change,
165
- position: { x: nextX, y: nextY },
166
- }
167
- })
168
-
169
- onNodesChangeExternal(nextChanges)
170
- }, [enabled, nodes, onNodesChangeExternal, snapThreshold])
171
-
172
- // 将画布坐标转换为屏幕坐标,用于 absolute 定位对齐线
173
- const helperLineX = useMemo((): number | null => {
174
- if (helperLines.x === null) {
175
- return null
176
- }
177
- return helperLines.x * viewport.zoom + viewport.x
178
- }, [helperLines.x, viewport.x, viewport.zoom])
179
-
180
- const helperLineY = useMemo((): number | null => {
181
- if (helperLines.y === null) {
182
- return null
183
- }
184
- return helperLines.y * viewport.zoom + viewport.y
185
- }, [helperLines.y, viewport.y, viewport.zoom])
186
-
187
- const helperLinesElement = useMemo((): ReactElement | null => {
188
- if (!enabled) {
189
- return null
190
- }
191
-
192
- const elements: ReactElement[] = []
193
- if (helperLineX !== null) {
194
- elements.push(createElement('div', {
195
- key: 'helper-line-x',
196
- className: 'pointer-events-none absolute top-0 h-full w-px bg-sky-500',
197
- style: { left: helperLineX },
198
- }))
199
- }
200
- if (helperLineY !== null) {
201
- elements.push(createElement('div', {
202
- key: 'helper-line-y',
203
- className: 'pointer-events-none absolute left-0 w-full h-px bg-sky-500',
204
- style: { top: helperLineY },
205
- }))
206
- }
207
-
208
- if (elements.length === 0) {
209
- return null
210
- }
211
-
212
- return createElement(Fragment, null, ...elements)
213
- }, [enabled, helperLineX, helperLineY])
214
-
215
- return {
216
- helperLines: helperLinesElement,
217
- onMove,
218
- onNodesChange,
219
- }
220
- }
@@ -1,272 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback, useEffect, useMemo, useRef } from 'react'
4
- import type { Edge, EdgeChange, Node, NodeChange } from '@xyflow/react'
5
-
6
- interface MindMapCopyPayload<TNodeData extends Record<string, unknown>> {
7
- nodes: Array<Node<TNodeData>>
8
- edges: Edge[]
9
- }
10
-
11
- interface MindMapClipboardData<TNodeData extends Record<string, unknown>> {
12
- t: 'mindmap'
13
- v: 1
14
- payload: MindMapCopyPayload<TNodeData>
15
- }
16
-
17
- interface UseCopyPasteParams<TNodeData extends Record<string, unknown>> {
18
- enabled?: boolean
19
- nodes: Array<Node<TNodeData>>
20
- edges: Edge[]
21
- onNodesChange?: (changes: Array<NodeChange<Node<TNodeData>>>) => void
22
- onEdgesChange?: (changes: Array<EdgeChange<Edge>>) => void
23
- pasteOffset?: number
24
- }
25
-
26
- const CLIPBOARD_MIME: string = 'application/x-mindmap+json'
27
-
28
- const getDescendantIdSet = (edges: Edge[], rootIds: string[]): Set<string> => {
29
- const sourceToTargets = new Map<string, string[]>()
30
- edges.forEach(edge => {
31
- const prev = sourceToTargets.get(edge.source) ?? []
32
- sourceToTargets.set(edge.source, [...prev, edge.target])
33
- })
34
-
35
- const visited = new Set<string>()
36
- const result = new Set<string>()
37
- const queue: string[] = [...rootIds]
38
-
39
- rootIds.forEach(id => {
40
- visited.add(id)
41
- result.add(id)
42
- })
43
-
44
- while (queue.length > 0) {
45
- const id = queue.shift()
46
- if (!id) {
47
- continue
48
- }
49
- const targets = sourceToTargets.get(id) ?? []
50
- for (const targetId of targets) {
51
- if (visited.has(targetId)) {
52
- continue
53
- }
54
- visited.add(targetId)
55
- result.add(targetId)
56
- queue.push(targetId)
57
- }
58
- }
59
-
60
- return result
61
- }
62
-
63
- const getCrypto = (): Crypto | undefined => {
64
- const anyGlobal = globalThis as unknown as { crypto?: Crypto }
65
- return anyGlobal.crypto
66
- }
67
-
68
- const createId = (): string => {
69
- const crypto = getCrypto()
70
- if (crypto?.randomUUID) {
71
- return crypto.randomUUID()
72
- }
73
- return `${Date.now()}-${Math.random().toString(16).slice(2)}`
74
- }
75
-
76
- const clone = <T,>(value: T): T => {
77
- const anyGlobal = globalThis as unknown as { structuredClone?: <V>(v: V) => V }
78
- if (typeof anyGlobal.structuredClone === 'function') {
79
- return anyGlobal.structuredClone(value)
80
- }
81
- return JSON.parse(JSON.stringify(value)) as T
82
- }
83
-
84
- const safeParseClipboard = <TNodeData extends Record<string, unknown>,>(text: string): MindMapCopyPayload<TNodeData> | null => {
85
- try {
86
- const parsed = JSON.parse(text) as MindMapClipboardData<TNodeData>
87
- if (parsed?.t !== 'mindmap' || parsed?.v !== 1) {
88
- return null
89
- }
90
- const payload = parsed.payload
91
- if (!payload?.nodes?.length) {
92
- return null
93
- }
94
- return payload
95
- } catch {
96
- return null
97
- }
98
- }
99
-
100
- export const useCopyPaste = <TNodeData extends Record<string, unknown>,>(params: UseCopyPasteParams<TNodeData>): void => {
101
- const {
102
- enabled = false,
103
- nodes,
104
- edges,
105
- onNodesChange,
106
- onEdgesChange,
107
- pasteOffset = 24,
108
- } = params
109
-
110
- const selectedNodes = useMemo((): Array<Node<TNodeData>> => {
111
- return nodes.filter(node => node.selected)
112
- }, [nodes])
113
-
114
- const copyIdSet = useMemo((): Set<string> => {
115
- if (selectedNodes.length === 0) {
116
- return new Set<string>()
117
- }
118
- const rootIds = selectedNodes.map(n => n.id)
119
- return getDescendantIdSet(edges, rootIds)
120
- }, [edges, selectedNodes])
121
-
122
- const nodesToCopy = useMemo((): Array<Node<TNodeData>> => {
123
- if (copyIdSet.size === 0) {
124
- return []
125
- }
126
- return nodes.filter(node => copyIdSet.has(node.id))
127
- }, [copyIdSet, nodes])
128
-
129
- const edgesToCopy = useMemo((): Edge[] => {
130
- if (copyIdSet.size === 0) {
131
- return []
132
- }
133
- return edges.filter(edge => copyIdSet.has(edge.source) && copyIdSet.has(edge.target))
134
- }, [copyIdSet, edges])
135
-
136
- const pasteIndexRef = useRef<number>(0)
137
-
138
- const isEditableTarget = useCallback((target: EventTarget | null): boolean => {
139
- const el = target as HTMLElement | null
140
- if (!el) {
141
- return false
142
- }
143
- const tag = el.tagName?.toLowerCase()
144
- if (tag === 'input' || tag === 'textarea') {
145
- return true
146
- }
147
- return el.isContentEditable
148
- }, [])
149
-
150
- const handleCopy = useCallback((event: ClipboardEvent): void => {
151
- if (!enabled) {
152
- return
153
- }
154
- if (isEditableTarget(event.target)) {
155
- return
156
- }
157
- if (nodesToCopy.length === 0) {
158
- return
159
- }
160
-
161
- const payload: MindMapCopyPayload<TNodeData> = {
162
- nodes: clone(nodesToCopy),
163
- edges: clone(edgesToCopy),
164
- }
165
-
166
- const clipboardData: MindMapClipboardData<TNodeData> = {
167
- t: 'mindmap',
168
- v: 1,
169
- payload,
170
- }
171
- const text = JSON.stringify(clipboardData)
172
-
173
- if (!event.clipboardData) {
174
- return
175
- }
176
- event.preventDefault()
177
- event.clipboardData.setData(CLIPBOARD_MIME, text)
178
- event.clipboardData.setData('text/plain', text)
179
- }, [enabled, edgesToCopy, isEditableTarget, nodesToCopy])
180
-
181
- const pastePayload = useCallback((payload: MindMapCopyPayload<TNodeData>): void => {
182
- if (!onNodesChange) {
183
- return
184
- }
185
-
186
- pasteIndexRef.current += 1
187
- const shift = pasteOffset * pasteIndexRef.current
188
-
189
- const idMap = new Map<string, string>()
190
- const nextNodes = payload.nodes.map(node => {
191
- const nextId = createId()
192
- idMap.set(node.id, nextId)
193
-
194
- const nextNode = clone(node)
195
- nextNode.id = nextId
196
- nextNode.position = {
197
- x: nextNode.position.x + shift,
198
- y: nextNode.position.y + shift,
199
- }
200
- nextNode.selected = true
201
- delete (nextNode as unknown as { positionAbsolute?: unknown }).positionAbsolute
202
- delete (nextNode as unknown as { measured?: unknown }).measured
203
- return nextNode
204
- })
205
-
206
- const nodeChanges: Array<NodeChange<Node<TNodeData>>> = nextNodes.map(item => ({
207
- type: 'add',
208
- item,
209
- }))
210
- onNodesChange(nodeChanges)
211
-
212
- if (!onEdgesChange || payload.edges.length === 0) {
213
- return
214
- }
215
-
216
- const nextEdges: Edge[] = payload.edges
217
- .map(edge => {
218
- const nextSource = idMap.get(edge.source)
219
- const nextTarget = idMap.get(edge.target)
220
- if (!nextSource || !nextTarget) {
221
- return null
222
- }
223
- const nextEdge = clone(edge)
224
- nextEdge.id = createId()
225
- nextEdge.source = nextSource
226
- nextEdge.target = nextTarget
227
- return nextEdge
228
- })
229
- .filter((e): e is Edge => !!e)
230
-
231
- if (nextEdges.length === 0) {
232
- return
233
- }
234
-
235
- const edgeChanges: Array<EdgeChange<Edge>> = nextEdges.map(item => ({
236
- type: 'add',
237
- item,
238
- }))
239
- onEdgesChange(edgeChanges)
240
- }, [onEdgesChange, onNodesChange, pasteOffset])
241
-
242
- const handlePaste = useCallback((event: ClipboardEvent): void => {
243
- if (!enabled) {
244
- return
245
- }
246
- if (isEditableTarget(event.target)) {
247
- return
248
- }
249
-
250
- const text = event.clipboardData?.getData(CLIPBOARD_MIME) || event.clipboardData?.getData('text/plain') || ''
251
- const payloadFromClipboard = text ? safeParseClipboard<TNodeData>(text) : null
252
- if (!payloadFromClipboard) {
253
- return
254
- }
255
-
256
- event.preventDefault()
257
- pastePayload(payloadFromClipboard)
258
- }, [enabled, isEditableTarget, pastePayload])
259
-
260
- useEffect(() => {
261
- if (!enabled) {
262
- return
263
- }
264
-
265
- document.addEventListener('copy', handleCopy)
266
- document.addEventListener('paste', handlePaste)
267
- return () => {
268
- document.removeEventListener('copy', handleCopy)
269
- document.removeEventListener('paste', handlePaste)
270
- }
271
- }, [enabled, handleCopy, handlePaste])
272
- }