import { type Listener } from '../event-dispatcher'; import type { CorePlugin, DefaultEditorPlugin, DependencyPlugin, NextEditorPlugin, NextEditorPluginMetadata, OptionalPlugin, PluginDependenciesAPI } from '../types'; import type { EditorPlugin } from '../types/editor-plugin'; import type { EditorPluginInjectionAPI } from './plugin-injection-api'; /********************* * * * BASE TYPES * * * **********************/ /** * 🧱 Internal Type: Editor FE Platform * */ type MaybePlugin = T | undefined; /** * 🧱 Internal Type: Editor FE Platform * */ type DependencyErrorMessage = { errorMessage: Message; }; /** * 🧱 Internal Type: Editor FE Platform * */ type PluginWithConfiguration = undefined extends ExtractPluginConfiguration ? [Plugin, ExtractPluginConfiguration?] : [Plugin, ExtractPluginConfiguration]; /**************************************************** * * * METADATA PROPERTIES EXTRACTION TYPES * * * ****************************************************/ /** * 🧱 Internal Type: Editor FE Platform * * Extracts the configuration type from a given plugin. * * * @returns The extracted plugin configuration type if applicable, or `never`. * * @example * ```typescript * type DogPlugin = NextEditorPlugin<'dog'>; * * // it returns never, since Dog has no configuration * type MyPluginConfiguration = ExtractPluginConfiguration; * * * type CatPlugin = NextEditorPlugin<'cat', { configuration: { color: 'red' | 'blue' } }>; * * // it returns this type { color: 'red' | 'blue' } * type MyPluginConfiguration = ExtractPluginConfiguration; * ``` */ type ExtractPluginConfiguration = Plugin extends NextEditorPlugin ? Plugin extends (props: { api: any; config: any; }) => DefaultEditorPlugin ? ExtractPluginConfigurationFromMetadata : never : never; /** * 🧱 Internal Type: Editor FE Platform * * Extracts and filters the plugin dependencies from the plugin metadata, excluding * optional dependencies. * * This type first checks if the `dependencies` property in the given `Metadata` type * is an array of `DependencyPlugin`. If true, it applies `FilterOptionalPlugins` to * filter out the optional dependencies. If the `dependencies` property does not exist * or is not an array of `DependencyPlugin`, the type resolves to an empty array. * * @returns An array of filtered plugin dependencies or an empty array. * * @example * ```typescript * type DogPlugin = NextEditorPlugin<'dog'>; * type LoudPlugin = NextEditorPlugin<'loud'>; * type BarkMetadata = {dependencies: [ * OptionalPlugin, * DogPlugin, * ]} * type BarkPlugin = NextEditorPlugin<'bark', BarkMetadata>; * * // It returns [DogPlugin] * type RequiredDependencies = ExtractPluginDependenciesFromMetadataWithoutOptionals; * * ``` * * You probably wants to use this other type util @see ExtractPluginDependencies * since you wouldn't need to infer the Metadata twice */ type ExtractPluginDependenciesFromMetadataWithoutOptionals = Metadata['dependencies'] extends DependencyPlugin[] ? FilterOptionalPlugins : []; /** * 🧱 Internal Type: Editor FE Platform * * Extracts the plugin configuration from the given plugin metadata if the * `pluginConfiguration` property exists. * * This type conditionally checks if the `Metadata` type includes a `pluginConfiguration` * key. If such a key exists, the type of `pluginConfiguration` is returned. If not, * the type resolves to `never`. */ type ExtractPluginConfigurationFromMetadata = 'pluginConfiguration' extends keyof Metadata ? Metadata['pluginConfiguration'] : never; /******************************** * * * TYPE INFER * * * *********************************/ /** * 🧱 Internal Type: Editor FE Platform * * Extracts the NextEditorPlugin type from a PresetPuglin, * this is useful because the EditorPresetBuilder can accept the plugin in multiple ways: * * @example * ``` * preset * // valid * .add([plugin, { myConfiguration }] // Type: [NextEditorPlugin, Configuration] * * // valid * .add([plugin]) // Type: [NextEditorPlugin, Configuration?] * * // valid * .add(plugin) // Type: NextEditorPlugin * * ``` * * This type conditionally checks if `Plugin` is an array. If it is an array, it then checks if the first element * (`MPlugin`) extends `NextEditorPlugin`. But if `Plugin` directly extends `NextEditorPlugin`, it returns the `Plugin` * type itself. Otherwise, it resolves to `never`. * * You probably wants to use this if you need to extract the NextEditorPlugin from a @see PresetPlugin . * Since the PresetPlugin is an union between a tuple and a plugin. */ type ExtractPluginAllBuilderPlugins = Plugin extends Array ? Plugin extends [infer MPlugin, ...any] ? MPlugin extends NextEditorPlugin ? MPlugin : never : never : Plugin extends NextEditorPlugin ? Plugin : never; /** * 🧱 Internal Type: Editor FE Platform * * Extracts non-optional plugin dependencies, excluding any optional dependencies, from a given plugin's metadata. * * We can declare the depencies like this: * * @example * ```typescript * NextEditorPlugin<'bark', { * dependencies: [DogPlugin, Optional] * }> * * ``` * * * This tyope is similar to @see ExtractPluginDependenciesFromMetadataWithoutOptionals * but you can use it to extract the non-optional-dependencies from any NextEditorPlugin without infer the metadata * * @example * ```typescript * type BarkPlugin = NextEditorPlugin<'bark', { * dependencies: [DogPlugin, Optional] * }> * * type PluginDependencies = ExtractPluginDependencies; // Type: [DogPlugin] * ``` */ type ExtractPluginDependencies = Plugin extends NextEditorPlugin ? ExtractPluginDependenciesFromMetadataWithoutOptionals : never; /** * 🧱 Internal Type: Editor FE Platform * * Extracts the NextEditorPlugin type from a PluginWithConfiguration. * * * You probably wants to use this if you need to extract the NextEditorPlugin from a @see PresetPlugin . * Since the PresetPlugin is an union between a tuple and a plugin. */ type ExtractNextEditorPluginFromPluginWithConfiguration = Plugin extends PluginWithConfiguration ? Plugin[0] : never; /** * 🧱 Internal Type: Editor FE Platform * * Extracts the plugin name from a PresetPlugins. * * @example * ```typescript * ExtractPluginNameFromAllBuilderPlugins> // 'bark' * * ExtractPluginNameFromAllBuilderPlugins<[NextEditorPlugin<'dog'>, { configuration: {} }> // 'dog' * * ``` * Similar to @see ExtractPluginAllBuilderPlugins, this type conditionally checks if `Plugin` is an array. If it is, * it attempts to extract the name of the first plugin (`MPlugin`) in the array that extends `NextEditorPlugin` with * a name and any metadata. If `Plugin` itself directly extends `NextEditorPlugin`, it extracts the plugin's name. * If none of these conditions are met, it resolves to `never`. * */ type ExtractPluginNameFromAllBuilderPlugins = Plugin extends Array ? Plugin extends [infer MPlugin, ...any] ? MPlugin extends NextEditorPlugin ? Name : never : never : Plugin extends NextEditorPlugin ? Name : never; /****************************** * * * MAPPED TUPLES * * * ******************************/ /** * 🧱 Internal Type: Editor FE Platform * * Filters out optional plugins from a tuple of dependency plugins. * * * This type is using the Tail Head trick to map a tuple to another one. * It does this by conditionally iterating over each element in the tuple: if the head of the tuple (the first element) * is an optional plugin, it is excluded from the resulting tuple; otherwise, it is included. This process is repeated * for the tail (the remaining elements) of the tuple until all elements have been evaluated. * */ type FilterOptionalPlugins = T extends [infer Head, ...infer Tail] ? Tail extends DependencyPlugin[] ? Head extends OptionalPlugin> ? FilterOptionalPlugins : [Head, ...FilterOptionalPlugins] : T : T; /** * 🧱 Internal Type: Editor FE Platform * * One of the main type system for the EditorPresetBuilder. * * Verifies if a given plugin's dependencies are satisfied within a provided stack of plugins. * * Usually, the stack of plugins are coming from a generic parameter in the EditorPresetBuilder. * * This type checks if the dependencies of the given `Plugin` are included in the provided `PluginsStack`. * * - If the plugin has no dependencies, it simply returns the plugin itself, (provided it is either a `PluginWithConfiguration` or `NextEditorPlugin`, in case someone tries to add a non-NextEditorPlugin to the Preset) * * - If the plugin has dependencies, it verifies each dependency against the `PluginsStack` to ensure * they are present. This includes checking direct dependencies as well as dependencies hidden inside tuples (by unwrapping * them). If all dependencies are satisfied, it returns the plugin; otherwise, it resolves to `never`. * * * @example * ```typescript * type DogPlugin = NextEditorPlugin<'dog'>; * type LoudPlugin = NextEditorPlugin<'loud'>; * type BarkPlugin = NextEditorPlugin<'bark', { dependencies: [DogPlugin, LoudPlugin] }>; * * * // When there we are missing dependencies * VerifyPluginDependencies // Type: never * * * // When there all dependencies are already added on the stack * VerifyPluginDependencies // Type: BarkPlugin * * ``` */ type VerifyPluginDependencies = ExtractPluginDependencies extends [] ? Plugin extends PluginWithConfiguration | NextEditorPlugin ? Plugin : never : /** * case 1: We're looking for its dependent plugins indexed on `AllEditorPresetPluginTypes` */ ExtractPluginDependencies[number] extends (ExtractPluginDependencies[number] & PluginsStack[number]) /** * case 2: * Otherwise check whether the dependent-plugin, is hidden inside a tuple, * unwrapping `Plugins` via `ExtractNextEditorPluginFromPluginWithConfiguration` */ | (ExtractPluginDependencies[number] & ExtractNextEditorPluginFromPluginWithConfiguration) ? Plugin : never; /******************************** * * * BETTER ERROR MESSAGE TYPES * * * *********************************/ /** * 🧱 Internal Type: Editor FE Platform * * TypeScript doesn't allow custom error messages (yet). So, use this type to force a specific error message to the user. * * This is useful because in a situation where a Preset has too many plugins, its become really hard to understand what the error message is. * * Extracts the names of required dependencies for a given plugin, or provides an error message if dependencies are * missing, invalid, or if the plugin itself is not a recognized NextEditorPlugin. * * This type evaluates whether a given `Plugin` has defined dependencies. If dependencies are absent, it returns * a message indicating no dependencies were found. If dependencies are present but do not conform to expected types, * or if an unspecified issue occurs, appropriate error messages are generated. Valid dependencies result in the * extraction of their names; otherwise, an error message specific to the situation is returned. * * It is used by the @see GetDependencyErrorMessage to group all error messages when a new plugin is being added into a preset. */ type ExtractRequiredDependencies = Plugin extends NextEditorPlugin ? Metadata['dependencies'] extends undefined ? DependencyErrorMessage<'No found dependencies'> : Metadata['dependencies'] extends DependencyPlugin[] ? FilterOptionalPlugins>[number] extends NextEditorPlugin ? Name : DependencyErrorMessage<`Invalid dependency for ${PluginName}`> : DependencyErrorMessage<`Invalid dependencies for ${PluginName}`> : DependencyErrorMessage<'Plugin is not NextEditorPlugin'>; /** * 🧱 Internal Type: Editor FE Platform * * Retrieves an error message if any dependency-related issues are detected for a given plugin within a specified * plugin stack. This includes missing dependencies or other errors as identified by `ExtractRequiredDependencies`. * * It attempts to extract required dependencies for the `Plugin` from the `StackPlugins`. If the result is a string, * it indicates a missing dependency and constructs an error message accordingly. Otherwise, it directly returns the * result from `ExtractRequiredDependencies`, which could be an error message detailing the issue encountered. * * It is used by the @see SafePresetCheck to make improve the error message */ type GetDependencyErrorMessage = ExtractRequiredDependencies extends string ? DependencyErrorMessage<`Missing dependency: ${ExtractRequiredDependencies}Plugin`> : ExtractRequiredDependencies; /** * 🧱 Internal Type: Editor FE Platform * * Filters through an array of dependency plugins, removing any that do not exist in the provided plugins stack. * * This type recursively checks each plugin dependency against the provided `PluginsStack`. If a dependency is found * within the stack, it is included in the result; otherwise, it is excluded. This process helps in identifying * missing plugins from a set of required dependencies. * */ type FilterExistingPlugins = T extends [infer CurrentPluginDependency, ...infer RemainingPluginDependencies] ? RemainingPluginDependencies extends DependencyPlugin[] ? CurrentPluginDependency extends PluginsStack[number] ? FilterExistingPlugins : [ CurrentPluginDependency, ...FilterExistingPlugins ] : T : T; /***************************** * * * VALIDATION HELPER TYPES * * * ******************************/ /** * 🧱 Internal Type: Editor FE Platform * * Checks for duplicate plugin entries within a stack of plugins. If a duplicate is found, it returns an error message; * otherwise, it proceeds without error. * * This type primarily serves to ensure that each plugin in the plugin stack is unique, preventing issues related to * duplicate plugin registration. It also includes a check to accommodate scenarios where strict typing is bypassed. * * If the plugin is used with other configuration this type will not complain. */ type CheckDuplicatePlugin = Plugin extends NextEditorPlugin ? Plugin extends StackPlugins[number] ? unknown extends StackPlugins[number] ? unknown : DependencyErrorMessage<`Duplicate plugin: ${PluginName}`> : unknown : unknown; /** * 🧱 Internal Type: Editor FE Platform * * Verifies if a given plugin meets basic requirements to be considered a valid editor plugin. * * This type checks if the plugin is a function that matches the expected signature for an next editor plugin. If it does, * it further checks the plugin's configuration requirements to ensure compatibility and adherence to expected * configurations. * */ type CheckBasicPlugin = Plugin extends (args: any, api: any) => EditorPlugin ? CheckTupleRequirements, PluginWithConfiguration> : never; /** * 🧱 Internal Type: Editor FE Platform * * Evaluates whether a plugin's configuration meets the requirements to be used either as a standalone plugin or * as part of a plugin-with-configuration tuple. * * This type assesses the plugin configuration's status—whether it's optional, mandatory, or not present—and determines * the valid ways in which the plugin can be registered or used. This is crucial for maintaining backward compatibility * and ensuring plugins are correctly configured upon registration into the Preset * */ type CheckTupleRequirements = unknown extends Config ? Plugin | ArrayType : undefined extends Config ? Plugin | ArrayType : [ Config ] extends [never] ? Plugin : ArrayType; /***************************** * * * EDITOR API HELPER TYPES * * * ******************************/ /** * 🧱 Internal Type: Editor FE Platform * * Extracts the numeric indices as literal types from a tuple. * * This utility type takes a tuple and produces a union of its numeric indices as literal types. It's useful for * iterating over tuples with TypeScript's mapped types, allowing for operations on each tuple element based on its index. * * It is being used to separate plugins registred with `preset.maybeAdd` and `preset.add`. */ type TupleIndices = Extract extends `${infer N extends number}` ? N : never; /** * 🧱 Internal Type: Editor FE Platform * * Constructs a plugin api type with optional properties based on the optional plugins from a given tuple of plugins. * * This type iterates over a tuple of plugins and checks for plugins marked as optional (indicated by the presence * of `undefined`). For each optional plugin, it attempts to extract the plugin's name and corresponding * `PluginDependenciesAPI` type. The resulting object type has properties with these plugin names as keys and their * respective APIs as optional values. * * @example * ```typescript * type DogPlugin = NextEditorPlugin<'dog'>; * type CatPlugin = NextEditorPlugin<'cat'>; * * * BuildOptionalAPIEntry<[DogPlugin, MaybePlugin]> // Type: { cat?: { } } * * ``` */ type BuildOptionalAPIEntry = { [K in TupleIndices as undefined extends T[K] ? T[K] extends MaybePlugin ? ExtractPluginNameFromAllBuilderPlugins

: never : never]?: undefined extends T[K] ? T[K] extends MaybePlugin ? PluginDependenciesAPI> | undefined : never : never; }; /** * 🧱 Internal Type: Editor FE Platform * Generates a plugin api type with properties based on the required plugins from a given tuple of plugins. * * This type traverses a tuple of plugins, focusing on those not marked as optional. For each required plugin, * it extracts the plugin's name to use as a key and determines the corresponding `PluginDependenciesAPI` type * for the value. The resulting object type includes these key-value pairs, ensuring that each required plugin * has a defined API entry in the object. * * @example * ```typescript * type DogPlugin = NextEditorPlugin<'dog'>; * type CatPlugin = NextEditorPlugin<'cat'>; * * * BuildOptionalAPIEntry<[DogPlugin, MaybePlugin]> // Type: { dog?: { } } * * ``` */ type BuildRequiredAPIEntry = { [K in TupleIndices as undefined extends T[K] ? never : T[K] extends PresetPlugin ? ExtractPluginNameFromAllBuilderPlugins : never]: undefined extends T[K] ? never : T[K] extends PresetPlugin ? PluginDependenciesAPI> : never; }; /** * 🧱 Internal Type: Editor FE Platform * * Forces the expansion (simplification/normalization) of conditional and mapped types. * This can be particularly useful for making the types more readable and manageable in * environments like IntelliSense or when generating type documentation. * * More info {@link https://github.com/microsoft/TypeScript/issues/47980 TypeScript/issues/47980} */ type Expand = T extends Function ? T : T extends unknown ? { [K in keyof T]: Expand; } : never; /************************* * * * PUBLIC TYPES * * * *************************/ /** * 🧱 Internal Type: Editor FE Platform * * Represents a utility type that wraps a string type in a tuple, often used to denote * plugin names that might be optionally included or excluded in certain contexts within * the editor preset builder. */ export type MaybePluginName = [T]; /** * 🧱 Internal Type: Editor FE Platform * * A union type that represents a plugin which could either be a standalone `NextEditorPlugin` * or a `PluginWithConfiguration` that bundles a plugin with its specific configuration. * * This type is fundamental in managing plugins within presets, allowing for flexible plugin * registration that accommodates plugins with or without explicit configurations. */ export type PresetPlugin = PluginWithConfiguration> | NextEditorPlugin; /** * 🧱 Internal Type: Editor FE Platform * * A union type that aggregates all possible plugin name representations within the editor preset builder, * including simple strings for direct plugin names and wrapped strings in tuples when a plugin is registred with `maybeAdd`. * */ export type AllPluginNames = string | MaybePluginName; /** * 🧱 Internal Type: Editor FE Platform * * Represents all possible types of plugins that can be included within an editor preset. * This includes both `PresetPlugin` types and `MaybePlugin` types, accommodating a wide range * of plugin registration scenarios likw: * * @example * ```typescript * preset * .add([plugin, { myConfiguration }] * .add([plugin]) * .add(plugin) * .maybeAdd(plugin, () => true); * .maybeAdd([plugin], () => true); * .maybeAdd([plugin, { myConfiguration }], () => true); * * ``` */ export type AllEditorPresetPluginTypes = PresetPlugin | MaybePlugin>; /** * 🧱 Internal Type: Editor FE Platform * * Performs a series of checks to ensure that a given plugin can be safely added to a preset. * This includes verifying the plugin's dependencies, checking for duplicate registrations, and ensuring * the plugin meets basic criteria for being considered a valid plugin. * * @returns The plugin type if all checks pass, or error messages detailing why the plugin cannot be added. */ export type SafePresetCheck = Plugin extends Plugin & VerifyPluginDependencies ? Plugin extends NextEditorPlugin ? CheckDuplicatePlugin & CheckBasicPlugin : never : GetDependencyErrorMessage; /** * 📢 Public Type API * * Extracts the complete API surface for a given editor preset, including both core and plugin-specific APIs. * This type dynamically assembles the API object based on the included plugins, differentiating between * optional and required plugins to accurately reflect the available API calls. * * @template Preset The editor preset builder instance from which to extract the API. * @returns An object type representing the complete API surface for the given preset. * * @example * ```typescript * const dogPlugin: NextEditorPlugin<'dog'>; * const catPlugin: NextEditorPlugin<'cat'>; * * const myPreset = new EditorPresetBuilder() * .add(dogPlugin) * .maybeAdd(catPlugin, () => true) * * const api: ExtractPresetAPI; * * * // Core is always available * api.core.actions * * // Dog was registred with `add`, so it will always be available * api.dog.actions * * // Cat was registred with `maybeAdd`, so it may not be available on runtime * api.cat?.actions * ``` */ export type ExtractPresetAPI> = Preset extends EditorPresetBuilder ? Expand<{ core: PluginDependenciesAPI; } & BuildOptionalAPIEntry & BuildRequiredAPIEntry> : never; /************************* * * * PROP TYPES * * * *************************/ type OldAndDeprecatedAddFunction = (pluginToAdd: T, builder: EditorPresetBuilder) => EditorPresetBuilder; type BuildProps = { excludePlugins?: Set; pluginInjectionAPI?: EditorPluginInjectionAPI; }; /** * This class is the main way to build an Editor. * * A Preset is an immutable object, any modification like `.add` or `.maybeAdd` * will always result in a new preset instance. * * ⚠️⚠️⚠️ ATTENTION ⚠️⚠️⚠️ * For ComposableEditor, a new Preset means a full redraw, * it is one of the most expensive operation. * Please make sure you aren't recreating this all the time. * * EditorAPI: * In case you need access to the EditorAPI type definition based in the preset you have. * Please use the util type exported in this package: @see ExtractPresetAPI * * ```typescript * const myPreset = new EditorPresetBuilder() * .add(pluginDog) * .add(pluginCat); * * * function someFunc(myApi: ExtractPresetAPI) { * * } * ``` * * If your code is inside an EditorPlugin you should be using the @see ExtractInjectionAPI. */ export declare class EditorPresetBuilder { private readonly data; /** * @deprecated Use `apiResolver` instead */ apiPromise: Promise; private resolver; /** * Returns the editor API when resolved. * This occurs when the preset is initially built. */ apiResolver: APIDispatcher; private apiEmitter; constructor(...more: [...StackPlugins]); add(nextOrTuple: SafePresetCheck): EditorPresetBuilder<[ ExtractPluginNameFromAllBuilderPlugins, ...PluginNames ], [ NewPlugin, ...StackPlugins ]>; maybeAdd(pluginToAdd: SafePresetCheck, shouldAdd: boolean | (() => boolean) | OldAndDeprecatedAddFunction): EditorPresetBuilder<[ MaybePluginName>, ...PluginNames ], [ MaybePlugin, ...StackPlugins ]>; has(plugin: AllEditorPresetPluginTypes): boolean; build({ pluginInjectionAPI, excludePlugins: maybeExcludePlugins, }?: BuildProps): EditorPlugin[]; private verifyDuplicatedPlugins; private processEditorPlugins; private removeExcludedPlugins; private safeEntry; } type Emitter = (value: unknown) => void; /** * WARNING: Internal object * * Dedicated wrapper around EventDispatcher for public API around the editor API. * This only has the public method `on` which is used to listen to updates to the editor API. * * This shouldn't really be used externally - the `editorAPI` should be accessed via `usePreset`. */ declare class APIDispatcher { private emitter; private eventDispatcher; private key; private initialEvent; constructor(emitter: (emitter: Emitter) => void); /** * Used to observe Editor API events * * @param cb Callback to listen to the editor API. * This will also emit the last event if the stream has already started. * @returns Cleanup function to cleanup the listener */ on(cb: Listener): () => void; destroy(): void; } export {};