intable 0.0.6 → 0.0.8

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 (197) hide show
  1. package/.github/copilot-instructions.md +102 -0
  2. package/README.md +16 -263
  3. package/docs/index-BaMALNy6.css +1 -0
  4. package/docs/index-CDN48t9E.js +3 -0
  5. package/docs/index-Cc4RNkLY.css +1 -0
  6. package/docs/index-MRnbkYmU.js +3 -0
  7. package/docs/index.html +15 -0
  8. package/docs/vite.svg +1 -0
  9. package/index.html +13 -0
  10. package/package.json +35 -38
  11. package/packages/intable/README.md +379 -0
  12. package/packages/intable/package.json +51 -0
  13. package/packages/intable/src/assets/ClearFormat.svg +3 -0
  14. package/packages/intable/src/assets/Forms.svg +4 -0
  15. package/packages/intable/src/assets/MergeCell.svg +4 -0
  16. package/packages/intable/src/assets/SplitCell.svg +4 -0
  17. package/packages/intable/src/assets/gap.svg +3 -0
  18. package/packages/intable/src/assets/loading.svg +12 -0
  19. package/packages/intable/src/assets/paint.svg +9 -0
  20. package/packages/intable/src/assets/solid.svg +1 -0
  21. package/packages/intable/src/components/Columns.tsx +86 -0
  22. package/packages/intable/src/components/DocTree.tsx +36 -0
  23. package/packages/intable/src/components/Menu.tsx +109 -0
  24. package/packages/intable/src/components/Popover.tsx +55 -0
  25. package/packages/intable/src/components/RecycleList.tsx +99 -0
  26. package/packages/intable/src/components/Render.tsx +26 -0
  27. package/packages/intable/src/components/Split.tsx +56 -0
  28. package/packages/intable/src/components/Tree.tsx +115 -0
  29. package/packages/intable/src/components/utils.tsx +12 -0
  30. package/packages/intable/src/hooks/index.ts +200 -0
  31. package/packages/intable/src/hooks/useDir.ts +78 -0
  32. package/packages/intable/src/hooks/useSelector.ts +91 -0
  33. package/packages/intable/src/hooks/useSort.tsx +118 -0
  34. package/packages/intable/src/hooks/useVirtualizer.ts +180 -0
  35. package/packages/intable/src/index.tsx +489 -0
  36. package/packages/intable/src/plugins/CellChangeHighlightPlugin.tsx +5 -0
  37. package/packages/intable/src/plugins/CellMergePlugin.tsx +153 -0
  38. package/packages/intable/src/plugins/CellSelectionPlugin.tsx +175 -0
  39. package/packages/intable/src/plugins/CommandPlugin.tsx +74 -0
  40. package/packages/intable/src/plugins/CopyPastePlugin.tsx +99 -0
  41. package/packages/intable/src/plugins/DiffPlugin.tsx +120 -0
  42. package/packages/intable/src/plugins/DragPlugin.tsx +81 -0
  43. package/packages/intable/src/plugins/EditablePlugin.tsx +252 -0
  44. package/packages/intable/src/plugins/ExpandPlugin.tsx +80 -0
  45. package/packages/intable/src/plugins/HeaderGroup.tsx +289 -0
  46. package/packages/intable/src/plugins/HistoryPlugin.tsx +49 -0
  47. package/packages/intable/src/plugins/MenuPlugin.tsx +195 -0
  48. package/packages/intable/src/plugins/RenderPlugin/components.tsx +51 -0
  49. package/packages/intable/src/plugins/RenderPlugin/index.tsx +81 -0
  50. package/packages/intable/src/plugins/ResizePlugin.tsx +122 -0
  51. package/packages/intable/src/plugins/RowGroupPlugin.tsx +122 -0
  52. package/packages/intable/src/plugins/RowSelectionPlugin.tsx +65 -0
  53. package/packages/intable/src/plugins/TreePlugin.tsx +212 -0
  54. package/packages/intable/src/plugins/VirtualScrollPlugin.tsx +190 -0
  55. package/packages/intable/src/plugins/ZodValidatorPlugin.tsx +61 -0
  56. package/packages/intable/src/style.scss +244 -0
  57. package/{dist → packages/intable/src}/theme/antd.scss +14 -5
  58. package/packages/intable/src/theme/dark.scss +46 -0
  59. package/{dist → packages/intable/src}/theme/element-plus.scss +6 -5
  60. package/packages/intable/src/theme/github.scss +80 -0
  61. package/packages/intable/src/theme/material.scss +73 -0
  62. package/packages/intable/src/theme/shadcn.scss +66 -0
  63. package/packages/intable/src/theme/stripe.scss +57 -0
  64. package/packages/intable/src/tree.ts +13 -0
  65. package/packages/intable/src/types/auto-imports.d.ts +13 -0
  66. package/packages/intable/src/utils.ts +122 -0
  67. package/packages/intable/src/wc.tsx +35 -0
  68. package/packages/intable/src/web-component.ts +1 -0
  69. package/packages/react/package.json +36 -0
  70. package/packages/react/src/index.ts +44 -0
  71. package/packages/react/src/plugins/antd.ts +94 -0
  72. package/packages/react/src/style.scss +12 -0
  73. package/packages/react/src/types/auto-imports.d.ts +10 -0
  74. package/packages/vue/package.json +34 -0
  75. package/packages/vue/src/index.ts +63 -0
  76. package/packages/vue/src/plugins/element-plus.ts +69 -0
  77. package/packages/vue/src/style.scss +12 -0
  78. package/packages/vue/src/types/auto-imports.d.ts +10 -0
  79. package/pnpm-workspace.yaml +2 -0
  80. package/public/vite.svg +1 -0
  81. package/scripts/build.js +184 -0
  82. package/scripts/publish.js +95 -0
  83. package/src/assets/ClearFormat.svg +3 -0
  84. package/src/assets/Forms.svg +4 -0
  85. package/src/assets/MergeCell.svg +4 -0
  86. package/src/assets/SplitCell.svg +4 -0
  87. package/src/assets/gap.svg +3 -0
  88. package/src/assets/loading.svg +12 -0
  89. package/src/assets/paint.svg +9 -0
  90. package/src/assets/solid.svg +1 -0
  91. package/src/demo-vue.ts +54 -0
  92. package/src/index.scss +105 -0
  93. package/src/index.tsx +20 -0
  94. package/src/pages/demo/BasicDemo.tsx +19 -0
  95. package/src/pages/demo/CellMergeDemo.tsx +41 -0
  96. package/src/pages/demo/CellSelectionDemo.tsx +24 -0
  97. package/src/pages/demo/CompositeDemo.tsx +60 -0
  98. package/src/pages/demo/CopyPasteDemo.tsx +26 -0
  99. package/src/pages/demo/DiffDemo.tsx +33 -0
  100. package/src/pages/demo/DragDemo.tsx +25 -0
  101. package/src/pages/demo/EditableDemo.tsx +58 -0
  102. package/src/pages/demo/ExpandDemo.tsx +32 -0
  103. package/src/pages/demo/HeaderGroupDemo.tsx +36 -0
  104. package/src/pages/demo/HistoryDemo.tsx +28 -0
  105. package/src/pages/demo/ReactDemo.tsx +59 -0
  106. package/src/pages/demo/ResizeDemo.tsx +24 -0
  107. package/src/pages/demo/RowGroupDemo.tsx +43 -0
  108. package/src/pages/demo/RowSelectionDemo.tsx +27 -0
  109. package/src/pages/demo/TreeDemo.tsx +45 -0
  110. package/src/pages/demo/VirtualScrollDemo.tsx +21 -0
  111. package/src/pages/demo/helpers.tsx +39 -0
  112. package/src/pages/demo/index.tsx +180 -0
  113. package/src/pages/index.tsx +2 -0
  114. package/src/pages/website.scss +37 -0
  115. package/src/pages/website.tsx +651 -0
  116. package/src/styles/index.scss +172 -0
  117. package/src/types/auto-imports.d.ts +13 -0
  118. package/stats.html +4949 -0
  119. package/tsconfig.app.json +34 -0
  120. package/tsconfig.json +7 -0
  121. package/tsconfig.node.json +26 -0
  122. package/vite.config.ts +70 -0
  123. package/dist/__uno.css +0 -1
  124. package/dist/chevron-right.js +0 -6
  125. package/dist/components/Columns.d.ts +0 -3
  126. package/dist/components/Columns.js +0 -71
  127. package/dist/components/DocTree.d.ts +0 -4
  128. package/dist/components/DocTree.js +0 -32
  129. package/dist/components/Menu.d.ts +0 -1
  130. package/dist/components/Menu.js +0 -107
  131. package/dist/components/Popover.d.ts +0 -14
  132. package/dist/components/Popover.js +0 -41
  133. package/dist/components/Render.d.ts +0 -4
  134. package/dist/components/Render.js +0 -20
  135. package/dist/components/Split.d.ts +0 -15
  136. package/dist/components/Split.js +0 -76
  137. package/dist/components/Tree.d.ts +0 -37
  138. package/dist/components/Tree.js +0 -82
  139. package/dist/components/utils.d.ts +0 -3
  140. package/dist/components/utils.js +0 -8
  141. package/dist/hooks/index.d.ts +0 -40
  142. package/dist/hooks/index.js +0 -157
  143. package/dist/hooks/useDir.d.ts +0 -11
  144. package/dist/hooks/useDir.js +0 -42
  145. package/dist/hooks/useSelector.d.ts +0 -16
  146. package/dist/hooks/useSelector.js +0 -35
  147. package/dist/hooks/useSort.d.ts +0 -18
  148. package/dist/hooks/useSort.js +0 -83
  149. package/dist/hooks/useVirtualizer.d.ts +0 -25
  150. package/dist/hooks/useVirtualizer.js +0 -67
  151. package/dist/index.d.ts +0 -130
  152. package/dist/index.js +0 -347
  153. package/dist/loading.js +0 -6
  154. package/dist/plugins/CellChangeHighlightPlugin.d.ts +0 -2
  155. package/dist/plugins/CellChangeHighlightPlugin.js +0 -4
  156. package/dist/plugins/CellMergePlugin.d.ts +0 -12
  157. package/dist/plugins/CellMergePlugin.js +0 -2
  158. package/dist/plugins/CellSelectionPlugin.d.ts +0 -15
  159. package/dist/plugins/CellSelectionPlugin.js +0 -115
  160. package/dist/plugins/CommandPlugin.d.ts +0 -14
  161. package/dist/plugins/CommandPlugin.js +0 -12
  162. package/dist/plugins/CopyPastePlugin.d.ts +0 -14
  163. package/dist/plugins/CopyPastePlugin.js +0 -42
  164. package/dist/plugins/DiffPlugin.d.ts +0 -23
  165. package/dist/plugins/DiffPlugin.js +0 -56
  166. package/dist/plugins/DragPlugin.d.ts +0 -14
  167. package/dist/plugins/DragPlugin.js +0 -47
  168. package/dist/plugins/EditablePlugin.d.ts +0 -48
  169. package/dist/plugins/EditablePlugin.js +0 -141
  170. package/dist/plugins/ExpandPlugin.d.ts +0 -18
  171. package/dist/plugins/ExpandPlugin.js +0 -50
  172. package/dist/plugins/HistoryPlugin.d.ts +0 -10
  173. package/dist/plugins/HistoryPlugin.js +0 -30
  174. package/dist/plugins/MenuPlugin.d.ts +0 -18
  175. package/dist/plugins/MenuPlugin.js +0 -107
  176. package/dist/plugins/RenderPlugin/components.d.ts +0 -5
  177. package/dist/plugins/RenderPlugin/components.js +0 -87
  178. package/dist/plugins/RenderPlugin/index.d.ts +0 -30
  179. package/dist/plugins/RenderPlugin/index.js +0 -49
  180. package/dist/plugins/ResizePlugin.d.ts +0 -27
  181. package/dist/plugins/ResizePlugin.js +0 -81
  182. package/dist/plugins/RowGroupPlugin.d.ts +0 -17
  183. package/dist/plugins/RowGroupPlugin.js +0 -83
  184. package/dist/plugins/RowSelectionPlugin.d.ts +0 -20
  185. package/dist/plugins/RowSelectionPlugin.js +0 -42
  186. package/dist/plugins/VirtualScrollPlugin.d.ts +0 -15
  187. package/dist/plugins/VirtualScrollPlugin.js +0 -96
  188. package/dist/plus.js +0 -6
  189. package/dist/style.css +0 -3
  190. package/dist/types/auto-imports.d.js +0 -0
  191. package/dist/utils.d.ts +0 -30
  192. package/dist/utils.js +0 -70
  193. package/dist/wc.d.ts +0 -1
  194. package/dist/wc.js +0 -21
  195. package/dist/web-component.d.ts +0 -1
  196. package/dist/web-component.js +0 -2
  197. package/dist/x.js +0 -6
