-
-
Notifications
You must be signed in to change notification settings - Fork 427
Expand file tree
/
Copy pathproxy.ts
More file actions
117 lines (108 loc) · 3.46 KB
/
proxy.ts
File metadata and controls
117 lines (108 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { computed, untracked } from '@angular/core'
import type { Signal, WritableSignal } from '@angular/core'
import type { Virtualizer } from '@tanstack/virtual-core'
import type { AngularVirtualizer } from './types'
export function proxyVirtualizer<
V extends Virtualizer<any, any>,
S extends Element | Window = V extends Virtualizer<infer U, any> ? U : never,
I extends Element = V extends Virtualizer<any, infer U> ? U : never,
>(
virtualizerSignal: WritableSignal<V>,
lazyInit: () => V,
): AngularVirtualizer<S, I> {
return new Proxy(virtualizerSignal, {
apply() {
return virtualizerSignal()
},
get(target, property) {
const untypedTarget = target as any
if (untypedTarget[property]) {
return untypedTarget[property]
}
let virtualizer = untracked(virtualizerSignal)
if (virtualizer == null) {
virtualizer = lazyInit()
untracked(() => virtualizerSignal.set(virtualizer))
}
// Create computed signals for each property that represents a reactive value
if (
typeof property === 'string' &&
[
'getTotalSize',
'getVirtualItems',
'isScrolling',
'options',
'range',
'scrollDirection',
'scrollElement',
'scrollOffset',
'scrollRect',
'measureElementCache',
'measurementsCache',
].includes(property)
) {
const isFunction =
typeof virtualizer[property as keyof V] === 'function'
Object.defineProperty(untypedTarget, property, {
value: isFunction
? computed(() => (target()[property as keyof V] as Function)())
: computed(() => target()[property as keyof V]),
configurable: true,
enumerable: true,
})
}
// Create plain signals for functions that accept arguments and return reactive values
if (
typeof property === 'string' &&
[
'getOffsetForAlignment',
'getOffsetForIndex',
'getVirtualItemForOffset',
'indexFromElement',
].includes(property)
) {
const fn = virtualizer[property as keyof V] as Function
Object.defineProperty(untypedTarget, property, {
value: toComputed(virtualizerSignal, fn),
configurable: true,
enumerable: true,
})
}
return untypedTarget[property] || virtualizer[property as keyof V]
},
has(_, property: string) {
return !!untracked(virtualizerSignal)[property as keyof V]
},
ownKeys() {
return Reflect.ownKeys(untracked(virtualizerSignal))
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
}
},
}) as unknown as AngularVirtualizer<S, I>
}
function toComputed<V extends Virtualizer<any, any>>(
signal: Signal<V>,
fn: Function,
) {
const computedCache: Record<string, Signal<unknown>> = {}
return (...args: Array<any>) => {
// Cache computeds by their arguments to avoid re-creating the computed on each call
const serializedArgs = serializeArgs(...args)
if (computedCache.hasOwnProperty(serializedArgs)) {
return computedCache[serializedArgs]?.()
}
const computedSignal = computed(() => {
void signal()
return fn(...args)
})
computedCache[serializedArgs] = computedSignal
return computedSignal()
}
}
function serializeArgs(...args: Array<any>) {
return JSON.stringify(args)
}