Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 892666b

Browse files
authored
feat(syncRef): enhance syncRef type restrict (#3515)
1 parent 8eb0b2d commit 892666b

File tree

2 files changed

+270
-16
lines changed

2 files changed

+270
-16
lines changed

packages/shared/syncRef/index.test.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,157 @@ describe('syncRef', () => {
9797
expect(right.value).toBe(10)
9898
expect(left.value).toBe(5)
9999
})
100+
101+
it('ts works', () => {
102+
const ref0 = ref(0)
103+
const ref1 = ref(1)
104+
const refString = ref('1')
105+
const refNumString = ref<number | string>(1)
106+
const refNumBoolean = ref<number | boolean>(1)
107+
// L = A && direction === 'both'
108+
syncRef(ref0, ref1)()
109+
syncRef(ref0, ref1, {
110+
direction: 'both',
111+
})()
112+
syncRef(ref0, ref1, {
113+
direction: 'both',
114+
transform: {},
115+
})()
116+
syncRef(ref0, ref1, {
117+
direction: 'both',
118+
transform: {
119+
ltr: v => v,
120+
},
121+
})()
122+
syncRef(ref0, ref1, {
123+
direction: 'both',
124+
transform: {
125+
rtl: v => v,
126+
},
127+
})()
128+
syncRef(ref0, ref1, {
129+
direction: 'both',
130+
transform: {
131+
ltr: v => v,
132+
rtl: v => v,
133+
},
134+
})()
135+
syncRef(ref0, ref1, {
136+
direction: 'both',
137+
transform: {
138+
// @ts-expect-error wrong type, should be (left: L) => R
139+
ltr: v => v.toString(),
140+
rtl: v => v,
141+
},
142+
})()
143+
// L = A && direction === 'ltr'
144+
syncRef(ref0, ref1, {
145+
direction: 'ltr',
146+
})()
147+
syncRef(ref0, ref1, {
148+
direction: 'ltr',
149+
transform: {},
150+
})()
151+
syncRef(ref0, ref1, {
152+
direction: 'ltr',
153+
transform: {
154+
ltr: v => v,
155+
},
156+
})()
157+
syncRef(ref0, ref1, {
158+
direction: 'ltr',
159+
transform: {
160+
// @ts-expect-error wrong transform type, should be ltr
161+
rtl: v => v,
162+
},
163+
})()
164+
// L = A && direction === 'rtl'
165+
syncRef(ref0, ref1, {
166+
direction: 'rtl',
167+
})()
168+
syncRef(ref0, ref1, {
169+
direction: 'rtl',
170+
transform: {},
171+
})()
172+
syncRef(ref0, ref1, {
173+
direction: 'rtl',
174+
transform: {
175+
rtl: v => v,
176+
},
177+
})()
178+
// L ⊆ R && direction === 'both'
179+
// @ts-expect-error wrong type, should provide transform
180+
syncRef(ref0, refNumString, {
181+
direction: 'both',
182+
})()
183+
syncRef(ref0, refNumString, {
184+
direction: 'both',
185+
transform: {
186+
ltr: v => v.toString(),
187+
rtl: v => Number(v),
188+
},
189+
})()
190+
// L ⊆ R && direction === 'ltr'
191+
syncRef(ref0, refNumString, {
192+
direction: 'ltr',
193+
transform: {
194+
ltr: v => v.toString(),
195+
},
196+
})()
197+
// L ⊆ R && direction === 'rtl'
198+
syncRef(ref0, refNumString, {
199+
direction: 'ltr',
200+
transform: {
201+
ltr: v => Number(v),
202+
},
203+
})()
204+
// L ∩ R = ∅ && direction === 'both'
205+
syncRef(ref0, refString, {
206+
direction: 'both',
207+
transform: {
208+
ltr: v => v.toString(),
209+
rtl: v => Number(v),
210+
},
211+
})()
212+
// L ∩ R = ∅ && direction === 'ltr'
213+
syncRef(ref0, refString, {
214+
direction: 'ltr',
215+
transform: {
216+
ltr: v => v.toString(),
217+
},
218+
})()
219+
// L ∩ R = ∅ && direction === 'rtl'
220+
syncRef(ref0, refString, {
221+
direction: 'rtl',
222+
transform: {
223+
rtl: v => Number(v),
224+
},
225+
})()
226+
// L ∩ R = ∅ && direction === 'both'
227+
syncRef(ref0, refString, {
228+
direction: 'both',
229+
// @ts-expect-error wrong type, should provide ltr
230+
transform: {
231+
rtl: v => Number(v),
232+
},
233+
})()
234+
// L ∩ R ≠ ∅
235+
syncRef(refNumString, refNumBoolean, {
236+
transform: {
237+
ltr: v => Number(v),
238+
rtl: v => Number(v),
239+
},
240+
})
241+
242+
// @ts-expect-error lack of options
243+
syncRef(ref0, refString)()
244+
245+
syncRef(ref0, refNumBoolean, {
246+
direction: 'ltr',
247+
})()
248+
249+
syncRef(refNumBoolean, ref0, {
250+
direction: 'rtl',
251+
})
252+
})
100253
})

packages/shared/syncRef/index.ts

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,105 @@
1-
import type { Ref } from 'vue-demi'
1+
import { type Ref } from 'vue-demi'
22
import type { ConfigurableFlushSync } from '../utils'
33
import type { WatchPausableReturn } from '../watchPausable'
44
import { pausableWatch } from '../watchPausable'
55

