storybook
Version:
Storybook: Develop, document, and test UI components in isolation
1,206 lines (1,167 loc) • 127 kB
JavaScript
import {
isTestEnvironment,
pauseAnimations,
waitForAnimations
} from "./chunk-IPA5A322.js";
import {
require_main
} from "./chunk-3OXGAGBE.js";
import {
SNIPPET_RENDERED,
combineParameters
} from "./chunk-VYJQ7RU5.js";
import {
isEqual
} from "./chunk-3IAH5M2U.js";
import {
invariant
} from "./chunk-AS2HQEYC.js";
import {
require_ansi_to_html
} from "./chunk-YKE5S47A.js";
import {
mapValues,
pickBy
} from "./chunk-AIOS4NGK.js";
import {
isPlainObject
} from "./chunk-GFLS4VP3.js";
import {
require_memoizerific
} from "./chunk-WJYERY3R.js";
import {
dedent
} from "./chunk-JP7NCOJX.js";
import {
__toESM
} from "./chunk-A242L54C.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() {
let transport = {
setHandler: () => {
},
send: () => {
}
};
return new Channel({ transport });
}
// src/preview-api/modules/addons/main.ts
var AddonStore = class {
constructor() {
this.getChannel = () => {
if (!this.channel) {
let channel = mockChannel();
return this.setChannel(channel), channel;
}
return this.channel;
};
this.ready = () => this.promise;
this.hasChannel = () => !!this.channel;
this.setChannel = (channel) => {
this.channel = channel, this.resolve();
};
this.promise = new Promise((res) => {
this.resolve = () => res(this.getChannel());
});
}
}, KEY = "__STORYBOOK_ADDONS_PREVIEW";
function getAddonsStore() {
return global[KEY] || (global[KEY] = new AddonStore()), global[KEY];
}
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 {
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 = (storyId) => {
storyId === this.currentContext?.id && (this.triggerEffects(), this.currentContext = null, this.removeRenderListeners());
};
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 = !1, this.currentContext = null;
}
clean() {
this.prevEffects.forEach((effect) => {
effect.destroy && effect.destroy();
}), this.init(), this.removeRenderListeners();
}
getNextHook() {
let hook = this.currentHooks[this.nextHookIndex];
return this.nextHookIndex += 1, hook;
}
triggerEffects() {
this.prevEffects.forEach((effect) => {
!this.currentEffects.includes(effect) && effect.destroy && effect.destroy();
}), this.currentEffects.forEach((effect) => {
this.prevEffects.includes(effect) || (effect.destroy = effect.create());
}), this.prevEffects = this.currentEffects, this.currentEffects = [];
}
addRenderListeners() {
this.removeRenderListeners(), addons.getChannel().on(STORY_RENDERED, this.renderListener);
}
removeRenderListeners() {
addons.getChannel().removeListener(STORY_RENDERED, this.renderListener);
}
};
function hookify(fn) {
let hookified = (...args) => {
let { hooks } = typeof args[0] == "function" ? args[1] : args[0], prevPhase = hooks.currentPhase, prevHooks = hooks.currentHooks, prevNextHookIndex = hooks.nextHookIndex, prevDecoratorName = hooks.currentDecoratorName;
hooks.currentDecoratorName = fn.name, hooks.prevMountedDecorators.has(fn) ? (hooks.currentPhase = "UPDATE", hooks.currentHooks = hooks.hookListsMap.get(fn) || []) : (hooks.currentPhase = "MOUNT", hooks.currentHooks = [], hooks.hookListsMap.set(fn, hooks.currentHooks), hooks.prevMountedDecorators.add(fn)), hooks.nextHookIndex = 0;
let prevContext = global2.STORYBOOK_HOOKS_CONTEXT;
global2.STORYBOOK_HOOKS_CONTEXT = hooks;
let result = fn(...args);
if (global2.STORYBOOK_HOOKS_CONTEXT = prevContext, hooks.currentPhase === "UPDATE" && hooks.getNextHook() != null)
throw new Error(
"Rendered fewer hooks than expected. This may be caused by an accidental early return statement."
);
return hooks.currentPhase = prevPhase, hooks.currentHooks = prevHooks, hooks.nextHookIndex = prevNextHookIndex, hooks.currentDecoratorName = prevDecoratorName, result;
};
return hookified.originalFn = fn, hookified;
}
var numberOfRenders = 0, RENDER_LIMIT = 25, applyHooks = (applyDecorators) => (storyFn, decorators) => {
let decorated = applyDecorators(
hookify(storyFn),
decorators.map((decorator) => hookify(decorator))
);
return (context) => {
let { hooks } = context;
hooks.prevMountedDecorators ??= /* @__PURE__ */ new Set(), hooks.mountedDecorators = /* @__PURE__ */ new Set([storyFn, ...decorators]), hooks.currentContext = context, hooks.hasUpdates = !1;
let result = decorated(context);
for (numberOfRenders = 1; hooks.hasUpdates; )
if (hooks.hasUpdates = !1, hooks.currentEffects = [], result = decorated(context), numberOfRenders += 1, numberOfRenders > RENDER_LIMIT)
throw new Error(
"Too many re-renders. Storybook limits the number of renders to prevent an infinite loop."
);
return hooks.addRenderListeners(), result;
};
}, areDepsEqual = (deps, nextDeps) => deps.length === nextDeps.length && deps.every((dep, i) => dep === nextDeps[i]), invalidHooksError = () => new Error("Storybook preview hooks can only be called inside decorators and story functions.");
function getHooksContextOrNull() {
return global2.STORYBOOK_HOOKS_CONTEXT || null;
}
function getHooksContextOrThrow() {
let hooks = getHooksContextOrNull();
if (hooks == null)
throw invalidHooksError();
return hooks;
}
function useHook(name, callback, deps) {
let hooks = getHooksContextOrThrow();
if (hooks.currentPhase === "MOUNT") {
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.`
);
let hook = { name, deps };
return hooks.currentHooks.push(hook), callback(hook), hook;
}
if (hooks.currentPhase === "UPDATE") {
let hook = hooks.getNextHook();
if (hook == null)
throw new Error("Rendered more hooks than during the previous render.");
return 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.`
), 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.`
), 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}`), (deps == null || hook.deps == null || !areDepsEqual(deps, hook.deps)) && (callback(hook), hook.deps = deps), hook;
}
throw invalidHooksError();
}
function useMemoLike(name, nextCreate, deps) {
let { memoizedState } = useHook(
name,
(hook) => {
hook.memoizedState = nextCreate();
},
deps
);
return memoizedState;
}
function useMemo(nextCreate, deps) {
return useMemoLike("useMemo", nextCreate, deps);
}
function useCallback(callback, deps) {
return useMemoLike("useCallback", () => callback, deps);
}
function useRefLike(name, initialValue) {
return useMemoLike(name, () => ({ current: initialValue }), []);
}
function useRef(initialValue) {
return useRefLike("useRef", initialValue);
}
function triggerUpdate() {
let hooks = getHooksContextOrNull();
if (hooks != null && hooks.currentPhase !== "NONE")
hooks.hasUpdates = !0;
else
try {
addons.getChannel().emit(FORCE_RE_RENDER);
} catch {
logger.warn("State updates of Storybook preview hooks work only in browser");
}
}
function useStateLike(name, initialState) {
let 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
), setState = (update) => {
stateRef.current = typeof update == "function" ? update(stateRef.current) : update, triggerUpdate();
};
return [stateRef.current, setState];
}
function useState(initialState) {
return useStateLike("useState", initialState);
}
function useReducer(reducer, initialArg, init) {
let initialState = init != null ? () => init(initialArg) : initialArg, [state, setState] = useStateLike("useReducer", initialState);
return [state, (action) => setState((prevState) => reducer(prevState, action))];
}
function useEffect(create, deps) {
let hooks = getHooksContextOrThrow(), effect = useMemoLike("useEffect", () => ({ create }), deps);
hooks.currentEffects.includes(effect) || hooks.currentEffects.push(effect);
}
function useChannel(eventMap, deps = []) {
let channel = addons.getChannel();
return useEffect(() => (Object.entries(eventMap).forEach(([type, listener]) => channel.on(type, listener)), () => {
Object.entries(eventMap).forEach(
([type, listener]) => channel.removeListener(type, listener)
);
}), [...Object.keys(eventMap), ...deps]), useCallback(channel.emit.bind(channel), [channel]);
}
function useStoryContext() {
let { currentContext } = getHooksContextOrThrow();
if (currentContext == null)
throw invalidHooksError();
return currentContext;
}
function useParameter(parameterKey, defaultValue) {
let { parameters } = useStoryContext();
if (parameterKey)
return parameters[parameterKey] ?? defaultValue;
}
function useArgs() {
let channel = addons.getChannel(), { id: storyId, args } = useStoryContext(), updateArgs = useCallback(
(updatedArgs) => channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }),
[channel, storyId]
), resetArgs = useCallback(
(argNames) => channel.emit(RESET_STORY_ARGS, { storyId, argNames }),
[channel, storyId]
);
return [args, updateArgs, resetArgs];
}
function useGlobals() {
let channel = addons.getChannel(), { globals } = useStoryContext(), updateGlobals = useCallback(
(newGlobals) => channel.emit(UPDATE_GLOBALS, { globals: newGlobals }),
[channel]
);
return [globals, updateGlobals];
}
// src/preview-api/modules/addons/make-decorator.ts
var makeDecorator = ({
name,
parameterName,
wrapper,
skipIfNoParametersOrOptions = !1
}) => {
let decorator = (options) => (storyFn, context) => {
let parameters = context.parameters && context.parameters[parameterName];
return parameters && parameters.disable || skipIfNoParametersOrOptions && !options && !parameters ? storyFn(context) : wrapper(storyFn, context, {
options,
parameters
});
};
return (...args) => typeof args[0] == "function" ? decorator()(...args) : (...innerArgs) => {
if (innerArgs.length > 1)
return args.length > 1 ? decorator(args)(...innerArgs) : 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`
);
};
};
// 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"), map = (arg, argType) => {
let type = argType.type;
if (arg == null || !type || 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":
return !type.value || !Array.isArray(arg) ? INCOMPATIBLE : arg.reduce((acc, item, index) => {
let mapped = map(item, { type: type.value });
return mapped !== INCOMPATIBLE && (acc[index] = mapped), acc;
}, new Array(arg.length));
case "object":
return typeof arg == "string" || typeof arg == "number" ? arg : !type.value || typeof arg != "object" ? INCOMPATIBLE : Object.entries(arg).reduce((acc, [key, val]) => {
let mapped = map(val, { type: type.value[key] });
return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped });
}, {});
case "other": {
let isPrimitiveArg = typeof arg == "string" || typeof arg == "number" || typeof arg == "boolean";
return type.value === "ReactNode" && isPrimitiveArg ? arg : INCOMPATIBLE;
}
default:
return INCOMPATIBLE;
}
}, mapArgsToTypes = (args, argTypes) => Object.entries(args).reduce((acc, [key, value]) => {
if (!argTypes[key])
return acc;
let mapped = map(value, argTypes[key]);
return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped });
}, {}), combineArgs = (value, update) => Array.isArray(value) && Array.isArray(update) ? update.reduce(
(acc, upd, index) => (acc[index] = combineArgs(value[index], update[index]), acc),
[...value]
).filter((v) => v !== void 0) : !isPlainObject(value) || !isPlainObject(update) ? update : Object.keys({ ...value, ...update }).reduce((acc, key) => {
if (key in update) {
let combined = combineArgs(value[key], update[key]);
combined !== void 0 && (acc[key] = combined);
} else
acc[key] = value[key];
return acc;
}, {}), validateOptions = (args, argTypes) => Object.entries(argTypes).reduce((acc, [key, { options }]) => {
function allowArg() {
return key in args && (acc[key] = args[key]), acc;
}
if (!options)
return allowArg();
if (!Array.isArray(options))
return once.error(dedent`
Invalid argType: '${key}.options' should be an array.
More info: https://storybook.js.org/docs/api/arg-types?ref=error
`), allowArg();
if (options.some((opt) => opt && ["object", "function"].includes(typeof opt)))
return 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
`), allowArg();
let isArray = Array.isArray(args[key]), invalidIndex = isArray && args[key].findIndex((val) => !options.includes(val)), isValidArray = isArray && invalidIndex === -1;
if (args[key] === void 0 || options.includes(args[key]) || isValidArray)
return allowArg();
let field = isArray ? `${key}[${invalidIndex}]` : key, supportedOptions = options.map((opt) => typeof opt == "string" ? `'${opt}'` : String(opt)).join(", ");
return once.warn(`Received illegal value for '${field}'. Supported options: ${supportedOptions}`), acc;
}, {}), DEEPLY_EQUAL = Symbol("Deeply equal"), deepDiff = (value, update) => {
if (typeof value != typeof update)
return update;
if (isEqual(value, update))
return DEEPLY_EQUAL;
if (Array.isArray(value) && Array.isArray(update)) {
let res = update.reduce((acc, upd, index) => {
let diff = deepDiff(value[index], upd);
return diff !== DEEPLY_EQUAL && (acc[index] = diff), acc;
}, new Array(update.length));
return update.length >= value.length ? res : res.concat(new Array(value.length - update.length).fill(void 0));
}
return isPlainObject(value) && isPlainObject(update) ? Object.keys({ ...value, ...update }).reduce((acc, key) => {
let diff = deepDiff(value?.[key], update?.[key]);
return diff === DEEPLY_EQUAL ? acc : Object.assign(acc, { [key]: diff });
}, {}) : update;
}, UNTARGETED = "UNTARGETED";
function groupArgsByTarget({
args,
argTypes
}) {
let groupedArgs = {};
return Object.entries(args).forEach(([name, value]) => {
let { target = UNTARGETED } = argTypes[name] || {};
groupedArgs[target] = groupedArgs[target] || {}, groupedArgs[target][name] = value;
}), groupedArgs;
}
// src/preview-api/modules/store/ArgsStore.ts
function deleteUndefined(obj) {
return Object.keys(obj).forEach((key) => obj[key] === void 0 && delete obj[key]), obj;
}
var ArgsStore = class {
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) {
let delta = deepDiff(this.initialArgsByStoryId[story.id], this.argsByStoryId[story.id]);
this.initialArgsByStoryId[story.id] = story.initialArgs, this.argsByStoryId[story.id] = story.initialArgs, delta !== DEEPLY_EQUAL && this.updateFromDelta(story, delta);
}
}
updateFromDelta(story, delta) {
let validatedDelta = validateOptions(delta, story.argTypes);
this.argsByStoryId[story.id] = combineArgs(this.argsByStoryId[story.id], validatedDelta);
}
updateFromPersisted(story, persisted) {
let 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
});
}
};
// 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 = (argTypes = {}) => Object.entries(argTypes).reduce((acc, [arg, { defaultValue }]) => (typeof defaultValue < "u" && (acc[arg] = defaultValue), acc), {});
// src/preview-api/modules/store/GlobalsStore.ts
var GlobalsStore = class {
constructor({
globals = {},
globalTypes = {}
}) {
this.set({ globals, globalTypes });
}
set({ globals = {}, globalTypes = {} }) {
let delta = this.initialGlobals && deepDiff(this.initialGlobals, this.globals);
this.allowedGlobalNames = /* @__PURE__ */ new Set([...Object.keys(globals), ...Object.keys(globalTypes)]);
let defaultGlobals = getValuesFromArgTypes(globalTypes);
this.initialGlobals = { ...defaultGlobals, ...globals }, this.globals = this.initialGlobals, delta && delta !== DEEPLY_EQUAL && this.updateFromPersisted(delta);
}
filterAllowedGlobals(globals) {
return Object.entries(globals).reduce((acc, [key, value]) => (this.allowedGlobalNames.has(key) ? acc[key] = value : logger2.warn(
`Attempted to set a global (${key}) that is not defined in initial globals or globalTypes`
), acc), {});
}
updateFromPersisted(persisted) {
let allowedUrlGlobals = this.filterAllowedGlobals(persisted);
this.globals = { ...this.globals, ...allowedUrlGlobals };
}
get() {
return this.globals;
}
update(newGlobals) {
this.globals = { ...this.globals, ...this.filterAllowedGlobals(newGlobals) };
for (let key in newGlobals)
newGlobals[key] === void 0 && (this.globals[key] = this.initialGlobals[key]);
}
};
// 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, acc),
{}
)
), StoryIndexStore = class {
constructor({ entries } = { v: 5, entries: {} }) {
this.entries = entries;
}
entryFromSpecifier(specifier) {
let entries = Object.values(this.entries);
if (specifier === "*")
return entries[0];
if (typeof specifier == "string")
return this.entries[specifier] ? this.entries[specifier] : entries.find((entry) => entry.id.startsWith(specifier));
let { name, title } = specifier;
return entries.find((entry) => entry.name === name && entry.title === title);
}
storyIdToEntry(storyId) {
let storyEntry = this.entries[storyId];
if (!storyEntry)
throw new MissingStoryAfterHmrError({ storyId });
return storyEntry;
}
importPathToEntry(importPath) {
return getImportPathMap(this.entries)[importPath];
}
};
// src/preview-api/modules/store/csf/normalizeInputTypes.ts
var normalizeType = (type) => typeof type == "string" ? { name: type } : type, normalizeControl = (control) => typeof control == "string" ? { type: control } : control, normalizeInputType = (inputType, key) => {
let { type, control, ...rest } = inputType, normalized = {
name: key,
...rest
};
return type && (normalized.type = normalizeType(type)), control ? normalized.control = normalizeControl(control) : control === !1 && (normalized.control = { disable: !0 }), normalized;
}, normalizeInputTypes = (inputTypes) => mapValues(inputTypes, normalizeInputType);
// 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 = (array) => Array.isArray(array) ? array : array ? [array] : [];
// 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) {
let storyObject = storyAnnotations, userStoryFn = typeof storyAnnotations == "function" ? storyAnnotations : null, { story } = storyObject;
story && (logger3.debug("deprecated story", story), deprecate(deprecatedStoryAnnotation));
let exportName = storyNameFromExport(key), name = typeof storyObject != "function" && storyObject.name || storyObject.storyName || story?.name || exportName, decorators = [
...normalizeArrays(storyObject.decorators),
...normalizeArrays(story?.decorators)
], parameters = { ...story?.parameters, ...storyObject.parameters }, args = { ...story?.args, ...storyObject.args }, argTypes = { ...story?.argTypes, ...storyObject.argTypes }, loaders = [...normalizeArrays(storyObject.loaders), ...normalizeArrays(story?.loaders)], beforeEach = [
...normalizeArrays(storyObject.beforeEach),
...normalizeArrays(story?.beforeEach)
], afterEach = [
...normalizeArrays(storyObject.afterEach),
...normalizeArrays(story?.afterEach)
], { render, play, tags = [], globals = {} } = storyObject, 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 }
};
}
// 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) {
let { id, argTypes } = defaultExport;
return {
id: sanitize(id || title),
...defaultExport,
title,
...argTypes && { argTypes: normalizeInputTypes(argTypes) },
parameters: {
fileName: importPath,
...defaultExport.parameters
}
};
}
// src/preview-api/modules/store/csf/processCSFFile.ts
var checkGlobals = (parameters) => {
let { globals, globalTypes } = parameters;
(globals || globalTypes) && logger4.error(
"Global args/argTypes can only be set globally",
JSON.stringify({
globals,
globalTypes
})
);
}, checkStorySort = (parameters) => {
let { options } = parameters;
options?.storySort && logger4.error("The storySort option parameter can only be set globally");
}, checkDisallowedParameters = (parameters) => {
parameters && (checkGlobals(parameters), checkStorySort(parameters));
};
function processCSFFile(moduleExports, importPath, title) {
let { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports, firstStory = Object.values(namedExports)[0];
if (isStory(firstStory)) {
let meta2 = normalizeComponentAnnotations(firstStory.meta.input, title, importPath);
checkDisallowedParameters(meta2.parameters);
let csfFile2 = { meta: meta2, stories: {}, moduleExports };
return Object.keys(namedExports).forEach((key) => {
if (isExportStory(key, meta2)) {
let story = namedExports[key], storyMeta = normalizeStory(key, story.input, meta2);
checkDisallowedParameters(storyMeta.parameters), csfFile2.stories[storyMeta.id] = storyMeta, getStoryChildren(story).forEach((child) => {
let name = child.input.name, 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, csfFile2;
}
let meta = normalizeComponentAnnotations(
defaultExport,
title,
importPath
);
checkDisallowedParameters(meta.parameters);
let csfFile = { meta, stories: {}, moduleExports };
return Object.keys(namedExports).forEach((key) => {
if (isExportStory(key, meta)) {
let storyMeta = normalizeStory(key, namedExports[key], meta);
checkDisallowedParameters(storyMeta.parameters), csfFile.stories[storyMeta.id] = storyMeta;
}
}), csfFile;
}
// 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");
}
function getUsedProps(fn) {
let match = fn.toString().match(/[^(]*\(([^)]*)/);
if (!match)
return [];
let args = splitByComma(match[1]);
if (!args.length)
return [];
let first = args[0];
return first.startsWith("{") && first.endsWith("}") ? splitByComma(first.slice(1, -1).replace(/\s/g, "")).map((prop) => prop.replace(/:.*|=.*/g, "")) : [];
}
function splitByComma(s) {
let result = [], stack = [], 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] === ",") {
let token = s.substring(start, i).trim();
token && result.push(token), start = i + 1;
}
let lastToken = s.substring(start).trim();
return lastToken && result.push(lastToken), result;
}
// src/preview-api/modules/store/decorators.ts
function decorateStory(storyFn, decorator, bindWithContext) {
let boundStoryFunction = bindWithContext(storyFn);
return (context) => decorator(boundStoryFunction, context);
}
function sanitizeStoryContextUpdate({
componentId,
title,
kind,
id,
name,
story,
parameters,
initialArgs,
argTypes,
...update
} = {}) {
return update;
}
function defaultDecorateStory(storyFn, decorators) {
let contextStore = {}, bindWithContext = (decoratedStoryFn) => (update) => {
if (!contextStore.value)
throw new Error("Decorated function called without init");
return contextStore.value = {
...contextStore.value,
...sanitizeStoryContextUpdate(update)
}, decoratedStoryFn(contextStore.value);
}, decoratedWithContextStore = decorators.reduce(
(story, decorator) => decorateStory(story, decorator, bindWithContext),
storyFn
);
return (context) => (contextStore.value = context, decoratedWithContextStore(context));
}
// src/preview-api/modules/store/csf/prepareStory.ts
function prepareStory(storyAnnotations, componentAnnotations, projectAnnotations) {
let { moduleExport, id, name } = storyAnnotations || {}, partialAnnotations = preparePartialAnnotations(
storyAnnotations,
componentAnnotations,
projectAnnotations
), applyLoaders = async (context) => {
let loaded = {};
for (let loaders of [
normalizeArrays(projectAnnotations.loaders),
normalizeArrays(componentAnnotations.loaders),
normalizeArrays(storyAnnotations.loaders)
]) {
if (context.abortSignal.aborted)
return loaded;
let loadResults = await Promise.all(loaders.map((loader) => loader(context)));
Object.assign(loaded, ...loadResults);
}
return loaded;
}, applyBeforeEach = async (context) => {
let cleanupCallbacks = new Array();
for (let beforeEach of [
...normalizeArrays(projectAnnotations.beforeEach),
...normalizeArrays(componentAnnotations.beforeEach),
...normalizeArrays(storyAnnotations.beforeEach)
]) {
if (context.abortSignal.aborted)
return cleanupCallbacks;
let cleanup = await beforeEach(context);
cleanup && cleanupCallbacks.push(cleanup);
}
return cleanupCallbacks;
}, applyAfterEach = async (context) => {
let reversedFinalizers = [
...normalizeArrays(projectAnnotations.afterEach),
...normalizeArrays(componentAnnotations.afterEach),
...normalizeArrays(storyAnnotations.afterEach)
].reverse();
for (let finalizer of reversedFinalizers) {
if (context.abortSignal.aborted)
return;
await finalizer(context);
}
}, undecoratedStoryFn = (context) => context.originalStoryFn(context.args, context), { applyDecorators = defaultDecorateStory, runStep } = projectAnnotations, decorators = [
...normalizeArrays(storyAnnotations?.decorators),
...normalizeArrays(componentAnnotations?.decorators),
...normalizeArrays(projectAnnotations?.decorators)
], render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render, decoratedStoryFn = applyHooks(applyDecorators)(undecoratedStoryFn, decorators), unboundStoryFn = (context) => decoratedStoryFn(context), playFunction = storyAnnotations?.play ?? componentAnnotations?.play, usesMount = mountDestructured(playFunction);
if (!render && !usesMount)
throw new NoRenderFunctionError({ id });
let defaultMount = (context) => async () => (await context.renderToCanvas(), context.canvas), mount = storyAnnotations.mount ?? componentAnnotations.mount ?? projectAnnotations.mount ?? defaultMount, 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
};
}
function prepareMeta(componentAnnotations, projectAnnotations, moduleExport) {
return {
...preparePartialAnnotations(void 0, componentAnnotations, projectAnnotations),
moduleExport
};
}
function preparePartialAnnotations(storyAnnotations, componentAnnotations, projectAnnotations) {
let defaultTags = ["dev", "test"], extraTags = globalThis2.DOCS_OPTIONS?.autodocs === !0 ? ["autodocs"] : [], overrideTags = storyAnnotations?.tags?.includes("test-fn") ? ["!autodocs"] : [], tags = combineTags(
...defaultTags,
...extraTags,
...projectAnnotations.tags ?? [],
...componentAnnotations.tags ?? [],
...overrideTags,
...storyAnnotations?.tags ?? []
), parameters = combineParameters(
projectAnnotations.parameters,
componentAnnotations.parameters,
storyAnnotations?.parameters
), { argTypesEnhancers = [], argsEnhancers = [] } = projectAnnotations, passedArgTypes = combineParameters(
projectAnnotations.argTypes,
componentAnnotations.argTypes,
storyAnnotations?.argTypes
);
if (storyAnnotations) {
let render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render;
parameters.__isArgsStory = render && render.length > 0;
}
let passedArgs = {
...projectAnnotations.args,
...componentAnnotations.args,
...storyAnnotations?.args
}, storyGlobals = {
...componentAnnotations.globals,
...storyAnnotations?.globals
}, 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
);
let initialArgsBeforeEnhancers = { ...passedArgs };
contextForEnhancers.initialArgs = [...argsEnhancers].reduce(
(accumulatedArgs, enhancer) => ({
...accumulatedArgs,
...enhancer({
...contextForEnhancers,
initialArgs: accumulatedArgs
})
}),
initialArgsBeforeEnhancers
);
let { name, story, ...withoutStoryIdentifiers } = contextForEnhancers;
return withoutStoryIdentifiers;
}
function prepareContext(context) {
let { args: unmappedArgs } = context, targetedContext = {
...context,
allArgs: void 0,
argsByTarget: void 0
};
if (global3.FEATURES?.argTypeTargetsV7) {
let argsByTarget = groupArgsByTarget(context);
targetedContext = {
...context,
allArgs: context.args,
argsByTarget,
args: argsByTarget[UNTARGETED] || {}
};
}
let mappedArgs = Object.entries(targetedContext.args).reduce((acc, [key, val]) => {
if (!targetedContext.argTypes[key]?.mapping)
return acc[key] = val, acc;
let mappingFn = (originalValue) => {
let mapping = targetedContext.argTypes[key].mapping;
return mapping && originalValue in mapping ? mapping[originalValue] : originalValue;
};
return acc[key] = Array.isArray(val) ? val.map(mappingFn) : mappingFn(val), acc;
}, {}), includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => {
let argType = targetedContext.argTypes[key] || {};
return includeConditionalArg(argType, mappedArgs, targetedContext.globals) && (acc[key] = val), acc;
}, {});
return { ...targetedContext, unmappedArgs, args: includedArgs };
}
// src/preview-api/modules/store/inferArgTypes.ts
import { logger as logger5 } from "storybook/internal/client-logger";
var inferType = (value, name, visited) => {
let type = typeof value;
switch (type) {
case "boolean":
case "string":
case "number":
case "function":
case "symbol":
return { name: type };
default:
break;
}
return value ? 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
`), { name: "other", value: "cyclic object" }) : (visited.add(value), Array.isArray(value) ? { name: "array", value: value.length > 0 ? inferType(value[0], name, new Set(visited)) : { name: "other", value: "unknown" } } : { name: "object", value: mapValues(value, (field) => inferType(field, name, new Set(visited))) }) : { name: "object", value: {} };
}, inferArgTypes = (context) => {
let { id, argTypes: userArgTypes = {}, initialArgs = {} } = context, argTypes = mapValues(initialArgs, (arg, key) => ({
name: key,
type: inferType(arg, `${id}.${key}`, /* @__PURE__ */ new Set())
})), userArgTypesNames = mapValues(userArgTypes, (argType, key) => ({
name: key
}));
return combineParameters(argTypes, userArgTypesNames, userArgTypes);
};
inferArgTypes.secondPass = !0;
// 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 = (name, descriptor) => Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor), filterArgTypes = (argTypes, include, exclude) => !include && !exclude ? argTypes : argTypes && pickBy(argTypes, (argType, key) => {
let name = argType.name || key.toString();
return !!(!include || matches(name, include)) && (!exclude || !matches(name, exclude));
});
// src/preview-api/modules/store/inferControls.ts
var inferControl = (argType, name, matchers) => {
let { type, options } = argType;
if (type) {
if (matchers.color && matchers.color.test(name)) {
let controlType = type.name;
if (controlType === "string")
return { control: { type: "color" } };
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": {
let { 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" } };
}
}
}, inferControls = (context) => {
let {
argTypes,
parameters: { __isArgsStory, controls: { include = null, exclude = null, matchers = {} } = {} }
} = context;
if (!__isArgsStory)
return argTypes;
let filteredArgTypes = filterArgTypes(argTypes, include, exclude), withControls = mapValues(filteredArgTypes, (argType, name) => argType?.type && inferControl(argType, name.toString(), matchers));
return combineParameters(withControls, filteredArgTypes);
};
inferControls.secondPass = !0;
// 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
};
}
// 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 = (hooks) => async () => {
let cleanups2 = [];
for (let hook of hooks) {
let cleanup = await hook();
cleanup && cleanups2.unshift(cleanup);
}
return async () => {
for (let cleanup of cleanups2)
await cleanup();
};
};
// src/preview-api/modules/store/csf/stepRunners.ts
function composeStepRunners(stepRunners) {
return async (label, play, playContext) => {
await stepRunners.reduceRight(
(innerPlay, stepRunner) => async () => stepRunner(label, innerPlay, playContext),
async () => play(playContext)
)();
};
}
// src/preview-api/modules/store/csf/composeConfigs.ts
function getField(moduleExportList, field) {
return moduleExportList.map((xs) => xs.default?.[field] ?? xs[field]).filter(Boolean);
}
function getArrayField(moduleExportList, field, options = {}) {
return getField(moduleExportList, field).reduce((prev, cur) => {
let normalized = normalizeArrays(cur);
return options.reverseFileOrder ? [...normalized, ...prev] : [...prev, ...normalized];
}, []);
}
function getObjectField(moduleExportList, field) {
return Object.assign({}, ...getField(moduleExportList, field));
}
function getSingletonField(moduleExportList, field) {
return getField(moduleExportList, field).pop();
}
function composeConfigs(moduleExportList) {
let allArgTypeEnhancers = getArrayField(moduleExportList, "argTypesEnhancers"), stepRunners = getField(moduleExportList, "runStep"), beforeAllHooks = getArrayField(moduleExportList, "beforeAll");
return {
parameters: combineParameters(...getField(moduleExportList, "parameters")),
decorators: getArrayField(moduleExportList, "decorators", {
reverseFileOrder: !(global4.FEATURES?.legacyDecoratorFileOrder ?? !1)
}),
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")
};
}
// 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 {
constructor() {
this.reports = [];
}
async addReport(report) {
this.reports.push(report);
}
};
// 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 };
}
// src/preview-api/modules/store/csf/portable-stories.ts
function setDefaultProjectAnnotations(_defaultProjectAnnotations) {
globalThis.defaultProjectAnnotations = _defaultProjectAnnotations;
}
var DEFAULT_STORY_TITLE = "ComposedStory", DEFAULT_STORY_NAME = "Unnamed Story";
function extractAnnotation(annotation) {
return annotation ? composeConfigs([annotation]) : {};
}
function setProjectAnnotations(projectAnnotations) {
let annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations];
return globalThis.globalProjectAnnotations = composeConfigs([
...getCoreAnnotations(),
globalThis.defaultProjectAnnotations ?? {},
composeConfigs(annotations.map(extractAnnotation))
]), globalThis.globalProjectAnnotations ?? {};
}
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;
let normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations), storyName = exportsName || storyAnnotations.storyName || storyAnnotations.story?.name || storyAnnotations.name || DEFAULT_STORY_NAME, normalizedStory = normalizeStory(
storyName,
storyAnnotations,
normalizedComponentAnnotations
), normalizedProjectAnnotations = normalizeProjectAnnotations(
composeConfigs([
defaultConfig ?? globalThis.globalProjectAnnotations ?? {},
projectAnnotations ?? {}
])
), story = prepareStory(
normalizedStory,
normalizedComponentAnnotations,
normalizedProjectAnnotations
), globals = {
...getValuesFromArgTypes(normalizedProjectAnnotations.globalTypes),
...normalizedProjectAnnotations.initialGlobals,
...story.storyGlobals
}, reporting = new ReporterAPI(), initializeContext = () => {
let context = prepareContext({
hooks: new HooksContext(),
globals,
args: { ...story.initialArgs },
viewMode: "story",
reporting,
loaded: {},
abortSignal: new AbortController().signal,
step: (label, play2) => story.runStep(label, play2, context),
canvasElement: null,
canvas: {},
userEvent: {},
globalTypes: normalizedProjectAnnotations.globalTypes,
...story,
context: null,
mount: null
});
return context.parameters.__isPortableStory = !0, context.context = context, story.renderToCanvas && (context.renderToCanvas = async () => {
let unmount = await story.renderToCanvas?.(
{
componentId: story.componentId,
title: story.title,
id: story.id,
name: story.name,
tags: story.tags,
showMain: () => {
},
showError: (error) => {
throw new Error(`${error.title}
${error.description}`);
},
showException: (error) => {
throw error;
},
forceRemount: !0,
storyContext: context,
storyFn: () => story.unboundStoryFn(context),
unboundStoryFn: story.unboundStoryFn
},
context.canvasElement
);
unmount && cleanups.push(unmount);
}), context.mount = story.mount(context), context;
}, loadedContext, play = async (extraContext) => {
let context = initializeContext();
return context.canvasElement ??= globalThis?.document?.body, loadedContext && (context.loaded = loadedContext.loaded), Object.assign(context, extraContext), story.playFunction(context);
}, run = (extraContext) => {
let context = initializeCon