UNPKG

storybook

Version:

Storybook: Develop, document, and test UI components in isolation

1,206 lines (1,167 loc) • 127 kB
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