intable 0.0.5 → 0.0.7

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 (169) hide show
  1. package/README.md +1 -2
  2. package/docs/index-BaMALNy6.css +1 -0
  3. package/docs/index-CDN48t9E.js +3 -0
  4. package/docs/index-Cc4RNkLY.css +1 -0
  5. package/docs/index-MRnbkYmU.js +3 -0
  6. package/docs/index.html +15 -0
  7. package/docs/vite.svg +1 -0
  8. package/index.html +14 -0
  9. package/package.json +30 -37
  10. package/packages/intable/README.md +379 -0
  11. package/packages/intable/package.json +51 -0
  12. package/packages/intable/src/assets/ClearFormat.svg +3 -0
  13. package/packages/intable/src/assets/Forms.svg +4 -0
  14. package/packages/intable/src/assets/MergeCell.svg +4 -0
  15. package/packages/intable/src/assets/SplitCell.svg +4 -0
  16. package/packages/intable/src/assets/gap.svg +3 -0
  17. package/packages/intable/src/assets/loading.svg +12 -0
  18. package/packages/intable/src/assets/paint.svg +9 -0
  19. package/packages/intable/src/assets/solid.svg +1 -0
  20. package/packages/intable/src/components/Columns.tsx +86 -0
  21. package/packages/intable/src/components/DocTree.tsx +36 -0
  22. package/packages/intable/src/components/Menu.tsx +109 -0
  23. package/packages/intable/src/components/Popover.tsx +55 -0
  24. package/packages/intable/src/components/RecycleList.tsx +99 -0
  25. package/packages/intable/src/components/Render.tsx +26 -0
  26. package/packages/intable/src/components/Split.tsx +56 -0
  27. package/packages/intable/src/components/Tree.tsx +115 -0
  28. package/packages/intable/src/components/utils.tsx +12 -0
  29. package/packages/intable/src/hooks/index.ts +200 -0
  30. package/packages/intable/src/hooks/useDir.ts +78 -0
  31. package/packages/intable/src/hooks/useSelector.ts +91 -0
  32. package/packages/intable/src/hooks/useSort.tsx +118 -0
  33. package/packages/intable/src/hooks/useVirtualizer.ts +180 -0
  34. package/packages/intable/src/index.tsx +481 -0
  35. package/packages/intable/src/plugins/CellChangeHighlightPlugin.tsx +5 -0
  36. package/packages/intable/src/plugins/CellMergePlugin.tsx +153 -0
  37. package/packages/intable/src/plugins/CellSelectionPlugin.tsx +175 -0
  38. package/packages/intable/src/plugins/CommandPlugin.tsx +74 -0
  39. package/packages/intable/src/plugins/CopyPastePlugin.tsx +63 -0
  40. package/packages/intable/src/plugins/DiffPlugin.tsx +107 -0
  41. package/packages/intable/src/plugins/DragPlugin.tsx +81 -0
  42. package/packages/intable/src/plugins/EditablePlugin.tsx +252 -0
  43. package/packages/intable/src/plugins/ExpandPlugin.tsx +80 -0
  44. package/packages/intable/src/plugins/HeaderGroup.tsx +289 -0
  45. package/packages/intable/src/plugins/HistoryPlugin.tsx +49 -0
  46. package/packages/intable/src/plugins/MenuPlugin.tsx +195 -0
  47. package/packages/intable/src/plugins/RenderPlugin/components.tsx +51 -0
  48. package/packages/intable/src/plugins/RenderPlugin/index.tsx +81 -0
  49. package/packages/intable/src/plugins/ResizePlugin.tsx +122 -0
  50. package/packages/intable/src/plugins/RowGroupPlugin.tsx +122 -0
  51. package/packages/intable/src/plugins/RowSelectionPlugin.tsx +65 -0
  52. package/packages/intable/src/plugins/TreePlugin.tsx +212 -0
  53. package/packages/intable/src/plugins/VirtualScrollPlugin.tsx +190 -0
  54. package/packages/intable/src/plugins/ZodValidatorPlugin.tsx +61 -0
  55. package/packages/intable/src/style.scss +244 -0
  56. package/{dist → packages/intable/src}/theme/antd.scss +14 -5
  57. package/{dist → packages/intable/src}/theme/element-plus.scss +6 -5
  58. package/packages/intable/src/tree.ts +13 -0
  59. package/packages/intable/src/types/auto-imports.d.ts +13 -0
  60. package/packages/intable/src/utils.ts +122 -0
  61. package/packages/intable/src/wc.tsx +35 -0
  62. package/packages/intable/src/web-component.ts +1 -0
  63. package/packages/react/package.json +31 -0
  64. package/packages/react/src/index.ts +44 -0
  65. package/packages/react/src/plugins/antd.ts +94 -0
  66. package/packages/react/src/style.scss +12 -0
  67. package/packages/react/src/types/auto-imports.d.ts +10 -0
  68. package/packages/vue/package.json +34 -0
  69. package/packages/vue/src/index.ts +63 -0
  70. package/packages/vue/src/plugins/element-plus.ts +69 -0
  71. package/packages/vue/src/style.scss +12 -0
  72. package/packages/vue/src/types/auto-imports.d.ts +10 -0
  73. package/pnpm-workspace.yaml +2 -0
  74. package/public/vite.svg +1 -0
  75. package/scripts/build.js +184 -0
  76. package/scripts/publish.js +95 -0
  77. package/src/assets/ClearFormat.svg +3 -0
  78. package/src/assets/Forms.svg +4 -0
  79. package/src/assets/MergeCell.svg +4 -0
  80. package/src/assets/SplitCell.svg +4 -0
  81. package/src/assets/gap.svg +3 -0
  82. package/src/assets/loading.svg +12 -0
  83. package/src/assets/paint.svg +9 -0
  84. package/src/assets/solid.svg +1 -0
  85. package/src/demo-vue.ts +54 -0
  86. package/src/demo.tsx +107 -0
  87. package/src/index.scss +105 -0
  88. package/src/styles/index.scss +172 -0
  89. package/src/types/auto-imports.d.ts +13 -0
  90. package/stats.html +4949 -0
  91. package/tsconfig.app.json +34 -0
  92. package/tsconfig.json +7 -0
  93. package/tsconfig.node.json +26 -0
  94. package/vite.config.ts +63 -0
  95. package/dist/__uno.css +0 -1
  96. package/dist/chevron-right.js +0 -6
  97. package/dist/components/Columns.d.ts +0 -3
  98. package/dist/components/Columns.js +0 -71
  99. package/dist/components/DocTree.d.ts +0 -4
  100. package/dist/components/DocTree.js +0 -32
  101. package/dist/components/Menu.d.ts +0 -1
  102. package/dist/components/Menu.js +0 -107
  103. package/dist/components/Popover.d.ts +0 -14
  104. package/dist/components/Popover.js +0 -41
  105. package/dist/components/Render.d.ts +0 -4
  106. package/dist/components/Render.js +0 -20
  107. package/dist/components/Split.d.ts +0 -15
  108. package/dist/components/Split.js +0 -76
  109. package/dist/components/Tree.d.ts +0 -37
  110. package/dist/components/Tree.js +0 -82
  111. package/dist/components/utils.d.ts +0 -3
  112. package/dist/components/utils.js +0 -8
  113. package/dist/hooks/index.d.ts +0 -40
  114. package/dist/hooks/index.js +0 -157
  115. package/dist/hooks/useDir.d.ts +0 -11
  116. package/dist/hooks/useDir.js +0 -42
  117. package/dist/hooks/useSelector.d.ts +0 -16
  118. package/dist/hooks/useSelector.js +0 -35
  119. package/dist/hooks/useSort.d.ts +0 -18
  120. package/dist/hooks/useSort.js +0 -83
  121. package/dist/hooks/useVirtualizer.d.ts +0 -25
  122. package/dist/hooks/useVirtualizer.js +0 -67
  123. package/dist/index.d.ts +0 -130
  124. package/dist/index.js +0 -347
  125. package/dist/loading.js +0 -6
  126. package/dist/plugins/CellChangeHighlightPlugin.d.ts +0 -2
  127. package/dist/plugins/CellChangeHighlightPlugin.js +0 -4
  128. package/dist/plugins/CellMergePlugin.d.ts +0 -12
  129. package/dist/plugins/CellMergePlugin.js +0 -2
  130. package/dist/plugins/CellSelectionPlugin.d.ts +0 -15
  131. package/dist/plugins/CellSelectionPlugin.js +0 -115
  132. package/dist/plugins/CommandPlugin.d.ts +0 -14
  133. package/dist/plugins/CommandPlugin.js +0 -12
  134. package/dist/plugins/CopyPastePlugin.d.ts +0 -14
  135. package/dist/plugins/CopyPastePlugin.js +0 -42
  136. package/dist/plugins/DiffPlugin.d.ts +0 -23
  137. package/dist/plugins/DiffPlugin.js +0 -56
  138. package/dist/plugins/DragPlugin.d.ts +0 -14
  139. package/dist/plugins/DragPlugin.js +0 -47
  140. package/dist/plugins/EditablePlugin.d.ts +0 -48
  141. package/dist/plugins/EditablePlugin.js +0 -141
  142. package/dist/plugins/ExpandPlugin.d.ts +0 -18
  143. package/dist/plugins/ExpandPlugin.js +0 -50
  144. package/dist/plugins/HistoryPlugin.d.ts +0 -10
  145. package/dist/plugins/HistoryPlugin.js +0 -30
  146. package/dist/plugins/MenuPlugin.d.ts +0 -18
  147. package/dist/plugins/MenuPlugin.js +0 -107
  148. package/dist/plugins/RenderPlugin/components.d.ts +0 -5
  149. package/dist/plugins/RenderPlugin/components.js +0 -87
  150. package/dist/plugins/RenderPlugin/index.d.ts +0 -30
  151. package/dist/plugins/RenderPlugin/index.js +0 -49
  152. package/dist/plugins/ResizePlugin.d.ts +0 -27
  153. package/dist/plugins/ResizePlugin.js +0 -81
  154. package/dist/plugins/RowGroupPlugin.d.ts +0 -17
  155. package/dist/plugins/RowGroupPlugin.js +0 -83
  156. package/dist/plugins/RowSelectionPlugin.d.ts +0 -20
  157. package/dist/plugins/RowSelectionPlugin.js +0 -42
  158. package/dist/plugins/VirtualScrollPlugin.d.ts +0 -15
  159. package/dist/plugins/VirtualScrollPlugin.js +0 -96
  160. package/dist/plus.js +0 -6
  161. package/dist/style.css +0 -3
  162. package/dist/types/auto-imports.d.js +0 -0
  163. package/dist/utils.d.ts +0 -30
  164. package/dist/utils.js +0 -70
  165. package/dist/wc.d.ts +0 -1
  166. package/dist/wc.js +0 -21
  167. package/dist/web-component.d.ts +0 -1
  168. package/dist/web-component.js +0 -2
  169. 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,63 @@
