mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(store): rename feature to slice (#373)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { defineSlice } from '@videojs/store';
|
||||
|
||||
import type { PlayerTarget } from './types';
|
||||
|
||||
export const definePlayerFeature = defineSlice<PlayerTarget>();
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './feature';
|
||||
export * from './store/features';
|
||||
export * from './store/selectors';
|
||||
export * from './types';
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { InferFeatureState } from '@videojs/store';
|
||||
|
||||
import { defineFeature } from '@videojs/store';
|
||||
import type { InferSliceState } from '@videojs/store';
|
||||
import { listen, serializeTimeRanges } from '@videojs/utils/dom';
|
||||
|
||||
import type { PlayerTarget } from '../../types';
|
||||
import { definePlayerFeature } from '../../feature';
|
||||
|
||||
export const bufferFeature = defineFeature<PlayerTarget>()({
|
||||
export const bufferFeature = definePlayerFeature({
|
||||
state: () => ({
|
||||
/** Buffered time ranges as [start, end] tuples. */
|
||||
buffered: [] as [number, number][],
|
||||
@@ -29,4 +27,4 @@ export const bufferFeature = defineFeature<PlayerTarget>()({
|
||||
},
|
||||
});
|
||||
|
||||
export type BufferState = InferFeatureState<typeof bufferFeature>;
|
||||
export type BufferState = InferSliceState<typeof bufferFeature>;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { InferFeatureState } from '@videojs/store';
|
||||
|
||||
import { defineFeature } from '@videojs/store';
|
||||
import type { InferSliceState } from '@videojs/store';
|
||||
import { listen } from '@videojs/utils/dom';
|
||||
|
||||
import type { PlayerTarget } from '../../types';
|
||||
import { definePlayerFeature } from '../../feature';
|
||||
|
||||
export const playbackFeature = defineFeature<PlayerTarget>()({
|
||||
export const playbackFeature = definePlayerFeature({
|
||||
state: ({ task }) => ({
|
||||
/** Whether playback is paused. */
|
||||
paused: true,
|
||||
@@ -59,4 +57,4 @@ export const playbackFeature = defineFeature<PlayerTarget>()({
|
||||
},
|
||||
});
|
||||
|
||||
export type PlaybackState = InferFeatureState<typeof playbackFeature>;
|
||||
export type PlaybackState = InferSliceState<typeof playbackFeature>;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { InferFeatureState } from '@videojs/store';
|
||||
|
||||
import { CANCEL_ALL, defineFeature } from '@videojs/store';
|
||||
import type { InferSliceState } from '@videojs/store';
|
||||
import { CANCEL_ALL } from '@videojs/store';
|
||||
import { listen } from '@videojs/utils/dom';
|
||||
|
||||
import type { PlayerTarget } from '../../types';
|
||||
import { definePlayerFeature } from '../../feature';
|
||||
|
||||
export const sourceFeature = defineFeature<PlayerTarget>()({
|
||||
export const sourceFeature = definePlayerFeature({
|
||||
state: ({ task }) => ({
|
||||
/** Current media source URL (null if none). */
|
||||
source: null as string | null,
|
||||
@@ -44,4 +43,4 @@ export const sourceFeature = defineFeature<PlayerTarget>()({
|
||||
},
|
||||
});
|
||||
|
||||
export type SourceState = InferFeatureState<typeof sourceFeature>;
|
||||
export type SourceState = InferSliceState<typeof sourceFeature>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createStore } from '@videojs/store';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { PlayerTarget } from '../../../types';
|
||||
import { bufferFeature } from '../buffer';
|
||||
|
||||
describe('bufferFeature', () => {
|
||||
@@ -11,7 +12,7 @@ describe('bufferFeature', () => {
|
||||
seekable: createTimeRanges([[0, 120]]),
|
||||
});
|
||||
|
||||
const store = createStore({ features: [bufferFeature] });
|
||||
const store = createStore<PlayerTarget>()(bufferFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.buffered).toEqual([[0, 60]]);
|
||||
@@ -27,7 +28,7 @@ describe('bufferFeature', () => {
|
||||
seekable: createTimeRanges([[0, 120]]),
|
||||
});
|
||||
|
||||
const store = createStore({ features: [bufferFeature] });
|
||||
const store = createStore<PlayerTarget>()(bufferFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.buffered).toEqual([
|
||||
@@ -42,7 +43,7 @@ describe('bufferFeature', () => {
|
||||
seekable: createTimeRanges([[0, 100]]),
|
||||
});
|
||||
|
||||
const store = createStore({ features: [bufferFeature] });
|
||||
const store = createStore<PlayerTarget>()(bufferFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
// Update the mock video's buffered range
|
||||
@@ -63,7 +64,7 @@ describe('bufferFeature', () => {
|
||||
seekable: createTimeRanges([[0, 100]]),
|
||||
});
|
||||
|
||||
const store = createStore({ features: [bufferFeature] });
|
||||
const store = createStore<PlayerTarget>()(bufferFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
// Update the mock video to have no buffered content
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createStore } from '@videojs/store';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { PlayerTarget } from '../../../types';
|
||||
import { playbackFeature } from '../playback';
|
||||
|
||||
describe('playbackFeature', () => {
|
||||
@@ -13,7 +14,7 @@ describe('playbackFeature', () => {
|
||||
readyState: HTMLMediaElement.HAVE_ENOUGH_DATA,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.paused).toBe(false);
|
||||
@@ -28,7 +29,7 @@ describe('playbackFeature', () => {
|
||||
readyState: HTMLMediaElement.HAVE_CURRENT_DATA,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.waiting).toBe(true);
|
||||
@@ -40,7 +41,7 @@ describe('playbackFeature', () => {
|
||||
currentTime: 5,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.started).toBe(true);
|
||||
@@ -52,7 +53,7 @@ describe('playbackFeature', () => {
|
||||
currentTime: 0,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.started).toBe(true);
|
||||
@@ -61,7 +62,7 @@ describe('playbackFeature', () => {
|
||||
it('updates on play event', () => {
|
||||
const video = createMockVideo({ paused: true });
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.paused).toBe(true);
|
||||
@@ -76,7 +77,7 @@ describe('playbackFeature', () => {
|
||||
it('updates on pause event', () => {
|
||||
const video = createMockVideo({ paused: false });
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.paused).toBe(false);
|
||||
@@ -91,7 +92,7 @@ describe('playbackFeature', () => {
|
||||
it('updates on ended event', () => {
|
||||
const video = createMockVideo({ ended: false });
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.ended).toBe(false);
|
||||
@@ -106,7 +107,7 @@ describe('playbackFeature', () => {
|
||||
it('stops listening when store is destroyed', () => {
|
||||
const video = createMockVideo({});
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
store.destroy();
|
||||
@@ -125,7 +126,7 @@ describe('playbackFeature', () => {
|
||||
const video = createMockVideo({});
|
||||
video.play = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
await store.play();
|
||||
@@ -137,7 +138,7 @@ describe('playbackFeature', () => {
|
||||
const video = createMockVideo({});
|
||||
video.pause = vi.fn();
|
||||
|
||||
const store = createStore({ features: [playbackFeature] });
|
||||
const store = createStore<PlayerTarget>()(playbackFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
store.pause();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createStore } from '@videojs/store';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { PlayerTarget } from '../../../types';
|
||||
import { sourceFeature } from '../source';
|
||||
|
||||
describe('sourceFeature', () => {
|
||||
@@ -12,7 +13,7 @@ describe('sourceFeature', () => {
|
||||
readyState: HTMLMediaElement.HAVE_ENOUGH_DATA,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.source).toBe('https://example.com/video.mp4');
|
||||
@@ -25,7 +26,7 @@ describe('sourceFeature', () => {
|
||||
Object.defineProperty(video, 'currentSrc', { value: '', writable: false });
|
||||
Object.defineProperty(video, 'readyState', { value: HTMLMediaElement.HAVE_NOTHING, writable: false });
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.source).toBe(null);
|
||||
@@ -38,7 +39,7 @@ describe('sourceFeature', () => {
|
||||
readyState: HTMLMediaElement.HAVE_NOTHING,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.canPlay).toBe(false);
|
||||
@@ -59,7 +60,7 @@ describe('sourceFeature', () => {
|
||||
currentSrc: 'https://example.com/video.mp4',
|
||||
});
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.source).toBe('https://example.com/video.mp4');
|
||||
@@ -81,7 +82,7 @@ describe('sourceFeature', () => {
|
||||
readyState: HTMLMediaElement.HAVE_ENOUGH_DATA,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.canPlay).toBe(true);
|
||||
@@ -106,7 +107,7 @@ describe('sourceFeature', () => {
|
||||
const video = createMockVideo({});
|
||||
video.load = vi.fn();
|
||||
|
||||
const store = createStore({ features: [sourceFeature] });
|
||||
const store = createStore<PlayerTarget>()(sourceFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.loadSource('https://example.com/new.mp4');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createStore } from '@videojs/store';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { PlayerTarget } from '../../../types';
|
||||
import { timeFeature } from '../time';
|
||||
|
||||
describe('timeFeature', () => {
|
||||
@@ -11,7 +12,7 @@ describe('timeFeature', () => {
|
||||
duration: 120,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.currentTime).toBe(30);
|
||||
@@ -24,7 +25,7 @@ describe('timeFeature', () => {
|
||||
duration: Number.NaN,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.duration).toBe(0);
|
||||
@@ -33,7 +34,7 @@ describe('timeFeature', () => {
|
||||
it('updates on timeupdate event', () => {
|
||||
const video = createMockVideo({ currentTime: 0 });
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.currentTime).toBe(0);
|
||||
@@ -48,7 +49,7 @@ describe('timeFeature', () => {
|
||||
it('updates on durationchange event', () => {
|
||||
const video = createMockVideo({ duration: 0 });
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.duration).toBe(0);
|
||||
@@ -63,7 +64,7 @@ describe('timeFeature', () => {
|
||||
it('updates on seeked event', () => {
|
||||
const video = createMockVideo({ currentTime: 0 });
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
// Update mock currentTime
|
||||
@@ -79,7 +80,7 @@ describe('timeFeature', () => {
|
||||
duration: 120,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
// Update mock to empty state
|
||||
@@ -96,7 +97,7 @@ describe('timeFeature', () => {
|
||||
describe('seek', () => {
|
||||
it('sets currentTime on target and waits for seeked event', async () => {
|
||||
const video = createMockVideo({});
|
||||
const store = createStore({ features: [timeFeature] });
|
||||
const store = createStore<PlayerTarget>()(timeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const resultPromise = store.seek(45);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createStore } from '@videojs/store';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { PlayerTarget } from '../../../types';
|
||||
import { volumeFeature } from '../volume';
|
||||
|
||||
describe('volumeFeature', () => {
|
||||
@@ -11,7 +12,7 @@ describe('volumeFeature', () => {
|
||||
muted: false,
|
||||
});
|
||||
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.volume).toBe(0.8);
|
||||
@@ -20,7 +21,7 @@ describe('volumeFeature', () => {
|
||||
|
||||
it('sets volumeAvailability on attach', () => {
|
||||
const video = createMockVideo({});
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
// Should be 'available' or 'unsupported' based on browser capability
|
||||
@@ -30,7 +31,7 @@ describe('volumeFeature', () => {
|
||||
it('updates on volumechange event', () => {
|
||||
const video = createMockVideo({ volume: 1, muted: false });
|
||||
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
expect(store.state.volume).toBe(1);
|
||||
@@ -49,7 +50,7 @@ describe('volumeFeature', () => {
|
||||
describe('changeVolume', () => {
|
||||
it('sets volume on target', async () => {
|
||||
const video = createMockVideo({});
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.changeVolume(0.7);
|
||||
@@ -60,7 +61,7 @@ describe('volumeFeature', () => {
|
||||
|
||||
it('clamps volume to min 0', async () => {
|
||||
const video = createMockVideo({});
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
await store.changeVolume(-0.5);
|
||||
@@ -70,7 +71,7 @@ describe('volumeFeature', () => {
|
||||
|
||||
it('clamps volume to max 1', async () => {
|
||||
const video = createMockVideo({});
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
await store.changeVolume(1.5);
|
||||
@@ -82,7 +83,7 @@ describe('volumeFeature', () => {
|
||||
describe('toggleMute', () => {
|
||||
it('toggles mute from false to true', async () => {
|
||||
const video = createMockVideo({ muted: false });
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.toggleMute();
|
||||
@@ -93,7 +94,7 @@ describe('volumeFeature', () => {
|
||||
|
||||
it('toggles mute from true to false', async () => {
|
||||
const video = createMockVideo({ muted: true });
|
||||
const store = createStore({ features: [volumeFeature] });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.toggleMute();
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { InferFeatureState } from '@videojs/store';
|
||||
|
||||
import { defineFeature } from '@videojs/store';
|
||||
import type { InferSliceState } from '@videojs/store';
|
||||
import { listen, onEvent } from '@videojs/utils/dom';
|
||||
|
||||
import type { PlayerTarget } from '../../types';
|
||||
import { definePlayerFeature } from '../../feature';
|
||||
|
||||
export const timeFeature = defineFeature<PlayerTarget>()({
|
||||
export const timeFeature = definePlayerFeature({
|
||||
state: ({ task }) => ({
|
||||
/** Current playback position in seconds. */
|
||||
currentTime: 0,
|
||||
@@ -44,4 +42,4 @@ export const timeFeature = defineFeature<PlayerTarget>()({
|
||||
},
|
||||
});
|
||||
|
||||
export type TimeState = InferFeatureState<typeof timeFeature>;
|
||||
export type TimeState = InferSliceState<typeof timeFeature>;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { InferFeatureState } from '@videojs/store';
|
||||
|
||||
import { defineFeature } from '@videojs/store';
|
||||
import type { InferSliceState } from '@videojs/store';
|
||||
import { listen } from '@videojs/utils/dom';
|
||||
|
||||
import type { FeatureAvailability, PlayerTarget } from '../../types';
|
||||
import { definePlayerFeature } from '../../feature';
|
||||
import type { FeatureAvailability } from '../../types';
|
||||
|
||||
export const volumeFeature = defineFeature<PlayerTarget>()({
|
||||
export const volumeFeature = definePlayerFeature({
|
||||
state: ({ task }) => ({
|
||||
/** Volume level from 0 (silent) to 1 (max). */
|
||||
volume: 1,
|
||||
@@ -50,7 +49,7 @@ export const volumeFeature = defineFeature<PlayerTarget>()({
|
||||
},
|
||||
});
|
||||
|
||||
export type VolumeState = InferFeatureState<typeof volumeFeature>;
|
||||
export type VolumeState = InferSliceState<typeof volumeFeature>;
|
||||
|
||||
/** Check if volume can be programmatically set (fails on iOS Safari). */
|
||||
function canSetVolume(): FeatureAvailability {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFeatureSelector } from '@videojs/store';
|
||||
import { createSelector } from '@videojs/store';
|
||||
|
||||
import { bufferFeature } from './features/buffer';
|
||||
import { playbackFeature } from './features/playback';
|
||||
@@ -6,8 +6,8 @@ import { sourceFeature } from './features/source';
|
||||
import { timeFeature } from './features/time';
|
||||
import { volumeFeature } from './features/volume';
|
||||
|
||||
export const selectBuffer = createFeatureSelector(bufferFeature);
|
||||
export const selectPlayback = createFeatureSelector(playbackFeature);
|
||||
export const selectSource = createFeatureSelector(sourceFeature);
|
||||
export const selectTime = createFeatureSelector(timeFeature);
|
||||
export const selectVolume = createFeatureSelector(volumeFeature);
|
||||
export const selectBuffer = createSelector(bufferFeature);
|
||||
export const selectPlayback = createSelector(playbackFeature);
|
||||
export const selectSource = createSelector(sourceFeature);
|
||||
export const selectTime = createSelector(timeFeature);
|
||||
export const selectVolume = createSelector(volumeFeature);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { AnySlice, Slice, Store, UnionSliceState } from '@videojs/store';
|
||||
|
||||
export interface Media extends HTMLMediaElement {}
|
||||
|
||||
export interface MediaContainer extends HTMLElement {}
|
||||
@@ -8,3 +10,9 @@ export interface PlayerTarget {
|
||||
}
|
||||
|
||||
export type FeatureAvailability = 'available' | 'unavailable' | 'unsupported';
|
||||
|
||||
export type PlayerFeature<State> = Slice<PlayerTarget, State>;
|
||||
|
||||
export type AnyPlayerFeature = AnySlice<PlayerTarget>;
|
||||
|
||||
export type PlayerStore<Features extends AnyPlayerFeature[] = []> = Store<PlayerTarget, UnionSliceState<Features>>;
|
||||
|
||||
Reference in New Issue
Block a user