6-
export interface SyncRefOptions<L, R = L> extends ConfigurableFlushSync {
6+
type Direction = 'ltr' | 'rtl' | 'both'
7+
type SpecificFieldPartial<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
8+
/**
9+
* A = B
10+
*/
11+
type Equal<A, B> = A extends B ? (B extends A ? true : false) : false
12+
13+
/**
14+
* A ∩ B ≠ ∅
15+
*/
16+
type IntersectButNotEqual<A, B> = Equal<A, B> extends true
17+
? false
18+
: A & B extends never
19+
? false
20+
: true
21+
22+
/**
23+
* A ⊆ B
24+
*/
25+
type IncludeButNotEqual<A, B> = Equal<A, B> extends true
26+
? false
27+
: A extends B
28+
? true
29+
: false
30+
31+
/**
32+
* A ∩ B = ∅
33+
*/
34+
type NotIntersect<A, B> = Equal<A, B> extends true
35+
? false
36+
: A & B extends never
37+
? true
38+
: false
39+
40+
// L = R
41+
interface EqualType<
42+
D extends Direction,
43+
L,
44+
R,
45+
O extends keyof Transform<L, R> = D extends 'both' ? 'ltr' | 'rtl' : D,
46+
> {
47+
transform?: SpecificFieldPartial<Pick<Transform<L, R>, O>, O>
48+
}
49+
50+
type StrictIncludeMap<IncludeType extends 'LR' | 'RL', D extends Exclude<Direction, 'both'>, L, R> = (Equal<[IncludeType, D], ['LR', 'ltr']>
51+
& Equal<[IncludeType, D], ['RL', 'rtl']>) extends true
52+
? {
53+
transform?: SpecificFieldPartial<Pick<Transform<L, R>, D>, D>
54+
} : {
55+
transform: Pick<Transform<L, R>, D>
56+
}
57+
58+
// L ⊆ R
59+
type StrictIncludeType<IncludeType extends 'LR' | 'RL', D extends Direction, L, R> = D extends 'both'
60+
? {
61+
transform: SpecificFieldPartial<Transform<L, R>, IncludeType extends 'LR' ? 'ltr' : 'rtl'>
62+
}
63+
: D extends Exclude<Direction, 'both'>
64+
? StrictIncludeMap<IncludeType, D, L, R>
65+
: never
66+
67+
// L ∩ R ≠ ∅
68+
type IntersectButNotEqualType<D extends Direction, L, R> = D extends 'both'
69+
? {
70+
transform: Transform<L, R>
71+
}
72+
: D extends Exclude<Direction, 'both'>
73+
? {
74+
transform: Pick<Transform<L, R>, D>
75+
}
76+
: never
77+
78+
// L ∩ R = ∅
79+
type NotIntersectType<D extends Direction, L, R> = IntersectButNotEqualType<D, L, R>
80+
interface Transform<L, R> {
81+
ltr: (left: L) => R
82+
rtl: (right: R) => L
83+
}
84+
85+
type TransformType<D extends Direction, L, R> = Equal<L, R> extends true
86+
// L = R
87+
? EqualType<D, L, R>
88+
: IncludeButNotEqual<L, R> extends true
89+
// L ⊆ R
90+
? StrictIncludeType<'LR', D, L, R>
91+
: IncludeButNotEqual<R, L> extends true
92+
// R ⊆ L
93+
? StrictIncludeType<'RL', D, L, R>
94+
: IntersectButNotEqual<L, R> extends true
95+
// L ∩ R ≠ ∅
96+
? IntersectButNotEqualType<D, L, R>
97+
: NotIntersect<L, R> extends true
98+
// L ∩ R = ∅
99+
? NotIntersectType<D, L, R>
100+
: never
101+
102+
export type SyncRefOptions<L, R, D extends Direction> = ConfigurableFlushSync & {
7103
/**
8104
* Watch deeply
9105
*
@@ -22,36 +118,41 @@ export interface SyncRefOptions<L, R = L> extends ConfigurableFlushSync {
22118
*
23119
* @default 'both'
24120
*/
25-
direction?: 'ltr' | 'rtl' | 'both'
121+
direction?: D
26122

27-
/**
28-
* Custom transform function
29-
*/
30-
transform?: {
31-
ltr?: (left: L) => R
32-
rtl?: (right: R) => L
33-
}
34-
}
123+
} & TransformType<D, L, R>
35124

36125
/**
37126
* Two-way refs synchronization.
38-
*
127+
* From the set theory perspective to restrict the option's type
128+
* Check in the following order:
129+
* 1. L = R
130+
* 2. L ∩ R ≠ ∅
131+
* 3. L ⊆ R
132+
* 4. L ∩ R = ∅
39133
* @param left
40134
* @param right
135+
* @param [options?]
41136
*/
42-
export function syncRef<L, R = L>(left: Ref<L>, right: Ref<R>, options: SyncRefOptions<L, R> = {}) {
137+
export function syncRef<L, R, D extends Direction>(
138+
left: Ref<L>,
139+
right: Ref<R>,
140+
...[options]: Equal<L, R> extends true
141+
? [options?: SyncRefOptions<L, R, D>]
142+
: [options: SyncRefOptions<L, R, D>]
143+
) {
43144
const {
44145
flush = 'sync',
45146
deep = false,
46147
immediate = true,
47148
direction = 'both',
48149
transform = {},
49-
} = options
150+
} = options || {}
50151

51152
const watchers: WatchPausableReturn[] = []
52153

53-
const transformLTR = transform.ltr ?? (v => v)
54-
const transformRTL = transform.rtl ?? (v => v)
154+
const transformLTR = ('ltr' in transform && transform.ltr) || (v => v)
155+
const transformRTL = ('rtl' in transform && transform.rtl) || (v => v)
55156

56157
if (direction === 'both' || direction === 'ltr') {
57158
watchers.push(pausableWatch(

0 commit comments

Comments
 (0)