1
+ import { createEffect } from 'solid-js'
2
+ import { Ctx, 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 == 0) 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
+ const cols = store.props!.columns!.slice(x1, x2 + 1)
44
+ const data = store.props!.data!.slice(y1, y2 + 1).map(row => cols.map(col => row[col.id]))
45
+ const text = data.map(row => row.join('\t')).join('\n')
46
+ navigator.clipboard.writeText(text)
47
+ },
48
+ paste: async () => {
49
+ const { start, end } = store.selected
50
+ if (start.length == 0) return
51
+ const text = await navigator.clipboard.readText()
52
+ const arr2 = text.split('\n').map(row => row.split('\t'))
53
+ const cols = store.props!.columns!.slice(start[0], start[0] + arr2[0].length)
54
+ const data = store.props!.data!.slice()
55
+ arr2.forEach((row, y) => {
56
+ row = Object.fromEntries(cols.map((col, x) => [col.id, row[x]]))
57
+ data[start[1] + y] = { ...data![start[1] + y], ...row }
58
+ })
59
+ store.selected.end = [start[0] + cols.length - 1, Math.min(start[1] + arr2.length - 1, store.props.data!.length - 1)]
60
+ store.props!.onDataChange?.(data)
61
+ },
62
+ })
63
+ }
@@ -0,0 +1,107 @@
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 true */ added?: boolean
14
+ /** @default true */ removed?: boolean
15
+ /** @default true */ changed?: boolean
16
+ onCommit?: (data: any, opt: { added: any[], removed: any[], changed: any[] }) => any
17
+ }
18
+ }
19
+ interface TableStore {
20
+ diffData: any[]
21
+ diffDataKeyed: () => any
22
+ }
23
+ interface Commands {
24
+ diffCommit(data?: any[]): void
25
+ }
26
+ }
27
+
28
+ const DEL = Symbol('del')
29
+ const NEW = Symbol('new')
30
+
31
+ export const DiffPlugin: Plugin = {
32
+ priority: Infinity,
33
+ store: store => {
34
+ const data = store.rawProps.data || []
35
+ data.forEach(row => unwrap(row)[store.rawProps.rowKey] ??= uuid())
36
+ return {
37
+ diffData: structuredClone(unwrap(data || [])),
38
+ diffDataKeyed: createLazyMemo(() => keyBy(store.diffData, e => e[store.props!.rowKey]))
39
+ }
40
+ },
41
+ commands: store => ({
42
+ async diffCommit(data = store.rawProps.data || []) {
43
+ const { rowKey } = store.props || {}
44
+ data.forEach(row => unwrap(row)[rowKey] ??= uuid())
45
+ data = structuredClone(unwrap(data))
46
+ const added = [], removed = [], changed = []
47
+ const keyed = keyBy(data, e => e[rowKey])
48
+ for (const e of data) {
49
+ const old = store.diffDataKeyed()[e[rowKey]]
50
+ if (!old) added.push(e)
51
+ else if (!isEqual(e, old)) changed.push(e)
52
+ }
53
+ for (const e of store.diffData) {
54
+ !keyed[e[rowKey]] && removed.push(e)
55
+ }
56
+ await store.props!.diff?.onCommit?.(data, { added, removed, changed })
57
+ added[NEW] = 0
58
+ store.diffData = data
59
+ }
60
+ }),
61
+ rewriteProps: {
62
+ diff: ({ diff }) => ({
63
+ added: true,
64
+ removed: true,
65
+ changed: true,
66
+ ...diff
67
+ }),
68
+ data: ({ data }, { store }) => {
69
+ const { rowKey, diff } = store.props || {}
70
+ const diffData = store.diffData || []
71
+
72
+ // Fast path: same number of rows, same keys in same order (edit-only, no add/delete/move).
73
+ // Skips the O(n²) diffArrays call which is the common case when only cell values changae.
74
+ if (data.length === diffData.length && data.length > 0) {
75
+ let sameOrder = true
76
+ for (let i = 0; i < data.length; i++) {
77
+ if (data[i]?.[rowKey] !== diffData[i]?.[rowKey]) { sameOrder = false; break }
78
+ }
79
+ if (sameOrder) return data
80
+ }
81
+
82
+ // Structural change (add / delete / move) — fall back to diff library
83
+ const diffArr = diffArrays(diffData, data, { comparator: (a, b) => a[rowKey] == b[rowKey] })
84
+ return diffArr.flatMap(e => (
85
+ // e.added ? e.value.map(e => ({ ...e, [NEW]: 1 })) :
86
+ e.added ? e.value.map(e => (e[NEW] = 1, e)) :
87
+ e.removed ? diff!.removed ? e.value.map(e => ({ ...e, [DEL]: 1, [store.internal]: 1 })) : [] :
88
+ e.value
89
+ ))
90
+ },
91
+ tdProps: ({ tdProps }, { store }) => o => combineProps(tdProps?.(o) || {}, {
92
+ get class() {
93
+ const { diff } = store.props || {}
94
+ const id = unwrap(o.data)[store.props!.rowKey]
95
+ return [
96
+ o.data[NEW] ? diff?.added ? 'bg-#dafaea!' : '' :
97
+ o.data[DEL] ? 'bg-#ffe8e8!' :
98
+ o.data[store.internal] ? '' :
99
+ diff!.changed && o.data[o.col.id] != store.diffDataKeyed()[id][o.col.id] ? 'bg-#dafaea!' : ''
100
+ ].join(' ')
101
+ }
102
+ }),
103
+ },
104
+ keybindings: (store) => ({
105
+ '$mod+S': () => store.commands.diffCommit(),
106
+ }),
107
+ }
@@ -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
+ }