intable 0.0.6 → 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 +16 -263
  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 -38
  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,78 @@
1
+ import { createEffect, mergeProps } from 'solid-js'
2
+ import { createEventListener } from '@solid-primitives/event-listener'
3
+ import { createPointerList } from '@solid-primitives/pointer'
4
+ import { access, type MaybeAccessor } from '@solid-primitives/utils'
5
+ import { createMutationObserver } from '@solid-primitives/mutation-observer'
6
+
7
+ interface UseDirOptions {
8
+ ref: MaybeAccessor<HTMLElement | undefined>,
9
+ list?: MaybeAccessor<HTMLElement | undefined>,
10
+ loop?: boolean
11
+ options?: EventListenerOptions
12
+ defaultFirst?: boolean
13
+ }
14
+
15
+ export function useDir(options: UseDirOptions) {
16
+ options = mergeProps({ loop: true }, options)
17
+ const list = () => access(options.list) ?? access(options.ref)
18
+ const ref = () => access(options.ref) ?? access(options.list)
19
+
20
+ createEffect(() => {
21
+ if (ref().tabIndex > -1) return
22
+ ref().tabIndex = 0
23
+ })
24
+
25
+ createEventListener(ref, 'keydown', e => {
26
+ if (!['ArrowDown', 'ArrowUp', 'Enter'].includes(e.key)) return
27
+
28
+ e.stopPropagation()
29
+ e.preventDefault()
30
+
31
+ let hover = list()?.querySelector('.hover')
32
+
33
+ if (e.key == 'Enter') {
34
+ hover?.click()
35
+ return
36
+ }
37
+
38
+ let index = (
39
+ e.key == 'ArrowDown' ? hover ? +hover?.getAttribute('data-index')! + 1 : 0 :
40
+ e.key == 'ArrowUp' ? hover ? +hover?.getAttribute('data-index')! - 1 : -1 :
41
+ 0
42
+ )
43
+
44
+ const el = list()?.querySelector(`[data-index='${index}']`)
45
+ if (!el) {
46
+ if (options.loop) {
47
+ hover?.classList.remove('hover')
48
+ index >= 0
49
+ ? list()?.querySelector(`[data-index='0']`)?.classList.add('hover')
50
+ : [...list()?.querySelectorAll(`[data-index]`)].at(-1)?.classList.add('hover')
51
+ }
52
+ } else {
53
+ // list()?.querySelector(`[data-index=0]`)!
54
+ hover?.classList.remove('hover')
55
+ el.classList.add('hover')
56
+ }
57
+ }, options.options)
58
+
59
+ createEffect(() => {
60
+ if (!options.defaultFirst) return
61
+ const el = list()
62
+ if (!el) return
63
+ const hover = () => {
64
+ if (el.querySelector('.hover')) return
65
+ el.querySelector(`[data-index]`)?.classList.add('hover')
66
+ }
67
+ createMutationObserver(el, { childList: true }, hover)
68
+ hover()
69
+ })
70
+
71
+ createEventListener(list, 'mouseover', () => {
72
+ list()?.querySelector('.hover')?.classList.remove('hover')
73
+ })
74
+ }
75
+
76
+ export function VDir(el: HTMLElement, options) {
77
+ useDir({ ...options(), list: el })
78
+ }
@@ -0,0 +1,91 @@
1
+ import { createSignal, createMemo, createSelector } from 'solid-js'
2
+ import { log, toArr } from '../utils'
3
+
4
+ interface UseSelectorOpt<T> {
5
+ value?: T
6
+ onChange?: (v: T) => void
7
+ multiple?: boolean
8
+ selectable?: (v) => boolean
9
+ }
10
+
11
+ class SingleSet implements Set<any> {
12
+ #value: any
13
+ constructor(value) { this.#value = Array.from(value || [])[0] }
14
+ add(value) { this.#value = value; return this }
15
+ clear() { this.#value = undefined }
16
+ delete(value) { if (this.#value === value) { this.#value = undefined; return true } return false }
17
+ forEach(callbackfn) { if (this.#value !== undefined) callbackfn.call(this, this.#value, this.#value, this) }
18
+ has(value) { return this.#value === value }
19
+ get size() { return this.#value !== undefined ? 1 : 0 }
20
+ entries() { return this.values() }
21
+ keys() { return this.values() }
22
+ values() { return this.#value !== undefined ? [[this.#value, this.#value]].entries() : [].entries() }
23
+ [Symbol.iterator]() { return this.values()[Symbol.iterator]() }
24
+ [Symbol.toStringTag] = 'SingleSet'
25
+ }
26
+
27
+ export function useSelector<T = any>(opt: UseSelectorOpt<T>) {
28
+ const { value: initialValue, onChange, multiple = false, selectable } = opt
29
+
30
+ const Set2 = (multiple ? Set : SingleSet) as SetConstructor
31
+
32
+ const [selected, setSelected] = createSignal(new Set2(toArr(initialValue)))
33
+
34
+ // 检查是否包含某个值
35
+ const has = createSelector<Set<any>, any>(selected, (a, b) => b.has(a as T))
36
+
37
+ // 检查值是否可选择
38
+ const isSelectable = (v: T): boolean => {
39
+ return selectable ? selectable(v) : true
40
+ }
41
+
42
+ // 清空选择
43
+ const clear = () => {
44
+ setSelected(new Set2())
45
+ onChange?.(value())
46
+ }
47
+
48
+ // 设置选择
49
+ const set = (v: T) => {
50
+ if (!isSelectable(v)) return
51
+ setSelected(new Set2(toArr(v)))
52
+ onChange?.(value())
53
+ }
54
+
55
+ // 添加选择
56
+ const add = (v: T) => {
57
+ if (!isSelectable(v)) return
58
+ const newSet = new Set2(selected())
59
+ newSet.add(v)
60
+ setSelected(newSet)
61
+ onChange?.(value())
62
+ }
63
+
64
+ // 删除选择
65
+ const del = (v: T) => {
66
+ const newSet = new Set2(selected())
67
+ newSet.delete(v)
68
+ setSelected(newSet)
69
+ onChange?.(value())
70
+ }
71
+
72
+ // 切换选择状态
73
+ const toggle = (v: T) => {
74
+ has(v) ? del(v) : add(v)
75
+ }
76
+
77
+ // 使用 createMemo 优化 selected 的计算
78
+ const value = createMemo(() => {
79
+ return (multiple ? Array.from(selected()) : Array.from(selected())[0]) as T
80
+ })
81
+
82
+ return {
83
+ clear,
84
+ set,
85
+ has,
86
+ add,
87
+ del,
88
+ toggle,
89
+ get value() { return value() }
90
+ }
91
+ }
@@ -0,0 +1,118 @@
1
+ import { createMutable, reconcile } from 'solid-js/store'
2
+ import { mergeProps, Portal } from 'solid-js/web'
3
+
4
+ import { createEventListener, createEventListenerMap } from '@solid-primitives/event-listener'
5
+ import { access, type MaybeAccessor } from '@solid-primitives/utils'
6
+ import { findAsync, log } from '../utils'
7
+
8
+ type Awaitable<T> = T | Promise<T>;
9
+
10
+ interface UseSortOption {
11
+ enable?: boolean
12
+ guideLine: any
13
+ draggable: (el: HTMLElement) => Awaitable<boolean>
14
+ dragover: (el: HTMLElement) => boolean
15
+ children: (el: HTMLElement) => HTMLElement[]
16
+ dragend: () => void
17
+ }
18
+
19
+ export const useSort = (el: MaybeAccessor<HTMLElement | undefined>, opt: UseSortOption) => {
20
+ opt = mergeProps({ enable: true } as UseSortOption, opt)
21
+ let count = 0
22
+
23
+ createEventListenerMap(() => opt.enable ? access(el) : void 0, {
24
+ async pointerdown(e) {
25
+ const _c = count
26
+ const drag = await findAsync(e.composedPath(), e => e instanceof HTMLElement && opt.draggable(e))
27
+ if (_c != count) return
28
+ if (!drag) return
29
+ e.stopPropagation()
30
+ state.drag = drag
31
+ state.drag?.setAttribute('draggable', 'true')
32
+ },
33
+ pointerup() {
34
+ count++
35
+ dragend()
36
+ },
37
+ pointermove() {
38
+ count++
39
+ },
40
+ dragstart(e) {
41
+ e.dataTransfer!.setDragImage(document.createElement('img'), 0, 0)
42
+ },
43
+ dragover(e) {
44
+ if (!state.drag) return
45
+ const aa = e.composedPath().filter(e => e instanceof HTMLElement)
46
+ const over = state.over = aa.find(e => opt.dragover(e)) ?? state.over
47
+ if (!over) return
48
+ e.preventDefault()
49
+ e.stopPropagation()
50
+ //
51
+ const children = opt.children(over) as HTMLElement[]
52
+ if (children) {
53
+ let i = 0, d = Infinity, s = ''
54
+ children.forEach((el, ii) => {
55
+ const display = getComputedStyle(el).display
56
+ const __ = ['table-cell', 'inline'].some(e => display.includes(e))
57
+ const rect = el.getBoundingClientRect()
58
+ if (__) {
59
+ let dd = Math.sqrt((e.clientX - rect.x) ** 2 + (e.clientY - rect.y + rect.height / 2) ** 2)
60
+ if (dd < d) (i = ii, d = dd, s = 'l')
61
+ dd = Math.sqrt((e.clientX - rect.right) ** 2 + (e.clientY - rect.y + rect.height / 2) ** 2)
62
+ if (dd < d) (i = ii, d = dd, s = 'r')
63
+ } else {
64
+ let dd = Math.sqrt((e.clientY - rect.y) ** 2 + (e.clientX - rect.x + rect.width / 2) ** 2)
65
+ if (dd < d) (i = ii, d = dd, s = 't')
66
+ dd = Math.sqrt((e.clientY - rect.bottom) ** 2 + (e.clientX - rect.x + rect.width / 2) ** 2)
67
+ if (dd < d) (i = ii, d = dd, s = 'b')
68
+ }
69
+ })
70
+
71
+ const rect1 = children[i].getBoundingClientRect()
72
+ const x = s == 'l' || s == 'r', y = s == 't' || s == 'b'
73
+ const size = 3
74
+ state.style = {
75
+ width: `${y ? rect1.width : size}px`,
76
+ height: `${y ? size : rect1.height}px`,
77
+ translate: `${y ? rect1.x : (s == 'l' ? rect1.x : rect1.right) - size / 2}px ${x ? rect1.y : (s == 't' ? rect1.y : rect1.bottom) - size / 2}px`
78
+ }
79
+ state.rel = children[i]
80
+ state.type = s == 'l' || s == 't' ? 'before' : 'after'
81
+ } else {
82
+ const rect1 = over.getBoundingClientRect()
83
+ state.style = {
84
+ width: rect1.width,
85
+ height: rect1.height,
86
+ translate: `${rect1.x}px ${rect1.y}px`
87
+ }
88
+ state.rel = over
89
+ state.type = 'inner'
90
+ }
91
+ },
92
+ dragend() {
93
+ dragend()
94
+ },
95
+ })
96
+
97
+ function dragend() {
98
+ state.drag?.removeAttribute('draggable')
99
+ if (state.drag && state.rel) opt.dragend?.()
100
+ reconcile({})(state)
101
+ }
102
+
103
+ const state = createMutable({
104
+ style: void 0 as any,
105
+ drag: void 0 as any,
106
+ over: void 0 as any,
107
+ rel: void 0 as any,
108
+ type: ''
109
+ })
110
+
111
+ ;(<>{state.style &&
112
+ <Portal mount={access(el)}>
113
+ <div {...opt.guideLine} style={state.style} />
114
+ </Portal>
115
+ }</>)
116
+
117
+ return state
118
+ }
@@ -0,0 +1,180 @@
1
+ import { createElementSize } from '@solid-primitives/resize-observer'
2
+ import { createScrollPosition } from '@solid-primitives/scroll'
3
+ import { keyBy, uniqBy } from 'es-toolkit'
4
+ import { createComputed, createMemo, createSignal, mergeProps, untrack } from 'solid-js'
5
+
6
+ /**
7
+ * Fenwick Tree (Binary Indexed Tree)
8
+ * O(log n) point-update and prefix-sum query.
9
+ * Used to compute virtual item positions without rebuilding the full items array.
10
+ */
11
+ class FenwickTree {
12
+ n: number
13
+ tree: Float64Array
14
+ constructor(n: number) {
15
+ this.n = n
16
+ this.tree = new Float64Array(n + 1)
17
+ }
18
+ /** O(log n) add delta to index i (0-based) */
19
+ add(i: number, delta: number) {
20
+ for (i += 1; i <= this.n; i += i & -i) this.tree[i] += delta
21
+ }
22
+ /** O(log n) prefix sum [0..i] inclusive (0-based) */
23
+ sum(i: number): number {
24
+ let s = 0
25
+ for (i += 1; i > 0; i -= i & -i) s += this.tree[i]
26
+ return s
27
+ }
28
+ /** Total sum of all elements */
29
+ total(): number {
30
+ return this.n > 0 ? this.sum(this.n - 1) : 0
31
+ }
32
+ /**
33
+ * O(log n) find the 0-based index of the element "containing" offset.
34
+ * Returns smallest i where prefix_sum(0..i) > target.
35
+ */
36
+ findByOffset(target: number): number {
37
+ let i = 0
38
+ for (let pw = 1 << Math.floor(Math.log2(this.n || 1)); pw > 0; pw >>= 1) {
39
+ if (i + pw <= this.n && this.tree[i + pw] <= target) {
40
+ i += pw
41
+ target -= this.tree[i]
42
+ }
43
+ }
44
+ return Math.min(i, this.n - 1)
45
+ }
46
+ /** O(n) build from a sizes array (more efficient than n individual adds) */
47
+ static build(values: number[]): FenwickTree {
48
+ const n = values.length
49
+ const ft = new FenwickTree(n)
50
+ for (let i = 0; i < n; i++) {
51
+ ft.tree[i + 1] += values[i]
52
+ const j = i + 1 + ((i + 1) & -(i + 1))
53
+ if (j <= n) ft.tree[j] += ft.tree[i + 1]
54
+ }
55
+ return ft
56
+ }
57
+ }
58
+
59
+ interface VirtualizerOptions {
60
+ enable?: boolean
61
+ overscan?: number
62
+ batch?: number
63
+ getScrollElement: () => Element
64
+ horizontal?: boolean
65
+ count: number
66
+ estimateSize: (i: number) => number
67
+ extras?: (startIdx: number, endIdx: number) => number[]
68
+ indexAttribute?: string
69
+ rangeExtractor?: (range: any) => number[]
70
+ }
71
+
72
+ export function useVirtualizer(opt: VirtualizerOptions) {
73
+ opt = mergeProps({ overscan: 0, batch: 0, enable: true }, opt)
74
+ const scrollSize = createElementSize(opt.getScrollElement)
75
+ const pos = createScrollPosition(opt.getScrollElement)
76
+ const scrollPos = createMemo(() => opt.horizontal ? pos.x : pos.y)
77
+ const viewSize = createMemo(() => opt.horizontal ? scrollSize.width : scrollSize.height)
78
+
79
+ // Plain (non-reactive) sizes array + Fenwick tree.
80
+ // Avoids O(n) full-rebuild on every resizeItem call.
81
+ const sizes: number[] = []
82
+ let tree = new FenwickTree(0)
83
+
84
+ // Version signal: bumped by resizeItem; triggers start/end/items recompute.
85
+ const [v, bumpV] = createSignal(undefined, { equals: false })
86
+
87
+ // Grow when count increases; shrink when it decreases.
88
+ createComputed(() => {
89
+ const { count } = opt
90
+ untrack(() => {
91
+ if (count === sizes.length) return
92
+ if (count > sizes.length) {
93
+ for (let i = sizes.length; i < count; i++) sizes.push(opt.estimateSize(i))
94
+ } else {
95
+ sizes.length = count
96
+ }
97
+ // O(n) rebuild of Fenwick tree preserving measured sizes
98
+ tree = FenwickTree.build(sizes.slice(0, count))
99
+ bumpV()
100
+ })
101
+ })
102
+
103
+ type Item = { start: number; end: number; index: number }
104
+ /** Compute item geometry directly from Fenwick tree — no array needed. */
105
+ const getItem = (i: number): Item => ({
106
+ index: i,
107
+ start: i > 0 ? tree.sum(i - 1) : 0,
108
+ end: tree.sum(i),
109
+ })
110
+
111
+ const startIdx = createMemo((prev: number) => {
112
+ v()
113
+ const { batch: batchSize, overscan = 0 } = opt
114
+ let i = tree.findByOffset(scrollPos()) - overscan
115
+ if (batchSize) {
116
+ if (i > prev) i = i <= prev + batchSize ? prev : (i > prev + batchSize * 2 ? i : prev + batchSize)
117
+ else i -= batchSize
118
+ }
119
+ return Math.max(i, 0)
120
+ }, 0)
121
+
122
+ const endIdx = createMemo((prev: number) => {
123
+ v()
124
+ const { batch: batchSize, overscan = 0 } = opt
125
+ let i = tree.findByOffset(scrollPos() + viewSize()) + overscan
126
+ if (batchSize) {
127
+ if (i < prev) i = i >= prev - batchSize ? prev : (i < prev - batchSize * 2 ? i : prev - batchSize)
128
+ else i += batchSize
129
+ }
130
+ return Math.min(i, opt.count - 1)
131
+ }, 0)
132
+
133
+ const items2 = createMemo(() => {
134
+ v()
135
+ if (!opt.enable) {
136
+ return Array.from({ length: opt.count }, (_, i) => getItem(i))
137
+ }
138
+ let arr: Item[] = []
139
+ for (let i = startIdx(); i <= endIdx(); i++) arr.push(getItem(i))
140
+ if (opt.extras) {
141
+ arr.push(...(opt.extras(startIdx(), endIdx())?.map(i => getItem(i)) || []))
142
+ arr = uniqBy(arr, e => e.index).sort((a, b) => a.index - b.index)
143
+ }
144
+ return arr
145
+ })
146
+
147
+ return {
148
+ options: opt,
149
+ startIdx,
150
+ endIdx,
151
+ getTotalSize: () => (v(), tree.total()),
152
+ resizeItem: (i: number, newSize: number) => {
153
+ const old = sizes[i] ?? 0
154
+ if (old === newSize) return
155
+ // Scroll-position correction to prevent jitter when items above viewport resize
156
+ if (i < startIdx()) {
157
+ const el = opt.getScrollElement()
158
+ const scroll = opt.horizontal ? el.scrollLeft : el.scrollTop
159
+ if (scroll !== 0) {
160
+ opt.horizontal
161
+ ? (el.scrollLeft += newSize - old)
162
+ : (el.scrollTop += newSize - old)
163
+ }
164
+ }
165
+ tree.add(i, newSize - old)
166
+ sizes[i] = newSize
167
+ bumpV()
168
+ },
169
+ getVirtualItems: items2,
170
+ getVirtualItem: (() => {
171
+ const keyed = createMemo(() => {
172
+ v()
173
+ const map: Record<number, Item> = {}
174
+ for (const item of items2()) map[item.index] = item
175
+ return map
176
+ })
177
+ return (i: number) => keyed()[i]
178
+ })()
179
+ }
180
+ }