mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 02:44:45 +00:00
Merge pull request #62 from robonen/feat/injection-store
feat(packages/vue): useInjectionStore
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
* sleep(1000).then(() => {
|
||||
* console.log('Hello, World!');
|
||||
* });
|
||||
*
|
||||
* @since 0.0.3
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
||||
"name": "@robonen/vue",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"exports": "./src/index.ts"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@robonen/vue",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Collection of powerful tools for Vue",
|
||||
"keywords": [
|
||||
@@ -16,9 +16,9 @@
|
||||
"url": "git+https://github.com/robonen/tools.git",
|
||||
"directory": "./packages/vue"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.5",
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"engines": {
|
||||
"node": ">=22.13.1"
|
||||
"node": ">=22.14.0"
|
||||
},
|
||||
"type": "module",
|
||||
"files": [
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from './useClamp';
|
||||
export * from './useContextFactory';
|
||||
export * from './useCounter';
|
||||
export * from './useFocusGuard';
|
||||
export * from './useInjectionStore';
|
||||
export * from './useLastChanged';
|
||||
export * from './useMounted';
|
||||
export * from './useOffsetPagination';
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('useContextFactory', () => {
|
||||
|
||||
const childComponent = mount(Child, {
|
||||
global: {
|
||||
plugins: [app => context.provide('test', app)],
|
||||
plugins: [app => context.appProvide(app)('test')],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import { VueToolsError } from '../..';
|
||||
|
||||
/**
|
||||
* @name useContextFactory
|
||||
* @category Utilities
|
||||
* @category State
|
||||
* @description A composable that provides a factory for creating context with unique key
|
||||
*
|
||||
* @param {string} name The name of the context
|
||||
* @returns {Object} An object with `inject`, `provide` and `key` properties
|
||||
* @returns {Object} An object with `inject`, `provide`, `appProvide` and `key` properties
|
||||
* @throws {VueToolsError} when the context is not provided
|
||||
*
|
||||
* @example
|
||||
@@ -17,12 +17,12 @@ import { VueToolsError } from '../..';
|
||||
* const value = inject();
|
||||
*
|
||||
* @example
|
||||
* const { inject: injectContext, provide: provideContext } = useContextFactory('MyContext');
|
||||
* const { inject: injectContext, appProvide } = useContextFactory('MyContext');
|
||||
*
|
||||
* // In a plugin
|
||||
* {
|
||||
* install(app) {
|
||||
* provideContext('Hello World', app);
|
||||
* appProvide(app)('Hello World');
|
||||
* }
|
||||
* }
|
||||
*
|
||||
@@ -43,14 +43,20 @@ export function useContextFactory<ContextValue>(name: string) {
|
||||
throw new VueToolsError(`useContextFactory: '${name}' context is not provided`);
|
||||
};
|
||||
|
||||
const provideContext = (context: ContextValue, app?: App) => {
|
||||
(app?.provide ?? provide)(injectionKey, context);
|
||||
const provideContext = (context: ContextValue) => {
|
||||
provide(injectionKey, context);
|
||||
return context;
|
||||
};
|
||||
|
||||
const appProvide = (app: App) => (context: ContextValue) => {
|
||||
app.provide(injectionKey, context);
|
||||
return context;
|
||||
};
|
||||
|
||||
return {
|
||||
inject: injectContext,
|
||||
provide: provideContext,
|
||||
appProvide,
|
||||
key: injectionKey,
|
||||
}
|
||||
}
|
||||
99
packages/vue/src/composables/useInjectionStore/index.test.ts
Normal file
99
packages/vue/src/composables/useInjectionStore/index.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
72
packages/vue/src/composables/useInjectionStore/index.ts
Normal file
72
packages/vue/src/composables/useInjectionStore/index.ts
Normal 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
|
||||
};
|
||||
}
|
||||
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@@ -51,15 +51,6 @@ importers:
|
||||
specifier: ^1.6.3
|
||||
version: 1.6.3(@algolia/client-search@5.20.0)(@types/node@22.13.1)(postcss@8.5.1)(search-insights@2.13.0)(typescript@5.4.4)
|
||||
|
||||
apps/vhs:
|
||||
devDependencies:
|
||||
'@robonen/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/tsconfig
|
||||
'@types/bun':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
|
||||
packages/platform:
|
||||
devDependencies:
|
||||
'@robonen/tsconfig':
|
||||
@@ -1817,9 +1808,6 @@ packages:
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
'@types/bun@1.2.2':
|
||||
resolution: {integrity: sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w==}
|
||||
|
||||
'@types/bunyan@1.8.9':
|
||||
resolution: {integrity: sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw==}
|
||||
|
||||
@@ -1898,9 +1886,6 @@ packages:
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/ws@8.5.12':
|
||||
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
@@ -2234,9 +2219,6 @@ packages:
|
||||
builtins@5.1.0:
|
||||
resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==}
|
||||
|
||||
bun-types@1.2.2:
|
||||
resolution: {integrity: sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg==}
|
||||
|
||||
bunyan@1.8.15:
|
||||
resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==}
|
||||
engines: {'0': node >=0.10.0}
|
||||
@@ -7314,10 +7296,6 @@ snapshots:
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
|
||||
'@types/bun@1.2.2':
|
||||
dependencies:
|
||||
bun-types: 1.2.2
|
||||
|
||||
'@types/bunyan@1.8.9':
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
@@ -7392,10 +7370,6 @@ snapshots:
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/ws@8.5.12':
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
@@ -7797,11 +7771,6 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.0
|
||||
|
||||
bun-types@1.2.2:
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
'@types/ws': 8.5.12
|
||||
|
||||
bunyan@1.8.15:
|
||||
optionalDependencies:
|
||||
dtrace-provider: 0.8.8
|
||||
|
||||
Reference in New Issue
Block a user