1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 02:44:45 +00:00

feat(packages/vue): add useInjectionStore

This commit is contained in:
2025-02-22 23:30:45 +07:00
parent caa7c4221a
commit 5594cef31e
3 changed files with 172 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ export * from './useClamp';
export * from './useContextFactory'; export * from './useContextFactory';
export * from './useCounter'; export * from './useCounter';
export * from './useFocusGuard'; export * from './useFocusGuard';
export * from './useInjectionStore';
export * from './useLastChanged'; export * from './useLastChanged';
export * from './useMounted'; export * from './useMounted';
export * from './useOffsetPagination'; export * from './useOffsetPagination';

View File

@@ -0,0 +1,99 @@
import { describe, it, expect } from 'vitest';
import { defineComponent, ref } from 'vue';
import { useInjectionStore } from '.';
import { mount } from '@vue/test-utils';
function testFactory<Args, Return>(
store: ReturnType<typeof useInjectionStore<Args[], Return>>,
) {
const { useProvidingState, useInjectedState } = store;
const Child = defineComponent({
setup() {
const state = useInjectedState();
return { state };
},
template: `{{ state }}`,
});
const Parent = defineComponent({
components: { Child },
setup() {
const state = useProvidingState();
return { state };
},
template: `<Child />`,
});
return {
Parent,
Child,
};
}
describe('useInjectionState', () => {
it('provides and injects state correctly', () => {
const { Parent } = testFactory(
useInjectionStore(() => ref('base'))
);
const wrapper = mount(Parent);
expect(wrapper.text()).toBe('base');
});
it('injects default value when state is not provided', () => {
const { Child } = testFactory(
useInjectionStore(() => ref('without provider'), {
defaultValue: ref('default'),
injectionKey: 'testKey',
})
);
const wrapper = mount(Child);
expect(wrapper.text()).toBe('default');
});
it('provides state at app level', () => {
const injectionStore = useInjectionStore(() => ref('app level'));
const { Child } = testFactory(injectionStore);
const wrapper = mount(Child, {
global: {
plugins: [
app => {
const state = injectionStore.useAppProvidingState(app)();
expect(state.value).toBe('app level');
},
],
},
});
expect(wrapper.text()).toBe('app level');
});
it('works with custom injection key', () => {
const { Parent } = testFactory(
useInjectionStore(() => ref('custom key'), {
injectionKey: Symbol('customKey'),
}),
);
const wrapper = mount(Parent);
expect(wrapper.text()).toBe('custom key');
});
it('handles state factory with arguments', () => {
const injectionStore = useInjectionStore((arg: string) => arg);
const { Child } = testFactory(injectionStore);
const wrapper = mount(Child, {
global: {
plugins: [
app => injectionStore.useAppProvidingState(app)('with args'),
],
},
});
expect(wrapper.text()).toBe('with args');
});
});

View File

@@ -0,0 +1,72 @@
import { inject, provide, type App, type InjectionKey } from 'vue';
export interface useInjectionStoreOptions<Return> {
injectionKey: string | InjectionKey<Return>;
defaultValue?: Return;
}
/**
* @name useInjectionStore
* @category State
* @description Create a global state that can be injected into components
*
* @param {Function} stateFactory A factory function that creates the state
* @param {useInjectionStoreOptions} options An object with the following properties
* @param {string | InjectionKey} options.injectionKey The key to use for the injection
* @param {any} options.defaultValue The default value to use when the state is not provided
* @returns {Object} An object with `useProvidingState`, `useAppProvidingState`, and `useInjectedState` functions
*
* @example
* const { useProvidingState, useInjectedState } = useInjectionStore(() => ref('Hello World'));
*
* // In a parent component
* const state = useProvidingState();
*
* // In a child component
* const state = useInjectedState();
*
* @example
* const { useProvidingState, useInjectedState } = useInjectionStore(() => ref('Hello World'), {
* injectionKey: 'MyState',
* defaultValue: 'Default Value'
* });
*
* // In a plugin
* {
* install(app) {
* const state = useAppProvidingState(app)();
* state.value = 'Hello World';
* }
* }
*
* // In a component
* const state = useInjectedState();
*
* @since 0.0.5
*/
export function useInjectionStore<Args extends any[], Return>(
stateFactory: (...args: Args) => Return,
options?: useInjectionStoreOptions<Return>,
) {
const key = options?.injectionKey ?? Symbol(stateFactory.name ?? 'InjectionStore');
const useProvidingState = (...args: Args) => {
const state = stateFactory(...args);
provide(key, state);
return state;
};
const useAppProvidingState = (app: App) => (...args: Args) => {
const state = stateFactory(...args);
app.provide(key, state);
return state;
};
const useInjectedState = () => inject(key, options?.defaultValue);
return {
useProvidingState,
useAppProvidingState,
useInjectedState
};
}