@@ -0,0 +1,175 @@
1
+ import { batch, createMemo } from 'solid-js'
2
+ import { combineProps } from '@solid-primitives/props'
3
+ import { type Plugin } from '..'
4
+ import { usePointerDrag } from '../hooks'
5
+
6
+ declare module '../index' {
7
+ interface TableProps {
8
+
9
+ }
10
+ interface TableStore {
11
+ selected: { start: number[]; end: number[] }
12
+ }
13
+ interface Commands {
14
+ getAreaRows(): any[]
15
+ }
16
+ }
17
+
18
+ const inrange = (v, min, max) => v <= max && v >= min
19
+
20
+ export const CellSelectionPlugin: Plugin = {
21
+ name: 'cell-selection',
22
+ store: () => ({
23
+ selected: { start: [], end: [] }
24
+ }),
25
+ commands: store => ({
26
+ getAreaRows() {
27
+ const { start, end } = store.selected
28
+ const [y1, y2] = [start[1], end[1]].sort((a, b) => a - b)
29
+ return store.props!.data.slice(y1, y2 + 1)
30
+ }
31
+ }),
32
+ rewriteProps: {
33
+ Table: ({ Table }, { store }) => (o) => {
34
+ store.cellSelectionRect ??= createMemo(() => {
35
+ const { start, end } = store.selected
36
+ const xs = [start[0], end[0]].sort((a, b) => a - b)
37
+ const ys = [start[1], end[1]].sort((a, b) => a - b)
38
+ return { xs, ys }
39
+ })
40
+
41
+ usePointerDrag(() => store.table, {
42
+ preventDefault: false,
43
+ start(e, move, end) {
44
+ batch(() => {
45
+ const findCell = (e: PointerEvent) => e.composedPath().find((e) => e.tagName == 'TH' || e.tagName == 'TD') as Element
46
+ const getXY = (cell: Element) => [cell.getAttribute('x'), cell.getAttribute('y')]
47
+ const cell = findCell(e)
48
+ if (!cell) return
49
+ if (e.buttons != 1 && cell.classList.contains('range-selected')) return
50
+ if (cell.tagName == 'TH') {
51
+ const [x, y] = getXY(cell)
52
+ if (x == null) return
53
+ store.selected.start = [+x, 0]
54
+ store.selected.end = [+x, Infinity]
55
+ move(e => {
56
+ const cell = findCell(e)
57
+ if (!cell) return
58
+ const [x, y] = getXY(cell)
59
+ if (x == null) return
60
+ store.selected.end = [+x, Infinity]
61
+ })
62
+ }
63
+ if (cell.classList.contains('index')) {
64
+ const [x, y] = getXY(cell)
65
+ if (x == null || y == null) return
66
+ store.selected.start = [0, +y]
67
+ store.selected.end = [Infinity, +y]
68
+ move(e => {
69
+ const cell = findCell(e)
70
+ if (!cell) return
71
+ const [x, y] = getXY(cell)
72
+ if (x == null || y == null) return
73
+ store.selected.end = [Infinity, +y]
74
+ })
75
+ }
76
+ else if (cell.tagName == 'TD') {
77
+ const [x, y] = getXY(cell)
78
+ if (x == null || y == null) return
79
+ store.selected.start = [+x, +y]
80
+ store.selected.end = [...store.selected.start]
81
+ move(e => {
82
+ const cell = findCell(e)
83
+ if (!cell) return
84
+ const [x, y] = getXY(cell)
85
+ if (x == null || y == null) return
86
+ store.selected.end = [+x, +y]
87
+ })
88
+ }
89
+ // scrollIntoView()
90
+ })
91
+ },
92
+ })
93
+
94
+ o = combineProps({ class: 'select-none' }, o)
95
+ return <Table {...o} />
96
+ },
97
+ Th: ({ Th }, { store }) => o => {
98
+ const clazz = createMemo(() => {
99
+ const { start, end } = store.selected
100
+ const inx = inrange(o.x, ...[start[0], end[0]].sort((a, b) => a - b))
101
+ return inx ? 'col-range-highlight' : ''
102
+ })
103
+ const mo = combineProps(o, { get class() { return clazz() } })
104
+ return (
105
+ <Th {...mo}>
106
+ {mo.children}
107
+ {clazz() && <div class='area' />}
108
+ </Th>
109
+ )
110
+ },
111
+ Td: ({ Td }, { store }) => (o) => {
112
+ const clazz = createMemo(() => {
113
+ let clazz = ''
114
+ const { xs, ys } = store.cellSelectionRect()
115
+ const inx = inrange(o.x, xs[0], xs[1])
116
+ const iny = inrange(o.y, ys[0], ys[1])
117
+ if (inx && iny) {
118
+ clazz += 'range-selected '
119
+ if (o.x == xs[0]) clazz += 'range-selected-l '
120
+ if (o.x == xs[1]) clazz += 'range-selected-r '
121
+ if (o.y == ys[0]) clazz += 'range-selected-t '
122
+ if (o.y == ys[1]) clazz += 'range-selected-b '
123
+ }
124
+ // if (o.x == 0 && iny) clazz += 'row-range-highlight '
125
+ if (o.col.id == store.$index.id && iny) clazz += 'row-range-highlight '
126
+ return clazz
127
+ })
128
+
129
+ const mo = combineProps(o, { get class() { return clazz() }, tabindex: -1 })
130
+ return (
131
+ <Td {...mo}>
132
+ {mo.children}
133
+ {clazz() && <div class='area' />}
134
+ </Td>
135
+ )
136
+ },
137
+ },
138
+ keybindings: (store) => {
139
+ const scrollIntoView = () => {
140
+ const cell = store.table?.querySelector(`td[x="${store.selected.start[0]}"][y="${store.selected.start[1]}"]`)
141
+ ;(cell as HTMLElement | null)?.scrollIntoViewIfNeeded(false)
142
+ ;(cell as HTMLElement | null)?.focus()
143
+ }
144
+ return {
145
+ 'ArrowLeft': () => {
146
+ const { start, end } = store.selected
147
+ if (!start.length) return
148
+ start[0] = end[0] = Math.max(start[0] - 1, 0)
149
+ end[1] = start[1]
150
+ scrollIntoView()
151
+ },
152
+ 'ArrowRight': () => {
153
+ const { start, end } = store.selected
154
+ if (!start.length) return
155
+ start[0] = end[0] = Math.min(start[0] + 1, store.props!.columns!.length - 1)
156
+ end[1] = start[1]
157
+ scrollIntoView()
158
+ },
159
+ 'ArrowUp': () => {
160
+ const { start, end } = store.selected
161
+ if (!start.length) return
162
+ start[1] = end[1] = Math.max(start[1] - 1, 0)
163
+ end[0] = start[0]
164
+ scrollIntoView()
165
+ },
166
+ 'ArrowDown': () => {
167
+ const { start, end } = store.selected
168
+ if (!start.length) return
169
+ start[1] = end[1] = Math.min(start[1] + 1, store.props!.data!.length - 1)
170
+ end[0] = start[0]
171
+ scrollIntoView()
172
+ },
173
+ }
174
+ },
175
+ }
@@ -0,0 +1,74 @@
1
+ import { createEffect, createMemo, getOwner, runWithOwner } from 'solid-js'
2
+ import { createEventListener } from '@solid-primitives/event-listener'
3
+ import { combineProps } from '@solid-primitives/props'
4
+ import { createKeybindingsHandler } from 'tinykeys'
5
+ import { type Commands, type Plugin } from '..'
6
+
7
+ declare module '../index' {
8
+ interface TableProps {
9
+
10
+ }
11
+ interface TableStore {
12
+ commands: Commands
13
+ }
14
+ interface Plugin {
15
+ commands?: (store: TableStore, commands: Partial<Commands>) => Partial<Commands> & Record<string, any>
16
+ }
17
+ interface Commands {
18
+
19
+ }
20
+ }
21
+
22
+ export const CommandPlugin: Plugin = {
23
+ name: 'command',
24
+ priority: Infinity,
25
+ store: (store) => {
26
+ const owner = getOwner()
27
+ const commands = createMemo(() => (
28
+ store.plugins.reduce((o, e) => (
29
+ Object.assign(o, runWithOwner(owner, () => e.commands?.(store, {...o})))
30
+ ), {} as Commands)
31
+ ))
32
+ return {
33
+ get commands() { return commands() }
34
+ }
35
+ },
36
+ rewriteProps: {
37
+ Table: ({ Table }, { store }) => o => {
38
+ const owner = getOwner()
39
+
40
+ // Merge keybindings from all plugins in priority order (later index = lower priority = outer wrapper).
41
+ // createMemo re-creates the tinykeys handler whenever plugins or user overrides change.
42
+ const handler = createMemo(() => {
43
+ const merged: Record<string, (e: KeyboardEvent) => void> = {}
44
+ for (const p of store.plugins) {
45
+ const bindings = runWithOwner(owner, () => p.keybindings?.(store))
46
+ if (bindings) Object.assign(merged, bindings)
47
+ }
48
+ // Apply user overrides: false = disable, function = replace
49
+ const overrides = store.props?.keybindings
50
+ if (overrides) {
51
+ for (const [key, val] of Object.entries(overrides)) {
52
+ if (val === false) delete merged[key]
53
+ else if (typeof val === 'function') merged[key] = val
54
+ }
55
+ }
56
+ // Wrap each handler: preventDefault + call
57
+ return createKeybindingsHandler(
58
+ Object.fromEntries(
59
+ Object.entries(merged).map(([k, fn]) => [k, (e: KeyboardEvent) => {
60
+ e.preventDefault()
61
+ fn(e)
62
+ }])
63
+ )
64
+ )
65
+ })
66
+
67
+ // Single keydown listener that proxies to the current merged handler
68
+ createEventListener(() => store.scroll_el, 'keydown', (e: KeyboardEvent) => handler()(e))
69
+
70
+ o = combineProps({ tabindex: -1 }, o)
71
+ return <Table {...o} />
72
+ }
73
+ },
74
+ }
@@ -0,0 +1,99 @@
1
+ import { createEffect } from 'solid-js'
2
+ import { type Plugin } from '..'
3
+
4
+ declare module '../index' {
5
+ interface TableProps {
6
+
7
+ }
8
+ interface TableStore {
9
+
10
+ }
11
+ interface Plugin {
12
+
13
+ }
14
+ interface Commands {
15
+ copy: () => void
16
+ paste: () => void
17
+ }
18
+ }
19
+
20
+ export const ClipboardPlugin: Plugin = {
21
+ name: 'clipboard',
22
+ keybindings: (store) => ({
23
+ '$mod+c': () => { store.commands.copy(); store.scroll_el?.classList.add('copied') },
24
+ '$mod+v': () => store.commands.paste(),
25
+ }),
26
+ onMount: (store) => {
27
+ // Remove the 'copied' CSS indicator whenever the selection changes
28
+ createEffect(() => {
29
+ JSON.stringify(store.selected)
30
+ store.scroll_el?.classList.remove('copied')
31
+ })
32
+ },
33
+ menus: (store) => [
34
+ // { label: '复制', onClick: () => store.commands.copy() },
35
+ // { label: '粘贴', onClick: () => store.commands.paste() },
36
+ ],
37
+ commands: store => ({
38
+ copy: () => {
39
+ const { start, end } = store.selected
40
+ if (!start?.length) return
41
+ const [x1, x2] = [start[0], end[0]].sort((a, b) => a - b)
42
+ const [y1, y2] = [start[1], end[1]].sort((a, b) => a - b)
43
+ // Skip internal columns (index, row-selection, etc.)
44
+ const cols = store.props!.columns!.slice(x1, x2 + 1).filter(col => !col[store.internal])
45
+ const rows = store.props!.data!.slice(y1, y2 + 1)
46
+ const text = rows.map(row =>
47
+ cols.map(col => {
48
+ const val = row[col.id as string] ?? ''
49
+ // Escape in-cell tabs/newlines so TSV structure is preserved
50
+ return String(val).replace(/[\t\r\n]/g, ' ')
51
+ }).join('\t')
52
+ ).join('\n')
53
+ navigator.clipboard.writeText(text)
54
+ },
55
+ paste: async () => {
56
+ const { start, end } = store.selected
57
+ if (!start?.length) return
58
+ const text = await navigator.clipboard.readText()
59
+ // Normalise CRLF (Excel) and CR (old Mac) line endings; trim trailing newline
60
+ const arr2 = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n$/, '').split('\n').map(r => r.split('\t'))
61
+ const clipH = arr2.length
62
+ const clipW = arr2[0].length
63
+
64
+ const [x1, x2] = [start[0], end[0]].sort((a, b) => a - b)
65
+ const [y1, y2] = [start[1], end[1]].sort((a, b) => a - b)
66
+ const selH = y2 - y1 + 1
67
+ const selW = x2 - x1 + 1
68
+ // Tile clipboard to fill the selection when it is an exact multiple
69
+ const pasteH = selH > clipH && selH % clipH === 0 ? selH : clipH
70
+ const pasteW = selW > clipW && selW % clipW === 0 ? selW : clipW
71
+
72
+ // Collect target user columns starting from x1 (skip internals), up to pasteW
73
+ const allCols = store.props!.columns!
74
+ const targetCols: typeof allCols = []
75
+ for (let i = x1; i < allCols.length && targetCols.length < pasteW; i++) {
76
+ if (!allCols[i][store.internal]) targetCols.push(allCols[i])
77
+ }
78
+
79
+ const data = store.props!.data!.slice()
80
+ const maxY = Math.min(y1 + pasteH - 1, data.length - 1)
81
+ for (let dy = 0; dy <= maxY - y1; dy++) {
82
+ const patch: Record<string, any> = {}
83
+ targetCols.forEach((col, dx) => {
84
+ patch[col.id as string] = arr2[dy % clipH][dx % clipW]
85
+ })
86
+ data[y1 + dy] = { ...data[y1 + dy], ...patch }
87
+ }
88
+
89
+ // Expand selection to cover the pasted region
90
+ let endX = x1
91
+ let userCount = 0
92
+ for (let i = x1; i < allCols.length && userCount < targetCols.length; i++) {
93
+ if (!allCols[i][store.internal]) { endX = i; userCount++ }
94
+ }
95
+ store.selected.end = [endX, maxY]
96
+ store.props!.onDataChange?.(data)
97
+ },
98
+ })
99
+ }
@@ -0,0 +1,120 @@
1
+ import { unwrap } from 'solid-js/store'
2
+ import { combineProps } from '@solid-primitives/props'
3
+ import { createLazyMemo } from '@solid-primitives/memo'
4
+ import { v4 as uuid } from 'uuid'
5
+ import { diffArrays } from 'diff'
6
+ import { isEqual, keyBy } from 'es-toolkit'
7
+ import { type Plugin } from '..'
8
+ import { log } from '../utils'
9
+
10
+ declare module '../index' {
11
+ interface TableProps {
12
+ diff?: {
13
+ /** @default false */
14
+ enable?: boolean
15
+ data?: any[]
16
+ /** @default true */
17
+ added?: boolean
18
+ /** @default true */
19
+ removed?: boolean
20
+ /** @default true */
21
+ changed?: boolean
22
+ onCommit?: (data: any, opt: { added: any[], removed: any[], changed: any[] }) => any
23
+ }
24
+ }
25
+ interface TableStore {
26
+ diffData: any[]
27
+ diffDataKeyed: () => any
28
+ }
29
+ interface Commands {
30
+ diffCommit(data?: any[]): void
31
+ }
32
+ }
33
+
34
+ const DEL = Symbol('del')
35
+ const NEW = Symbol('new')
36
+
37
+ export const DiffPlugin: Plugin = {
38
+ priority: Infinity,
39
+ store: store => {
40
+ const data = store.rawProps.data || []
41
+ data.forEach(row => unwrap(row)[store.rawProps.rowKey] ??= uuid())
42
+ return {
43
+ diffData: structuredClone(unwrap(data || [])),
44
+ diffData2: () => store.props.diff?.data ?? store.diffData,
45
+ diffDataKeyed: createLazyMemo(() => keyBy(store.diffData2(), e => e[store.props!.rowKey]))
46
+ }
47
+ },
48
+ commands: store => ({
49
+ async diffCommit(data = store.rawProps.data || []) {
50
+ const { rowKey } = store.props || {}
51
+ data.forEach(row => unwrap(row)[rowKey] ??= uuid())
52
+ data = structuredClone(unwrap(data))
53
+ const added = [], removed = [], changed = []
54
+ const keyed = keyBy(data, e => e[rowKey])
55
+ for (const e of data) {
56
+ const old = store.diffDataKeyed()[e[rowKey]]
57
+ if (!old) added.push(e)
58
+ else if (!isEqual(e, old)) changed.push(e)
59
+ }
60
+ for (const e of store.diffData2()) {
61
+ !keyed[e[rowKey]] && removed.push(e)
62
+ }
63
+ await store.props!.diff?.onCommit?.(data, { added, removed, changed })
64
+ added[NEW] = 0
65
+ store.diffData = data
66
+ }
67
+ }),
68
+ rewriteProps: {
69
+ diff: ({ diff }) => ({
70
+ enable: false,
71
+ added: true,
72
+ removed: true,
73
+ changed: true,
74
+ ...diff
75
+ }),
76
+ data: ({ data }, { store }) => {
77
+ if (!store.props.diff?.enable) return data
78
+
79
+ const { rowKey, diff } = store.props || {}
80
+ const diffData = store.diffData2() || []
81
+
82
+ // Fast path: same number of rows, same keys in same order (edit-only, no add/delete/move).
83
+ // Skips the O(n²) diffArrays call which is the common case when only cell values changae.
84
+ if (data.length === diffData.length && data.length > 0) {
85
+ let sameOrder = true
86
+ for (let i = 0; i < data.length; i++) {
87
+ if (data[i]?.[rowKey] !== diffData[i]?.[rowKey]) { sameOrder = false; break }
88
+ }
89
+ if (sameOrder) return data
90
+ }
91
+
92
+ // Structural change (add / delete / move) — fall back to diff library
93
+ const diffArr = diffArrays(diffData, data, { comparator: (a, b) => a[rowKey] == b[rowKey] })
94
+ return diffArr.flatMap(e => (
95
+ // e.added ? e.value.map(e => ({ ...e, [NEW]: 1 })) :
96
+ e.added ? e.value.map(e => (e[NEW] = 1, e)) :
97
+ e.removed ? diff!.removed ? e.value.map(e => ({ ...e, [DEL]: 1, [store.internal]: 1 })) : [] :
98
+ e.value
99
+ ))
100
+ },
101
+ Td: ({ Td }, { store }) => !store.props.diff?.enable ? Td : o => {
102
+ o = combineProps(o, {
103
+ get class() {
104
+ const { diff } = store.props
105
+ const id = o.data[store.props!.rowKey]
106
+ return [
107
+ o.data[NEW] ? 'bg-#dafaea!' :
108
+ o.data[DEL] ? 'bg-#ffe8e8!' :
109
+ o.data[store.internal] ? '' :
110
+ diff!.changed && o.data[o.col.id] != store.diffDataKeyed()[id]?.[o.col.id] ? 'bg-#dafaea!' : ''
111
+ ].join(' ')
112
+ }
113
+ })
114
+ return <Td {...o} />
115
+ },
116
+ },
117
+ keybindings: (store) => ({
118
+ '$mod+S': () => store.commands.diffCommit(),
119
+ }),
120
+ }
@@ -0,0 +1,81 @@
1
+ import { delay } from "es-toolkit"
2
+ import { isMatch } from "es-toolkit/compat"
3
+ import { Ctx, type Plugin, type TableColumn, type THProps } from "../index"
4
+ import { useSort } from '../hooks/useSort'
5
+
6
+ declare module '../index' {
7
+ interface TableProps {
8
+ colDrag?: boolean
9
+ rowDrag?: boolean
10
+ }
11
+ interface TableColumn {
12
+
13
+ }
14
+ interface TableStore {
15
+
16
+ }
17
+ interface Commands {
18
+
19
+ }
20
+ }
21
+
22
+ export const DragPlugin: Plugin = {
23
+ rewriteProps: {
24
+ colDrag: ({ colDrag = false }) => colDrag,
25
+ rowDrag: ({ rowDrag = false }) => rowDrag,
26
+ },
27
+ onMount(store) {
28
+ const colDrag = useSort(() => store.scroll_el, {
29
+ get enable() { return store.props?.colDrag },
30
+ guideLine: { class: 'col__guide-line' },
31
+ draggable: el => ((x, y) => el.tagName == 'TH' && isMatch(store.selected, { start: [x, 0] }) && !store.props?.columns[x][store.internal] && store.thead.contains(el) && delay(300).then(() => true))(+el.getAttribute('x')!, +el.getAttribute('y')!),
32
+ dragover: el => el.tagName == 'THEAD',
33
+ children: el => [...colDrag.drag.parentElement.children].filter(e => !store.props?.columns[e.getAttribute('x')][store.internal]),
34
+ dragend: onColDragend
35
+ })
36
+
37
+ const rowDrag = useSort(() => store.scroll_el, {
38
+ get enable() { return store.props?.rowDrag },
39
+ guideLine: { class: 'row__guide-line' },
40
+ draggable: el => ((x, y) => el.tagName == 'TD' && isMatch(store.selected, { start: [0, y] }) && x == 0 && !store.props?.data[y][store.internal] && store.tbody.contains(el) && delay(300).then(() => true))(+el.getAttribute('x')!, +el.getAttribute('y')!),
41
+ dragover: el => el.tagName == 'TBODY',
42
+ children: el => [...rowDrag.over.children].filter(e => !store.props!.data[e.getAttribute('y')][store.internal]),
43
+ dragend: onRowDragend
44
+ })
45
+
46
+ async function onColDragend() {
47
+ if (colDrag.drag == colDrag.rel) return
48
+ const [cols, rawCols] = [store.props!.columns, [...store.rawProps.columns || []]]
49
+ const col1 = (col => col[store.raw] ?? col)(cols[colDrag.drag.getAttribute('x')])
50
+ const col2 = (col => col[store.raw] ?? col)(cols[colDrag.rel.getAttribute('x')])
51
+ const i1 = rawCols.indexOf(col1)
52
+ const i2 = rawCols.indexOf(col2)
53
+ if (i1 < 0 || i2 < 0) return
54
+ rawCols[i1].fixed = rawCols[i2].fixed
55
+ rawCols.splice(i2 - (i1 > i2 ? 0 : 1) + (colDrag.type == 'before' ? 0 : 1), 0, rawCols.splice(i1, 1)[0])
56
+ store.props!.onColumnsChange?.(rawCols)
57
+ // select area
58
+ await Promise.resolve()
59
+ const i = store.props!.columns.findIndex(e => e == col1 || e[store.raw] == col1)
60
+ if (i < 0) return
61
+ store.selected.start[0] = store.selected.end[0] = i
62
+ }
63
+
64
+ async function onRowDragend() {
65
+ if (rowDrag.drag == rowDrag.rel) return
66
+ const [data, rawData] = [store.props!.data, [...store.rawProps.data || []]]
67
+ const data1 = (row => row[store.raw] ?? row)(data[rowDrag.drag.getAttribute('y')])
68
+ const data2 = (row => row[store.raw] ?? row)(data[rowDrag.rel.getAttribute('y')])
69
+ const i1 = rawData.indexOf(data1)
70
+ const i2 = rawData.indexOf(data2)
71
+ if (i1 < 0 || i2 < 0) return
72
+ rawData.splice(i2 - (i1 > i2 ? 0 : 1) + (rowDrag.type == 'before' ? 0 : 1), 0, rawData.splice(i1, 1)[0])
73
+ store.props!.onDataChange?.(rawData)
74
+ // select area
75
+ await Promise.resolve()
76
+ const i = store.props!.data.findIndex(e => e == data1 || e[store.raw] == data1)
77
+ if (i < 0) return
78
+ store.selected.start[1] = store.selected.end[1] = i
79
+ }
80
+ },
81
+ }