storybook
Version:
Storybook: Develop, document, and test UI components in isolation
1,583 lines (1,497 loc) • 127 kB
TypeScript
import { StoryId as StoryId$1, StoryName, ComponentTitle, Tag as Tag$1, Parameters, Args, ArgTypes, ComponentId, StoryKind, Globals as Globals$1, GlobalTypes as GlobalTypes$1, InputType, Renderer, StoryContext, PartialStoryFn, LegacyStoryFn, ArgsStoryFn, StoryFn, DecoratorFunction, LoaderFunction as LoaderFunction$1, ViewMode as ViewMode$1, StoryIdentifier, ProjectAnnotations as ProjectAnnotations$1, StrictArgTypes, StrictGlobalTypes, StepRunner, BeforeAll, ComponentAnnotations, StoryAnnotations, StoryContextForEnhancers, CleanupCallback, Canvas, StoryAnnotationsOrFn, AnnotatedStoryFn } from 'storybook/internal/csf';
export { AfterEach, AnnotatedStoryFn, ArgTypes, ArgTypesEnhancer, Args, ArgsEnhancer, ArgsFromMeta, ArgsStoryFn, BaseAnnotations, ProjectAnnotations as BaseProjectAnnotations, BeforeAll, BeforeEach, Canvas, CleanupCallback, ComponentAnnotations, ComponentId, ComponentTitle, Conditional, DecoratorApplicator, DecoratorFunction, GlobalTypes, Globals, IncludeExcludeOptions, InputType, LegacyAnnotatedStoryFn, LegacyStoryAnnotationsOrFn, LegacyStoryFn, LoaderFunction, Parameters, PartialStoryFn, PlayFunction, PlayFunctionContext, Renderer, SBArrayType, SBEnumType, SBIntersectionType, SBObjectType, SBOtherType, SBScalarType, SBType, SBUnionType, SeparatorOptions, StepFunction, StepLabel, StepRunner, StoryAnnotations, StoryAnnotationsOrFn, StoryContext, StoryContextForEnhancers, StoryContextForLoaders, StoryContextUpdate, StoryFn, StoryId, StoryIdentifier, StoryKind, StoryName, StrictArgTypes, StrictArgs, StrictGlobalTypes, StrictInputType, Tag, TestFunction } from 'storybook/internal/csf';
import { ReactElement, FC, ReactNode, PropsWithChildren } from 'react';
import { RouterData as RouterData$1 } from 'storybook/internal/router';
import { Globals, GlobalTypes, API_Layout as API_Layout$1, API_LayoutCustomisations as API_LayoutCustomisations$1, API_UI as API_UI$1, API_Notification as API_Notification$1, API_Refs as API_Refs$1, API_Settings as API_Settings$1, API_LoadedRefData as API_LoadedRefData$1, StoryId, API_PreparedStoryIndex as API_PreparedStoryIndex$1, API_ViewMode as API_ViewMode$1, API_FilterFunction as API_FilterFunction$1, API_Versions as API_Versions$2, API_UnknownEntries as API_UnknownEntries$1, API_OptionsData as API_OptionsData$1, NormalizedProjectAnnotations as NormalizedProjectAnnotations$1, ProjectAnnotations as ProjectAnnotations$2, ComposedStoryFn as ComposedStoryFn$1 } from 'storybook/internal/types';
import { ThemeVars as ThemeVars$1 } from 'storybook/theming';
import { WhatsNewData } from 'storybook/internal/core-events';
import { FileSystemCache } from 'storybook/internal/common';
import { StoryIndexGenerator } from 'storybook/internal/core-server';
import { CsfFile } from 'storybook/internal/csf-tools';
import { LogLevel } from 'storybook/internal/node-logger';
import { Server, IncomingMessage, ServerResponse } from 'http';
import { Server as Server$1 } from 'net';
import { Channel as Channel$1 } from 'storybook/internal/channels';
/**
* Actions represent the type of change to a location value.
*/
declare enum Action {
/**
* A POP indicates a change to an arbitrary index in the history stack, such
* as a back or forward navigation. It does not describe the direction of the
* navigation, only that the current index changed.
*
* Note: This is the default action for newly created history objects.
*/
Pop = "POP",
/**
* A PUSH indicates a new entry being added to the history stack, such as when
* a link is clicked and a new page loads. When this happens, all subsequent
* entries in the stack are lost.
*/
Push = "PUSH",
/**
* A REPLACE indicates the entry at the current index in the history stack
* being replaced by a new one.
*/
Replace = "REPLACE"
}
/**
* The pathname, search, and hash values of a URL.
*/
interface Path$1 {
/**
* A URL pathname, beginning with a /.
*/
pathname: string;
/**
* A URL search string, beginning with a ?.
*/
search: string;
/**
* A URL fragment identifier, beginning with a #.
*/
hash: string;
}
/**
* An entry in a history stack. A location contains information about the
* URL path, as well as possibly some arbitrary state and a key.
*/
interface Location extends Path$1 {
/**
* A value of arbitrary data associated with this location.
*/
state: any;
/**
* A unique string associated with this location. May be used to safely store
* and retrieve data in some other storage API, like `localStorage`.
*
* Note: This value is always "default" on the initial location.
*/
key: string;
}
/**
* Describes a location that is the destination of some navigation, either via
* `history.push` or `history.replace`. May be either a URL or the pieces of a
* URL path.
*/
type To = string | Partial<Path$1>;
/**
* Map of routeId -> data returned from a loader/action/error
*/
interface RouteData {
[routeId: string]: any;
}
declare enum ResultType {
data = "data",
deferred = "deferred",
redirect = "redirect",
error = "error"
}
/**
* Successful result from a loader or action
*/
interface SuccessResult {
type: ResultType.data;
data: any;
statusCode?: number;
headers?: Headers;
}
/**
* Successful defer() result from a loader or action
*/
interface DeferredResult {
type: ResultType.deferred;
deferredData: DeferredData;
statusCode?: number;
headers?: Headers;
}
/**
* Redirect result from a loader or action
*/
interface RedirectResult {
type: ResultType.redirect;
status: number;
location: string;
revalidate: boolean;
reloadDocument?: boolean;
}
/**
* Unsuccessful result from a loader or action
*/
interface ErrorResult {
type: ResultType.error;
error: any;
headers?: Headers;
}
/**
* Result from a loader or action - potentially successful or unsuccessful
*/
type DataResult = SuccessResult | DeferredResult | RedirectResult | ErrorResult;
type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>;
/**
* Active navigation/fetcher form methods are exposed in lowercase on the
* RouterState
*/
type FormMethod = LowerCaseFormMethod;
/**
* In v7, active navigation/fetcher form methods are exposed in uppercase on the
* RouterState. This is to align with the normalization done via fetch().
*/
type V7_FormMethod = UpperCaseFormMethod;
type FormEncType = "application/x-www-form-urlencoded" | "multipart/form-data" | "application/json" | "text/plain";
type JsonObject = {
[Key in string]: JsonValue;
} & {
[Key in string]?: JsonValue | undefined;
};
type JsonArray = JsonValue[] | readonly JsonValue[];
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
/**
* @private
* Internal interface to pass around for action submissions, not intended for
* external consumption
*/
type Submission = {
formMethod: FormMethod | V7_FormMethod;
formAction: string;
formEncType: FormEncType;
formData: FormData;
json: undefined;
text: undefined;
} | {
formMethod: FormMethod | V7_FormMethod;
formAction: string;
formEncType: FormEncType;
formData: undefined;
json: JsonValue;
text: undefined;
} | {
formMethod: FormMethod | V7_FormMethod;
formAction: string;
formEncType: FormEncType;
formData: undefined;
json: undefined;
text: string;
};
/**
* @private
* Arguments passed to route loader/action functions. Same for now but we keep
* this as a private implementation detail in case they diverge in the future.
*/
interface DataFunctionArgs {
request: Request;
params: Params;
context?: any;
}
/**
* Arguments passed to loader functions
*/
interface LoaderFunctionArgs extends DataFunctionArgs {
}
/**
* Arguments passed to action functions
*/
interface ActionFunctionArgs extends DataFunctionArgs {
}
/**
* Loaders and actions can return anything except `undefined` (`null` is a
* valid return value if there is no data to return). Responses are preferred
* and will ease any future migration to Remix
*/
type DataFunctionValue = Response | NonNullable<unknown> | null;
/**
* Route loader function signature
*/
interface LoaderFunction {
(args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
}
/**
* Route action function signature
*/
interface ActionFunction {
(args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
}
/**
* Route shouldRevalidate function signature. This runs after any submission
* (navigation or fetcher), so we flatten the navigation/fetcher submission
* onto the arguments. It shouldn't matter whether it came from a navigation
* or a fetcher, what really matters is the URLs and the formData since loaders
* have to re-run based on the data models that were potentially mutated.
*/
interface ShouldRevalidateFunction {
(args: {
currentUrl: URL;
currentParams: AgnosticDataRouteMatch["params"];
nextUrl: URL;
nextParams: AgnosticDataRouteMatch["params"];
formMethod?: Submission["formMethod"];
formAction?: Submission["formAction"];
formEncType?: Submission["formEncType"];
text?: Submission["text"];
formData?: Submission["formData"];
json?: Submission["json"];
actionResult?: DataResult;
defaultShouldRevalidate: boolean;
}): boolean;
}
/**
* Keys we cannot change from within a lazy() function. We spread all other keys
* onto the route. Either they're meaningful to the router, or they'll get
* ignored.
*/
type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
type RequireOne<T, Key = keyof T> = Exclude<{
[K in keyof T]: K extends Key ? Omit<T, K> & Required<Pick<T, K>> : never;
}[keyof T], undefined>;
/**
* lazy() function to load a route definition, which can add non-matching
* related properties to a route
*/
interface LazyRouteFunction<R extends AgnosticRouteObject> {
(): Promise<RequireOne<Omit<R, ImmutableRouteKey>>>;
}
/**
* Base RouteObject with common props shared by all types of routes
*/
type AgnosticBaseRouteObject = {
caseSensitive?: boolean;
path?: string;
id?: string;
loader?: LoaderFunction;
action?: ActionFunction;
hasErrorBoundary?: boolean;
shouldRevalidate?: ShouldRevalidateFunction;
handle?: any;
lazy?: LazyRouteFunction<AgnosticBaseRouteObject>;
};
/**
* Index routes must not have children
*/
type AgnosticIndexRouteObject = AgnosticBaseRouteObject & {
children?: undefined;
index: true;
};
/**
* Non-index routes may have children, but cannot have index
*/
type AgnosticNonIndexRouteObject = AgnosticBaseRouteObject & {
children?: AgnosticRouteObject[];
index?: false;
};
/**
* A route object represents a logical route, with (optionally) its child
* routes organized in a tree-like structure.
*/
type AgnosticRouteObject = AgnosticIndexRouteObject | AgnosticNonIndexRouteObject;
type AgnosticDataIndexRouteObject = AgnosticIndexRouteObject & {
id: string;
};
type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObject & {
children?: AgnosticDataRouteObject[];
id: string;
};
/**
* A data route object, which is just a RouteObject with a required unique ID
*/
type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
/**
* The parameters that were parsed from the URL path.
*/
type Params<Key extends string = string> = {
readonly [key in Key]: string | undefined;
};
/**
* A RouteMatch contains info about how a route matched a URL.
*/
interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject> {
/**
* The names and values of dynamic parameters in the URL.
*/
params: Params<ParamKey>;
/**
* The portion of the URL pathname that was matched.
*/
pathname: string;
/**
* The portion of the URL pathname that was matched before child routes.
*/
pathnameBase: string;
/**
* The route object that was used to match.
*/
route: RouteObjectType;
}
interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
}
declare class DeferredData {
private pendingKeysSet;
private controller;
private abortPromise;
private unlistenAbortSignal;
private subscribers;
data: Record<string, unknown>;
init?: ResponseInit;
deferredKeys: string[];
constructor(data: Record<string, unknown>, responseInit?: ResponseInit);
private trackPromise;
private onSettle;
private emit;
subscribe(fn: (aborted: boolean, settledKey?: string) => void): () => boolean;
cancel(): void;
resolveData(signal: AbortSignal): Promise<boolean>;
get done(): boolean;
get unwrappedData(): {};
get pendingKeys(): string[];
}
/**
* State maintained internally by the router. During a navigation, all states
* reflect the the "old" location unless otherwise noted.
*/
interface RouterState {
/**
* The action of the most recent navigation
*/
historyAction: Action;
/**
* The current location reflected by the router
*/
location: Location;
/**
* The current set of route matches
*/
matches: AgnosticDataRouteMatch[];
/**
* Tracks whether we've completed our initial data load
*/
initialized: boolean;
/**
* Current scroll position we should start at for a new view
* - number -> scroll position to restore to
* - false -> do not restore scroll at all (used during submissions)
* - null -> don't have a saved position, scroll to hash or top of page
*/
restoreScrollPosition: number | false | null;
/**
* Indicate whether this navigation should skip resetting the scroll position
* if we are unable to restore the scroll position
*/
preventScrollReset: boolean;
/**
* Tracks the state of the current navigation
*/
navigation: Navigation;
/**
* Tracks any in-progress revalidations
*/
revalidation: RevalidationState;
/**
* Data from the loaders for the current matches
*/
loaderData: RouteData;
/**
* Data from the action for the current matches
*/
actionData: RouteData | null;
/**
* Errors caught from loaders for the current matches
*/
errors: RouteData | null;
/**
* Map of current fetchers
*/
fetchers: Map<string, Fetcher>;
/**
* Map of current blockers
*/
blockers: Map<string, Blocker>;
}
/**
* Data that can be passed into hydrate a Router from SSR
*/
type HydrationState = Partial<Pick<RouterState, "loaderData" | "actionData" | "errors">>;
type RelativeRoutingType = "route" | "path";
/**
* Potential states for state.navigation
*/
type NavigationStates = {
Idle: {
state: "idle";
location: undefined;
formMethod: undefined;
formAction: undefined;
formEncType: undefined;
formData: undefined;
json: undefined;
text: undefined;
};
Loading: {
state: "loading";
location: Location;
formMethod: Submission["formMethod"] | undefined;
formAction: Submission["formAction"] | undefined;
formEncType: Submission["formEncType"] | undefined;
formData: Submission["formData"] | undefined;
json: Submission["json"] | undefined;
text: Submission["text"] | undefined;
};
Submitting: {
state: "submitting";
location: Location;
formMethod: Submission["formMethod"];
formAction: Submission["formAction"];
formEncType: Submission["formEncType"];
formData: Submission["formData"];
json: Submission["json"];
text: Submission["text"];
};
};
type Navigation = NavigationStates[keyof NavigationStates];
type RevalidationState = "idle" | "loading";
/**
* Potential states for fetchers
*/
type FetcherStates<TData = any> = {
Idle: {
state: "idle";
formMethod: undefined;
formAction: undefined;
formEncType: undefined;
text: undefined;
formData: undefined;
json: undefined;
data: TData | undefined;
" _hasFetcherDoneAnything "?: boolean;
};
Loading: {
state: "loading";
formMethod: Submission["formMethod"] | undefined;
formAction: Submission["formAction"] | undefined;
formEncType: Submission["formEncType"] | undefined;
text: Submission["text"] | undefined;
formData: Submission["formData"] | undefined;
json: Submission["json"] | undefined;
data: TData | undefined;
" _hasFetcherDoneAnything "?: boolean;
};
Submitting: {
state: "submitting";
formMethod: Submission["formMethod"];
formAction: Submission["formAction"];
formEncType: Submission["formEncType"];
text: Submission["text"];
formData: Submission["formData"];
json: Submission["json"];
data: TData | undefined;
" _hasFetcherDoneAnything "?: boolean;
};
};
type Fetcher<TData = any> = FetcherStates<TData>[keyof FetcherStates<TData>];
interface BlockerBlocked {
state: "blocked";
reset(): void;
proceed(): void;
location: Location;
}
interface BlockerUnblocked {
state: "unblocked";
reset: undefined;
proceed: undefined;
location: undefined;
}
interface BlockerProceeding {
state: "proceeding";
reset: undefined;
proceed: undefined;
location: Location;
}
type Blocker = BlockerUnblocked | BlockerBlocked | BlockerProceeding;
interface NavigateOptions$1 {
replace?: boolean;
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
}
/**
* NOTE: If you refactor this to split up the modules into separate files,
* you'll need to update the rollup config for react-router-dom-v5-compat.
*/
declare global {
var __staticRouterHydrationData: HydrationState | undefined;
}
interface StoryData {
viewMode?: string;
storyId?: string;
refId?: string;
}
interface Other$1 extends StoryData {
path: string;
singleStory?: boolean;
}
type NavigateOptions = NavigateOptions$1 & {
plain?: boolean;
};
type NavigateFunction = (to: To | number, options?: NavigateOptions) => void;
type RouterData = {
location: Partial<Location>;
navigate: NavigateFunction;
} & Other$1;
type RenderData = Pick<RouterData, 'location'> & Other$1;
interface ThemeVars extends ThemeVarsBase, ThemeVarsColors {
}
interface ThemeVarsBase {
base: 'light' | 'dark';
}
interface ThemeVarsColors {
colorPrimary: string;
colorSecondary: string;
appBg: string;
appContentBg: string;
appHoverBg: string;
appPreviewBg: string;
appBorderColor: string;
appBorderRadius: number;
fontBase: string;
fontCode: string;
textColor: string;
textInverseColor: string;
textMutedColor: string;
barTextColor: string;
barHoverColor: string;
barSelectedColor: string;
barBg: string;
buttonBg: string;
buttonBorder: string;
booleanBg: string;
booleanSelectedBg: string;
inputBg: string;
inputBorder: string;
inputTextColor: string;
inputBorderRadius: number;
brandTitle?: string;
brandUrl?: string;
brandImage?: string;
brandTarget?: string;
gridCellSize?: number;
}
type ChannelHandler = (event: ChannelEvent) => void;
interface ChannelTransport {
send(event: ChannelEvent, options?: any): void;
setHandler(handler: ChannelHandler): void;
}
interface ChannelEvent {
type: string;
from: string;
args: any[];
}
interface Listener$1 {
(...args: any[]): void;
}
interface ChannelArgsSingle {
transport?: ChannelTransport;
async?: boolean;
}
interface ChannelArgsMulti {
transports: ChannelTransport[];
async?: boolean;
}
declare class Channel {
readonly isAsync: boolean;
private sender;
private events;
private data;
private readonly transports;
constructor(input: ChannelArgsMulti);
constructor(input: ChannelArgsSingle);
get hasTransport(): boolean;
addListener(eventName: string, listener: Listener$1): void;
emit(eventName: string, ...args: any): void;
last(eventName: string): any;
eventNames(): string[];
listenerCount(eventName: string): number;
listeners(eventName: string): Listener$1[] | undefined;
once(eventName: string, listener: Listener$1): void;
removeAllListeners(eventName?: string): void;
removeListener(eventName: string, listener: Listener$1): void;
on(eventName: string, listener: Listener$1): void;
off(eventName: string, listener: Listener$1): void;
private handleEvent;
private onceListener;
}
interface SubState$9 {
globals?: Globals;
userGlobals?: Globals;
storyGlobals?: Globals;
globalTypes?: GlobalTypes;
}
interface SubState$8 {
layout: API_Layout$1;
layoutCustomisations: API_LayoutCustomisations$1;
ui: API_UI$1;
selectedPanel: string | undefined;
theme: ThemeVars$1;
}
interface SubState$7 {
notifications: API_Notification$1[];
}
interface SubState$6 {
refs: API_Refs$1;
}
interface SubState$5 {
settings: API_Settings$1;
}
interface SubState$4 {
shortcuts: API_Shortcuts;
}
type API_KeyCollection = string[];
interface API_Shortcuts {
fullScreen: API_KeyCollection;
togglePanel: API_KeyCollection;
panelPosition: API_KeyCollection;
toggleNav: API_KeyCollection;
toolbar: API_KeyCollection;
search: API_KeyCollection;
focusNav: API_KeyCollection;
focusIframe: API_KeyCollection;
focusPanel: API_KeyCollection;
prevComponent: API_KeyCollection;
nextComponent: API_KeyCollection;
prevStory: API_KeyCollection;
nextStory: API_KeyCollection;
shortcutsPage: API_KeyCollection;
aboutPage: API_KeyCollection;
escape: API_KeyCollection;
collapseAll: API_KeyCollection;
expandAll: API_KeyCollection;
remount: API_KeyCollection;
openInEditor: API_KeyCollection;
copyStoryLink: API_KeyCollection;
}
interface SubState$3 extends API_LoadedRefData$1 {
storyId: StoryId;
internal_index?: API_PreparedStoryIndex$1;
viewMode: API_ViewMode$1;
filters: Record<string, API_FilterFunction$1>;
}
interface SubState$2 {
customQueryParams: QueryParams;
}
interface QueryParams {
[key: string]: string | undefined;
}
interface SubState$1 {
versions: API_Versions$2 & API_UnknownEntries$1;
lastVersionCheck: number;
dismissedVersionNotification: undefined | string;
}
type SubState = {
whatsNewData?: WhatsNewData;
};
type State = SubState$8 & SubState$3 & SubState$6 & SubState$7 & SubState$1 & SubState$2 & SubState$4 & SubState$5 & SubState$9 & SubState & RouterData$1 & API_OptionsData$1 & Other;
interface Other {
[key: string]: any;
}
type EnvironmentType = (typeof UniversalStore.Environment)[keyof typeof UniversalStore.Environment];
type StatusType = (typeof UniversalStore.Status)[keyof typeof UniversalStore.Status];
type StateUpdater<TState> = (prevState: TState) => TState;
type Actor = {
id: string;
type: (typeof UniversalStore.ActorType)[keyof typeof UniversalStore.ActorType];
environment: EnvironmentType;
};
type EventInfo = {
actor: Actor;
forwardingActor?: Actor;
};
type Listener<TEvent> = (event: TEvent, eventInfo: EventInfo) => void;
type BaseEvent = {
type: string;
payload?: any;
};
interface SetStateEvent<TState> extends BaseEvent {
type: typeof UniversalStore.InternalEventType.SET_STATE;
payload: {
state: TState;
previousState: TState;
};
}
interface ExistingStateRequestEvent extends BaseEvent {
type: typeof UniversalStore.InternalEventType.EXISTING_STATE_REQUEST;
payload: never;
}
interface ExistingStateResponseEvent<TState> extends BaseEvent {
type: typeof UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE;
payload: TState;
}
interface LeaderCreatedEvent extends BaseEvent {
type: typeof UniversalStore.InternalEventType.LEADER_CREATED;
payload: never;
}
interface FollowerCreatedEvent extends BaseEvent {
type: typeof UniversalStore.InternalEventType.FOLLOWER_CREATED;
payload: never;
}
type InternalEvent<TState> = SetStateEvent<TState> | ExistingStateRequestEvent | ExistingStateResponseEvent<TState> | FollowerCreatedEvent | LeaderCreatedEvent;
type Event<TState, TEvent extends BaseEvent> = TEvent | InternalEvent<TState>;
type ChannelLike = Pick<Channel$1, 'on' | 'off' | 'emit'>;
type StoreOptions<TState> = {
id: string;
leader?: boolean;
initialState?: TState;
debug?: boolean;
};
type EnvironmentOverrides = {
channel: ChannelLike;
environment: EnvironmentType;
};
/**
* A universal store implementation that synchronizes state across different environments using a
* channel-based communication.
*
* The store follows a leader-follower pattern where:
*
* - Leader: The main store instance that owns and manages the state
* - Follower: Store instances that mirror the leader's state
*
* Features:
*
* - State synchronization across environments
* - Event-based communication
* - Type-safe state and custom events
* - Subscription system for state changes and custom events
*
* @remarks
* - The store must be created using the static `create()` method, not the constructor
* - Follower stores will automatically sync with their leader's state. If they have initial state, it
* will be replaced immediately when it has synced with the leader.
*
* @example
*
* ```typescript
* interface MyState {
* count: number;
* }
* interface MyCustomEvent {
* type: 'INCREMENT';
* payload: number;
* }
*
* // Create a leader store
* const leaderStore = UniversalStore.create<MyState, MyCustomEvent>({
* id: 'my-store',
* leader: true,
* initialState: { count: 0 },
* });
*
* // Create a follower store
* const followerStore = UniversalStore.create<MyState, MyCustomEvent>({
* id: 'my-store',
* leader: false,
* });
* ```
*
* @template State - The type of state managed by the store
* @template CustomEvent - Custom events that can be sent through the store. Must have a `type`
* string and optional `payload`
* @throws {Error} If constructed directly instead of using `create()`
* @throws {Error} If created without setting a channel first
* @throws {Error} If a follower is created with initial state
* @throws {Error} If a follower cannot find its leader within 1 second
*/
declare class UniversalStore<State, CustomEvent extends {
type: string;
payload?: any;
} = {
type: string;
payload?: any;
}> {
/**
* Defines the possible actor types in the store system
*
* @readonly
*/
static readonly ActorType: {
readonly LEADER: "LEADER";
readonly FOLLOWER: "FOLLOWER";
};
/**
* Defines the possible environments the store can run in
*
* @readonly
*/
static readonly Environment: {
readonly SERVER: "SERVER";
readonly MANAGER: "MANAGER";
readonly PREVIEW: "PREVIEW";
readonly UNKNOWN: "UNKNOWN";
readonly MOCK: "MOCK";
};
/**
* Internal event types used for store synchronization
*
* @readonly
*/
static readonly InternalEventType: {
readonly EXISTING_STATE_REQUEST: "__EXISTING_STATE_REQUEST";
readonly EXISTING_STATE_RESPONSE: "__EXISTING_STATE_RESPONSE";
readonly SET_STATE: "__SET_STATE";
readonly LEADER_CREATED: "__LEADER_CREATED";
readonly FOLLOWER_CREATED: "__FOLLOWER_CREATED";
};
static readonly Status: {
readonly UNPREPARED: "UNPREPARED";
readonly SYNCING: "SYNCING";
readonly READY: "READY";
readonly ERROR: "ERROR";
};
protected static isInternalConstructing: boolean;
/**
* The preparation construct is used to keep track of all store's preparation state the promise is
* resolved when the store is prepared with the static __prepare() method which will also change
* the state from PENDING to RESOLVED
*/
private static preparation;
private static setupPreparationPromise;
/** Enable debug logs for this store */
debugging: boolean;
/** The actor object representing the store instance with a unique ID and a type */
get actor(): Actor;
/**
* The current state of the store, that signals both if the store is prepared by Storybook and
* also - in the case of a follower - if the state has been synced with the leader's state.
*/
get status(): StatusType;
/**
* A promise that resolves when the store is fully ready. A leader will be ready when the store
* has been prepared by Storybook, which is almost instantly.
*
* A follower will be ready when the state has been synced with the leader's state, within a few
* hundred milliseconds.
*/
untilReady(): Promise<[{
channel: ChannelLike;
environment: EnvironmentType;
}, void | undefined]>;
/**
* The syncing construct is used to keep track of if the instance's state has been synced with the
* other instances. A leader will immediately have the promise resolved. A follower will initially
* be in a PENDING state, and resolve the the leader has sent the existing state, or reject if no
* leader has responded before the timeout.
*/
private syncing?;
private channelEventName;
private state;
private channel?;
private environment?;
private listeners;
private id;
private actorId;
private actorType;
protected constructor(options: StoreOptions<State>, environmentOverrides?: EnvironmentOverrides);
/** Creates a new instance of UniversalStore */
static create<State = any, CustomEvent extends {
type: string;
payload?: any;
} = {
type: string;
payload?: any;
}>(options: StoreOptions<State>): UniversalStore<State, CustomEvent>;
/** Gets the current state */
getState: () => State;
/**
* Updates the store's state
*
* Either a new state or a state updater function can be passed to the method.
*/
setState(updater: State | StateUpdater<State>): void;
/**
* Subscribes to store events
*
* @returns A function to unsubscribe
*/
subscribe: {
(listener: Listener<Event<State, CustomEvent>>): () => void;
<EventType extends Event<State, CustomEvent>['type']>(eventType: EventType, listener: Listener<Extract<Event<State, CustomEvent>, {
type: EventType;
}>>): () => void;
};
/**
* Subscribes to state changes
*
* @returns Unsubscribe function
*/
onStateChange(listener: (state: State, previousState: State, eventInfo: EventInfo) => void): () => void;
/** Sends a custom event to the other stores */
send: (event: CustomEvent) => void;
private emitToChannel;
private prepareThis;
private emitToListeners;
private handleChannelEvents;
private debug;
}
/**
* A mock universal store that can be used when testing code that relies on a universal store. It
* functions exactly like a normal universal store, with a few exceptions:
*
* - It is fully isolated, meaning that it doesn't interact with any channel, and it is always a
* leader.
*
* If the second testUtils argument is provided, all the public methods are spied on, so they can be
* asserted.
*
* When a mock store is re-used across tests (eg. in stories), you manually need to reset the state
* after each test.
*
* @example
*
* ```ts
* import * as testUtils from 'storybook/test'; // in stories
* import { vi as testUtils } from 'vitest'; // ... or in Vitest tests
*
* const initialState = { ... };
* const store = new MockUniversalStore({ initialState }, testUtils);
*
* export default {
* title: 'My story',
* beforeEach: () => {
* return () => {
* store.setState(initialState);
* };
* }
* }
* ```
*/
declare class MockUniversalStore<State, CustomEvent extends {
type: string;
payload?: any;
} = {
type: string;
payload?: any;
}> extends UniversalStore<State, CustomEvent> {
private testUtils;
constructor(options: StoreOptions<State>, testUtils?: any);
/** Create a mock universal store. This is just an alias for the constructor */
static create<State = any, CustomEvent extends {
type: string;
payload?: any;
} = {
type: string;
payload?: any;
}>(options: StoreOptions<State>, testUtils?: any): MockUniversalStore<State, CustomEvent>;
unsubscribeAll(): void;
}
type StatusValue = 'status-value:pending' | 'status-value:success' | 'status-value:error' | 'status-value:warning' | 'status-value:unknown';
type StatusTypeId = string;
type StatusByTypeId = Record<StatusTypeId, Status>;
type StatusesByStoryIdAndTypeId = Record<StoryId$1, StatusByTypeId>;
interface Status {
value: StatusValue;
typeId: StatusTypeId;
storyId: StoryId$1;
title: string;
description: string;
data?: any;
sidebarContextMenu?: boolean;
}
type StatusStore = {
getAll: () => StatusesByStoryIdAndTypeId;
set: (statuses: Status[]) => void;
onAllStatusChange: (listener: (statuses: StatusesByStoryIdAndTypeId, previousStatuses: StatusesByStoryIdAndTypeId) => void) => () => void;
onSelect: (listener: (selectedStatuses: Status[]) => void) => () => void;
unset: (storyIds?: StoryId$1[]) => void;
};
type StatusStoreByTypeId = StatusStore & {
typeId: StatusTypeId;
};
type UseStatusStore = <T = StatusesByStoryIdAndTypeId>(selector?: (statuses: StatusesByStoryIdAndTypeId) => T) => T;
type TestProviderState = 'test-provider-state:pending' | 'test-provider-state:running' | 'test-provider-state:succeeded' | 'test-provider-state:crashed';
type TestProviderId = string;
type TestProviderStateByProviderId = Record<TestProviderId, TestProviderState>;
type BaseTestProviderStore = {
/**
* Notifies all listeners that settings have changed for test providers. The Storybook UI will
* highlight the test providers to tell the user that settings has changed.
*/
settingsChanged: () => void;
/**
* Subscribe to clicks on the "Run All" button, that is supposed to trigger all test providers to
* run. Your test provider should do the "main thing" when this happens, similar to when the user
* triggers your test provider specifically.
*
* @example
*
* ```typescript
* // Subscribe to run-all events
* const unsubscribe = myTestProviderStore.onRunAll(() => {
* await runAllMyTests();
* });
* ```
*/
onRunAll: (listener: () => void) => () => void;
/**
* Subscribe to clicks on the "Clear All" button, that is supposed to clear all state from test
* providers. Storybook already clears all statuses, but if your test provider has more
* non-status-based state, you can use this to clear that here.
*
* @remarks
* The purpose of this is _not_ to clear your test provider's settings, only the test results.
* @example
*
* ```typescript
* // Subscribe to clear-all events
* const unsubscribe = myTestProviderStore.onClearAll(() => {
* clearMyTestResults();
* });
*
* // Later, when no longer needed
* unsubscribe();
* ```
*/
onClearAll: (listener: () => void) => () => void;
};
/**
* Represents a store for a specific test provider, identified by its unique ID. This store provides
* methods to manage the state of an individual test provider, including getting and setting its
* state, running operations with automatic state management, and accessing its unique identifier.
*
* Each test provider has its own instance of this store, allowing for independent state management
* across different test providers in the application.
*
* @example
*
* ```typescript
* // Get a store for a specific test provider
* const grammarStore = getTestProviderStoreById('addon-grammar');
*
* // Check the current state
* if (grammarStore.getState() === 'test-provider-state:pending') {
* console.log('Grammar tests are ready to run');
* }
*
* // Run tests with automatic state management
* grammarStore.runWithState(async () => {
* await runGrammarTests();
* });
* ```
*
* @see {@link TestProviderState} for possible state values
* @see {@link BaseTestProviderStore} for methods inherited from the base store
*/
type TestProviderStoreById = BaseTestProviderStore & {
/**
* Gets the current state of this specific test provider
*
* The state represents the current execution status of the test provider, which can be one of the
* following:
*
* - 'test-provider-state:pending': Tests have not been run yet
* - 'test-provider-state:running': Tests are currently running
* - 'test-provider-state:succeeded': Tests completed successfully
* - 'test-provider-state:crashed': Running tests failed or encountered an error
*
* Storybook UI will use this state to determine what to show in the UI.
*
* @remarks
* The 'test-provider-state:crashed' is meant to signify that the test run as a whole failed to
* execute for some reason. It should _not_ be set just because a number of tests failed, use
* statuses and the status store for that. See {@link TestStatusStore} for managing individual test
* statuses.
* @example
*
* ```typescript
* // Get the current state of a specific test provider
* const state = testProviderStore.getState();
*
* // Conditionally render UI based on the state
* const TestStatus = () => {
* const state = testProviderStore.getState();
*
* if (state === 'test-provider-state:running') {
* return <Spinner />;
* } else if (state === 'test-provider-state:succeeded') {
* return <SuccessIcon />;
* } else if (state === 'test-provider-state:crashed') {
* return <ErrorIcon />;
* }
*
* return <PendingIcon />;
* };
* ```
*/
getState: () => TestProviderState;
/**
* Sets the state of this specific test provider
*
* This method allows you to manually update the execution state of the test provider. It's
* typically used when you need to reflect the current status of test execution in the UI or when
* you want to programmatically control the test provider's state.
*
* Common use cases include:
*
* - Setting to 'running' when tests start
* - Setting to 'succeeded' when tests complete successfully
* - Setting to 'crashed' when tests fail or encounter errors
* - Setting to 'pending' to reset the state
*
* The state represents the current execution status of the test provider, which can be one of the
* following:
*
* - 'test-provider-state:pending': Tests have not been run yet
* - 'test-provider-state:running': Tests are currently running
* - 'test-provider-state:succeeded': Tests completed successfully
* - 'test-provider-state:crashed': Running tests failed or encountered an error
*
* Storybook UI will use this state to determine what to show in the UI.
*
* @remarks
* The 'test-provider-state:crashed' is meant to signify that the test run as a whole failed to
* execute for some reason. It should _not_ be set just because a number of tests failed, use
* statuses and the status store for that. See {@link TestStatusStore} for managing individual test
* statuses.
*
* For most use cases, consider using {@link runWithState} instead, which provides automatic state
* management and error handling during test execution.
* @example
*
* ```typescript
* // Update the state when tests start running
* const startTests = async () => {
* testProviderStore.setState('test-provider-state:running');
* ... run tests ...
* };
* ```
*/
setState: (state: TestProviderState) => void;
/**
* Runs a callback and automatically updates the test provider's state with running, succeeded or
* crashed, depending on the end result.
*
* - Immediately changes the state to 'running'
* - If the callback returns/resolves, change the state to 'succeeded'.
* - If the callback throws an error/rejects, change the state to 'crashed'.
*
* This approach helps prevent state inconsistencies that might occur if exceptions are thrown
* during test execution.
*
* @example
*
* ```typescript
* // Run tests with automatic state management
* const runTests = () => {
* testProviderStore.runWithState(async () => {
* // The state is automatically set to 'running' before this callback
*
* // Run tests here...
* const results = await executeTests();
* });
* };
* ```
*/
runWithState: (callback: () => void | Promise<void>) => Promise<void>;
/** The unique identifier for this test provider */
testProviderId: TestProviderId;
};
/**
* React OR preview hook for accessing the state of _all_ test providers. This hook will only
* trigger a re-render when the state changes. It is recommended to pass the optional selector, to
* get more fine-grained control of re-renders.
*
* @example
*
* ```typescript
* const TestStatus = () => {
* const state = useTestProviderStore((state) => state['my-test-provider']);
* };
* ```
*/
type UseTestProviderStore = <T = TestProviderStateByProviderId>(
/**
* Optional selector function to extract or transform specific parts of the state
*
* @example
*
* ```typescript
* // Use the entire state
* const allProviderStates = useTestProviderStore();
*
* // Get state for a specific provider
* const myProviderState = useTestProviderStore((state) => state['my-test-provider']);
*
* // Get a count of providers in each state
* const statusCounts = useTestProviderStore((state) => {
* const counts = {
* pending: 0,
* running: 0,
* succeeded: 0,
* crashed: 0,
* };
*
* Object.values(state).forEach((status) => {
* if (status === 'test-provider-state:pending') counts.pending++;
* else if (status === 'test-provider-state:running') counts.running++;
* else if (status === 'test-provider-state:succeeded') counts.succeeded++;
* else if (status === 'test-provider-state:crashed') counts.crashed++;
* });
*
* return counts;
* });
*
* // Check if all tests have completed
* const allTestsCompleted = useTestProviderStore((state) => {
* return Object.values(state).every(
* (status) =>
* status === 'test-provider-state:succeeded' ||
* status === 'test-provider-state:crashed'
* );
* });
* ```
*/
selector?: (state: TestProviderStateByProviderId) => T) => T;
interface Options$1 {
allowRegExp: boolean;
allowSymbol: boolean;
allowDate: boolean;
allowUndefined: boolean;
allowError: boolean;
maxDepth: number;
space: number | undefined;
}
/**
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
@category Type
*/
type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint;
declare global {
interface SymbolConstructor {
readonly observable: symbol;
}
}
/**
Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
Currently, when a union type of a primitive type is combined with literal types, TypeScript loses all information about the combined literals. Thus, when such type is used in an IDE with autocompletion, no suggestions are made for the declared literals.
This type is a workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). It will be removed as soon as it's not needed anymore.
@example
```
import type {LiteralUnion} from 'type-fest';
// Before
type Pet = 'dog' | 'cat' | string;
const pet: Pet = '';
// Start typing in your TypeScript-enabled IDE.
// You **will not** get auto-completion for `dog` and `cat` literals.
// After
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
const pet: Pet2 = '';
// You **will** get auto-completion for `dog` and `cat` literals.
```
@category Type
*/
type LiteralUnion<
LiteralType,
BaseType extends Primitive,
> = LiteralType | (BaseType & Record<never, never>);
declare namespace PackageJson$1 {
/**
A person who has been involved in creating or maintaining the package.
*/
export type Person =
| string
| {
name: string;
url?: string;
email?: string;
};
export type BugsLocation =
| string
| {
/**
The URL to the package's issue tracker.
*/
url?: string;
/**
The email address to which issues should be reported.
*/
email?: string;
};
export interface DirectoryLocations {
[directoryType: string]: unknown;
/**
Location for executable scripts. Sugar to generate entries in the `bin` property by walking the folder.
*/
bin?: string;
/**
Location for Markdown files.
*/
doc?: string;
/**
Location for example scripts.
*/
example?: string;
/**
Location for the bulk of the library.
*/
lib?: string;
/**
Location for man pages. Sugar to generate a `man` array by walking the folder.
*/
man?: string;
/**
Location for test files.
*/
test?: string;
}
export type Scripts = {
/**
Run **before** the package is published (Also run on local `npm install` without any arguments).
*/
prepublish?: string;
/**
Run both **before** the package is packed and published, and on local `npm install` without any arguments. This is run **after** `prepublish`, but **before** `prepublishOnly`.
*/
prepare?: string;
/**
Run **before** the package is prepared and packed, **only** on `npm publish`.
*/
prepublishOnly?: string;
/**
Run **before** a tarball is packed (on `npm pack`, `npm publish`, and when installing git dependencies).
*/
prepack?: string;
/**
Run **after** the tarball has been generated and moved to its final destination.
*/
postpack?: string;
/**
Run **after** the package is published.
*/
publish?: string;
/**
Run **after** the package is published.
*/
postpublish?: string;
/**
Run **before** the package is installed.
*/
preinstall?: string;
/**
Run **after** the package is installed.
*/
install?: string;
/**
Run **after** the package is installed and after `install`.
*/
postinstall?: string;
/**
Run **before** the package is uninstalled and before `uninstall`.
*/
preuninstall?: string;
/**
Run **before** the package is uninstalled.
*/
uninstall?: string;
/**
Run **after** the package is uninstalled.
*/
postuninstall?: string;
/**
Run **before** bump the package version and before `version`.
*/
preversion?: string;
/**
Run **before** bump the package version.
*/
version?: string;
/**
Run **after** bump the package version.
*/
postversion?: string;
/**
Run with the `npm test` command, before `test`.
*/
pretest?: string;
/**
Run with the `npm test` command.
*/
test?: string;
/**
Run with the `npm test` command, after `test`.
*/
posttest?: string;
/**
Run with the `npm stop` command, before `stop`.
*/
prestop?: string;
/**
Run with the `npm stop` command.
*/
stop?: string;
/**
Run with the `npm stop` command, after `stop`.
*/
poststop?: string;
/**
Run with the `npm start` command, before `start`.
*/
prestart?: string;
/**
Run with the `npm start` command.
*/
start?: string;
/**
Run with the `npm start` command, after `start`.
*/
poststart?: string;
/**
Run with the `npm restart` command, before `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
prerestart?: string;
/**
Run with the `npm restart` command. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
restart?: string;
/**
Run with the `npm restart` command, after `restart`. Note: `npm restart` will run the `stop` and `start` scripts if no `restart` script is provided.
*/
postrestart?: string;
} & Partial<Record<string, string>>;
/**
Dependencies of the package. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or Git URL.
*/
export type Dependency = Partial<Record<string, string>>;
/**
Conditions which provide a way to resolve a package entry point based on the environment.
*/
export type ExportCondition = LiteralUnion<
| 'import'
| 'require'
| 'node'
| 'node-addons'
| 'deno'
| 'browser'
| 'electron'
| 'react-native'
| 'default',
string
>;
type ExportConditions = {[condition in ExportCondition]: Exports};
/**
Entry points of a module, optionally with conditions and subpath exports.
*/
export type Exports =
| null
| string
| Array<string | ExportConditions>
| ExportConditions
| {[path: string]: Exports}; // eslint-disable-line @typescript-eslint/consistent-indexed-object-style
/**
Import map entries of a module, optionally with conditions.
*/
export type Imports = { // eslint-disable-line @typescript-eslint/consistent-indexed-object-style