Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: writable computeds to be picked up by mapWritableState #2847

Merged
merged 4 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion packages/pinia/__tests__/mapHelpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
setMapStoreSuffix,
} from '../src'
import { mount } from '@vue/test-utils'
import { nextTick, defineComponent } from 'vue'
import { nextTick, defineComponent, ref, computed } from 'vue'
import { mockWarn } from './vitest-mock-warn'

describe('Map Helpers', () => {
Expand Down Expand Up @@ -245,5 +245,27 @@ describe('Map Helpers', () => {
'replaced replaced'
)
})

it('setup store', async () => {
const useSetupStore = defineStore('setup', () => {
const text = ref('initial')

const textUpper = computed({
get: () => text.value.toUpperCase(),
set: (v) => {
text.value = v
},
})

return { text, textUpper }
})

await testComponent(
mapWritableState(useSetupStore, ['text', 'textUpper']),
`{{ text }} {{ textUpper }}`,
`initial INITIAL`,
'replaced REPLACED'
)
})
})
})
100 changes: 60 additions & 40 deletions packages/pinia/src/mapHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ComponentPublicInstance, ComputedRef, UnwrapRef } from 'vue-demi'
import type {
_GettersTree,
_Method,
_StoreWithGetters_Writable,
StateTree,
Store,
StoreDefinition,
Expand Down Expand Up @@ -431,23 +431,35 @@ export function mapActions<
/**
* For internal use **only**
*/
export type _MapWritableStateReturn<S> = {
[key in keyof S]: {
get: () => S[key]
set: (value: S[key]) => any
export type _MapWritableStateKeys<S extends StateTree, G> =
| keyof UnwrapRef<S>
| keyof _StoreWithGetters_Writable<G>

/**
* For internal use **only**
*/
export type _MapWritableStateReturn<
S extends StateTree,
G,
Keys extends _MapWritableStateKeys<S, G>,
> = {
[key in Keys]: {
get: () => UnwrapRef<(S & G)[key]>
set: (value: UnwrapRef<(S & G)[key]>) => any
}
}

/**
* For internal use **only**
*/
export type _MapWritableStateObjectReturn<
S,
T extends Record<string, keyof S>,
S extends StateTree,
G,
KeyMapper extends Record<string, _MapWritableStateKeys<S, G>>,
> = {
[key in keyof T]: {
get: () => S[T[key]]
set: (value: S[T[key]]) => any
[key in keyof KeyMapper]: {
get: () => UnwrapRef<(S & G)[KeyMapper[key]]>
set: (value: UnwrapRef<(S & G)[KeyMapper[key]]>) => any
}
}

Expand All @@ -462,13 +474,13 @@ export type _MapWritableStateObjectReturn<
export function mapWritableState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
G,
A,
KeyMapper extends Record<string, keyof UnwrapRef<S>>,
KeyMapper extends Record<string, _MapWritableStateKeys<S, G>>,
>(
useStore: StoreDefinition<Id, S, G, A>,
keyMapper: KeyMapper
): _MapWritableStateObjectReturn<UnwrapRef<S>, KeyMapper>
): _MapWritableStateObjectReturn<S, G, KeyMapper>
/**
* Allows using state and getters from one store without using the composition
* API (`setup()`) by generating an object to be spread in the `computed` field
Expand All @@ -480,13 +492,13 @@ export function mapWritableState<
export function mapWritableState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
G,
A,
Keys extends keyof UnwrapRef<S>,
Keys extends _MapWritableStateKeys<S, G>,
>(
useStore: StoreDefinition<Id, S, G, A>,
keys: readonly Keys[]
): Pick<_MapWritableStateReturn<UnwrapRef<S>>, Keys>
): Pick<_MapWritableStateReturn<S, G, Keys>, Keys>
/**
* Allows using state and getters from one store without using the composition
* API (`setup()`) by generating an object to be spread in the `computed` field
Expand All @@ -498,43 +510,51 @@ export function mapWritableState<
export function mapWritableState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
G,
A,
KeyMapper extends Record<string, keyof S>,
Keys extends _MapWritableStateKeys<S, G>,
KeyArr extends Keys[],
KeyMapper extends Record<string, Keys>,
>(
useStore: StoreDefinition<Id, S, G, A>,
keysOrMapper: Array<keyof S> | KeyMapper
): _MapWritableStateReturn<S> | _MapWritableStateObjectReturn<S, KeyMapper> {
keysOrMapper: KeyArr | KeyMapper
):
| _MapWritableStateReturn<S, G, Keys>
| _MapWritableStateObjectReturn<S, G, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
// @ts-ignore
reduced[key] = {
get(this: ComponentPublicInstance) {
// @ts-expect-error: FIXME: should work?
return useStore(this.$pinia)[key]
},
set(this: ComponentPublicInstance, value) {
// @ts-expect-error: FIXME: should work?
return (useStore(this.$pinia)[key] = value)
},
}
return reduced
}, {} as _MapWritableStateReturn<S>)
? keysOrMapper.reduce(
(reduced, key) => {
reduced[key] = {
get(this: ComponentPublicInstance) {
return useStore(this.$pinia)[key] as (S & G)[typeof key]
},
set(
this: ComponentPublicInstance,
value: Store<Id, S, G, A>[typeof key]
) {
return (useStore(this.$pinia)[key] = value)
},
}
return reduced
},
{} as _MapWritableStateReturn<S, G, Keys>
)
: Object.keys(keysOrMapper).reduce(
(reduced, key: keyof KeyMapper) => {
// @ts-ignore
reduced[key] = {
get(this: ComponentPublicInstance) {
// @ts-expect-error: FIXME: should work?
return useStore(this.$pinia)[keysOrMapper[key]]
return useStore(this.$pinia)[keysOrMapper[key]] as (S &
G)[KeyMapper[typeof key]]
},
set(this: ComponentPublicInstance, value) {
// @ts-expect-error: FIXME: should work?
set(
this: ComponentPublicInstance,
value: Store<Id, S, G, A>[KeyMapper[typeof key]]
) {
return (useStore(this.$pinia)[keysOrMapper[key]] = value)
},
}
return reduced
},
{} as _MapWritableStateObjectReturn<S, KeyMapper>
{} as _MapWritableStateObjectReturn<S, G, KeyMapper>
)
}
13 changes: 12 additions & 1 deletion packages/pinia/test-dts/mapHelpers.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ describe('mapHelpers', () => {
const useSetupStore = defineStore('setupStore', () => {
const a = ref('on' as 'on' | 'off')
const upper = computed(() => a.value.toUpperCase())
const writableUpper = computed({
get: () => a.value.toUpperCase(),
set: (v: 'on' | 'off') => (a.value = v),
})
function toggleA() {
a.value = a.value === 'off' ? 'on' : 'off'
}
function setToggle(aVal: 'on' | 'off') {
return (a.value = aVal)
}
return { a, upper, toggleA, setToggle }
return { a, upper, writableUpper, toggleA, setToggle }
})

const useCounter = defineStore({
Expand Down Expand Up @@ -161,6 +165,13 @@ describe('mapHelpers', () => {
set: (v: 'on' | 'off') => any
}
}>(mapWritableState(useSetupStore, ['a']))

expectTypeOf<{
writableUpper: {
get: () => string
set: (v: 'on' | 'off') => any
}
}>(mapWritableState(useSetupStore, ['writableUpper']))
})
})
})