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,489 @@
1
+ import { createContext, createMemo, createSignal, For, useContext, createEffect, type JSX, type Component, createComputed, onMount, mergeProps, mapArray, onCleanup, getOwner, runWithOwner, on, untrack, batch, Index, $PROXY } from 'solid-js'
2
+ import { createMutable, reconcile } from 'solid-js/store'
3
+ import { combineProps } from '@solid-primitives/props'
4
+ import { createLazyMemo } from '@solid-primitives/memo'
5
+ import { createElementSize, createResizeObserver, makeResizeObserver } from '@solid-primitives/resize-observer'
6
+ import { createScrollPosition } from '@solid-primitives/scroll'
7
+ import { difference, mapValues, memoize, sumBy } from 'es-toolkit'
8
+ import { toReactive, useMemo } from './hooks'
9
+ import { log, unFn } from './utils'
10
+
11
+ import 'virtual:uno.css'
12
+ import './style.scss'
13
+
14
+ import { CellSelectionPlugin } from './plugins/CellSelectionPlugin'
15
+ import { ClipboardPlugin } from './plugins/CopyPastePlugin'
16
+ import { EditablePlugin } from './plugins/EditablePlugin'
17
+ import { RenderPlugin } from './plugins/RenderPlugin'
18
+ import { MenuPlugin } from './plugins/MenuPlugin'
19
+ import { CommandPlugin } from './plugins/CommandPlugin'
20
+ import { RowSelectionPlugin } from './plugins/RowSelectionPlugin'
21
+ import { ResizePlugin } from './plugins/ResizePlugin'
22
+ import { DragPlugin } from './plugins/DragPlugin'
23
+ import { solidComponent } from './components/utils'
24
+ import { RowGroupPlugin } from './plugins/RowGroupPlugin'
25
+ import { ExpandPlugin } from './plugins/ExpandPlugin'
26
+ import { CellMergePlugin } from './plugins/CellMergePlugin'
27
+ import { TreePlugin } from './plugins/TreePlugin'
28
+ import { HeaderGroupPlugin } from './plugins/HeaderGroup'
29
+
30
+ export const Ctx = createContext({
31
+ props: {} as TableProps2,
32
+ store: {} as TableStore,
33
+ })
34
+
35
+ type Requireds<T, K extends keyof T> = Pri<Omit<T, K> & Required<Pick<T, K>>>
36
+ type Pri<T> = { [K in keyof T]: T[K] }
37
+ type TableProps2 = Requireds<TableProps, (
38
+ 'Table' | 'Thead' | 'Tbody' | 'Tr' | 'Th' | 'Td' | 'EachRows' | 'EachCells' |
39
+ 'rowKey' | 'data' | 'columns' |
40
+ 'newRow'
41
+ )>
42
+
43
+ type Each<T = any> = (props: { each: T[]; children: (e: () => any, i: () => number) => JSX.Element }) => JSX.Element
44
+
45
+ type ProcessProps = {
46
+ [K in keyof TableProps]?: (prev: TableProps2, ctx: { store: TableStore }) => TableProps[K]
47
+ }
48
+
49
+ export interface Plugin {
50
+ name: string
51
+ priority?: number
52
+ store?: (store: TableStore) => Partial<TableStore> | void
53
+ rewriteProps?: ProcessProps
54
+ layers?: Component<TableStore>[]
55
+ onMount?: (store: TableStore) => void
56
+ /**
57
+ * Declare keyboard shortcuts for this plugin.
58
+ * Collected and registered as a **single** keydown listener by CommandPlugin.
59
+ * Keys use tinykeys syntax, e.g. `'$mod+Z'`, `'$mod+Shift+K'`.
60
+ */
61
+ keybindings?: (store: TableStore) => Record<string, (e?: KeyboardEvent) => void>
62
+ }
63
+
64
+ export type Plugin$0 = Plugin | ((store: TableStore) => Plugin)
65
+
66
+ export interface TableProps {
67
+ columns?: TableColumn[]
68
+ data?: any[]
69
+ index?: boolean
70
+ border?: boolean
71
+ stickyHeader?: boolean
72
+ class?: any
73
+ style?: any
74
+ rowKey?: any
75
+ size?: string
76
+ newRow?: (i: number) => any
77
+ // Component
78
+ Table?: Component<any>
79
+ Thead?: Component<any>
80
+ Tbody?: Component<any>
81
+ Td?: TD
82
+ Th?: Component<THProps>
83
+ Tr?: Component<{ y?: number; data?: any; style?: any; children: JSX.Element }>
84
+ EachRows?: Each
85
+ EachCells?: Each<TableColumn>
86
+ //
87
+ cellClass?: ((props: Omit<TDProps, 'y' | 'data'> & { y?:number, data? }) => string) | string
88
+ cellStyle?: ((props: Omit<TDProps, 'y' | 'data'> & { y?:number, data? }) => string) | string
89
+ //
90
+ renderer?: (comp: (props) => JSX.Element) => ((props) => JSX.Element)
91
+ // Plugin
92
+ plugins?: Plugin$0[]
93
+ /**
94
+ * Override or disable individual plugin keybindings.
95
+ * - Override: `{ '$mod+Z': (e) => myUndo() }`
96
+ * - Disable: `{ '$mod+Z': false }`
97
+ */
98
+ keybindings?: Record<string, ((e?: KeyboardEvent) => void) | false>
99
+
100
+ onDataChange?: (data: any[]) => void
101
+ }
102
+
103
+ export type THProps = { x: number; col: TableColumn; children: JSX.Element; rowspan?: number; colspan?: number; style?: any }
104
+ export type TDProps = { x: number; y: number; data: any; col: TableColumn; children: JSX.Element; rowspan?: number; colspan?: number }
105
+ export type TD = Component<TDProps>
106
+
107
+ type Obj = Record<string | symbol, any>
108
+
109
+ export interface TableColumn extends Obj {
110
+ id?: any
111
+ name?: string
112
+ width?: number
113
+ fixed?: 'left' | 'right'
114
+ class?: any
115
+ style?: any
116
+ props?: (props) => JSX.HTMLAttributes<any>
117
+ }
118
+
119
+ type Nullable<T> = T | undefined
120
+
121
+ export interface TableStore extends Obj {
122
+ scroll_el?: HTMLElement
123
+ table: HTMLElement
124
+ thead: HTMLElement
125
+ tbody: HTMLElement
126
+ ths: Nullable<Element>[]
127
+ thSizes: Nullable<{ width: number; height: number }>[]
128
+ trs: Nullable<Element>[]
129
+ trSizes: Nullable<{ width: number; height: number }>[]
130
+ internal: symbol
131
+ raw: symbol
132
+ props: TableProps2
133
+ rawProps: TableProps
134
+ plugins: Plugin[]
135
+ }
136
+
137
+ export const Intable = (props: TableProps) => {
138
+ props = mergeProps({ rowKey: 'id' } as Partial<TableProps>, props)
139
+ const owner = getOwner()!
140
+
141
+ const store = createMutable({
142
+ get rawProps() { return props },
143
+ get plugins() { return plugins() }
144
+ } as TableStore)
145
+
146
+ const unplugin = memoize((e: Plugin$0) => runWithOwner(owner, () => unFn(e, store)) as Plugin)
147
+
148
+ const plugins = createMemo(() => [
149
+ ...defaultsPlugins,
150
+ ...props.plugins || [],
151
+ RenderPlugin
152
+ ].map(unplugin).sort((a, b) => (b.priority || 0) - (a.priority || 0)))
153
+
154
+ // init store
155
+ createComputed((old: Plugin[]) => {
156
+ const added = difference(plugins(), old)
157
+ runWithOwner(owner, () => {
158
+ added.forEach(e => Object.assign(store, e.store?.(store)))
159
+ })
160
+ return plugins()
161
+ }, [])
162
+
163
+ // init processProps
164
+ const pluginsProps = mapArray(plugins, () => createSignal<Partial<TableProps>>())
165
+ store.props = toReactive(createMemo(() => pluginsProps()[pluginsProps().length - 1][0]() || props)) as TableProps2
166
+ // store.props = useMemoState(createMemo(() => pluginsProps()[pluginsProps().length - 1][0]() || props)) as TableProps2
167
+
168
+ createComputed(mapArray(plugins, (e, i) => {
169
+ const prev = createMemo(() => pluginsProps()[i() - 1]?.[0]() || props)
170
+ const ret = mergeProps(prev, toReactive(mapValues(e.rewriteProps || {}, v => useMemo(() => v(prev(), { store })) )))
171
+ pluginsProps()[i()][1](ret)
172
+ }))
173
+
174
+ // on mount
175
+ onMount(() => {
176
+ createEffect(mapArray(plugins, e => e.onMount?.(store)))
177
+ })
178
+
179
+ const ctx = createMutable({ props: store.props, store })
180
+
181
+ window.store = store
182
+ window.ctx = ctx
183
+
184
+ return (
185
+ <Ctx.Provider value={ctx}>
186
+ <ctx.props.Table>
187
+ <THead />
188
+ <TBody />
189
+ </ctx.props.Table>
190
+ </Ctx.Provider>
191
+ )
192
+ }
193
+
194
+ const THead = () => {
195
+ const { props } = useContext(Ctx)
196
+ return (
197
+ <props.Thead>
198
+ <props.Tr>
199
+ <props.EachCells each={props.columns || []}>
200
+ {(col, colIndex) => <props.Th col={col()} x={colIndex()}>{col().name}</props.Th>}
201
+ </props.EachCells>
202
+ </props.Tr>
203
+ </props.Thead>
204
+ )
205
+ }
206
+
207
+ const TBody = () => {
208
+ const { props } = useContext(Ctx)
209
+ return (
210
+ <props.Tbody>
211
+ <props.EachRows each={props.data}>{(row, rowIndex) => (
212
+ <props.Tr y={rowIndex()} data={row()}>
213
+ <props.EachCells each={props.columns}>{(col, colIndex) => (
214
+ <props.Td col={col()} x={colIndex()} y={rowIndex()} data={row()}>
215
+ {row()[col().id]}
216
+ </props.Td>
217
+ )}</props.EachCells>
218
+ </props.Tr>
219
+ )}</props.EachRows>
220
+ </props.Tbody>
221
+ )
222
+ }
223
+
224
+ export default Intable
225
+
226
+ // process ===================================================================================================================================================================================================
227
+
228
+ function BasePlugin(): Plugin$0 {
229
+ const omits = { col: null, data: null }
230
+
231
+ const table = o => <table {...o} /> as any
232
+ const thead = o => <thead {...o} /> as any
233
+ const tbody = o => <tbody {...o} /> as any
234
+ const tr = o => <tr {...o} {...omits} /> as any
235
+ const th = o => <th {...o} {...omits} /> as any
236
+ const td = o => <td {...o} {...omits} /> as any
237
+
238
+ return {
239
+ name: 'base',
240
+ priority: Infinity,
241
+ store: (store) => {
242
+ // 共享一个 ResizeObserver 观察所有 th,回调按 index 分发,替代每列独立 createElementSize
243
+ const thObs = makeResizeObserver((es) => {
244
+ for (const e of es) {
245
+ const el = e.target
246
+ const { inlineSize: width, blockSize: height } = e.borderBoxSize[0]
247
+ const h = store.ths.indexOf(el)
248
+ if (h >= 0 && el.parentElement) store.thSizes[h] = { width, height }
249
+ }
250
+ })
251
+ // 共享一个 ResizeObserver 观察所有 tr,回调按 index 分发,替代每行独立 createElementSize
252
+ const trObs = makeResizeObserver((es) => {
253
+ for (const e of es) {
254
+ const el = e.target
255
+ const { inlineSize: width, blockSize: height } = e.borderBoxSize[0]
256
+ const y = store.trs.indexOf(el)
257
+ if (y >= 0 && el.parentElement) store.trSizes[y] = { width, height }
258
+ }
259
+ })
260
+ return {
261
+ thObs,
262
+ trObs,
263
+ ths: [],
264
+ thSizes: [],
265
+ trs: [],
266
+ trSizes: [],
267
+ internal: Symbol('internal'),
268
+ raw: Symbol('raw'),
269
+ }
270
+ },
271
+ rewriteProps: {
272
+ data: ({ data = [] }) => data,
273
+ columns: ({ columns = [] }) => columns,
274
+ newRow: ({ newRow = () => ({}) }) => newRow,
275
+ Table: ({ Table = table }, { store }) => o => {
276
+ const [el, setEl] = createSignal<HTMLElement>()
277
+ const { props } = useContext(Ctx)
278
+ o = combineProps({
279
+ ref: setEl,
280
+ get class() { return `data-table ${props.class} ${props.border && 'data-table--border'} data-table--${props.size}` },
281
+ get style() { return props.style }
282
+ }, o)
283
+ return <Table {...o} />
284
+ },
285
+ Thead: ({ Thead = thead }, { store }) => o => {
286
+ o = combineProps({ ref: el => store.thead = el }, o)
287
+ return <Thead {...o} />
288
+ },
289
+ Tbody: ({ Tbody = tbody }, { store }) => o => {
290
+ o = combineProps({ ref: el => store.tbody = el }, o)
291
+ return <Tbody {...o} />
292
+ },
293
+ Tr: ({ Tr = tr }, { store }) => o => {
294
+ const [el, setEl] = createSignal<HTMLElement>()
295
+ o = combineProps({ ref: setEl }, o)
296
+
297
+ createEffect(() => {
298
+ const { y } = o
299
+ if (y == null) return
300
+ store.trs[y] = el()
301
+ store.trObs.observe(el())
302
+ onCleanup(() => {
303
+ store.trSizes[y] = store.trs[y] = void 0
304
+ store.trObs.unobserve(el())
305
+ })
306
+ })
307
+
308
+ return <Tr {...o} />
309
+ },
310
+ Th: ({ Th = th }, { store }) => o => {
311
+ const [el, setEl] = createSignal<HTMLElement>()
312
+
313
+ const { props } = useContext(Ctx)
314
+ const mProps = combineProps(
315
+ o,
316
+ { ref: setEl },
317
+ { get class() { return unFn(props.cellClass, o) }, get style() { return unFn(props.cellStyle, o) } },
318
+ { get class() { return o.col.class }, get style() { return o.col.style } },
319
+ { get style() { return o.col.width ? `width: ${o.col.width}px` : '' } },
320
+ )
321
+
322
+ createEffect(() => {
323
+ if (o.covered) return
324
+ if ((o.colspan ?? 1) != 1) return
325
+ const { x } = o
326
+ store.ths[x] = el()
327
+ store.thObs.observe(el())
328
+ onCleanup(() => {
329
+ store.thSizes[x] = store.ths[x] = void 0
330
+ store.thObs.unobserve(el())
331
+ })
332
+ })
333
+
334
+ return <Th {...mProps}>{o.children}</Th>
335
+ },
336
+ Td: ({ Td = td }, { store }) => o => {
337
+ const { props } = useContext(Ctx)
338
+ const mProps = combineProps(
339
+ o,
340
+ { get class() { return unFn(props.cellClass, o) }, get style() { return unFn(props.cellStyle, o) } },
341
+ { get class() { return o.col.class }, get style() { return o.col.style } },
342
+ { get style() { return o.col.width ? `width: ${o.col.width}px` : '' } },
343
+ )
344
+ return <Td {...mProps}>{o.children}</Td>
345
+ },
346
+ EachRows: ({ EachRows }) => EachRows || (o => <For each={o.each}>{(e, i) => o.children(() => e, i)}</For>),
347
+ EachCells: ({ EachCells }) => EachCells || (o => <For each={o.each}>{(e, i) => o.children(() => e, i)}</For>),
348
+ // EachRows: ({ EachRows }) => EachRows || (o => <Index each={o.each}>{(e, i) => o.children(e, () => i)}</Index>),
349
+ // EachCells: ({ EachCells }) => EachCells || (o => <Index each={o.each}>{(e, i) => o.children(e, () => i)}</Index>),
350
+ renderer: ({ renderer = a => a }) => renderer
351
+ }
352
+ }
353
+ }
354
+
355
+ const IndexPlugin: Plugin = {
356
+ name: 'index',
357
+ priority: -Infinity,
358
+ store: (store) => ({
359
+ $index: { name: '', id: Symbol('index'), fixed: 'left', [store.internal]: 1, width: 40, style: 'text-align: center', class: 'index', render: solidComponent((o) => <>{o.y + 1}</>) } as TableColumn
360
+ }),
361
+ rewriteProps: {
362
+ columns: ({ columns }, { store }) => store.props?.index ? [store.$index, ...columns || []] : columns
363
+ }
364
+ }
365
+
366
+ const StickyHeaderPlugin: Plugin = {
367
+ name: 'sticky-header',
368
+ rewriteProps: {
369
+ Thead: ({ Thead }) => o => {
370
+ const { props } = useContext(Ctx)
371
+ o = combineProps({ get class() { return props.stickyHeader ? 'sticky-header' : '' } }, o)
372
+ return <Thead {...o} />
373
+ },
374
+ }
375
+ }
376
+
377
+ const FixedColumnPlugin: Plugin$0 = store => {
378
+ const fixedOffsets = createLazyMemo(() => {
379
+ const offsets = {}
380
+ for (const [i, col] of store.props.columns.entries()) {
381
+ if (col.fixed === 'left') offsets[i] = sumBy(store.thSizes.slice(0, i), s => s?.width || 0)
382
+ if (col.fixed === 'right') offsets[i] = sumBy(store.thSizes.slice(i + 1), s => s?.width || 0)
383
+ }
384
+ return offsets
385
+ })
386
+ const last = createLazyMemo(() => store.props.columns.filter(e => e.fixed == 'left').length - 1)
387
+ const first = createLazyMemo(() => store.props.columns.length - store.props.columns.filter(e => e.fixed == 'right').length)
388
+ return {
389
+ name: 'fixed-column',
390
+ rewriteProps: {
391
+ columns: ({ columns }) => [
392
+ ...columns?.filter(e => e.fixed == 'left') || [],
393
+ ...columns?.filter(e => !e.fixed) || [],
394
+ ...columns?.filter(e => e.fixed == 'right') || [],
395
+ ],
396
+ cellClass: ({ cellClass }) => o => (unFn(cellClass, o) || '') + (o.col.fixed ? ` fixed-${o.col.fixed} ${o.x == last() ? 'is-last' : ''} ${o.x == first() ? 'is-first' : ''}` : ''),
397
+ cellStyle: ({ cellStyle }) => o => (unFn(cellStyle, o) || '') + (o.col.fixed ? `; ${o.col.fixed}: ${fixedOffsets()[o.x]}px` : '')
398
+ }
399
+ }
400
+ }
401
+
402
+ const FitColWidthPlugin: Plugin$0 = store => {
403
+ const size = createMutable({ width: 0 })
404
+ createResizeObserver(() => store.scroll_el!, (_, el, e) => size.width = e.contentBoxSize[0].inlineSize)
405
+ const __fit_col_width__cols_temp = createMutable([] as any[])
406
+
407
+ let lock = false
408
+ createEffect(on(() => [size.width, store.props.columns.map(e => e.width)], async () => {
409
+ if (!size.width) return
410
+ if (lock) return
411
+ __fit_col_width__cols_temp.length = 0
412
+ lock = true
413
+ await Promise.resolve()
414
+ const gap = (size.width - store.table.getBoundingClientRect().width) / store.props!.columns.filter(e => !e.width).length
415
+ const cols = store.props!.columns.map((e, i) => (e.width ? null : { width: Math.max((store.ths[i]?.getBoundingClientRect().width || 0) + gap, 80) }))
416
+ __fit_col_width__cols_temp.push(...cols)
417
+ lock = false
418
+ }))
419
+ return {
420
+ name: 'fit-col-width',
421
+ priority: -Infinity,
422
+ rewriteProps: {
423
+ columns: ({ columns }, { store }) => (
424
+ columns = columns.map((e, i) => ({ ...e, ...__fit_col_width__cols_temp?.[i], [store.raw]: e[store.raw] ?? e })),
425
+ untrack(() => batch(() => reconcile(columns, { key: store.raw })(store.__fit_col_width__cols ??= [])))
426
+ )
427
+ }
428
+ }
429
+ }
430
+
431
+ export const ScrollPlugin: Plugin = {
432
+ name: 'scroll',
433
+ priority: Infinity,
434
+ rewriteProps: {
435
+ Table: (prev, { store }) => o => {
436
+ const pos = createScrollPosition(() => store.scroll_el)
437
+ const size = createElementSize(() => store.scroll_el)
438
+
439
+ const clazz = createMemo(() => {
440
+ const el = store.scroll_el
441
+ if (!el) return
442
+ const isleft = pos.x == 0
443
+ const isright = pos.x >= el.scrollWidth - (size.width || 0)
444
+ return (
445
+ isleft && isright ? '' :
446
+ !isleft && !isright ? 'is-scroll-mid' :
447
+ isleft ? 'is-scroll-left' :
448
+ isright ? 'is-scroll-right' :
449
+ ''
450
+ )
451
+ })
452
+
453
+ o = combineProps(o, { ref: el => store.scroll_el = el, class: 'data-table--scroll-view' }, { get class() { return clazz() } })
454
+
455
+ const layers = mapArray(() => store.plugins.flatMap(e => e.layers ?? []), Layer => <Layer {...store} />)
456
+
457
+ return (
458
+ <div tabindex={-1} {...o}>
459
+ <div class='data-table__layers'>
460
+ {layers()}
461
+ </div>
462
+ <table ref={el => store.table = el} class={`data-table--table`}>{o.children}</table>
463
+ </div>
464
+ )
465
+ }
466
+ }
467
+ }
468
+
469
+ export const defaultsPlugins = [
470
+ ScrollPlugin,
471
+ BasePlugin,
472
+ CommandPlugin,
473
+ MenuPlugin,
474
+ CellSelectionPlugin,
475
+ StickyHeaderPlugin,
476
+ HeaderGroupPlugin,
477
+ FixedColumnPlugin,
478
+ DragPlugin,
479
+ ClipboardPlugin,
480
+ ExpandPlugin,
481
+ RowSelectionPlugin,
482
+ IndexPlugin,
483
+ EditablePlugin,
484
+ CellMergePlugin,
485
+ TreePlugin,
486
+ FitColWidthPlugin,
487
+ RowGroupPlugin,
488
+ ResizePlugin,
489
+ ]
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from '..'
2
+
3
+ export function CellChangeHighlightPlugin(): Plugin {
4
+ return {}
5
+ }
@@ -0,0 +1,153 @@
1
+ import { createMemo, Show } from 'solid-js'
2
+ import { combineProps } from '@solid-primitives/props'
3
+ import { type Plugin, type TableColumn, type TableStore } from '../index'
4
+
5
+ declare module '../index' {
6
+ interface TableProps {
7
+ /**
8
+ * Per-cell merge config. Return `{ rowspan?, colspan? }` for an anchor cell.
9
+ * Covered cells are hidden automatically.
10
+ *
11
+ * @example
12
+ * merge={(row, col, y, x) => {
13
+ * if (col.id === 'name' && row.name === data[y - 1]?.name) return null
14
+ * if (col.id === 'name') {
15
+ * let rowspan = 1
16
+ * while (data[y + rowspan]?.name === row.name) rowspan++
17
+ * return rowspan > 1 ? { rowspan } : null
18
+ * }
19
+ * }}
20
+ */
21
+ merge?: (row: any, col: TableColumn, y: number, x: number) => { rowspan?: number; colspan?: number } | null | void
22
+ }
23
+ interface TableColumn {
24
+ /**
25
+ * Shorthand: auto-merge consecutive rows in this column that share the same cell value.
26
+ * Equivalent to writing a `merge` function that compares adjacent row values.
27
+ */
28
+ mergeRow?: boolean
29
+ }
30
+ interface TableStore {
31
+ /** Pre-computed merge result: anchor spans and covered cell keys. */
32
+ _mergeMap?: ReturnType<typeof createMemo<MergeMap>>
33
+ }
34
+ }
35
+
36
+ type CellKey = `${number},${number}`
37
+ interface MergeMap {
38
+ spans: Map<CellKey, { rowspan: number; colspan: number }>
39
+ covered: Set<CellKey>
40
+ }
41
+
42
+ /** Build a { spans, covered } map in O(rows × cols) from the merge function. */
43
+ function buildMergeMap(
44
+ data: any[],
45
+ columns: TableColumn[],
46
+ merge: TableStore['props']['merge'],
47
+ ): MergeMap {
48
+ const spans = new Map<CellKey, { rowspan: number; colspan: number }>()
49
+ const covered = new Set<CellKey>()
50
+
51
+ for (let y = 0; y < data.length; y++) {
52
+ for (let x = 0; x < columns.length; x++) {
53
+ const key: CellKey = `${y},${x}`
54
+ if (covered.has(key)) continue
55
+
56
+ const col = columns[x]
57
+ const row = data[y]
58
+
59
+ // Resolve span: explicit `merge` prop wins, then column.mergeRow shorthand
60
+ let rs = 1, cs = 1
61
+ if (merge) {
62
+ const r = merge(row, col, y, x)
63
+ if (r) { rs = r.rowspan ?? 1; cs = r.colspan ?? 1 }
64
+ } else if (col.mergeRow) {
65
+ // Count forward while consecutive rows have the equal value
66
+ while (y + rs < data.length && data[y + rs]?.[col.id] === row[col.id]) rs++
67
+ }
68
+
69
+ if (rs > 1 || cs > 1) {
70
+ spans.set(key, { rowspan: rs, colspan: cs })
71
+ for (let dy = 0; dy < rs; dy++) {
72
+ for (let dx = 0; dx < cs; dx++) {
73
+ if (dy === 0 && dx === 0) continue
74
+ covered.add(`${y + dy},${x + dx}`)
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ return { spans, covered }
82
+ }
83
+
84
+ export const CellMergePlugin: Plugin = {
85
+ name: 'cell-merge',
86
+ rewriteProps: {
87
+ /**
88
+ * Build the merge map once per data/columns change inside the Table wrapper
89
+ * so it's available to every Td without redundant recomputation.
90
+ */
91
+ Table: ({ Table }, { store }) => o => {
92
+ store._mergeMap ??= createMemo<MergeMap>(() => {
93
+ const { merge, data, columns } = store.props!
94
+ if ((!merge && !columns?.some(c => c.mergeRow)) || !data?.length || !columns?.length) {
95
+ return { spans: new Map(), covered: new Set() }
96
+ }
97
+ return buildMergeMap(data, columns, merge)
98
+ })
99
+ return <Table {...o} />
100
+ },
101
+
102
+ Td: ({ Td }, { store }) => o => {
103
+ const key = (): CellKey => `${o.y},${o.x}`
104
+ const isCovered = () => store._mergeMap?.().covered.has(key()) ?? false
105
+ const span = () => store._mergeMap?.().spans.get(key())
106
+
107
+ o = combineProps(o, {
108
+ get rowspan() { return span()?.rowspan },
109
+ get colspan() { return span()?.colspan },
110
+ })
111
+
112
+ return (
113
+ // Covered cells must not render any DOM node — HTML collapses the layout automatically
114
+ <Show when={!isCovered()}>
115
+ <Td {...o} />
116
+ </Show>
117
+ )
118
+ },
119
+
120
+ newRow: ({ newRow }, { store }) => function (i) {
121
+ const row = newRow(i)
122
+ const { data, columns, merge } = store.props!
123
+ if (!data?.length || !columns?.length) return row
124
+
125
+ columns.forEach((col, x) => {
126
+ // mergeRow shorthand: copy the value from the row at the insertion point
127
+ // so the new row automatically joins the same merge group.
128
+ if (col.mergeRow) {
129
+ const ref = data[i] ?? data[i - 1]
130
+ if (ref != null) row[col.id] ??= ref[col.id]
131
+ return
132
+ }
133
+
134
+ // Explicit merge function: if (i, x) is covered by a span, walk up to
135
+ // find the anchor row and inherit its cell value.
136
+ if (merge && store._mergeMap) {
137
+ const key: CellKey = `${i},${x}`
138
+ if (!store._mergeMap().covered.has(key) || !store._mergeMap().spans.has(key)) return
139
+ for (let ay = i; ay >= 0; ay--) {
140
+ const anchorKey: CellKey = `${ay},${x}`
141
+ const span = store._mergeMap().spans.get(anchorKey)
142
+ if (span && span.rowspan > 1) {
143
+ row[col.id] ??= data[ay]?.[col.id]
144
+ break
145
+ }
146
+ }
147
+ }
148
+ })
149
+
150
+ return row
151
+ }
152
+ },
153
+ }