storybook
Version:
Storybook: Develop, document, and test UI components in isolation
4,183 lines • 144 kB
JavaScript
import {
isTestEnvironment,
pauseAnimations,
waitForAnimations
} from "./chunk-LYCSRYYR.js";
import {
require_main
} from "./chunk-Y3M7TW6K.js";
import {
SNIPPET_RENDERED,
combineParameters
} from "./chunk-B4A3ADP3.js";
import {
isEqual
} from "./chunk-ZNRFDIVA.js";
import {
invariant
} from "./chunk-FUOHXXZT.js";
import {
require_ansi_to_html
} from "./chunk-F4Q6SGTB.js";
import {
mapValues,
pickBy
} from "./chunk-UTNZYD2N.js";
import {
isPlainObject
} from "./chunk-FSBVR7H5.js";
import {
require_memoizerific
} from "./chunk-NVV6MIOE.js";
import {
dedent
} from "./chunk-OPCDBBL3.js";
import {
__name,
__toESM
} from "./chunk-MM7DTO55.js";
// src/preview-api/modules/addons/main.ts
import { global } from "@storybook/global";
// src/preview-api/modules/addons/storybook-channel-mock.ts
import { Channel } from "storybook/internal/channels";
function mockChannel() {
const transport = {
setHandler: /* @__PURE__ */ __name(() => {
}, "setHandler"),
send: /* @__PURE__ */ __name(() => {
}, "send")
};
return new Channel({ transport });
}
__name(mockChannel, "mockChannel");
// src/preview-api/modules/addons/main.ts
var _AddonStore = class _AddonStore {
constructor() {
this.getChannel = /* @__PURE__ */ __name(() => {
if (!this.channel) {
const channel = mockChannel();
this.setChannel(channel);
return channel;
}
return this.channel;
}, "getChannel");
this.ready = /* @__PURE__ */ __name(() => this.promise, "ready");
this.hasChannel = /* @__PURE__ */ __name(() => !!this.channel, "hasChannel");
this.setChannel = /* @__PURE__ */ __name((channel) => {
this.channel = channel;
this.resolve();
}, "setChannel");
this.promise = new Promise((res) => {
this.resolve = () => res(this.getChannel());
});
}
};
__name(_AddonStore, "AddonStore");
var AddonStore = _AddonStore;
var KEY = "__STORYBOOK_ADDONS_PREVIEW";
function getAddonsStore() {
if (!global[KEY]) {
global[KEY] = new AddonStore();
}
return global[KEY];
}
__name(getAddonsStore, "getAddonsStore");
var addons = getAddonsStore();
// src/preview-api/modules/addons/hooks.ts
import { logger } from "storybook/internal/client-logger";
import {
FORCE_RE_RENDER,
RESET_STORY_ARGS,
STORY_RENDERED,
UPDATE_GLOBALS,
UPDATE_STORY_ARGS
} from "storybook/internal/core-events";
import { global as global2 } from "@storybook/global";
var _HooksContext = class _HooksContext {
constructor() {
this.hookListsMap = void 0;
this.mountedDecorators = void 0;
this.prevMountedDecorators = void 0;
this.currentHooks = void 0;
this.nextHookIndex = void 0;
this.currentPhase = void 0;
this.currentEffects = void 0;
this.prevEffects = void 0;
this.currentDecoratorName = void 0;
this.hasUpdates = void 0;
this.currentContext = void 0;
this.renderListener = /* @__PURE__ */ __name((storyId) => {
if (storyId !== this.currentContext?.id) {
return;
}
this.triggerEffects();
this.currentContext = null;
this.removeRenderListeners();
}, "renderListener");
this.init();
}
init() {
this.hookListsMap = /* @__PURE__ */ new WeakMap();
this.mountedDecorators = /* @__PURE__ */ new Set();
this.prevMountedDecorators = /* @__PURE__ */ new Set();
this.currentHooks = [];
this.nextHookIndex = 0;
this.currentPhase = "NONE";
this.currentEffects = [];
this.prevEffects = [];
this.currentDecoratorName = null;
this.hasUpdates = false;
this.currentContext = null;
}
clean() {
this.prevEffects.forEach((effect) => {
if (effect.destroy) {
effect.destroy();
}
});
this.init();
this.removeRenderListeners();
}
getNextHook() {
const hook = this.currentHooks[this.nextHookIndex];
this.nextHookIndex += 1;
return hook;
}
triggerEffects() {
this.prevEffects.forEach((effect) => {
if (!this.currentEffects.includes(effect) && effect.destroy) {
effect.destroy();
}
});
this.currentEffects.forEach((effect) => {
if (!this.prevEffects.includes(effect)) {
effect.destroy = effect.create();
}
});
this.prevEffects = this.currentEffects;
this.currentEffects = [];
}
addRenderListeners() {
this.removeRenderListeners();
const channel = addons.getChannel();
channel.on(STORY_RENDERED, this.renderListener);
}
removeRenderListeners() {
const channel = addons.getChannel();
channel.removeListener(STORY_RENDERED, this.renderListener);
}
};
__name(_HooksContext, "HooksContext");
var HooksContext = _HooksContext;
function hookify(fn) {
const hookified = /* @__PURE__ */ __name((...args) => {
const { hooks } = typeof args[0] === "function" ? args[1] : args[0];
const prevPhase = hooks.currentPhase;
const prevHooks = hooks.currentHooks;
const prevNextHookIndex = hooks.nextHookIndex;
const prevDecoratorName = hooks.currentDecoratorName;
hooks.currentDecoratorName = fn.name;
if (hooks.prevMountedDecorators.has(fn)) {
hooks.currentPhase = "UPDATE";
hooks.currentHooks = hooks.hookListsMap.get(fn) || [];
} else {
hooks.currentPhase = "MOUNT";
hooks.currentHooks = [];
hooks.hookListsMap.set(fn, hooks.currentHooks);
hooks.prevMountedDecorators.add(fn);
}
hooks.nextHookIndex = 0;
const prevContext = global2.STORYBOOK_HOOKS_CONTEXT;
global2.STORYBOOK_HOOKS_CONTEXT = hooks;
const result = fn(...args);
global2.STORYBOOK_HOOKS_CONTEXT = prevContext;
if (hooks.currentPhase === "UPDATE" && hooks.getNextHook() != null) {
throw new Error(
"Rendered fewer hooks than expected. This may be caused by an accidental early return statement."
);
}
hooks.currentPhase = prevPhase;
hooks.currentHooks = prevHooks;
hooks.nextHookIndex = prevNextHookIndex;
hooks.currentDecoratorName = prevDecoratorName;
return result;
}, "hookified");
hookified.originalFn = fn;
return hookified;
}
__name(hookify, "hookify");
var numberOfRenders = 0;
var RENDER_LIMIT = 25;
var applyHooks = /* @__PURE__ */ __name((applyDecorators) => (storyFn, decorators) => {
const decorated = applyDecorators(
hookify(storyFn),
decorators.map((decorator) => hookify(decorator))
);
return (context) => {
const { hooks } = context;
hooks.prevMountedDecorators ??= /* @__PURE__ */ new Set();
hooks.mountedDecorators = /* @__PURE__ */ new Set([storyFn, ...decorators]);
hooks.currentContext = context;
hooks.hasUpdates = false;
let result = decorated(context);
numberOfRenders = 1;
while (hooks.hasUpdates) {
hooks.hasUpdates = false;
hooks.currentEffects = [];
result = decorated(context);
numberOfRenders += 1;
if (numberOfRenders > RENDER_LIMIT) {
throw new Error(
"Too many re-renders. Storybook limits the number of renders to prevent an infinite loop."
);
}
}
hooks.addRenderListeners();
return result;
};
}, "applyHooks");
var areDepsEqual = /* @__PURE__ */ __name((deps, nextDeps) => deps.length === nextDeps.length && deps.every((dep, i) => dep === nextDeps[i]), "areDepsEqual");
var invalidHooksError = /* @__PURE__ */ __name(() => new Error("Storybook preview hooks can only be called inside decorators and story functions."), "invalidHooksError");
function getHooksContextOrNull() {
return global2.STORYBOOK_HOOKS_CONTEXT || null;
}
__name(getHooksContextOrNull, "getHooksContextOrNull");
function getHooksContextOrThrow() {
const hooks = getHooksContextOrNull();
if (hooks == null) {
throw invalidHooksError();
}
return hooks;
}
__name(getHooksContextOrThrow, "getHooksContextOrThrow");
function useHook(name, callback, deps) {
const hooks = getHooksContextOrThrow();
if (hooks.currentPhase === "MOUNT") {
if (deps != null && !Array.isArray(deps)) {
logger.warn(
`${name} received a final argument that is not an array (instead, received ${deps}). When specified, the final argument must be an array.`
);
}
const hook = { name, deps };
hooks.currentHooks.push(hook);
callback(hook);
return hook;
}
if (hooks.currentPhase === "UPDATE") {
const hook = hooks.getNextHook();
if (hook == null) {
throw new Error("Rendered more hooks than during the previous render.");
}
if (hook.name !== name) {
logger.warn(
`Storybook has detected a change in the order of Hooks${hooks.currentDecoratorName ? ` called by ${hooks.currentDecoratorName}` : ""}. This will lead to bugs and errors if not fixed.`
);
}
if (deps != null && hook.deps == null) {
logger.warn(
`${name} received a final argument during this render, but not during the previous render. Even though the final argument is optional, its type cannot change between renders.`
);
}
if (deps != null && hook.deps != null && deps.length !== hook.deps.length) {
logger.warn(`The final argument passed to ${name} changed size between renders. The order and size of this array must remain constant.
Previous: ${hook.deps}
Incoming: ${deps}`);
}
if (deps == null || hook.deps == null || !areDepsEqual(deps, hook.deps)) {
callback(hook);
hook.deps = deps;
}
return hook;
}
throw invalidHooksError();
}
__name(useHook, "useHook");
function useMemoLike(name, nextCreate, deps) {
const { memoizedState } = useHook(
name,
(hook) => {
hook.memoizedState = nextCreate();
},
deps
);
return memoizedState;
}
__name(useMemoLike, "useMemoLike");
function useMemo(nextCreate, deps) {
return useMemoLike("useMemo", nextCreate, deps);
}
__name(useMemo, "useMemo");
function useCallback(callback, deps) {
return useMemoLike("useCallback", () => callback, deps);
}
__name(useCallback, "useCallback");
function useRefLike(name, initialValue) {
return useMemoLike(name, () => ({ current: initialValue }), []);
}
__name(useRefLike, "useRefLike");
function useRef(initialValue) {
return useRefLike("useRef", initialValue);
}
__name(useRef, "useRef");
function triggerUpdate() {
const hooks = getHooksContextOrNull();
if (hooks != null && hooks.currentPhase !== "NONE") {
hooks.hasUpdates = true;
} else {
try {
addons.getChannel().emit(FORCE_RE_RENDER);
} catch (e) {
logger.warn("State updates of Storybook preview hooks work only in browser");
}
}
}
__name(triggerUpdate, "triggerUpdate");
function useStateLike(name, initialState) {
const stateRef = useRefLike(
name,
// @ts-expect-error S type should never be function, but there's no way to tell that to TypeScript
typeof initialState === "function" ? initialState() : initialState
);
const setState = /* @__PURE__ */ __name((update) => {
stateRef.current = typeof update === "function" ? update(stateRef.current) : update;
triggerUpdate();
}, "setState");
return [stateRef.current, setState];
}
__name(useStateLike, "useStateLike");
function useState(initialState) {
return useStateLike("useState", initialState);
}
__name(useState, "useState");
function useReducer(reducer, initialArg, init) {
const initialState = init != null ? () => init(initialArg) : initialArg;
const [state, setState] = useStateLike("useReducer", initialState);
const dispatch = /* @__PURE__ */ __name((action) => setState((prevState) => reducer(prevState, action)), "dispatch");
return [state, dispatch];
}
__name(useReducer, "useReducer");
function useEffect(create, deps) {
const hooks = getHooksContextOrThrow();
const effect = useMemoLike("useEffect", () => ({ create }), deps);
if (!hooks.currentEffects.includes(effect)) {
hooks.currentEffects.push(effect);
}
}
__name(useEffect, "useEffect");
function useChannel(eventMap, deps = []) {
const channel = addons.getChannel();
useEffect(() => {
Object.entries(eventMap).forEach(([type, listener]) => channel.on(type, listener));
return () => {
Object.entries(eventMap).forEach(
([type, listener]) => channel.removeListener(type, listener)
);
};
}, [...Object.keys(eventMap), ...deps]);
return useCallback(channel.emit.bind(channel), [channel]);
}
__name(useChannel, "useChannel");
function useStoryContext() {
const { currentContext } = getHooksContextOrThrow();
if (currentContext == null) {
throw invalidHooksError();
}
return currentContext;
}
__name(useStoryContext, "useStoryContext");
function useParameter(parameterKey, defaultValue) {
const { parameters } = useStoryContext();
if (parameterKey) {
return parameters[parameterKey] ?? defaultValue;
}
return void 0;
}
__name(useParameter, "useParameter");
function useArgs() {
const channel = addons.getChannel();
const { id: storyId, args } = useStoryContext();
const updateArgs = useCallback(
(updatedArgs) => channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }),
[channel, storyId]
);
const resetArgs = useCallback(
(argNames) => channel.emit(RESET_STORY_ARGS, { storyId, argNames }),
[channel, storyId]
);
return [args, updateArgs, resetArgs];
}
__name(useArgs, "useArgs");
function useGlobals() {
const channel = addons.getChannel();
const { globals } = useStoryContext();
const updateGlobals = useCallback(
(newGlobals) => channel.emit(UPDATE_GLOBALS, { globals: newGlobals }),
[channel]
);
return [globals, updateGlobals];
}
__name(useGlobals, "useGlobals");
// src/preview-api/modules/addons/make-decorator.ts
var makeDecorator = /* @__PURE__ */ __name(({
name,
parameterName,
wrapper,
skipIfNoParametersOrOptions = false
}) => {
const decorator = /* @__PURE__ */ __name((options) => (storyFn, context) => {
const parameters = context.parameters && context.parameters[parameterName];
if (parameters && parameters.disable) {
return storyFn(context);
}
if (skipIfNoParametersOrOptions && !options && !parameters) {
return storyFn(context);
}
return wrapper(storyFn, context, {
options,
parameters
});
}, "decorator");
return (...args) => {
if (typeof args[0] === "function") {
return decorator()(...args);
}
return (...innerArgs) => {
if (innerArgs.length > 1) {
if (args.length > 1) {
return decorator(args)(...innerArgs);
}
return decorator(...args)(...innerArgs);
}
throw new Error(
`Passing stories directly into ${name}() is not allowed,
instead use addDecorator(${name}) and pass options with the '${parameterName}' parameter`
);
};
};
}, "makeDecorator");
// src/preview-api/modules/store/StoryStore.ts
import { getCoreAnnotations as getCoreAnnotations2 } from "storybook/internal/csf";
import {
CalledExtractOnStoreError,
MissingStoryFromCsfFileError
} from "storybook/internal/preview-errors";
var import_memoizerific2 = __toESM(require_memoizerific(), 1);
// src/preview-api/modules/store/args.ts
import { once } from "storybook/internal/client-logger";
var INCOMPATIBLE = Symbol("incompatible");
var map = /* @__PURE__ */ __name((arg, argType) => {
const type = argType.type;
if (arg === void 0 || arg === null || !type) {
return arg;
}
if (argType.mapping) {
return arg;
}
switch (type.name) {
case "string":
return String(arg);
case "enum":
return arg;
case "number":
return Number(arg);
case "boolean":
return String(arg) === "true";
case "array":
if (!type.value || !Array.isArray(arg)) {
return INCOMPATIBLE;
}
return arg.reduce((acc, item, index) => {
const mapped = map(item, { type: type.value });
if (mapped !== INCOMPATIBLE) {
acc[index] = mapped;
}
return acc;
}, new Array(arg.length));
case "object":
if (typeof arg === "string" || typeof arg === "number") {
return arg;
}
if (!type.value || typeof arg !== "object") {
return INCOMPATIBLE;
}
return Object.entries(arg).reduce((acc, [key, val]) => {
const mapped = map(val, { type: type.value[key] });
return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped });
}, {});
case "other": {
const isPrimitiveArg = typeof arg === "string" || typeof arg === "number" || typeof arg === "boolean";
if (type.value === "ReactNode" && isPrimitiveArg) {
return arg;
}
return INCOMPATIBLE;
}
default:
return INCOMPATIBLE;
}
}, "map");
var mapArgsToTypes = /* @__PURE__ */ __name((args, argTypes) => {
return Object.entries(args).reduce((acc, [key, value]) => {
if (!argTypes[key]) {
return acc;
}
const mapped = map(value, argTypes[key]);
return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped });
}, {});
}, "mapArgsToTypes");
var combineArgs = /* @__PURE__ */ __name((value, update) => {
if (Array.isArray(value) && Array.isArray(update)) {
return update.reduce(
(acc, upd, index) => {
acc[index] = combineArgs(value[index], update[index]);
return acc;
},
[...value]
).filter((v) => v !== void 0);
}
if (!isPlainObject(value) || !isPlainObject(update)) {
return update;
}
return Object.keys({ ...value, ...update }).reduce((acc, key) => {
if (key in update) {
const combined = combineArgs(value[key], update[key]);
if (combined !== void 0) {
acc[key] = combined;
}
} else {
acc[key] = value[key];
}
return acc;
}, {});
}, "combineArgs");
var validateOptions = /* @__PURE__ */ __name((args, argTypes) => {
return Object.entries(argTypes).reduce((acc, [key, { options }]) => {
function allowArg() {
if (key in args) {
acc[key] = args[key];
}
return acc;
}
__name(allowArg, "allowArg");
if (!options) {
return allowArg();
}
if (!Array.isArray(options)) {
once.error(dedent`
Invalid argType: '${key}.options' should be an array.
More info: https://storybook.js.org/docs/api/arg-types?ref=error
`);
return allowArg();
}
if (options.some((opt) => opt && ["object", "function"].includes(typeof opt))) {
once.error(dedent`
Invalid argType: '${key}.options' should only contain primitives. Use a 'mapping' for complex values.
More info: https://storybook.js.org/docs/writing-stories/args?ref=error#mapping-to-complex-arg-values
`);
return allowArg();
}
const isArray = Array.isArray(args[key]);
const invalidIndex = isArray && args[key].findIndex((val) => !options.includes(val));
const isValidArray = isArray && invalidIndex === -1;
if (args[key] === void 0 || options.includes(args[key]) || isValidArray) {
return allowArg();
}
const field = isArray ? `${key}[${invalidIndex}]` : key;
const supportedOptions = options.map((opt) => typeof opt === "string" ? `'${opt}'` : String(opt)).join(", ");
once.warn(`Received illegal value for '${field}'. Supported options: ${supportedOptions}`);
return acc;
}, {});
}, "validateOptions");
var DEEPLY_EQUAL = Symbol("Deeply equal");
var deepDiff = /* @__PURE__ */ __name((value, update) => {
if (typeof value !== typeof update) {
return update;
}
if (isEqual(value, update)) {
return DEEPLY_EQUAL;
}
if (Array.isArray(value) && Array.isArray(update)) {
const res = update.reduce((acc, upd, index) => {
const diff = deepDiff(value[index], upd);
if (diff !== DEEPLY_EQUAL) {
acc[index] = diff;
}
return acc;
}, new Array(update.length));
if (update.length >= value.length) {
return res;
}
return res.concat(new Array(value.length - update.length).fill(void 0));
}
if (isPlainObject(value) && isPlainObject(update)) {
return Object.keys({ ...value, ...update }).reduce((acc, key) => {
const diff = deepDiff(value?.[key], update?.[key]);
return diff === DEEPLY_EQUAL ? acc : Object.assign(acc, { [key]: diff });
}, {});
}
return update;
}, "deepDiff");
var UNTARGETED = "UNTARGETED";
function groupArgsByTarget({
args,
argTypes
}) {
const groupedArgs = {};
Object.entries(args).forEach(([name, value]) => {
const { target = UNTARGETED } = argTypes[name] || {};
groupedArgs[target] = groupedArgs[target] || {};
groupedArgs[target][name] = value;
});
return groupedArgs;
}
__name(groupArgsByTarget, "groupArgsByTarget");
// src/preview-api/modules/store/ArgsStore.ts
function deleteUndefined(obj) {
Object.keys(obj).forEach((key) => obj[key] === void 0 && delete obj[key]);
return obj;
}
__name(deleteUndefined, "deleteUndefined");
var _ArgsStore = class _ArgsStore {
constructor() {
this.initialArgsByStoryId = {};
this.argsByStoryId = {};
}
get(storyId) {
if (!(storyId in this.argsByStoryId)) {
throw new Error(`No args known for ${storyId} -- has it been rendered yet?`);
}
return this.argsByStoryId[storyId];
}
setInitial(story) {
if (!this.initialArgsByStoryId[story.id]) {
this.initialArgsByStoryId[story.id] = story.initialArgs;
this.argsByStoryId[story.id] = story.initialArgs;
} else if (this.initialArgsByStoryId[story.id] !== story.initialArgs) {
const delta = deepDiff(this.initialArgsByStoryId[story.id], this.argsByStoryId[story.id]);
this.initialArgsByStoryId[story.id] = story.initialArgs;
this.argsByStoryId[story.id] = story.initialArgs;
if (delta !== DEEPLY_EQUAL) {
this.updateFromDelta(story, delta);
}
}
}
updateFromDelta(story, delta) {
const validatedDelta = validateOptions(delta, story.argTypes);
this.argsByStoryId[story.id] = combineArgs(this.argsByStoryId[story.id], validatedDelta);
}
updateFromPersisted(story, persisted) {
const mappedPersisted = mapArgsToTypes(persisted, story.argTypes);
return this.updateFromDelta(story, mappedPersisted);
}
update(storyId, argsUpdate) {
if (!(storyId in this.argsByStoryId)) {
throw new Error(`No args known for ${storyId} -- has it been rendered yet?`);
}
this.argsByStoryId[storyId] = deleteUndefined({
...this.argsByStoryId[storyId],
...argsUpdate
});
}
};
__name(_ArgsStore, "ArgsStore");
var ArgsStore = _ArgsStore;
// src/preview-api/modules/store/GlobalsStore.ts
import { logger as logger2 } from "storybook/internal/client-logger";
// src/preview-api/modules/store/csf/getValuesFromArgTypes.ts
var getValuesFromArgTypes = /* @__PURE__ */ __name((argTypes = {}) => Object.entries(argTypes).reduce((acc, [arg, { defaultValue }]) => {
if (typeof defaultValue !== "undefined") {
acc[arg] = defaultValue;
}
return acc;
}, {}), "getValuesFromArgTypes");
// src/preview-api/modules/store/GlobalsStore.ts
var _GlobalsStore = class _GlobalsStore {
constructor({
globals = {},
globalTypes = {}
}) {
this.set({ globals, globalTypes });
}
set({ globals = {}, globalTypes = {} }) {
const delta = this.initialGlobals && deepDiff(this.initialGlobals, this.globals);
this.allowedGlobalNames = /* @__PURE__ */ new Set([...Object.keys(globals), ...Object.keys(globalTypes)]);
const defaultGlobals = getValuesFromArgTypes(globalTypes);
this.initialGlobals = { ...defaultGlobals, ...globals };
this.globals = this.initialGlobals;
if (delta && delta !== DEEPLY_EQUAL) {
this.updateFromPersisted(delta);
}
}
filterAllowedGlobals(globals) {
return Object.entries(globals).reduce((acc, [key, value]) => {
if (this.allowedGlobalNames.has(key)) {
acc[key] = value;
} else {
logger2.warn(
`Attempted to set a global (${key}) that is not defined in initial globals or globalTypes`
);
}
return acc;
}, {});
}
updateFromPersisted(persisted) {
const allowedUrlGlobals = this.filterAllowedGlobals(persisted);
this.globals = { ...this.globals, ...allowedUrlGlobals };
}
get() {
return this.globals;
}
update(newGlobals) {
this.globals = { ...this.globals, ...this.filterAllowedGlobals(newGlobals) };
for (const key in newGlobals) {
if (newGlobals[key] === void 0) {
this.globals[key] = this.initialGlobals[key];
}
}
}
};
__name(_GlobalsStore, "GlobalsStore");
var GlobalsStore = _GlobalsStore;
// src/preview-api/modules/store/StoryIndexStore.ts
var import_memoizerific = __toESM(require_memoizerific(), 1);
import { MissingStoryAfterHmrError } from "storybook/internal/preview-errors";
var getImportPathMap = (0, import_memoizerific.default)(1)(
(entries) => Object.values(entries).reduce(
(acc, entry) => {
acc[entry.importPath] = acc[entry.importPath] || entry;
return acc;
},
{}
)
);
var _StoryIndexStore = class _StoryIndexStore {
constructor({ entries } = { v: 5, entries: {} }) {
this.entries = entries;
}
entryFromSpecifier(specifier) {
const entries = Object.values(this.entries);
if (specifier === "*") {
return entries[0];
}
if (typeof specifier === "string") {
if (this.entries[specifier]) {
return this.entries[specifier];
}
return entries.find((entry) => entry.id.startsWith(specifier));
}
const { name, title } = specifier;
return entries.find((entry) => entry.name === name && entry.title === title);
}
storyIdToEntry(storyId) {
const storyEntry = this.entries[storyId];
if (!storyEntry) {
throw new MissingStoryAfterHmrError({ storyId });
}
return storyEntry;
}
importPathToEntry(importPath) {
return getImportPathMap(this.entries)[importPath];
}
};
__name(_StoryIndexStore, "StoryIndexStore");
var StoryIndexStore = _StoryIndexStore;
// src/preview-api/modules/store/csf/normalizeInputTypes.ts
var normalizeType = /* @__PURE__ */ __name((type) => {
return typeof type === "string" ? { name: type } : type;
}, "normalizeType");
var normalizeControl = /* @__PURE__ */ __name((control) => typeof control === "string" ? { type: control } : control, "normalizeControl");
var normalizeInputType = /* @__PURE__ */ __name((inputType, key) => {
const { type, control, ...rest } = inputType;
const normalized = {
name: key,
...rest
};
if (type) {
normalized.type = normalizeType(type);
}
if (control) {
normalized.control = normalizeControl(control);
} else if (control === false) {
normalized.control = { disable: true };
}
return normalized;
}, "normalizeInputType");
var normalizeInputTypes = /* @__PURE__ */ __name((inputTypes) => mapValues(inputTypes, normalizeInputType), "normalizeInputTypes");
// src/preview-api/modules/store/csf/normalizeStory.ts
import { deprecate, logger as logger3 } from "storybook/internal/client-logger";
import { storyNameFromExport, toId } from "storybook/internal/csf";
// src/preview-api/modules/store/csf/normalizeArrays.ts
var normalizeArrays = /* @__PURE__ */ __name((array) => {
if (Array.isArray(array)) {
return array;
}
return array ? [array] : [];
}, "normalizeArrays");
// src/preview-api/modules/store/csf/normalizeStory.ts
var deprecatedStoryAnnotation = dedent`
CSF .story annotations deprecated; annotate story functions directly:
- StoryFn.story.name => StoryFn.storyName
- StoryFn.story.(parameters|decorators) => StoryFn.(parameters|decorators)
See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#hoisted-csf-annotations for details and codemod.
`;
function normalizeStory(key, storyAnnotations, meta) {
const storyObject = storyAnnotations;
const userStoryFn = typeof storyAnnotations === "function" ? storyAnnotations : null;
const { story } = storyObject;
if (story) {
logger3.debug("deprecated story", story);
deprecate(deprecatedStoryAnnotation);
}
const exportName = storyNameFromExport(key);
const name = typeof storyObject !== "function" && storyObject.name || storyObject.storyName || story?.name || exportName;
const decorators = [
...normalizeArrays(storyObject.decorators),
...normalizeArrays(story?.decorators)
];
const parameters = { ...story?.parameters, ...storyObject.parameters };
const args = { ...story?.args, ...storyObject.args };
const argTypes = { ...story?.argTypes, ...storyObject.argTypes };
const loaders = [...normalizeArrays(storyObject.loaders), ...normalizeArrays(story?.loaders)];
const beforeEach = [
...normalizeArrays(storyObject.beforeEach),
...normalizeArrays(story?.beforeEach)
];
const afterEach = [
...normalizeArrays(storyObject.afterEach),
...normalizeArrays(story?.afterEach)
];
const { render, play, tags = [], globals = {} } = storyObject;
const id = parameters.__id || toId(meta.id, exportName);
return {
moduleExport: storyAnnotations,
id,
name,
tags,
decorators,
parameters,
args,
argTypes: normalizeInputTypes(argTypes),
loaders,
beforeEach,
afterEach,
globals,
...render && { render },
...userStoryFn && { userStoryFn },
...play && { play }
};
}
__name(normalizeStory, "normalizeStory");
// src/preview-api/modules/store/csf/processCSFFile.ts
import { logger as logger4 } from "storybook/internal/client-logger";
import { getStoryChildren, isExportStory, isStory, toTestId } from "storybook/internal/csf";
// src/preview-api/modules/store/csf/normalizeComponentAnnotations.ts
import { sanitize } from "storybook/internal/csf";
function normalizeComponentAnnotations(defaultExport, title = defaultExport.title, importPath) {
const { id, argTypes } = defaultExport;
return {
id: sanitize(id || title),
...defaultExport,
title,
...argTypes && { argTypes: normalizeInputTypes(argTypes) },
parameters: {
fileName: importPath,
...defaultExport.parameters
}
};
}
__name(normalizeComponentAnnotations, "normalizeComponentAnnotations");
// src/preview-api/modules/store/csf/processCSFFile.ts
var checkGlobals = /* @__PURE__ */ __name((parameters) => {
const { globals, globalTypes } = parameters;
if (globals || globalTypes) {
logger4.error(
"Global args/argTypes can only be set globally",
JSON.stringify({
globals,
globalTypes
})
);
}
}, "checkGlobals");
var checkStorySort = /* @__PURE__ */ __name((parameters) => {
const { options } = parameters;
if (options?.storySort) {
logger4.error("The storySort option parameter can only be set globally");
}
}, "checkStorySort");
var checkDisallowedParameters = /* @__PURE__ */ __name((parameters) => {
if (!parameters) {
return;
}
checkGlobals(parameters);
checkStorySort(parameters);
}, "checkDisallowedParameters");
function processCSFFile(moduleExports, importPath, title) {
const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports;
const firstStory = Object.values(namedExports)[0];
if (isStory(firstStory)) {
const meta2 = normalizeComponentAnnotations(firstStory.meta.input, title, importPath);
checkDisallowedParameters(meta2.parameters);
const csfFile2 = { meta: meta2, stories: {}, moduleExports };
Object.keys(namedExports).forEach((key) => {
if (isExportStory(key, meta2)) {
const story = namedExports[key];
const storyMeta = normalizeStory(key, story.input, meta2);
checkDisallowedParameters(storyMeta.parameters);
csfFile2.stories[storyMeta.id] = storyMeta;
getStoryChildren(story).forEach((child) => {
const name = child.input.name;
const childId = toTestId(storyMeta.id, name);
child.input.parameters ??= {};
child.input.parameters.__id = childId;
csfFile2.stories[childId] = normalizeStory(name, child.input, meta2);
});
}
});
csfFile2.projectAnnotations = firstStory.meta.preview.composed;
return csfFile2;
}
const meta = normalizeComponentAnnotations(
defaultExport,
title,
importPath
);
checkDisallowedParameters(meta.parameters);
const csfFile = { meta, stories: {}, moduleExports };
Object.keys(namedExports).forEach((key) => {
if (isExportStory(key, meta)) {
const storyMeta = normalizeStory(key, namedExports[key], meta);
checkDisallowedParameters(storyMeta.parameters);
csfFile.stories[storyMeta.id] = storyMeta;
}
});
return csfFile;
}
__name(processCSFFile, "processCSFFile");
// src/preview-api/modules/store/csf/prepareStory.ts
import { combineTags, includeConditionalArg } from "storybook/internal/csf";
import { NoRenderFunctionError } from "storybook/internal/preview-errors";
import { global as global3 } from "@storybook/global";
import { global as globalThis2 } from "@storybook/global";
// src/preview-api/modules/preview-web/render/mount-utils.ts
function mountDestructured(playFunction) {
return playFunction != null && getUsedProps(playFunction).includes("mount");
}
__name(mountDestructured, "mountDestructured");
function getUsedProps(fn) {
const match = fn.toString().match(/[^(]*\(([^)]*)/);
if (!match) {
return [];
}
const args = splitByComma(match[1]);
if (!args.length) {
return [];
}
const first = args[0];
if (!(first.startsWith("{") && first.endsWith("}"))) {
return [];
}
const props = splitByComma(first.slice(1, -1).replace(/\s/g, "")).map((prop) => {
return prop.replace(/:.*|=.*/g, "");
});
return props;
}
__name(getUsedProps, "getUsedProps");
function splitByComma(s) {
const result = [];
const stack = [];
let start = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === "{" || s[i] === "[") {
stack.push(s[i] === "{" ? "}" : "]");
} else if (s[i] === stack[stack.length - 1]) {
stack.pop();
} else if (!stack.length && s[i] === ",") {
const token = s.substring(start, i).trim();
if (token) {
result.push(token);
}
start = i + 1;
}
}
const lastToken = s.substring(start).trim();
if (lastToken) {
result.push(lastToken);
}
return result;
}
__name(splitByComma, "splitByComma");
// src/preview-api/modules/store/decorators.ts
function decorateStory(storyFn, decorator, bindWithContext) {
const boundStoryFunction = bindWithContext(storyFn);
return (context) => decorator(boundStoryFunction, context);
}
__name(decorateStory, "decorateStory");
function sanitizeStoryContextUpdate({
componentId,
title,
kind,
id,
name,
story,
parameters,
initialArgs,
argTypes,
...update
} = {}) {
return update;
}
__name(sanitizeStoryContextUpdate, "sanitizeStoryContextUpdate");
function defaultDecorateStory(storyFn, decorators) {
const contextStore = {};
const bindWithContext = /* @__PURE__ */ __name((decoratedStoryFn) => (update) => {
if (!contextStore.value) {
throw new Error("Decorated function called without init");
}
contextStore.value = {
...contextStore.value,
...sanitizeStoryContextUpdate(update)
};
return decoratedStoryFn(contextStore.value);
}, "bindWithContext");
const decoratedWithContextStore = decorators.reduce(
(story, decorator) => decorateStory(story, decorator, bindWithContext),
storyFn
);
return (context) => {
contextStore.value = context;
return decoratedWithContextStore(context);
};
}
__name(defaultDecorateStory, "defaultDecorateStory");
// src/preview-api/modules/store/csf/prepareStory.ts
function prepareStory(storyAnnotations, componentAnnotations, projectAnnotations) {
const { moduleExport, id, name } = storyAnnotations || {};
const partialAnnotations = preparePartialAnnotations(
storyAnnotations,
componentAnnotations,
projectAnnotations
);
const applyLoaders = /* @__PURE__ */ __name(async (context) => {
const loaded = {};
for (const loaders of [
normalizeArrays(projectAnnotations.loaders),
normalizeArrays(componentAnnotations.loaders),
normalizeArrays(storyAnnotations.loaders)
]) {
if (context.abortSignal.aborted) {
return loaded;
}
const loadResults = await Promise.all(loaders.map((loader) => loader(context)));
Object.assign(loaded, ...loadResults);
}
return loaded;
}, "applyLoaders");
const applyBeforeEach = /* @__PURE__ */ __name(async (context) => {
const cleanupCallbacks = new Array();
for (const beforeEach of [
...normalizeArrays(projectAnnotations.beforeEach),
...normalizeArrays(componentAnnotations.beforeEach),
...normalizeArrays(storyAnnotations.beforeEach)
]) {
if (context.abortSignal.aborted) {
return cleanupCallbacks;
}
const cleanup = await beforeEach(context);
if (cleanup) {
cleanupCallbacks.push(cleanup);
}
}
return cleanupCallbacks;
}, "applyBeforeEach");
const applyAfterEach = /* @__PURE__ */ __name(async (context) => {
const reversedFinalizers = [
...normalizeArrays(projectAnnotations.afterEach),
...normalizeArrays(componentAnnotations.afterEach),
...normalizeArrays(storyAnnotations.afterEach)
].reverse();
for (const finalizer of reversedFinalizers) {
if (context.abortSignal.aborted) {
return;
}
await finalizer(context);
}
}, "applyAfterEach");
const undecoratedStoryFn = /* @__PURE__ */ __name((context) => context.originalStoryFn(context.args, context), "undecoratedStoryFn");
const { applyDecorators = defaultDecorateStory, runStep } = projectAnnotations;
const decorators = [
...normalizeArrays(storyAnnotations?.decorators),
...normalizeArrays(componentAnnotations?.decorators),
...normalizeArrays(projectAnnotations?.decorators)
];
const render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render;
const decoratedStoryFn = applyHooks(applyDecorators)(undecoratedStoryFn, decorators);
const unboundStoryFn = /* @__PURE__ */ __name((context) => decoratedStoryFn(context), "unboundStoryFn");
const playFunction = storyAnnotations?.play ?? componentAnnotations?.play;
const usesMount = mountDestructured(playFunction);
if (!render && !usesMount) {
throw new NoRenderFunctionError({ id });
}
const defaultMount = /* @__PURE__ */ __name((context) => {
return async () => {
await context.renderToCanvas();
return context.canvas;
};
}, "defaultMount");
const mount = storyAnnotations.mount ?? componentAnnotations.mount ?? projectAnnotations.mount ?? defaultMount;
const testingLibraryRender = projectAnnotations.testingLibraryRender;
return {
storyGlobals: {},
...partialAnnotations,
moduleExport,
id,
name,
story: name,
originalStoryFn: render,
undecoratedStoryFn,
unboundStoryFn,
applyLoaders,
applyBeforeEach,
applyAfterEach,
playFunction,
runStep,
mount,
testingLibraryRender,
renderToCanvas: projectAnnotations.renderToCanvas,
usesMount
};
}
__name(prepareStory, "prepareStory");
function prepareMeta(componentAnnotations, projectAnnotations, moduleExport) {
return {
...preparePartialAnnotations(void 0, componentAnnotations, projectAnnotations),
moduleExport
};
}
__name(prepareMeta, "prepareMeta");
function preparePartialAnnotations(storyAnnotations, componentAnnotations, projectAnnotations) {
const defaultTags = ["dev", "test"];
const extraTags = globalThis2.DOCS_OPTIONS?.autodocs === true ? ["autodocs"] : [];
const overrideTags = storyAnnotations?.tags?.includes("test-fn") ? ["!autodocs"] : [];
const tags = combineTags(
...defaultTags,
...extraTags,
...projectAnnotations.tags ?? [],
...componentAnnotations.tags ?? [],
...overrideTags,
...storyAnnotations?.tags ?? []
);
const parameters = combineParameters(
projectAnnotations.parameters,
componentAnnotations.parameters,
storyAnnotations?.parameters
);
const { argTypesEnhancers = [], argsEnhancers = [] } = projectAnnotations;
const passedArgTypes = combineParameters(
projectAnnotations.argTypes,
componentAnnotations.argTypes,
storyAnnotations?.argTypes
);
if (storyAnnotations) {
const render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render;
parameters.__isArgsStory = render && render.length > 0;
}
const passedArgs = {
...projectAnnotations.args,
...componentAnnotations.args,
...storyAnnotations?.args
};
const storyGlobals = {
...componentAnnotations.globals,
...storyAnnotations?.globals
};
const contextForEnhancers = {
componentId: componentAnnotations.id,
title: componentAnnotations.title,
kind: componentAnnotations.title,
// Back compat
id: storyAnnotations?.id || componentAnnotations.id,
// if there's no story name, we create a fake one since enhancers expect a name
name: storyAnnotations?.name || "__meta",
story: storyAnnotations?.name || "__meta",
// Back compat
component: componentAnnotations.component,
subcomponents: componentAnnotations.subcomponents,
tags,
parameters,
initialArgs: passedArgs,
argTypes: passedArgTypes,
storyGlobals
};
contextForEnhancers.argTypes = argTypesEnhancers.reduce(
(accumulatedArgTypes, enhancer) => enhancer({ ...contextForEnhancers, argTypes: accumulatedArgTypes }),
contextForEnhancers.argTypes
);
const initialArgsBeforeEnhancers = { ...passedArgs };
contextForEnhancers.initialArgs = [...argsEnhancers].reduce(
(accumulatedArgs, enhancer) => ({
...accumulatedArgs,
...enhancer({
...contextForEnhancers,
initialArgs: accumulatedArgs
})
}),
initialArgsBeforeEnhancers
);
const { name, story, ...withoutStoryIdentifiers } = contextForEnhancers;
return withoutStoryIdentifiers;
}
__name(preparePartialAnnotations, "preparePartialAnnotations");
function prepareContext(context) {
const { args: unmappedArgs } = context;
let targetedContext = {
...context,
allArgs: void 0,
argsByTarget: void 0
};
if (global3.FEATURES?.argTypeTargetsV7) {
const argsByTarget = groupArgsByTarget(context);
targetedContext = {
...context,
allArgs: context.args,
argsByTarget,
args: argsByTarget[UNTARGETED] || {}
};
}
const mappedArgs = Object.entries(targetedContext.args).reduce((acc, [key, val]) => {
if (!targetedContext.argTypes[key]?.mapping) {
acc[key] = val;
return acc;
}
const mappingFn = /* @__PURE__ */ __name((originalValue) => {
const mapping = targetedContext.argTypes[key].mapping;
return mapping && originalValue in mapping ? mapping[originalValue] : originalValue;
}, "mappingFn");
acc[key] = Array.isArray(val) ? val.map(mappingFn) : mappingFn(val);
return acc;
}, {});
const includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => {
const argType = targetedContext.argTypes[key] || {};
if (includeConditionalArg(argType, mappedArgs, targetedContext.globals)) {
acc[key] = val;
}
return acc;
}, {});
return { ...targetedContext, unmappedArgs, args: includedArgs };
}
__name(prepareContext, "prepareContext");
// src/preview-api/modules/store/inferArgTypes.ts
import { logger as logger5 } from "storybook/internal/client-logger";
var inferType = /* @__PURE__ */ __name((value, name, visited) => {
const type = typeof value;
switch (type) {
case "boolean":
case "string":
case "number":
case "function":
case "symbol":
return { name: type };
default:
break;
}
if (value) {
if (visited.has(value)) {
logger5.warn(dedent`
We've detected a cycle in arg '${name}'. Args should be JSON-serializable.
Consider using the mapping feature or fully custom args:
- Mapping: https://storybook.js.org/docs/writing-stories/args#mapping-to-complex-arg-values
- Custom args: https://storybook.js.org/docs/essentials/controls#fully-custom-args
`);
return { name: "other", value: "cyclic object" };
}
visited.add(value);
if (Array.isArray(value)) {
const childType = value.length > 0 ? inferType(value[0], name, new Set(visited)) : { name: "other", value: "unknown" };
return { name: "array", value: childType };
}
const fieldTypes = mapValues(value, (field) => inferType(field, name, new Set(visited)));
return { name: "object", value: fieldTypes };
}
return { name: "object", value: {} };
}, "inferType");
var inferArgTypes = /* @__PURE__ */ __name((context) => {
const { id, argTypes: userArgTypes = {}, initialArgs = {} } = context;
const argTypes = mapValues(initialArgs, (arg, key) => ({
name: key,
type: inferType(arg, `${id}.${key}`, /* @__PURE__ */ new Set())
}));
const userArgTypesNames = mapValues(userArgTypes, (argType, key) => ({
name: key
}));
return combineParameters(argTypes, userArgTypesNames, userArgTypes);
}, "inferArgTypes");
inferArgTypes.secondPass = true;
// src/preview-api/modules/store/inferControls.ts
import { logger as logger6 } from "storybook/internal/client-logger";
// src/preview-api/modules/store/filterArgTypes.ts
var matches = /* @__PURE__ */ __name((name, descriptor) => Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor), "matches");
var filterArgTypes = /* @__PURE__ */ __name((argTypes, include, exclude) => {
if (!include && !exclude) {
return argTypes;
}
return argTypes && pickBy(argTypes, (argType, key) => {
const name = argType.name || key.toString();
return !!(!include || matches(name, include)) && (!exclude || !matches(name, exclude));
});
}, "filterArgTypes");
// src/preview-api/modules/store/inferControls.ts
var inferControl = /* @__PURE__ */ __name((argType, name, matchers) => {
const { type, options } = argType;
if (!type) {
return void 0;
}
if (matchers.color && matchers.color.test(name)) {
const controlType = type.name;
if (controlType === "string") {
return { control: { type: "color" } };
}
if (controlType !== "enum") {
logger6.warn(
`Addon controls: Control of type color only supports string, received "${controlType}" instead`
);
}
}
if (matchers.date && matchers.date.test(name)) {
return { control: { type: "date" } };
}
switch (type.name) {
case "array":
return { control: { type: "object" } };
case "boolean":
return { control: { type: "boolean" } };
case "string":
return { control: { type: "text" } };
case "number":
return { control: { type: "number" } };
case "enum": {
const { value } = type;
return { control: { type: value?.length <= 5 ? "radio" : "select" }, options: value };
}
case "function":
case "symbol":
return null;
default:
return { control: { type: options ? "select" : "object" } };
}
}, "inferControl");
var inferControls = /* @__PURE__ */ __name((context) => {
const {
argTypes,
parameters: { __isArgsStory, controls: { include = null, exclude = null, matchers = {} } = {} }
} = context;
if (!__isArgsStory) {
return argTypes;
}
const filteredArgTypes = filterArgTypes(argTypes, include, exclude);
const withControls = mapValues(filteredArgTypes, (argType, name) => {
return argType?.type && inferControl(argType, name.toString(), matchers);
});
return combineParameters(withControls, filteredArgTypes);
}, "inferControls");
inferControls.secondPass = true;
// src/preview-api/modules/store/csf/normalizeProjectAnnotations.ts
function normalizeProjectAnnotations({
argTypes,
globalTypes,
argTypesEnhancers,
decorators,
loaders,
beforeEach,
afterEach,
initialGlobals,
...annotations
}) {
return {
...argTypes && { argTypes: normalizeInputTypes(argTypes) },
...globalTypes && { globalTypes: normalizeInputTypes(globalTypes) },
decorators: normalizeArrays(decorators),
loaders: normalizeArrays(loaders),
beforeEach: normalizeArrays(beforeEach),
afterEach: normalizeArrays(afterEach),
argTypesEnhancers: [
...argTypesEnhancers || [],
inferArgTypes,
// There's an architectural decision to be made regarding embedded addons in core:
//
// Option 1: Keep embedded addons but ensure consistency by moving addon-specific code
// (like inferControls) to live alongside the addon code itself. This maintains the
// concept of core addons while improving code organization.
//
// Option 2: Fully integrate these addons into core, potentially moving UI components
// into the manager and treating them as core features rather than addons. This is a
// bigger architectural change requiring careful consideration.
//
// For now, we're keeping inferControls here as we need time to properly evaluate
// these options and their implications. Some features (like Angular's cleanArgsDecorator)
// currently rely on this behavior.
//
// TODO: Make an architectural decision on the handling of core addons
inferControls
],
initialGlobals,
...annotations
};
}
__name(normalizeProjectAnnotations, "normalizeProjectAnnotations");
// src/preview-api/modules/store/csf/composeConfigs.ts
import { global as global4 } from "@storybook/global";
// src/preview-api/modules/store/csf/beforeAll.ts
var composeBeforeAllHooks = /* @__PURE__ */ __name((hooks) => {
return async () => {
const cleanups2 = [];
for (const hook of hooks) {
const cleanup = await hook();
if (cleanup) {
cleanups2.unshift(cleanup);
}
}
return async () => {
for (const cleanup of cleanups2) {
await cleanup();
}
};
};
}, "composeBeforeAllHooks");
// src/preview-api/modules/store/csf/stepRunners.ts
function composeStepRunners(stepRunners) {
return async (label, play, playContext) => {
const composedPlay = stepRunners.reduceRight(
(innerPlay, stepRunner) => async () => stepRunner(label, innerPlay, playContext),
async () => play(playContext)
);
await composedPlay();
};
}
__name(composeStepRunners, "composeStepRunners");
// src/preview-api/modules/store/csf/composeConfigs.ts
function getField(moduleExportList, field) {
return moduleExportList.map((xs) => xs.default?.[field] ?? xs[field]).filter(Boolean);
}
__name(getField, "getField");
function getArrayField(moduleExportList, field, options = {}) {
return getField(moduleExportList, field).reduce((prev, cur) => {
const normalized = normalizeArrays(cur);
return options.reverseFileOrder ? [...normalized, ...prev] : [...prev, ...normalized];
}, []);
}
__name(getArrayField, "getArrayField");
function getObjectField(moduleExportList, field) {
return Object.assign({}, ...getField(moduleExportList, field));
}
__name(getObjectField, "getObjectField");
function getSingletonField(moduleExportList, field) {
return getField(moduleExportList, field).pop();
}
__name(getSingletonField, "getSingletonField");
function composeConfigs(moduleExportList) {
const allArgTypeEnhancers = getArrayField(moduleExportList, "argTypesEnhancers");
const stepRunners = getField(moduleExportList, "runStep");
const beforeAllHooks = getArrayField(moduleExportList, "beforeAll");
return {
parameters: combineParameters(...getField(moduleExportList, "parameters")),
decorators: getArrayField(moduleExportList, "decorators", {
reverseFileOrder: !(global4.FEATURES?.legacyDecoratorFileOrder ?? false)
}),
args: getObjectField(moduleExportList, "args"),
argsEnhancers: getArrayField(moduleExportList, "argsEnhancers"),
argTypes: getObjectField(moduleExportList, "argTypes"),
argTypesEnhancers: [
...allArgTypeEnhancers.filter((e) => !e.secondPass),
...allArgTypeEnhancers.filter((e) => e.secondPass)
],
initialGlobals: getObjectField(moduleExportList, "initialGlobals"),
globalTypes: getObjectField(moduleExportList, "globalTypes"),
loaders: getArrayField(moduleExportList, "loaders"),
beforeAll: composeBeforeAllHooks(beforeAllHooks),
beforeEach: getArrayField(moduleExportList, "beforeEach"),
afterEach: getArrayField(moduleExportList, "afterEach"),
render: getSingletonField(moduleExportList, "render"),
renderToCanvas: getSingletonField(moduleExportList, "renderToCanvas"),
applyDecorators: getSingletonField(moduleExportList, "applyDecorators"),
runStep: composeStepRunners(stepRunners),
tags: getArrayField(moduleExportList, "tags"),
mount: getSingletonField(moduleExportList, "mount"),
testingLibraryRender: getSingletonField(moduleExportList, "testingLibraryRender")
};
}
__name(composeConfigs, "composeConfigs");
// src/preview-api/modules/store/csf/portable-stories.ts
import { isExportStory as isExportStory2 } from "storybook/internal/csf";
import { getCoreAnnotations } from "storybook/internal/csf";
import { MountMustBeDestructuredError } from "storybook/internal/preview-errors";
// src/preview-api/modules/store/reporter-api.ts
var _ReporterAPI = class _ReporterAPI {
constructor() {
this.reports = [];
}
async addReport(report) {
this.reports.push(report);
}
};
__name(_ReporterAPI, "ReporterAPI");
var ReporterAPI = _ReporterAPI;
// src/preview-api/modules/store/csf/csf-factory-utils.ts
import { isMeta, isStory as isStory2 } from "storybook/internal/csf";
function getCsfFactoryAnnotations(story, meta, projectAnnotations) {
return isStory2(story) ? {
story: story.input,
meta: story.meta.input,
preview: story.meta.preview.composed
} : { story, meta: isMeta(meta) ? meta.input : meta, preview: projectAnnotations };
}
__name(getCsfFactoryAnnotations, "getCsfFactoryAnnotations");
// src/preview-api/modules/store/csf/portable-stories.ts
function setDefaultProjectAnnotations(_defaultProjectAnnotations) {
globalThis.defaultProjectAnnotations = _defaultProjectAnnotations;
}
__name(setDefaultProjectAnnotations, "setDefaultProjectAnnotations");
var DEFAULT_STORY_TITLE = "ComposedStory";
var DEFAULT_STORY_NAME = "Unnamed Story";
function extractAnnotation(annotation) {
if (!annotation) {
return {};
}
return composeConfigs([annotation]);
}
__name(extractAnnotation, "extractAnnotation");
function setProjectAnnotations(projectAnnotations) {
const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations];
globalThis.globalProjectAnnotations = composeConfigs([
...getCoreAnnotations(),
globalThis.defaultProjectAnnotations ?? {},
composeConfigs(annotations.map(extractAnnotation))
]);
return globalThis.globalProjectAnnotations ?? {};
}
__name(setProjectAnnotations, "setProjectAnnotations");
var cleanups = [];
function composeStory(storyAnnotations, componentAnnotations, projectAnnotations, defaultConfig, exportsName) {
if (storyAnnotations === void 0) {
throw new Error("Expected a story but received undefined.");
}
componentAnnotations.title = componentAnnotations.title ?? DEFAULT_STORY_TITLE;
const normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations);
const storyName = exportsName || storyAnnotations.storyName || storyAnnotations.story?.name || storyAnnotations.name || DEFAULT_STORY_NAME;
const normalizedStory = normalizeStory(
storyName,
storyAnnotations,
normalizedComponentAnnotations
);
const normalizedProjectAnnotations = normalizeProjectAnnotations(
composeConfigs([
defaultConfig ?? globalThis.globalProjectAnnotations ?? {},
projectAnnotations ?? {}
])
);
const story = prepareStory(
normalizedStory,
normalizedComponentAnnotations,
normalizedProjectAnnotations
);
const globalsFromGlobalTypes = getValuesFromArgTypes(normalizedProjectAnnotations.globalTypes);
const globals = {
...globalsFromGlobalTypes,
...normalizedProjectAnnotations.initialGlobals,
...story.storyGlobals
};
const reporting = new ReporterAPI();
const initializeContext = /* @__PURE__ */ __name(() => {
const context = prepareContext({
hooks: new HooksContext(),
globals,
args: { ...story.initialArgs },
viewMode: "story",
reporting,
loaded: {},
abortSignal: new AbortController().signal,
step: /* @__PURE__ */ __name((label, play2) => story.runStep(label, play2, context), "step"),
canvasElement: null,
canvas: {},
userEvent: {},
globalTypes: normalizedProjectAnnotations.globalTypes,
...story,
context: null,
mount: null
});
context.parameters.__isPortableStory = true;
context.context = context;
if (story.renderToCanvas) {
context.renderToCanvas = async () => {
const unmount = await story.renderToCanvas?.(
{
componentId: story.componentId,
title: story.title,
id: story.id,
name: story.name,
tags: story.tags,
showMain: /* @__PURE__ */ __name(() => {
}, "showMain"),
showError: /* @__PURE__ */ __name((error) => {
throw new Error(`${error.title}
${error.description}`);
}, "showError"),
showException: /* @__PURE__ */ __name((error) => {
throw error;
}, "showException"),
forceRemount: true,
storyContext: context,
storyFn: /* @__PURE__ */ __name(() => story.unboundStoryFn(context), "storyFn"),
unboundStoryFn: story.unboundStoryFn
},
context.canvasElement
);
if (unmount) {
cleanups.push(unmount);
}
};
}
context.mount = story.mount(context);
return context;
}, "initializeContext");
let loadedContext;
const play = /* @__PURE__ */ __name(async (extraContext) => {
const context = initializeContext();
context.canvasElement ??= globalThis?.document?.body;
if (loadedContext) {
context.loaded = loadedContext.loaded;
}
Object.assign(context, extraContext);
return story.playFunction(context);
}, "play");
const run = /* @__PURE__ */ __name((extraContext) => {
const context = initializeContext();
Object.assign(context, extraContext);
return runStory(story, context);
}, "run");
const playFunction = story.playFunction ? play : void 0;
const composedStory = Object.assign(
/* @__PURE__ */ __name(function storyFn(extraArgs) {
const context = initializeContext();
if (loadedContext) {
context.loaded = loadedContext.loaded;
}
context.args = {
...context.initialArgs,
...extraArgs
};
return story.unboundStoryFn(context);
}, "storyFn"),
{
id: story.id,
storyName,
load: /* @__PURE__ */ __name(async () => {
for (const callback of [...cleanups].reverse()) {
await callback();
}
cleanups.length = 0;
const context = initializeContext();
context.loaded = await story.applyLoaders(context);
cleanups.push(...(await story.applyBeforeEach(context)).filter(Boolean));
loadedContext = context;
}, "load"),
globals,
args: story.initialArgs,
parameters: story.parameters,
argTypes: story.argTypes,
play: playFunction,
run,
reporting,
tags: story.tags
}
);
return composedStory;
}
__name(composeStory, "composeStory");
var defaultComposeStory = /* @__PURE__ */ __name((story, component, project, exportsName) => composeStory(story, component, project, {}, exportsName), "defaultComposeStory");
function composeStories(storiesImport, globalConfig, composeStoryFn = defaultComposeStory) {
const { default: metaExport, __esModule, __namedExportsOrder, ...stories } = storiesImport;
let meta = metaExport;
const composedStories = Object.entries(stories).reduce(
(storiesMap, [exportsName, story]) => {
const { story: storyAnnotations, meta: componentAnnotations } = getCsfFactoryAnnotations(story);
if (!meta && componentAnnotations) {
meta = componentAnnotations;
}
if (!isExportStory2(exportsName, meta)) {
return storiesMap;
}
const result = Object.assign(storiesMap, {
[exportsName]: composeStoryFn(storyAnnotations, meta, globalConfig, exportsName)
});
return result;
},
{}
);
return composedStories;
}
__name(composeStories, "composeStories");
function createPlaywrightTest(baseTest) {
return baseTest.extend({
mount: /* @__PURE__ */ __name(async ({ mount, page }, use) => {
await use(async (storyRef, ...restArgs) => {
if (!("__pw_type" in storyRef) || "__pw_type" in storyRef && storyRef.__pw_type !== "jsx") {
throw new Error(dedent`
Portable stories in Playwright CT only work when referencing JSX elements.
Please use JSX format for your components such as:
instead of:
await mount(MyComponent, { props: { foo: 'bar' } })
do:
await mount(<MyComponent foo="bar"/>)
More info: https://storybook.js.org/docs/api/portable-stories/portable-stories-playwright?ref=error
`);
}
const { props, ...storyRefWithoutProps } = storyRef;
await page.evaluate(async (wrappedStoryRef) => {
const unwrappedStoryRef = await globalThis.__pwUnwrapObject?.(wrappedStoryRef);
const story = "__pw_type" in unwrappedStoryRef ? unwrappedStoryRef.type : unwrappedStoryRef;
return story?.load?.();
}, storyRefWithoutProps);
const mountResult = await mount(storyRef, ...restArgs);
await page.evaluate(async (wrappedStoryRef) => {
const unwrappedStoryRef = await globalThis.__pwUnwrapObject?.(wrappedStoryRef);
const story = "__pw_type" in unwrappedStoryRef ? unwrappedStoryRef.type : unwrappedStoryRef;
const canvasElement = document.querySelector("#root");
return story?.play?.({ canvasElement });
}, storyRefWithoutProps);
return mountResult;
});
}, "mount")
});
}
__name(createPlaywrightTest, "createPlaywrightTest");
async function runStory(story, context) {
for (const callback of [...cleanups].reverse()) {
await callback();
}
cleanups.length = 0;
if (!context.canvasElement) {
const container = document.createElement("div");
globalThis?.document?.body?.appendChild(container);
context.canvasElement = container;
cleanups.push(() => {
if (globalThis?.document?.body?.contains(container)) {
globalThis?.document?.body?.removeChild(container);
}
});
}
context.loaded = await story.applyLoaders(context);
if (context.abortSignal.aborted) {
return;
}
cleanups.push(...(await story.applyBeforeEach(context)).filter(Boolean));
const playFunction = story.playFunction;
const isMountDestructured = story.usesMount;
if (!isMountDestructured) {
await context.mount();
}
if (context.abortSignal.aborted) {
return;
}
if (playFunction) {
if (!isMountDestructured) {
context.mount = async () => {
throw new MountMustBeDestructuredError({ playFunction: playFunction.toString() });
};
}
await playFunction(context);
}
let cleanUp;
if (isTestEnvironment()) {
cleanUp = pauseAnimations();
} else {
await waitForAnimations(context.abortSignal);
}
await story.applyAfterEach(context);
await cleanUp?.();
}
__name(runStory, "runStory");
// src/preview-api/modules/store/StoryStore.ts
var CSF_CACHE_SIZE = 1e3;
var STORY_CACHE_SIZE = 1e4;
var _StoryStore = class _StoryStore {
constructor(storyIndex, importFn, projectAnnotations) {
this.importFn = importFn;
this.storyIndex = new StoryIndexStore(storyIndex);
this.projectAnnotations = normalizeProjectAnnotations(
composeConfigs([...getCoreAnnotations2(), projectAnnotations])
);
const { initialGlobals, globalTypes } = this.projectAnnotations;
this.args = new ArgsStore();
this.userGlobals = new GlobalsStore({ globals: initialGlobals, globalTypes });
this.hooks = {};
this.cleanupCallbacks = {};
this.processCSFFileWithCache = (0, import_memoizerific2.default)(CSF_CACHE_SIZE)(processCSFFile);
this.prepareMetaWithCache = (0, import_memoizerific2.default)(CSF_CACHE_SIZE)(prepareMeta);
this.prepareStoryWithCache = (0, import_memoizerific2.default)(STORY_CACHE_SIZE)(prepareStory);
}
setProjectAnnotations(projectAnnotations) {
this.projectAnnotations = normalizeProjectAnnotations(projectAnnotations);
const { initialGlobals, globalTypes } = projectAnnotations;
this.userGlobals.set({ globals: initialGlobals, globalTypes });
}
// This means that one of the CSF files has changed.
// If the `importFn` has changed, we will invalidate both caches.
// If the `storyIndex` data has changed, we may or may not invalidate the caches, depending
// on whether we've loaded the relevant files yet.
async onStoriesChanged({
importFn,
storyIndex
}) {
if (importFn) {
this.importFn = importFn;
}
if (storyIndex) {
this.storyIndex.entries = storyIndex.entries;
}
if (this.cachedCSFFiles) {
await this.cacheAllCSFFiles();
}
}
// Get an entry from the index, waiting on initialization if necessary
async storyIdToEntry(storyId) {
return this.storyIndex.storyIdToEntry(storyId);
}
// To load a single CSF file to service a story we need to look up the importPath in the index
async loadCSFFileByStoryId(storyId) {
const { importPath, title } = this.storyIndex.storyIdToEntry(storyId);
const moduleExports = await this.importFn(importPath);
return this.processCSFFileWithCache(moduleExports, importPath, title);
}
async loadAllCSFFiles() {
const importPaths = {};
Object.entries(this.storyIndex.entries).forEach(([storyId, { importPath }]) => {
importPaths[importPath] = storyId;
});
const list = await Promise.all(
Object.entries(importPaths).map(async ([importPath, storyId]) => ({
importPath,
csfFile: await this.loadCSFFileByStoryId(storyId)
}))
);
return list.reduce(
(acc, { importPath, csfFile }) => {
acc[importPath] = csfFile;
return acc;
},
{}
);
}
async cacheAllCSFFiles() {
this.cachedCSFFiles = await this.loadAllCSFFiles();
}
preparedMetaFromCSFFile({ csfFile }) {
const componentAnnotations = csfFile.meta;
return this.prepareMetaWithCache(
componentAnnotations,
this.projectAnnotations,
csfFile.moduleExports.default
);
}
// Load the CSF file for a story and prepare the story from it and the project annotations.
async loadStory({ storyId }) {
const csfFile = await this.loadCSFFileByStoryId(storyId);
return this.storyFromCSFFile({ storyId, csfFile });
}
// This function is synchronous for convenience -- often times if you have a CSF file already
// it is easier not to have to await `loadStory`.
storyFromCSFFile({
storyId,
csfFile
}) {
const storyAnnotations = csfFile.stories[storyId];
if (!storyAnnotations) {
throw new MissingStoryFromCsfFileError({ storyId });
}
const componentAnnotations = csfFile.meta;
const story = this.prepareStoryWithCache(
storyAnnotations,
componentAnnotations,
csfFile.projectAnnotations ?? this.projectAnnotations
);
this.args.setInitial(story);
this.hooks[story.id] = this.hooks[story.id] || new HooksContext();
return story;
}
// If we have a CSF file we can get all the stories from it synchronously
componentStoriesFromCSFFile({
csfFile
}) {
return Object.keys(this.storyIndex.entries).filter((storyId) => !!csfFile.stories[storyId]).map((storyId) => this.storyFromCSFFile({ storyId, csfFile }));
}
async loadEntry(id) {
const entry = await this.storyIdToEntry(id);
const storyImports = entry.type === "docs" ? entry.storiesImports : [];
const [entryExports, ...csfFiles] = await Promise.all([
this.importFn(entry.importPath),
...storyImports.map((storyImportPath) => {
const firstStoryEntry = this.storyIndex.importPathToEntry(storyImportPath);
return this.loadCSFFileByStoryId(firstStoryEntry.id);
})
]);
return { entryExports, csfFiles };
}
// A prepared story does not include args, globals or hooks. These are stored in the story store
// and updated separately to the (immutable) story.
getStoryContext(story, { forceInitialArgs = false } = {}) {
const userGlobals = this.userGlobals.get();
const { initialGlobals } = this.userGlobals;
const reporting = new ReporterAPI();
return prepareContext({
...story,
args: forceInitialArgs ? story.initialArgs : this.args.get(story.id),
initialGlobals,
globalTypes: this.projectAnnotations.globalTypes,
userGlobals,
reporting,
globals: {
...userGlobals,
...story.storyGlobals
},
hooks: this.hooks[story.id]
});
}
addCleanupCallbacks(story, ...callbacks) {
this.cleanupCallbacks[story.id] = (this.cleanupCallbacks[story.id] || []).concat(callbacks);
}
async cleanupStory(story) {
this.hooks[story.id].clean();
const callbacks = this.cleanupCallbacks[story.id];
if (callbacks) {
for (const callback of [...callbacks].reverse()) {
await callback();
}
}
delete this.cleanupCallbacks[story.id];
}
extract(options = { includeDocsOnly: false }) {
const { cachedCSFFiles } = this;
console.log("extract: extracting stories", cachedCSFFiles);
if (!cachedCSFFiles) {
throw new CalledExtractOnStoreError();
}
const stories = Object.entries(this.storyIndex.entries).reduce(
(acc, [storyId, entry]) => {
if (entry.type === "docs") {
return acc;
}
const csfFile = cachedCSFFiles[entry.importPath];
const story = this.storyFromCSFFile({ storyId, csfFile });
if (!options.includeDocsOnly && story.parameters.docsOnly) {
return acc;
}
acc[storyId] = Object.entries(story).reduce(
(storyAcc, [key, value]) => {
if (key === "story" && entry.subtype === "test") {
return { ...storyAcc, story: entry.parentName };
}
if (key === "moduleExport") {
return storyAcc;
}
if (typeof value === "function") {
return storyAcc;
}
if (Array.isArray(value)) {
return Object.assign(storyAcc, { [key]: value.slice().sort() });
}
return Object.assign(storyAcc, { [key]: value });
},
{
args: story.initialArgs,
globals: {
...this.userGlobals.initialGlobals,
...this.userGlobals.globals,
...story.storyGlobals
},
storyId: entry.parent ? entry.parent : storyId
}
);
return acc;
},
{}
);
console.log("extract: stories", stories);
return stories;
}
};
__name(_StoryStore, "StoryStore");
var StoryStore = _StoryStore;
// src/preview-api/modules/store/autoTitle.ts
import { once as once2 } from "storybook/internal/client-logger";
// ../node_modules/slash/index.js
function slash(path) {
const isExtendedLengthPath = path.startsWith("\\\\?\\");
if (isExtendedLengthPath) {
return path;
}
return path.replace(/\\/g, "/");
}
__name(slash, "slash");
// src/preview-api/modules/store/autoTitle.ts
var sanitize2 = /* @__PURE__ */ __name((parts) => {
if (parts.length === 0) {
return parts;
}
const last = parts[parts.length - 1];
const lastStripped = last?.replace(/(?:[.](?:story|stories))?([.][^.]+)$/i, "");
if (parts.length === 1) {
return [lastStripped];
}
const nextToLast = parts[parts.length - 2];
if (lastStripped && nextToLast && lastStripped.toLowerCase() === nextToLast.toLowerCase()) {
return [...parts.slice(0, -2), lastStripped];
}
return lastStripped && (/^(story|stories)([.][^.]+)$/i.test(last) || /^index$/i.test(lastStripped)) ? parts.slice(0, -1) : [...parts.slice(0, -1), lastStripped];
}, "sanitize");
function pathJoin(paths) {
return paths.flatMap((p) => p.split("/")).filter(Boolean).join("/");
}
__name(pathJoin, "pathJoin");
var userOrAutoTitleFromSpecifier = /* @__PURE__ */ __name((fileName, entry, userTitle) => {
const { directory, importPathMatcher, titlePrefix = "" } = entry || {};
if (typeof fileName === "number") {
once2.warn(dedent`
CSF Auto-title received a numeric fileName. This typically happens when
webpack is mis-configured in production mode. To force webpack to produce
filenames, set optimization.moduleIds = "named" in your webpack config.
`);
}
const normalizedFileName = slash(String(fileName));
if (importPathMatcher.exec(normalizedFileName)) {
if (!userTitle) {
const suffix = normalizedFileName.replace(directory, "");
let parts = pathJoin([titlePrefix, suffix]).split("/");
parts = sanitize2(parts);
return parts.join("/");
}
if (!titlePrefix) {
return userTitle;
}
return pathJoin([titlePrefix, userTitle]);
}
return void 0;
}, "userOrAutoTitleFromSpecifier");
var userOrAutoTitle = /* @__PURE__ */ __name((fileName, storiesEntries, userTitle) => {
for (let i = 0; i < storiesEntries.length; i += 1) {
const title = userOrAutoTitleFromSpecifier(fileName, storiesEntries[i], userTitle);
if (title) {
return title;
}
}
return userTitle || void 0;
}, "userOrAutoTitle");
// src/preview-api/modules/store/storySort.ts
var STORY_KIND_PATH_SEPARATOR = /\s*\/\s*/;
var storySort = /* @__PURE__ */ __name((options = {}) => (a, b) => {
if (a.title === b.title && !options.includeNames) {
return 0;
}
const method = options.method || "configure";
let order = options.order || [];
const storyTitleA = a.title.trim().split(STORY_KIND_PATH_SEPARATOR);
const storyTitleB = b.title.trim().split(STORY_KIND_PATH_SEPARATOR);
if (options.includeNames) {
storyTitleA.push(a.name);
storyTitleB.push(b.name);
}
let depth = 0;
while (storyTitleA[depth] || storyTitleB[depth]) {
if (!storyTitleA[depth]) {
return -1;
}
if (!storyTitleB[depth]) {
return 1;
}
const nameA = storyTitleA[depth];
const nameB = storyTitleB[depth];
if (nameA !== nameB) {
let indexA = order.indexOf(nameA);
let indexB = order.indexOf(nameB);
const indexWildcard = order.indexOf("*");
if (indexA !== -1 || indexB !== -1) {
if (indexA === -1) {
if (indexWildcard !== -1) {
indexA = indexWildcard;
} else {
indexA = order.length;
}
}
if (indexB === -1) {
if (indexWildcard !== -1) {
indexB = indexWildcard;
} else {
indexB = order.length;
}
}
return indexA - indexB;
}
if (method === "configure") {
return 0;
}
return nameA.localeCompare(nameB, options.locales ? options.locales : void 0, {
numeric: true,
sensitivity: "accent"
});
}
let index = order.indexOf(nameA);
if (index === -1) {
index = order.indexOf("*");
}
order = index !== -1 && Array.isArray(order[index + 1]) ? order[index + 1] : [];
depth += 1;
}
return 0;
}, "storySort");
// src/preview-api/modules/store/sortStories.ts
var sortStoriesCommon = /* @__PURE__ */ __name((stories, storySortParameter, fileNameOrder) => {
if (storySortParameter) {
let sortFn;
if (typeof storySortParameter === "function") {
sortFn = storySortParameter;
} else {
sortFn = storySort(storySortParameter);
}
stories.sort(sortFn);
} else {
stories.sort(
(s1, s2) => fileNameOrder.indexOf(s1.importPath) - fileNameOrder.indexOf(s2.importPath)
);
}
return stories;
}, "sortStoriesCommon");
var sortStoriesV7 = /* @__PURE__ */ __name((stories, storySortParameter, fileNameOrder) => {
try {
return sortStoriesCommon(stories, storySortParameter, fileNameOrder);
} catch (err) {
throw new Error(dedent`
Error sorting stories with sort parameter ${storySortParameter}:
> ${err.message}
Are you using a V6-style sort function in V7 mode?
More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#v7-style-story-sort
`);
}
}, "sortStoriesV7");
// src/preview-api/modules/preview-web/Preview.tsx
import { deprecate as deprecate2, logger as logger7 } from "storybook/internal/client-logger";
import {
ARGTYPES_INFO_REQUEST,
ARGTYPES_INFO_RESPONSE,
CONFIG_ERROR,
FORCE_REMOUNT,
FORCE_RE_RENDER as FORCE_RE_RENDER2,
GLOBALS_UPDATED,
PREVIEW_INITIALIZED,
RESET_STORY_ARGS as RESET_STORY_ARGS2,
SET_GLOBALS,
STORY_ARGS_UPDATED,
STORY_HOT_UPDATED,
STORY_INDEX_INVALIDATED,
UPDATE_GLOBALS as UPDATE_GLOBALS2,
UPDATE_STORY_ARGS as UPDATE_STORY_ARGS2
} from "storybook/internal/core-events";
import {
CalledPreviewMethodBeforeInitializationError,
MissingRenderToCanvasError,
StoryIndexFetchError,
StoryStoreAccessedBeforeInitializationError
} from "storybook/internal/preview-errors";
import { global as global5 } from "@storybook/global";
// src/preview-api/modules/preview-web/render/StoryRender.ts
import {
PLAY_FUNCTION_THREW_EXCEPTION,
STORY_FINISHED,
STORY_RENDERED as STORY_RENDERED2,
STORY_RENDER_PHASE_CHANGED,
UNHANDLED_ERRORS_WHILE_PLAYING
} from "storybook/internal/core-events";
import {
MountMustBeDestructuredError as MountMustBeDestructuredError2,
NoStoryMountedError
} from "storybook/internal/preview-errors";
// src/preview-api/modules/preview-web/render/Render.ts
var PREPARE_ABORTED = new Error("prepareAborted");
// src/preview-api/modules/preview-web/render/StoryRender.ts
var { AbortController: AbortController2 } = globalThis;
function serializeError(error) {
try {
const { name = "Error", message = String(error), stack } = error;
return { name, message, stack };
} catch (e) {
return { name: "Error", message: String(error) };
}
}
__name(serializeError, "serializeError");
var _StoryRender = class _StoryRender {
constructor(channel, store, renderToScreen, callbacks, id, viewMode, renderOptions = { autoplay: true, forceInitialArgs: false }, story) {
this.channel = channel;
this.store = store;
this.renderToScreen = renderToScreen;
this.callbacks = callbacks;
this.id = id;
this.viewMode = viewMode;
this.renderOptions = renderOptions;
this.type = "story";
this.notYetRendered = true;
this.rerenderEnqueued = false;
this.disableKeyListeners = false;
this.teardownRender = /* @__PURE__ */ __name(() => {
}, "teardownRender");
this.torndown = false;
this.abortController = new AbortController2();
this.renderId = Date.now();
if (story) {
this.story = story;
this.phase = "preparing";
}
}
async runPhase(signal, phase, phaseFn) {
this.phase = phase;
this.channel.emit(STORY_RENDER_PHASE_CHANGED, {
newPhase: this.phase,
renderId: this.renderId,
storyId: this.id
});
if (phaseFn) {
await phaseFn();
this.checkIfAborted(signal);
}
}
checkIfAborted(signal) {
if (signal.aborted && !["finished", "aborted", "errored"].includes(this.phase)) {
this.phase = "aborted";
this.channel.emit(STORY_RENDER_PHASE_CHANGED, {
newPhase: this.phase,
renderId: this.renderId,
storyId: this.id
});
}
return signal.aborted;
}
async prepare() {
await this.runPhase(this.abortController.signal, "preparing", async () => {
this.story = await this.store.loadStory({ storyId: this.id });
});
if (this.abortController.signal.aborted) {
await this.store.cleanupStory(this.story);
throw PREPARE_ABORTED;
}
}
// The two story "renders" are equal and have both loaded the same story
isEqual(other) {
return !!(this.id === other.id && this.story && this.story === other.story);
}
isPreparing() {
return ["preparing"].includes(this.phase);
}
isPending() {
return ["loading", "beforeEach", "rendering", "playing", "afterEach"].includes(
this.phase
);
}
async renderToElement(canvasElement) {
this.canvasElement = canvasElement;
return this.render({ initial: true, forceRemount: true });
}
storyContext() {
if (!this.story) {
throw new Error(`Cannot call storyContext before preparing`);
}
const { forceInitialArgs } = this.renderOptions;
return this.store.getStoryContext(this.story, { forceInitialArgs });
}
async render({
initial = false,
forceRemount = false
} = {}) {
const { canvasElement } = this;
if (!this.story) {
throw new Error("cannot render when not prepared");
}
const story = this.story;
if (!canvasElement) {
throw new Error("cannot render when canvasElement is unset");
}
const {
id,
componentId,
title,
name,
tags,
applyLoaders,
applyBeforeEach,
applyAfterEach,
unboundStoryFn,
playFunction,
runStep
} = story;
if (forceRemount && !initial) {
this.cancelRender();
this.abortController = new AbortController2();
}
const abortSignal = this.abortController.signal;
let mounted = false;
const isMountDestructured = story.usesMount;
try {
const context = {
...this.storyContext(),
viewMode: this.viewMode,
abortSignal,
canvasElement,
loaded: {},
step: /* @__PURE__ */ __name((label, play) => runStep(label, play, context), "step"),
context: null,
canvas: {},
userEvent: {},
renderToCanvas: /* @__PURE__ */ __name(async () => {
const teardown = await this.renderToScreen(renderContext, canvasElement);
this.teardownRender = teardown || (() => {
});
mounted = true;
}, "renderToCanvas"),
// The story provides (set in a renderer) a mount function that is a higher order function
// (context) => (...args) => Canvas
//
// Before assigning it to the context, we resolve the context dependency,
// so that a user can just call it as await mount(...args) in their play function.
mount: /* @__PURE__ */ __name(async (...args) => {
this.callbacks.showStoryDuringRender?.();
let mountReturn = null;
await this.runPhase(abortSignal, "rendering", async () => {
mountReturn = await story.mount(context)(...args);
});
if (isMountDestructured) {
await this.runPhase(abortSignal, "playing");
}
return mountReturn;
}, "mount")
};
context.context = context;
const renderContext = {
componentId,
title,
kind: title,
id,
name,
story: name,
tags,
...this.callbacks,
showError: /* @__PURE__ */ __name((error) => {
this.phase = "errored";
return this.callbacks.showError(error);
}, "showError"),
showException: /* @__PURE__ */ __name((error) => {
this.phase = "errored";
return this.callbacks.showException(error);
}, "showException"),
forceRemount: forceRemount || this.notYetRendered,
storyContext: context,
storyFn: /* @__PURE__ */ __name(() => unboundStoryFn(context), "storyFn"),
unboundStoryFn
};
await this.runPhase(abortSignal, "loading", async () => {
context.loaded = await applyLoaders(context);
});
if (abortSignal.aborted) {
return;
}
const cleanupCallbacks = await applyBeforeEach(context);
this.store.addCleanupCallbacks(story, ...cleanupCallbacks);
if (this.checkIfAborted(abortSignal)) {
return;
}
if (!mounted && !isMountDestructured) {
await context.mount();
}
this.notYetRendered = false;
if (abortSignal.aborted) {
return;
}
const ignoreUnhandledErrors = this.story.parameters?.test?.dangerouslyIgnoreUnhandledErrors === true;
const unhandledErrors = /* @__PURE__ */ new Set();
const onError = /* @__PURE__ */ __name((event) => {
if (event.error) {
unhandledErrors.add(event.error);
}
}, "onError");
const onUnhandledRejection = /* @__PURE__ */ __name((event) => {
if (event.reason) {
unhandledErrors.add(event.reason);
}
}, "onUnhandledRejection");
if (this.renderOptions.autoplay && forceRemount && playFunction && this.phase !== "errored") {
window?.addEventListener?.("error", onError);
window?.addEventListener?.("unhandledrejection", onUnhandledRejection);
this.disableKeyListeners = true;
try {
if (!isMountDestructured) {
context.mount = async () => {
throw new MountMustBeDestructuredError2({ playFunction: playFunction.toString() });
};
await this.runPhase(abortSignal, "playing", async () => playFunction(context));
} else {
await playFunction(context);
}
if (!mounted) {
throw new NoStoryMountedError();
}
this.checkIfAborted(abortSignal);
if (!ignoreUnhandledErrors && unhandledErrors.size > 0) {
await this.runPhase(abortSignal, "errored");
} else {
await this.runPhase(abortSignal, "played");
}
} catch (error) {
this.callbacks.showStoryDuringRender?.();
await this.runPhase(abortSignal, "errored", async () => {
this.channel.emit(PLAY_FUNCTION_THREW_EXCEPTION, serializeError(error));
});
if (this.story.parameters.throwPlayFunctionExceptions !== false) {
throw error;
}
console.error(error);
}
if (!ignoreUnhandledErrors && unhandledErrors.size > 0) {
this.channel.emit(
UNHANDLED_ERRORS_WHILE_PLAYING,
Array.from(unhandledErrors).map(serializeError)
);
}
this.disableKeyListeners = false;
window?.removeEventListener?.("unhandledrejection", onUnhandledRejection);
window?.removeEventListener?.("error", onError);
if (abortSignal.aborted) {
return;
}
}
await this.runPhase(abortSignal, "completing", async () => {
if (isTestEnvironment()) {
this.store.addCleanupCallbacks(story, pauseAnimations());
} else {
await waitForAnimations(abortSignal);
}
});
await this.runPhase(abortSignal, "completed", async () => {
this.channel.emit(STORY_RENDERED2, id);
});
if (this.phase !== "errored") {
await this.runPhase(abortSignal, "afterEach", async () => {
await applyAfterEach(context);
});
}
const hasUnhandledErrors = !ignoreUnhandledErrors && unhandledErrors.size > 0;
const hasSomeReportsFailed = context.reporting.reports.some(
(report) => report.status === "failed"
);
const hasStoryErrored = hasUnhandledErrors || hasSomeReportsFailed;
await this.runPhase(
abortSignal,
"finished",
async () => this.channel.emit(STORY_FINISHED, {
storyId: id,
status: hasStoryErrored ? "error" : "success",
reporters: context.reporting.reports
})
);
} catch (err) {
this.phase = "errored";
this.callbacks.showException(err);
await this.runPhase(
abortSignal,
"finished",
async () => this.channel.emit(STORY_FINISHED, {
storyId: id,
status: "error",
reporters: []
})
);
}
if (this.rerenderEnqueued) {
this.rerenderEnqueued = false;
this.render();
}
}
/**
* Rerender the story. If the story is currently pending (loading/rendering), the rerender will be
* enqueued, and will be executed after the current render is completed. Rerendering while playing
* will not be enqueued, and will be executed immediately, to support rendering args changes while
* playing.
*/
async rerender() {
if (this.isPending() && this.phase !== "playing") {
this.rerenderEnqueued = true;
} else {
return this.render();
}
}
async remount() {
await this.teardown();
return this.render({ forceRemount: true });
}
// If the story is torn down (either a new story is rendered or the docs page removes it)
// we need to consider the fact that the initial render may not be finished
// (possibly the loaders or the play function are still running). We use the controller
// as a method to abort them, ASAP, but this is not foolproof as we cannot control what
// happens inside the user's code.
cancelRender() {
this.abortController.abort();
}
cancelPlayFunction() {
if (this.phase === "playing") {
this.abortController.abort();
this.runPhase(this.abortController.signal, "aborted");
}
}
async teardown() {
this.torndown = true;
this.cancelRender();
if (this.story) {
await this.store.cleanupStory(this.story);
}
for (let i = 0; i < 3; i += 1) {
if (!this.isPending()) {
await this.teardownRender();
return;
}
await new Promise((resolve) => setTimeout(resolve, 0));
}
window?.location?.reload?.();
await new Promise(() => {
});
}
};
__name(_StoryRender, "StoryRender");
var StoryRender = _StoryRender;
// src/preview-api/modules/preview-web/Preview.tsx
var { fetch } = global5;
var STORY_INDEX_PATH = "./index.json";
var _Preview = class _Preview {
constructor(importFn, getProjectAnnotations, channel = addons.getChannel(), shouldInitialize = true) {
this.importFn = importFn;
this.getProjectAnnotations = getProjectAnnotations;
this.channel = channel;
this.storyRenders = [];
this.storeInitializationPromise = new Promise((resolve, reject) => {
this.resolveStoreInitializationPromise = resolve;
this.rejectStoreInitializationPromise = reject;
});
if (shouldInitialize) {
this.initialize();
}
}
// Create a proxy object for `__STORYBOOK_STORY_STORE__` and `__STORYBOOK_PREVIEW__.storyStore`
// That proxies through to the store once ready, and errors beforehand. This means we can set
// `__STORYBOOK_STORY_STORE__ = __STORYBOOK_PREVIEW__.storyStore` without having to wait, and
// similarly integrators can access the `storyStore` on the preview at any time, although
// it is considered deprecated and we will no longer allow access in 9.0
get storyStore() {
return new Proxy(
{},
{
get: /* @__PURE__ */ __name((_, method) => {
if (this.storyStoreValue) {
deprecate2("Accessing the Story Store is deprecated and will be removed in 9.0");
return this.storyStoreValue[method];
}
throw new StoryStoreAccessedBeforeInitializationError();
}, "get")
}
);
}
// INITIALIZATION
async initialize() {
this.setupListeners();
try {
const projectAnnotations = await this.getProjectAnnotationsOrRenderError();
await this.runBeforeAllHook(projectAnnotations);
await this.initializeWithProjectAnnotations(projectAnnotations);
const userAgent = globalThis?.navigator?.userAgent;
await this.channel.emit(PREVIEW_INITIALIZED, { userAgent });
} catch (err) {
this.rejectStoreInitializationPromise(err);
}
}
ready() {
return this.storeInitializationPromise;
}
setupListeners() {
this.channel.on(STORY_INDEX_INVALIDATED, this.onStoryIndexChanged.bind(this));
this.channel.on(UPDATE_GLOBALS2, this.onUpdateGlobals.bind(this));
this.channel.on(UPDATE_STORY_ARGS2, this.onUpdateArgs.bind(this));
this.channel.on(ARGTYPES_INFO_REQUEST, this.onRequestArgTypesInfo.bind(this));
this.channel.on(RESET_STORY_ARGS2, this.onResetArgs.bind(this));
this.channel.on(FORCE_RE_RENDER2, this.onForceReRender.bind(this));
this.channel.on(FORCE_REMOUNT, this.onForceRemount.bind(this));
this.channel.on(STORY_HOT_UPDATED, this.onStoryHotUpdated.bind(this));
}
async getProjectAnnotationsOrRenderError() {
try {
const projectAnnotations = await this.getProjectAnnotations();
this.renderToCanvas = projectAnnotations.renderToCanvas;
if (!this.renderToCanvas) {
throw new MissingRenderToCanvasError();
}
return projectAnnotations;
} catch (err) {
this.renderPreviewEntryError("Error reading preview.js:", err);
throw err;
}
}
// If initialization gets as far as project annotations, this function runs.
async initializeWithProjectAnnotations(projectAnnotations) {
this.projectAnnotationsBeforeInitialization = projectAnnotations;
try {
const storyIndex = await this.getStoryIndexFromServer();
return this.initializeWithStoryIndex(storyIndex);
} catch (err) {
this.renderPreviewEntryError("Error loading story index:", err);
throw err;
}
}
async runBeforeAllHook(projectAnnotations) {
try {
await this.beforeAllCleanup?.();
this.beforeAllCleanup = await projectAnnotations.beforeAll?.();
} catch (err) {
this.renderPreviewEntryError("Error in beforeAll hook:", err);
throw err;
}
}
async getStoryIndexFromServer() {
const result = await fetch(STORY_INDEX_PATH);
if (result.status === 200) {
return result.json();
}
throw new StoryIndexFetchError({ text: await result.text() });
}
// If initialization gets as far as the story index, this function runs.
initializeWithStoryIndex(storyIndex) {
if (!this.projectAnnotationsBeforeInitialization) {
throw new Error("Cannot call initializeWithStoryIndex until project annotations resolve");
}
this.storyStoreValue = new StoryStore(
storyIndex,
this.importFn,
this.projectAnnotationsBeforeInitialization
);
delete this.projectAnnotationsBeforeInitialization;
this.setInitialGlobals();
this.resolveStoreInitializationPromise();
}
async setInitialGlobals() {
this.emitGlobals();
}
emitGlobals() {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "emitGlobals" });
}
const payload = {
globals: this.storyStoreValue.userGlobals.get() || {},
globalTypes: this.storyStoreValue.projectAnnotations.globalTypes || {}
};
this.channel.emit(SET_GLOBALS, payload);
}
// EVENT HANDLERS
// This happens when a config file gets reloaded
async onGetProjectAnnotationsChanged({
getProjectAnnotations
}) {
delete this.previewEntryError;
this.getProjectAnnotations = getProjectAnnotations;
const projectAnnotations = await this.getProjectAnnotationsOrRenderError();
await this.runBeforeAllHook(projectAnnotations);
if (!this.storyStoreValue) {
await this.initializeWithProjectAnnotations(projectAnnotations);
return;
}
this.storyStoreValue.setProjectAnnotations(projectAnnotations);
this.emitGlobals();
}
async onStoryIndexChanged() {
delete this.previewEntryError;
if (!this.storyStoreValue && !this.projectAnnotationsBeforeInitialization) {
return;
}
try {
const storyIndex = await this.getStoryIndexFromServer();
if (this.projectAnnotationsBeforeInitialization) {
this.initializeWithStoryIndex(storyIndex);
return;
}
await this.onStoriesChanged({ storyIndex });
} catch (err) {
this.renderPreviewEntryError("Error loading story index:", err);
throw err;
}
}
// This happens when a glob gets HMR-ed
async onStoriesChanged({
importFn,
storyIndex
}) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "onStoriesChanged" });
}
await this.storyStoreValue.onStoriesChanged({ importFn, storyIndex });
}
async onUpdateGlobals({
globals: updatedGlobals,
currentStory
}) {
if (!this.storyStoreValue) {
await this.storeInitializationPromise;
}
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "onUpdateGlobals" });
}
this.storyStoreValue.userGlobals.update(updatedGlobals);
if (currentStory) {
const { initialGlobals, storyGlobals, userGlobals, globals } = this.storyStoreValue.getStoryContext(currentStory);
this.channel.emit(GLOBALS_UPDATED, {
initialGlobals,
userGlobals,
storyGlobals,
globals
});
} else {
const { initialGlobals, globals } = this.storyStoreValue.userGlobals;
this.channel.emit(GLOBALS_UPDATED, {
initialGlobals,
userGlobals: globals,
storyGlobals: {},
globals
});
}
await Promise.all(this.storyRenders.map((r) => r.rerender()));
}
async onUpdateArgs({ storyId, updatedArgs }) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "onUpdateArgs" });
}
this.storyStoreValue.args.update(storyId, updatedArgs);
await Promise.all(
this.storyRenders.filter((r) => r.id === storyId && !r.renderOptions.forceInitialArgs).map(
(r) => (
// We only run the play function, with in a force remount.
// But when mount is destructured, the rendering happens inside of the play function.
r.story && r.story.usesMount ? r.remount() : r.rerender()
)
)
);
this.channel.emit(STORY_ARGS_UPDATED, {
storyId,
args: this.storyStoreValue.args.get(storyId)
});
}
async onRequestArgTypesInfo({ id, payload }) {
try {
await this.storeInitializationPromise;
const story = await this.storyStoreValue?.loadStory(payload);
this.channel.emit(ARGTYPES_INFO_RESPONSE, {
id,
success: true,
payload: { argTypes: story?.argTypes || {} },
error: null
});
} catch (e) {
this.channel.emit(ARGTYPES_INFO_RESPONSE, {
id,
success: false,
error: e?.message
});
}
}
async onResetArgs({ storyId, argNames }) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "onResetArgs" });
}
const render = this.storyRenders.find((r) => r.id === storyId);
const story = render?.story || await this.storyStoreValue.loadStory({ storyId });
const argNamesToReset = argNames || [
.../* @__PURE__ */ new Set([
...Object.keys(story.initialArgs),
...Object.keys(this.storyStoreValue.args.get(storyId))
])
];
const updatedArgs = argNamesToReset.reduce((acc, argName) => {
acc[argName] = story.initialArgs[argName];
return acc;
}, {});
await this.onUpdateArgs({ storyId, updatedArgs });
}
// ForceReRender does not include a story id, so we simply must
// re-render all stories in case they are relevant
async onForceReRender() {
await Promise.all(this.storyRenders.map((r) => r.rerender()));
}
async onForceRemount({ storyId }) {
await Promise.all(this.storyRenders.filter((r) => r.id === storyId).map((r) => r.remount()));
}
async onStoryHotUpdated() {
await Promise.all(this.storyRenders.map((r) => r.cancelPlayFunction()));
}
// Used by docs to render a story to a given element
// Note this short-circuits the `prepare()` phase of the StoryRender,
// main to be consistent with the previous behaviour. In the future,
// we will change it to go ahead and load the story, which will end up being
// "instant", although async.
renderStoryToElement(story, element, callbacks, options) {
if (!this.renderToCanvas || !this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({
methodName: "renderStoryToElement"
});
}
const render = new StoryRender(
this.channel,
this.storyStoreValue,
this.renderToCanvas,
callbacks,
story.id,
"docs",
options,
story
);
render.renderToElement(element);
this.storyRenders.push(render);
return async () => {
await this.teardownRender(render);
};
}
async teardownRender(render, { viewModeChanged } = {}) {
this.storyRenders = this.storyRenders.filter((r) => r !== render);
await render?.teardown?.({ viewModeChanged });
}
// API
async loadStory({ storyId }) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "loadStory" });
}
return this.storyStoreValue.loadStory({ storyId });
}
getStoryContext(story, { forceInitialArgs = false } = {}) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "getStoryContext" });
}
return this.storyStoreValue.getStoryContext(story, { forceInitialArgs });
}
async extract(options) {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError({ methodName: "extract" });
}
if (this.previewEntryError) {
throw this.previewEntryError;
}
await this.storyStoreValue.cacheAllCSFFiles();
return this.storyStoreValue.extract(options);
}
// UTILITIES
renderPreviewEntryError(reason, err) {
this.previewEntryError = err;
logger7.error(reason);
logger7.error(err);
this.channel.emit(CONFIG_ERROR, err);
}
};
__name(_Preview, "Preview");
var Preview = _Preview;
// src/preview-api/modules/preview-web/PreviewWeb.tsx
import { global as global8 } from "@storybook/global";
// src/preview-api/modules/preview-web/PreviewWithSelection.tsx
import { logger as logger8 } from "storybook/internal/client-logger";
import {
CURRENT_STORY_WAS_SET,
DOCS_PREPARED,
GLOBALS_UPDATED as GLOBALS_UPDATED2,
PRELOAD_ENTRIES,
PREVIEW_KEYDOWN,
SET_CURRENT_STORY,
STORY_CHANGED,
STORY_ERRORED,
STORY_MISSING,
STORY_PREPARED,
STORY_RENDER_PHASE_CHANGED as STORY_RENDER_PHASE_CHANGED2,
STORY_SPECIFIED,
STORY_THREW_EXCEPTION,
STORY_UNCHANGED,
UPDATE_QUERY_PARAMS
} from "storybook/internal/core-events";
import {
CalledPreviewMethodBeforeInitializationError as CalledPreviewMethodBeforeInitializationError2,
EmptyIndexError,
MdxFileWithNoCsfReferencesError,
NoStoryMatchError
} from "storybook/internal/preview-errors";
// src/preview-api/modules/preview-web/render/CsfDocsRender.ts
import { DOCS_RENDERED } from "storybook/internal/core-events";
// src/preview-api/modules/preview-web/docs-context/DocsContext.ts
import { isStory as isStory3 } from "storybook/internal/csf";
var _DocsContext = class _DocsContext {
constructor(channel, store, renderStoryToElement, csfFiles) {
this.channel = channel;
this.store = store;
this.renderStoryToElement = renderStoryToElement;
this.storyIdByName = /* @__PURE__ */ __name((storyName) => {
const storyId = this.nameToStoryId.get(storyName);
if (storyId) {
return storyId;
}
throw new Error(`No story found with that name: ${storyName}`);
}, "storyIdByName");
this.componentStories = /* @__PURE__ */ __name(() => {
return this.componentStoriesValue;
}, "componentStories");
this.componentStoriesFromCSFFile = /* @__PURE__ */ __name((csfFile) => {
return this.store.componentStoriesFromCSFFile({ csfFile });
}, "componentStoriesFromCSFFile");
this.storyById = /* @__PURE__ */ __name((storyId) => {
if (!storyId) {
if (!this.primaryStory) {
throw new Error(
`No primary story defined for docs entry. Did you forget to use \`<Meta>\`?`
);
}
return this.primaryStory;
}
const csfFile = this.storyIdToCSFFile.get(storyId);
if (!csfFile) {
throw new Error(`Called \`storyById\` for story that was never loaded: ${storyId}`);
}
return this.store.storyFromCSFFile({ storyId, csfFile });
}, "storyById");
this.getStoryContext = /* @__PURE__ */ __name((story) => {
return {
...this.store.getStoryContext(story),
loaded: {},
viewMode: "docs"
};
}, "getStoryContext");
this.loadStory = /* @__PURE__ */ __name((id) => {
return this.store.loadStory({ storyId: id });
}, "loadStory");
this.componentStoriesValue = [];
this.storyIdToCSFFile = /* @__PURE__ */ new Map();
this.exportToStory = /* @__PURE__ */ new Map();
this.exportsToCSFFile = /* @__PURE__ */ new Map();
this.nameToStoryId = /* @__PURE__ */ new Map();
this.attachedCSFFiles = /* @__PURE__ */ new Set();
csfFiles.forEach((csfFile, index) => {
this.referenceCSFFile(csfFile);
});
}
// This docs entry references this CSF file and can synchronously load the stories, as well
// as reference them by module export. If the CSF is part of the "component" stories, they
// can also be referenced by name and are in the componentStories list.
referenceCSFFile(csfFile) {
this.exportsToCSFFile.set(csfFile.moduleExports, csfFile);
this.exportsToCSFFile.set(csfFile.moduleExports.default, csfFile);
const stories = this.store.componentStoriesFromCSFFile({ csfFile });
stories.forEach((story) => {
const annotation = csfFile.stories[story.id];
this.storyIdToCSFFile.set(annotation.id, csfFile);
this.exportToStory.set(annotation.moduleExport, story);
});
}
attachCSFFile(csfFile) {
if (!this.exportsToCSFFile.has(csfFile.moduleExports)) {
throw new Error("Cannot attach a CSF file that has not been referenced");
}
if (this.attachedCSFFiles.has(csfFile)) {
return;
}
this.attachedCSFFiles.add(csfFile);
const stories = this.store.componentStoriesFromCSFFile({ csfFile });
stories.forEach((story) => {
this.nameToStoryId.set(story.name, story.id);
this.componentStoriesValue.push(story);
if (!this.primaryStory) {
this.primaryStory = story;
}
});
}
referenceMeta(metaExports, attach) {
const resolved = this.resolveModuleExport(metaExports);
if (resolved.type !== "meta") {
throw new Error(
"<Meta of={} /> must reference a CSF file module export or meta export. Did you mistakenly reference your component instead of your CSF file?"
);
}
if (attach) {
this.attachCSFFile(resolved.csfFile);
}
}
get projectAnnotations() {
const { projectAnnotations } = this.store;
if (!projectAnnotations) {
throw new Error("Can't get projectAnnotations from DocsContext before they are initialized");
}
return projectAnnotations;
}
resolveAttachedModuleExportType(moduleExportType) {
if (moduleExportType === "story") {
if (!this.primaryStory) {
throw new Error(
`No primary story attached to this docs file, did you forget to use <Meta of={} />?`
);
}
return { type: "story", story: this.primaryStory };
}
if (this.attachedCSFFiles.size === 0) {
throw new Error(
`No CSF file attached to this docs file, did you forget to use <Meta of={} />?`
);
}
const firstAttachedCSFFile = Array.from(this.attachedCSFFiles)[0];
if (moduleExportType === "meta") {
return { type: "meta", csfFile: firstAttachedCSFFile };
}
const { component } = firstAttachedCSFFile.meta;
if (!component) {
throw new Error(
`Attached CSF file does not defined a component, did you forget to export one?`
);
}
return { type: "component", component };
}
resolveModuleExport(moduleExportOrType) {
const csfFile = this.exportsToCSFFile.get(moduleExportOrType);
if (csfFile) {
return { type: "meta", csfFile };
}
const story = this.exportToStory.get(
isStory3(moduleExportOrType) ? moduleExportOrType.input : moduleExportOrType
);
if (story) {
return { type: "story", story };
}
return { type: "component", component: moduleExportOrType };
}
resolveOf(moduleExportOrType, validTypes = []) {
let resolved;
if (["component", "meta", "story"].includes(moduleExportOrType)) {
const type = moduleExportOrType;
resolved = this.resolveAttachedModuleExportType(type);
} else {
resolved = this.resolveModuleExport(moduleExportOrType);
}
if (validTypes.length && !validTypes.includes(resolved.type)) {
const prettyType = resolved.type === "component" ? "component or unknown" : resolved.type;
throw new Error(dedent`Invalid value passed to the 'of' prop. The value was resolved to a '${prettyType}' type but the only types for this block are: ${validTypes.join(
", "
)}.
- Did you pass a component to the 'of' prop when the block only supports a story or a meta?
- ... or vice versa?
- Did you pass a story, CSF file or meta to the 'of' prop that is not indexed, ie. is not targeted by the 'stories' globs in the main configuration?`);
}
switch (resolved.type) {
case "component": {
return {
...resolved,
projectAnnotations: this.projectAnnotations
};
}
case "meta": {
return {
...resolved,
preparedMeta: this.store.preparedMetaFromCSFFile({ csfFile: resolved.csfFile })
};
}
case "story":
default: {
return resolved;
}
}
}
};
__name(_DocsContext, "DocsContext");
var DocsContext = _DocsContext;
// src/preview-api/modules/preview-web/render/CsfDocsRender.ts
var _CsfDocsRender = class _CsfDocsRender {
constructor(channel, store, entry, callbacks) {
this.channel = channel;
this.store = store;
this.entry = entry;
this.callbacks = callbacks;
this.type = "docs";
this.subtype = "csf";
this.torndown = false;
this.disableKeyListeners = false;
this.preparing = false;
this.id = entry.id;
this.renderId = Date.now();
}
isPreparing() {
return this.preparing;
}
async prepare() {
this.preparing = true;
const { entryExports, csfFiles = [] } = await this.store.loadEntry(this.id);
if (this.torndown) {
throw PREPARE_ABORTED;
}
const { importPath, title } = this.entry;
const primaryCsfFile = this.store.processCSFFileWithCache(
entryExports,
importPath,
title
);
const primaryStoryId = Object.keys(primaryCsfFile.stories)[0];
this.story = this.store.storyFromCSFFile({ storyId: primaryStoryId, csfFile: primaryCsfFile });
this.csfFiles = [primaryCsfFile, ...csfFiles];
this.preparing = false;
}
isEqual(other) {
return !!(this.id === other.id && this.story && this.story === other.story);
}
docsContext(renderStoryToElement) {
if (!this.csfFiles) {
throw new Error("Cannot render docs before preparing");
}
const docsContext = new DocsContext(
this.channel,
this.store,
renderStoryToElement,
this.csfFiles
);
this.csfFiles.forEach((csfFile) => docsContext.attachCSFFile(csfFile));
return docsContext;
}
async renderToElement(canvasElement, renderStoryToElement) {
if (!this.story || !this.csfFiles) {
throw new Error("Cannot render docs before preparing");
}
const docsContext = this.docsContext(renderStoryToElement);
const { docs: docsParameter } = this.story.parameters || {};
if (!docsParameter) {
throw new Error(
`Cannot render a story in viewMode=docs if \`@storybook/addon-docs\` is not installed`
);
}
const renderer = await docsParameter.renderer();
const { render } = renderer;
const renderDocs = /* @__PURE__ */ __name(async () => {
try {
await render(docsContext, docsParameter, canvasElement);
this.channel.emit(DOCS_RENDERED, this.id);
} catch (err) {
this.callbacks.showException(err);
}
}, "renderDocs");
this.rerender = async () => renderDocs();
this.teardownRender = async ({ viewModeChanged }) => {
if (!viewModeChanged || !canvasElement) {
return;
}
renderer.unmount(canvasElement);
};
return renderDocs();
}
async teardown({ viewModeChanged } = {}) {
this.teardownRender?.({ viewModeChanged });
this.torndown = true;
}
};
__name(_CsfDocsRender, "CsfDocsRender");
var CsfDocsRender = _CsfDocsRender;
// src/preview-api/modules/preview-web/render/MdxDocsRender.ts
import { DOCS_RENDERED as DOCS_RENDERED2 } from "storybook/internal/core-events";
var _MdxDocsRender = class _MdxDocsRender {
constructor(channel, store, entry, callbacks) {
this.channel = channel;
this.store = store;
this.entry = entry;
this.callbacks = callbacks;
this.type = "docs";
this.subtype = "mdx";
this.torndown = false;
this.disableKeyListeners = false;
this.preparing = false;
this.id = entry.id;
this.renderId = Date.now();
}
isPreparing() {
return this.preparing;
}
async prepare() {
this.preparing = true;
const { entryExports, csfFiles = [] } = await this.store.loadEntry(this.id);
if (this.torndown) {
throw PREPARE_ABORTED;
}
this.csfFiles = csfFiles;
this.exports = entryExports;
this.preparing = false;
}
isEqual(other) {
return !!(this.id === other.id && this.exports && this.exports === other.exports);
}
docsContext(renderStoryToElement) {
if (!this.csfFiles) {
throw new Error("Cannot render docs before preparing");
}
return new DocsContext(
this.channel,
this.store,
renderStoryToElement,
this.csfFiles
);
}
async renderToElement(canvasElement, renderStoryToElement) {
if (!this.exports || !this.csfFiles || !this.store.projectAnnotations) {
throw new Error("Cannot render docs before preparing");
}
const docsContext = this.docsContext(renderStoryToElement);
const { docs } = this.store.projectAnnotations.parameters ?? {};
if (!docs) {
throw new Error(
`Cannot render a story in viewMode=docs if \`@storybook/addon-docs\` is not installed`
);
}
const docsParameter = { ...docs, page: this.exports.default };
const renderer = await docs.renderer();
const { render } = renderer;
const renderDocs = /* @__PURE__ */ __name(async () => {
try {
await render(docsContext, docsParameter, canvasElement);
this.channel.emit(DOCS_RENDERED2, this.id);
} catch (err) {
this.callbacks.showException(err);
}
}, "renderDocs");
this.rerender = async () => renderDocs();
this.teardownRender = async ({ viewModeChanged } = {}) => {
if (!viewModeChanged || !canvasElement) {
return;
}
renderer.unmount(canvasElement);
this.torndown = true;
};
return renderDocs();
}
async teardown({ viewModeChanged } = {}) {
this.teardownRender?.({ viewModeChanged });
this.torndown = true;
}
};
__name(_MdxDocsRender, "MdxDocsRender");
var MdxDocsRender = _MdxDocsRender;
// src/preview-api/modules/preview-web/PreviewWithSelection.tsx
var globalWindow = globalThis;
function focusInInput(event) {
const target = event.composedPath && event.composedPath()[0] || event.target;
return /input|textarea/i.test(target.tagName) || target.getAttribute("contenteditable") !== null;
}
__name(focusInInput, "focusInInput");
var ATTACHED_MDX_TAG = "attached-mdx";
var UNATTACHED_MDX_TAG = "unattached-mdx";
function isMdxEntry({ tags }) {
return tags?.includes(UNATTACHED_MDX_TAG) || tags?.includes(ATTACHED_MDX_TAG);
}
__name(isMdxEntry, "isMdxEntry");
function isStoryRender(r) {
return r.type === "story";
}
__name(isStoryRender, "isStoryRender");
function isDocsRender(r) {
return r.type === "docs";
}
__name(isDocsRender, "isDocsRender");
function isCsfDocsRender(r) {
return isDocsRender(r) && r.subtype === "csf";
}
__name(isCsfDocsRender, "isCsfDocsRender");
var _PreviewWithSelection = class _PreviewWithSelection extends Preview {
constructor(importFn, getProjectAnnotations, selectionStore, view) {
super(importFn, getProjectAnnotations, void 0, false);
this.importFn = importFn;
this.getProjectAnnotations = getProjectAnnotations;
this.selectionStore = selectionStore;
this.view = view;
this.initialize();
}
setupListeners() {
super.setupListeners();
globalWindow.onkeydown = this.onKeydown.bind(this);
this.channel.on(SET_CURRENT_STORY, this.onSetCurrentStory.bind(this));
this.channel.on(UPDATE_QUERY_PARAMS, this.onUpdateQueryParams.bind(this));
this.channel.on(PRELOAD_ENTRIES, this.onPreloadStories.bind(this));
}
async setInitialGlobals() {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError2({ methodName: "setInitialGlobals" });
}
const { globals } = this.selectionStore.selectionSpecifier || {};
if (globals) {
this.storyStoreValue.userGlobals.updateFromPersisted(globals);
}
this.emitGlobals();
}
// If initialization gets as far as the story index, this function runs.
async initializeWithStoryIndex(storyIndex) {
await super.initializeWithStoryIndex(storyIndex);
return this.selectSpecifiedStory();
}
// Use the selection specifier to choose a story, then render it
async selectSpecifiedStory() {
if (!this.storyStoreValue) {
throw new CalledPreviewMethodBeforeInitializationError2({
methodName: "selectSpecifiedStory"
});
}
if (this.selectionStore.selection) {
await this.renderSelection();
return;
}
if (!this.selectionStore.selectionSpecifier) {
this.renderMissingStory();
return;
}
const { storySpecifier, args } = this.selectionStore.selectionSpecifier;
const entry = this.storyStoreValue.storyIndex.entryFromSpecifier(storySpecifier);
if (!entry) {
if (storySpecifier === "*") {
this.renderStoryLoadingException(storySpecifier, new EmptyIndexError());
} else {
this.renderStoryLoadingException(
storySpecifier,
new NoStoryMatchError({ storySpecifier: storySpecifier.toString() })
);
}
return;
}
const { id: storyId, type: viewMode } = entry;
this.selectionStore.setSelection({ storyId, viewMode });
this.channel.emit(STORY_SPECIFIED, this.selectionStore.selection);
this.channel.emit(CURRENT_STORY_WAS_SET, this.selectionStore.selection);
await this.renderSelection({ persistedArgs: args });
}
// EVENT HANDLERS
// This happens when a config file gets reloaded
async onGetProjectAnnotationsChanged({
getProjectAnnotations
}) {
await super.onGetProjectAnnotationsChanged({ getProjectAnnotations });
if (this.selectionStore.selection) {
this.renderSelection();
}
}
// This happens when a glob gets HMR-ed
async onStoriesChanged({
importFn,
storyIndex
}) {
await super.onStoriesChanged({ importFn, storyIndex });
if (this.selectionStore.selection) {
await this.renderSelection();
} else {
await this.selectSpecifiedStory();
}
}
onKeydown(event) {
if (!this.storyRenders.find((r) => r.disableKeyListeners) && !focusInInput(event)) {
const { altKey, ctrlKey, metaKey, shiftKey, key, code, keyCode } = event;
this.channel.emit(PREVIEW_KEYDOWN, {
event: { altKey, ctrlKey, metaKey, shiftKey, key, code, keyCode }
});
}
}
async onSetCurrentStory(selection) {
this.selectionStore.setSelection({ viewMode: "story", ...selection });
await this.storeInitializationPromise;
this.channel.emit(CURRENT_STORY_WAS_SET, this.selectionStore.selection);
this.renderSelection();
}
onUpdateQueryParams(queryParams) {
this.selectionStore.setQueryParams(queryParams);
}
async onUpdateGlobals({ globals }) {
const currentStory = this.currentRender instanceof StoryRender && this.currentRender.story || void 0;
super.onUpdateGlobals({ globals, currentStory });
if (this.currentRender instanceof MdxDocsRender || this.currentRender instanceof CsfDocsRender) {
await this.currentRender.rerender?.();
}
}
async onUpdateArgs({ storyId, updatedArgs }) {
super.onUpdateArgs({ storyId, updatedArgs });
}
async onPreloadStories({ ids }) {
await this.storeInitializationPromise;
if (this.storyStoreValue) {
await Promise.allSettled(ids.map((id) => this.storyStoreValue?.loadEntry(id)));
}
}
// RENDERING
// We can either have:
// - a story selected in "story" viewMode,
// in which case we render it to the root element, OR
// - a story selected in "docs" viewMode,
// in which case we render the docsPage for that story
async renderSelection({ persistedArgs } = {}) {
const { renderToCanvas } = this;
if (!this.storyStoreValue || !renderToCanvas) {
throw new CalledPreviewMethodBeforeInitializationError2({ methodName: "renderSelection" });
}
const { selection } = this.selectionStore;
if (!selection) {
throw new Error("Cannot call renderSelection as no selection was made");
}
const { storyId } = selection;
let entry;
try {
entry = await this.storyStoreValue.storyIdToEntry(storyId);
} catch (err) {
if (this.currentRender) {
await this.teardownRender(this.currentRender);
}
this.renderStoryLoadingException(storyId, err);
return;
}
const storyIdChanged = this.currentSelection?.storyId !== storyId;
const viewModeChanged = this.currentRender?.type !== entry.type;
if (entry.type === "story") {
this.view.showPreparingStory({ immediate: viewModeChanged });
} else {
this.view.showPreparingDocs({ immediate: viewModeChanged });
}
if (this.currentRender?.isPreparing()) {
await this.teardownRender(this.currentRender);
}
let render;
if (entry.type === "story") {
render = new StoryRender(
this.channel,
this.storyStoreValue,
renderToCanvas,
this.mainStoryCallbacks(storyId),
storyId,
"story"
);
} else if (isMdxEntry(entry)) {
render = new MdxDocsRender(
this.channel,
this.storyStoreValue,
entry,
this.mainStoryCallbacks(storyId)
);
} else {
render = new CsfDocsRender(
this.channel,
this.storyStoreValue,
entry,
this.mainStoryCallbacks(storyId)
);
}
const lastSelection = this.currentSelection;
this.currentSelection = selection;
const lastRender = this.currentRender;
this.currentRender = render;
try {
await render.prepare();
} catch (err) {
if (lastRender) {
await this.teardownRender(lastRender);
}
if (err !== PREPARE_ABORTED) {
this.renderStoryLoadingException(storyId, err);
}
return;
}
const implementationChanged = !storyIdChanged && lastRender && !render.isEqual(lastRender);
if (persistedArgs && isStoryRender(render)) {
invariant(!!render.story);
this.storyStoreValue.args.updateFromPersisted(render.story, persistedArgs);
}
if (lastRender && !lastRender.torndown && !storyIdChanged && !implementationChanged && !viewModeChanged) {
this.currentRender = lastRender;
this.channel.emit(STORY_UNCHANGED, storyId);
this.view.showMain();
return;
}
if (lastRender) {
await this.teardownRender(lastRender, { viewModeChanged });
}
if (lastSelection && (storyIdChanged || viewModeChanged)) {
this.channel.emit(STORY_CHANGED, storyId);
}
if (isStoryRender(render)) {
invariant(!!render.story);
const {
parameters,
initialArgs,
argTypes,
unmappedArgs,
initialGlobals,
userGlobals,
storyGlobals,
globals
} = this.storyStoreValue.getStoryContext(render.story);
this.channel.emit(STORY_PREPARED, {
id: storyId,
parameters,
initialArgs,
argTypes,
args: unmappedArgs
});
this.channel.emit(GLOBALS_UPDATED2, { userGlobals, storyGlobals, globals, initialGlobals });
} else {
let { parameters } = this.storyStoreValue.projectAnnotations;
const { initialGlobals, globals } = this.storyStoreValue.userGlobals;
this.channel.emit(GLOBALS_UPDATED2, {
globals,
initialGlobals,
storyGlobals: {},
userGlobals: globals
});
if (isCsfDocsRender(render) || render.entry.tags?.includes(ATTACHED_MDX_TAG)) {
if (!render.csfFiles) {
throw new MdxFileWithNoCsfReferencesError({ storyId });
}
({ parameters } = this.storyStoreValue.preparedMetaFromCSFFile({
csfFile: render.csfFiles[0]
}));
}
this.channel.emit(DOCS_PREPARED, {
id: storyId,
parameters
});
}
if (isStoryRender(render)) {
invariant(!!render.story);
this.storyRenders.push(render);
this.currentRender.renderToElement(
this.view.prepareForStory(render.story)
);
} else {
this.currentRender.renderToElement(
this.view.prepareForDocs(),
// This argument is used for docs, which is currently only compatible with HTMLElements
this.renderStoryToElement.bind(this)
);
}
}
async teardownRender(render, { viewModeChanged = false } = {}) {
this.storyRenders = this.storyRenders.filter((r) => r !== render);
await render?.teardown?.({ viewModeChanged });
}
// UTILITIES
mainStoryCallbacks(storyId) {
return {
showStoryDuringRender: /* @__PURE__ */ __name(() => this.view.showStoryDuringRender(), "showStoryDuringRender"),
showMain: /* @__PURE__ */ __name(() => this.view.showMain(), "showMain"),
showError: /* @__PURE__ */ __name((err) => this.renderError(storyId, err), "showError"),
showException: /* @__PURE__ */ __name((err) => this.renderException(storyId, err), "showException")
};
}
renderPreviewEntryError(reason, err) {
super.renderPreviewEntryError(reason, err);
this.view.showErrorDisplay(err);
}
renderMissingStory() {
this.view.showNoPreview();
this.channel.emit(STORY_MISSING);
}
renderStoryLoadingException(storySpecifier, err) {
logger8.error(err);
this.view.showErrorDisplay(err);
this.channel.emit(STORY_MISSING, storySpecifier);
}
// renderException is used if we fail to render the story and it is uncaught by the app layer
renderException(storyId, error) {
const { name = "Error", message = String(error), stack } = error;
const renderId = this.currentRender?.renderId;
this.channel.emit(STORY_THREW_EXCEPTION, { name, message, stack });
this.channel.emit(STORY_RENDER_PHASE_CHANGED2, { newPhase: "errored", renderId, storyId });
this.view.showErrorDisplay(error);
logger8.error(`Error rendering story '${storyId}':`);
logger8.error(error);
}
// renderError is used by the various app layers to inform the user they have done something
// wrong -- for instance returned the wrong thing from a story
renderError(storyId, { title, description }) {
const renderId = this.currentRender?.renderId;
this.channel.emit(STORY_ERRORED, { title, description });
this.channel.emit(STORY_RENDER_PHASE_CHANGED2, { newPhase: "errored", renderId, storyId });
this.view.showErrorDisplay({ message: title, stack: description });
logger8.error(`Error rendering story ${title}: ${description}`);
}
};
__name(_PreviewWithSelection, "PreviewWithSelection");
var PreviewWithSelection = _PreviewWithSelection;
// src/preview-api/modules/preview-web/UrlStore.ts
var import_picoquery2 = __toESM(require_main(), 1);
import { global as global6 } from "@storybook/global";
// src/preview-api/modules/preview-web/parseArgsParam.ts
import { once as once3 } from "storybook/internal/client-logger";
var import_picoquery = __toESM(require_main(), 1);
var VALIDATION_REGEXP = /^[a-zA-Z0-9 _-]*$/;
var NUMBER_REGEXP = /^-?[0-9]+(\.[0-9]+)?$/;
var HEX_REGEXP = /^#([a-f0-9]{3,4}|[a-f0-9]{6}|[a-f0-9]{8})$/i;
var COLOR_REGEXP = /^(rgba?|hsla?)\(([0-9]{1,3}),\s?([0-9]{1,3})%?,\s?([0-9]{1,3})%?,?\s?([0-9](\.[0-9]{1,2})?)?\)$/i;
var validateArgs = /* @__PURE__ */ __name((key = "", value) => {
if (key === null) {
return false;
}
if (key === "" || !VALIDATION_REGEXP.test(key)) {
return false;
}
if (value === null || value === void 0) {
return true;
}
if (value instanceof Date) {
return true;
}
if (typeof value === "number" || typeof value === "boolean") {
return true;
}
if (typeof value === "string") {
return VALIDATION_REGEXP.test(value) || NUMBER_REGEXP.test(value) || HEX_REGEXP.test(value) || COLOR_REGEXP.test(value);
}
if (Array.isArray(value)) {
return value.every((v) => validateArgs(key, v));
}
if (isPlainObject(value)) {
return Object.entries(value).every(([k, v]) => validateArgs(k, v));
}
return false;
}, "validateArgs");
var QUERY_OPTIONS = {
delimiter: ";",
// we're parsing a single query param
nesting: true,
arrayRepeat: true,
arrayRepeatSyntax: "bracket",
nestingSyntax: "js",
// objects are encoded using dot notation
valueDeserializer(str) {
if (str.startsWith("!")) {
if (str === "!undefined") {
return void 0;
}
if (str === "!null") {
return null;
}
if (str === "!true") {
return true;
}
if (str === "!false") {
return false;
}
if (str.startsWith("!date(") && str.endsWith(")")) {
return new Date(str.replaceAll(" ", "+").slice(6, -1));
}
if (str.startsWith("!hex(") && str.endsWith(")")) {
return `#${str.slice(5, -1)}`;
}
const color = str.slice(1).match(COLOR_REGEXP);
if (color) {
if (str.startsWith("!rgba") || str.startsWith("!RGBA")) {
return `${color[1]}(${color[2]}, ${color[3]}, ${color[4]}, ${color[5]})`;
}
if (str.startsWith("!hsla") || str.startsWith("!HSLA")) {
return `${color[1]}(${color[2]}, ${color[3]}%, ${color[4]}%, ${color[5]})`;
}
return str.startsWith("!rgb") || str.startsWith("!RGB") ? `${color[1]}(${color[2]}, ${color[3]}, ${color[4]})` : `${color[1]}(${color[2]}, ${color[3]}%, ${color[4]}%)`;
}
}
if (NUMBER_REGEXP.test(str)) {
return Number(str);
}
return str;
}
};
var parseArgsParam = /* @__PURE__ */ __name((argsString) => {
const parts = argsString.split(";").map((part) => part.replace("=", "~").replace(":", "="));
return Object.entries((0, import_picoquery.parse)(parts.join(";"), QUERY_OPTIONS)).reduce((acc, [key, value]) => {
if (validateArgs(key, value)) {
return Object.assign(acc, { [key]: value });
}
once3.warn(dedent`
Omitted potentially unsafe URL args.
More info: https://storybook.js.org/docs/writing-stories/args#setting-args-through-the-url?ref=error
`);
return acc;
}, {});
}, "parseArgsParam");
// src/preview-api/modules/preview-web/UrlStore.ts
var { history, document: document2 } = global6;
function pathToId(path) {
const match = (path || "").match(/^\/story\/(.+)/);
if (!match) {
throw new Error(`Invalid path '${path}', must start with '/story/'`);
}
return match[1];
}
__name(pathToId, "pathToId");
var getQueryString = /* @__PURE__ */ __name(({
selection,
extraParams
}) => {
const search = document2?.location.search.slice(1);
const { path, selectedKind, selectedStory, ...rest } = (0, import_picoquery2.parse)(search);
const queryStr = (0, import_picoquery2.stringify)({
...rest,
...extraParams,
...selection && { id: selection.storyId, viewMode: selection.viewMode }
});
return `?${queryStr}`;
}, "getQueryString");
var setPath = /* @__PURE__ */ __name((selection) => {
if (!selection) {
return;
}
const query = getQueryString({ selection });
const { hash = "" } = document2.location;
document2.title = selection.storyId;
history.replaceState({}, "", `${document2.location.pathname}${query}${hash}`);
}, "setPath");
var isObject = /* @__PURE__ */ __name((val) => val != null && typeof val === "object" && Array.isArray(val) === false, "isObject");
var getFirstString = /* @__PURE__ */ __name((v) => {
if (v === void 0) {
return void 0;
}
if (typeof v === "string") {
return v;
}
if (Array.isArray(v)) {
return getFirstString(v[0]);
}
if (isObject(v)) {
return getFirstString(
Object.values(v).filter(Boolean)
);
}
return void 0;
}, "getFirstString");
var getSelectionSpecifierFromPath = /* @__PURE__ */ __name(() => {
if (typeof document2 !== "undefined") {
const queryStr = document2.location.search.slice(1);
const query = (0, import_picoquery2.parse)(queryStr);
const args = typeof query.args === "string" ? parseArgsParam(query.args) : void 0;
const globals = typeof query.globals === "string" ? parseArgsParam(query.globals) : void 0;
let viewMode = getFirstString(query.viewMode);
if (typeof viewMode !== "string" || !viewMode.match(/docs|story/)) {
viewMode = "story";
}
const path = getFirstString(query.path);
const storyId = path ? pathToId(path) : getFirstString(query.id);
if (storyId) {
return { storySpecifier: storyId, args, globals, viewMode };
}
}
return null;
}, "getSelectionSpecifierFromPath");
var _UrlStore = class _UrlStore {
constructor() {
this.selectionSpecifier = getSelectionSpecifierFromPath();
}
setSelection(selection) {
this.selection = selection;
setPath(this.selection);
}
setQueryParams(queryParams) {
const query = getQueryString({ extraParams: queryParams });
const { hash = "" } = document2.location;
history.replaceState({}, "", `${document2.location.pathname}${query}${hash}`);
}
};
__name(_UrlStore, "UrlStore");
var UrlStore = _UrlStore;
// src/preview-api/modules/preview-web/WebView.ts
var import_ansi_to_html = __toESM(require_ansi_to_html(), 1);
var import_picoquery3 = __toESM(require_main(), 1);
import { logger as logger9 } from "storybook/internal/client-logger";
import { global as global7 } from "@storybook/global";
var { document: document3 } = global7;
var PREPARING_DELAY = 100;
var Mode = /* @__PURE__ */ ((Mode2) => {
Mode2["MAIN"] = "MAIN";
Mode2["NOPREVIEW"] = "NOPREVIEW";
Mode2["PREPARING_STORY"] = "PREPARING_STORY";
Mode2["PREPARING_DOCS"] = "PREPARING_DOCS";
Mode2["ERROR"] = "ERROR";
return Mode2;
})(Mode || {});
var classes = {
PREPARING_STORY: "sb-show-preparing-story",
PREPARING_DOCS: "sb-show-preparing-docs",
MAIN: "sb-show-main",
NOPREVIEW: "sb-show-nopreview",
ERROR: "sb-show-errordisplay"
};
var layoutClassMap = {
centered: "sb-main-centered",
fullscreen: "sb-main-fullscreen",
padded: "sb-main-padded"
};
var ansiConverter = new import_ansi_to_html.default({
escapeXML: true
});
var _WebView = class _WebView {
constructor() {
this.testing = false;
if (typeof document3 !== "undefined") {
const { __SPECIAL_TEST_PARAMETER__ } = (0, import_picoquery3.parse)(document3.location.search.slice(1));
switch (__SPECIAL_TEST_PARAMETER__) {
case "preparing-story": {
this.showPreparingStory();
this.testing = true;
break;
}
case "preparing-docs": {
this.showPreparingDocs();
this.testing = true;
break;
}
default:
}
}
}
// Get ready to render a story, returning the element to render to
prepareForStory(story) {
this.showStory();
this.applyLayout(story.parameters.layout);
document3.documentElement.scrollTop = 0;
document3.documentElement.scrollLeft = 0;
return this.storyRoot();
}
storyRoot() {
return document3.getElementById("storybook-root");
}
prepareForDocs() {
this.showMain();
this.showDocs();
this.applyLayout("fullscreen");
document3.documentElement.scrollTop = 0;
document3.documentElement.scrollLeft = 0;
return this.docsRoot();
}
docsRoot() {
return document3.getElementById("storybook-docs");
}
applyLayout(layout = "padded") {
if (layout === "none") {
document3.body.classList.remove(this.currentLayoutClass);
this.currentLayoutClass = null;
return;
}
this.checkIfLayoutExists(layout);
const layoutClass = layoutClassMap[layout];
document3.body.classList.remove(this.currentLayoutClass);
document3.body.classList.add(layoutClass);
this.currentLayoutClass = layoutClass;
}
checkIfLayoutExists(layout) {
if (!layoutClassMap[layout]) {
logger9.warn(
dedent`
The desired layout: ${layout} is not a valid option.
The possible options are: ${Object.keys(layoutClassMap).join(", ")}, none.
`
);
}
}
showMode(mode) {
clearTimeout(this.preparingTimeout);
Object.keys(Mode).forEach((otherMode) => {
if (otherMode === mode) {
document3.body.classList.add(classes[otherMode]);
} else {
document3.body.classList.remove(classes[otherMode]);
}
});
}
showErrorDisplay({ message = "", stack = "" }) {
let header = message;
let detail = stack;
const parts = message.split("\n");
if (parts.length > 1) {
[header] = parts;
detail = parts.slice(1).join("\n").replace(/^\n/, "");
}
document3.getElementById("error-message").innerHTML = ansiConverter.toHtml(header);
document3.getElementById("error-stack").innerHTML = ansiConverter.toHtml(detail);
this.showMode("ERROR" /* ERROR */);
}
showNoPreview() {
if (this.testing) {
return;
}
this.showMode("NOPREVIEW" /* NOPREVIEW */);
this.storyRoot()?.setAttribute("hidden", "true");
this.docsRoot()?.setAttribute("hidden", "true");
}
showPreparingStory({ immediate = false } = {}) {
clearTimeout(this.preparingTimeout);
if (immediate) {
this.showMode("PREPARING_STORY" /* PREPARING_STORY */);
} else {
this.preparingTimeout = setTimeout(
() => this.showMode("PREPARING_STORY" /* PREPARING_STORY */),
PREPARING_DELAY
);
}
}
showPreparingDocs({ immediate = false } = {}) {
clearTimeout(this.preparingTimeout);
if (immediate) {
this.showMode("PREPARING_DOCS" /* PREPARING_DOCS */);
} else {
this.preparingTimeout = setTimeout(() => this.showMode("PREPARING_DOCS" /* PREPARING_DOCS */), PREPARING_DELAY);
}
}
showMain() {
this.showMode("MAIN" /* MAIN */);
}
showDocs() {
this.storyRoot().setAttribute("hidden", "true");
this.docsRoot().removeAttribute("hidden");
}
showStory() {
this.docsRoot().setAttribute("hidden", "true");
this.storyRoot().removeAttribute("hidden");
}
showStoryDuringRender() {
document3.body.classList.add(classes.MAIN);
}
};
__name(_WebView, "WebView");
var WebView = _WebView;
// src/preview-api/modules/preview-web/PreviewWeb.tsx
var _PreviewWeb = class _PreviewWeb extends PreviewWithSelection {
constructor(importFn, getProjectAnnotations) {
super(importFn, getProjectAnnotations, new UrlStore(), new WebView());
this.importFn = importFn;
this.getProjectAnnotations = getProjectAnnotations;
global8.__STORYBOOK_PREVIEW__ = this;
}
};
__name(_PreviewWeb, "PreviewWeb");
var PreviewWeb = _PreviewWeb;
// src/preview-api/modules/preview-web/simulate-pageload.ts
import { global as global9 } from "@storybook/global";
var { document: document4 } = global9;
var runScriptTypes = [
"application/javascript",
"application/ecmascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
// Support modern javascript
"module"
];
var SCRIPT = "script";
var SCRIPTS_ROOT_ID = "scripts-root";
function simulateDOMContentLoaded() {
const DOMContentLoadedEvent = document4.createEvent("Event");
DOMContentLoadedEvent.initEvent("DOMContentLoaded", true, true);
document4.dispatchEvent(DOMContentLoadedEvent);
}
__name(simulateDOMContentLoaded, "simulateDOMContentLoaded");
function insertScript($script, callback, $scriptRoot) {
const scriptEl = document4.createElement("script");
scriptEl.type = $script.type === "module" ? "module" : "text/javascript";
if ($script.src) {
scriptEl.onload = callback;
scriptEl.onerror = callback;
scriptEl.src = $script.src;
} else {
scriptEl.textContent = $script.innerText;
}
if ($scriptRoot) {
$scriptRoot.appendChild(scriptEl);
} else {
document4.head.appendChild(scriptEl);
}
$script.parentNode.removeChild($script);
if (!$script.src) {
callback();
}
}
__name(insertScript, "insertScript");
function insertScriptsSequentially(scriptsToExecute, callback, index = 0) {
scriptsToExecute[index](() => {
index++;
if (index === scriptsToExecute.length) {
callback();
} else {
insertScriptsSequentially(scriptsToExecute, callback, index);
}
});
}
__name(insertScriptsSequentially, "insertScriptsSequentially");
function simulatePageLoad($container) {
let $scriptsRoot = document4.getElementById(SCRIPTS_ROOT_ID);
if (!$scriptsRoot) {
$scriptsRoot = document4.createElement("div");
$scriptsRoot.id = SCRIPTS_ROOT_ID;
document4.body.appendChild($scriptsRoot);
} else {
$scriptsRoot.innerHTML = "";
}
const $scripts = Array.from($container.querySelectorAll(SCRIPT));
if ($scripts.length) {
const scriptsToExecute = [];
$scripts.forEach(($script) => {
const typeAttr = $script.getAttribute("type");
if (!typeAttr || runScriptTypes.includes(typeAttr)) {
scriptsToExecute.push((callback) => insertScript($script, callback, $scriptsRoot));
}
});
if (scriptsToExecute.length) {
insertScriptsSequentially(scriptsToExecute, simulateDOMContentLoaded, void 0);
}
} else {
simulateDOMContentLoaded();
}
}
__name(simulatePageLoad, "simulatePageLoad");
// src/preview-api/modules/preview-web/emitTransformCode.ts
async function emitTransformCode(source, context) {
const transform = context.parameters?.docs?.source?.transform;
const { id, unmappedArgs } = context;
const transformed = transform && source ? transform?.(source, context) : source;
const result = transformed ? await transformed : void 0;
addons.getChannel().emit(SNIPPET_RENDERED, {
id,
source: result,
args: unmappedArgs
});
}
__name(emitTransformCode, "emitTransformCode");
export {
mockChannel,
addons,
HooksContext,
applyHooks,
useMemo,
useCallback,
useRef,
useState,
useReducer,
useEffect,
useChannel,
useStoryContext,
useParameter,
useArgs,
useGlobals,
makeDecorator,
combineArgs,
normalizeArrays,
normalizeStory,
mountDestructured,
decorateStory,
sanitizeStoryContextUpdate,
defaultDecorateStory,
prepareStory,
prepareMeta,
filterArgTypes,
inferControls,
normalizeProjectAnnotations,
composeStepRunners,
composeConfigs,
ReporterAPI,
getCsfFactoryAnnotations,
setDefaultProjectAnnotations,
setProjectAnnotations,
composeStory,
composeStories,
createPlaywrightTest,
StoryStore,
userOrAutoTitleFromSpecifier,
userOrAutoTitle,
sortStoriesV7,
Preview,
DocsContext,
PreviewWithSelection,
UrlStore,
WebView,
PreviewWeb,
simulateDOMContentLoaded,
simulatePageLoad,
emitTransformCode
};