From 0f60c0bf58ded7d0081ef6bf973a2bf3b3a77ea0 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 20 May 2026 14:54:53 +0400 Subject: [PATCH] Initial setup for typed commands --- packages/core/package.json | 2 +- packages/core/src/commands/config/config.ts | 15 ++- packages/core/src/commands/index.ts | 63 ++++++--- packages/core/src/commands/registry.ts | 73 +++++++++++ packages/core/src/commands/registryHelpers.ts | 9 ++ .../core/src/commands/view/CommandAbstract.ts | 73 ++++++++--- packages/core/src/commands/view/Fullscreen.ts | 123 ++++++++++++------ packages/core/src/editor/index.ts | 9 +- packages/core/src/index.ts | 1 + .../test/specs/commands/view/Fullscreen.ts | 58 +++++++++ packages/core/test/types/commandRegistries.ts | 86 ++++++++++++ packages/core/test/types/tsconfig.json | 18 +++ 12 files changed, 447 insertions(+), 83 deletions(-) create mode 100644 packages/core/src/commands/registry.ts create mode 100644 packages/core/src/commands/registryHelpers.ts create mode 100644 packages/core/test/specs/commands/view/Fullscreen.ts create mode 100644 packages/core/test/types/commandRegistries.ts create mode 100644 packages/core/test/types/tsconfig.json diff --git a/packages/core/package.json b/packages/core/package.json index 6ecf19a43..1871ee470 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,7 +69,7 @@ "build:mjs": "cross-env BUILD_MODULE=true node node_modules/grapesjs-cli/dist/cli.js build --dts='skip' --patch=false --targets=\"defaults\"", "build:css": "sass src/styles/scss/main.scss dist/css/grapes.min.css --no-source-map --style=compressed --load-path=node_modules", "ts:build": "node node_modules/grapesjs-cli/dist/cli.js build --dts='only' --patch=false", - "ts:check": "tsc --noEmit --esModuleInterop dist/index.d.ts", + "ts:check": "tsc -p test/types/tsconfig.json", "start": "cross-env NODE_ENV=development run-p start:*", "start:js": "node node_modules/grapesjs-cli/dist/cli.js serve", "start:css": "npm run build:css -- --watch", diff --git a/packages/core/src/commands/config/config.ts b/packages/core/src/commands/config/config.ts index 3f1822bb3..cb01f6636 100644 --- a/packages/core/src/commands/config/config.ts +++ b/packages/core/src/commands/config/config.ts @@ -1,10 +1,15 @@ -import type { CommandObject, CommandOptions } from '../view/CommandAbstract'; +import type { CommandObject } from '../view/CommandAbstract'; +import type { CommandKnownId, CommandRunOptions, CommandStopOptions } from '../registry'; -interface CommandConfigDefaultOptions { - run?: (options: CommandOptions) => CommandOptions; - stop?: (options: CommandOptions) => CommandOptions; +export interface CommandConfigDefaultOptions { + run?: (options: CommandRunOptions) => CommandRunOptions; + stop?: (options: CommandStopOptions) => CommandStopOptions; } +export type CommandsDefaultOptions = { + [TId in CommandKnownId]?: CommandConfigDefaultOptions; +} & Record; + export interface CommandsConfig { /** * Style prefix @@ -50,7 +55,7 @@ export interface CommandsConfig { * } * } */ - defaultOptions?: Record; + defaultOptions?: CommandsDefaultOptions; } const config: () => CommandsConfig = () => ({ diff --git a/packages/core/src/commands/index.ts b/packages/core/src/commands/index.ts index a9c65b972..90405c019 100644 --- a/packages/core/src/commands/index.ts +++ b/packages/core/src/commands/index.ts @@ -36,16 +36,32 @@ */ import { isFunction, includes } from 'underscore'; -import CommandAbstract, { Command, CommandOptions, CommandObject, CommandFunction } from './view/CommandAbstract'; +import CommandAbstract, { + Command, + CommandConstructor, + CommandOptions, + CommandStored, +} from './view/CommandAbstract'; import defConfig, { CommandsConfig } from './config/config'; import { Module } from '../abstract'; import Component from '../dom_components/model/Component'; import { ComponentsEvents } from '../dom_components/types'; import type Editor from '../editor/model/Editor'; import type { ObjectAny } from '../common'; +import type { + CommandDefinitionById, + CommandObjectById, + CommandRunArgs, + CommandRunResult, + CommandStopArgs, + CommandStopResult, +} from './registry'; import CommandsEvents from './types'; export type { CommandEvent } from './types'; +const isCommandConstructor = (command: Command): command is CommandConstructor => + isFunction(command) && (command === CommandAbstract || command.prototype instanceof CommandAbstract); + const commandsDef = [ ['preview', 'Preview', 'preview'], ['resize', 'Resize', 'resize'], @@ -99,7 +115,7 @@ export const getOnComponentDragEnd = export default class CommandsModule extends Module { CommandAbstract = CommandAbstract; defaultCommands: Record = {}; - commands: Record = {}; + commands: Record = {}; active: Record = {}; events = CommandsEvents; @@ -119,7 +135,7 @@ export default class CommandsModule extends Module { const obj = config.defaults![k]; - if (obj.id) this.add(obj.id, obj); + if (obj.id) this.add(obj.id, obj as CommandDefinitionById); }); defaultCommands['tlb-delete'] = { @@ -180,7 +196,7 @@ export default class CommandsModule extends Module { ... }); * */ - add(id: string, command: CommandFunction | CommandObject) { - let result: CommandObject = isFunction(command) ? { run: command } : command; + add(id: TId, command: CommandDefinitionById) { + if (isCommandConstructor(command)) { + const { prototype } = command; + const noStop = prototype.stop === CommandAbstract.prototype.stop; + + prototype.noStop = noStop; + prototype.id = id; + this.commands[id] = command; + + return this; + } + + let result = (isFunction(command) ? { run: command } : command) as CommandObjectById; if (!result.stop) { result.noStop = true; @@ -247,7 +274,7 @@ export default class CommandsModule extends Module(id: TId, cmd: CommandObjectById = {} as CommandObjectById) { const command = this.get(id); if (command) { @@ -327,8 +354,8 @@ export default class CommandsModule extends Module(id: TId, ...args: CommandRunArgs): CommandRunResult { + return this.runCommand(this.get(id), args[0] as CommandOptions) as CommandRunResult; } /** @@ -339,8 +366,8 @@ export default class CommandsModule extends Module(id: TId, ...args: CommandStopArgs): CommandStopResult { + return this.stopCommand(this.get(id), args[0] as CommandOptions) as CommandStopResult; } /** @@ -380,7 +407,7 @@ export default class CommandsModule extends Module) { if (!command.stop) command.noStop = true; - const cmd = CommandAbstract.extend(command); + const cmd = CommandAbstract.extend(command) as CommandConstructor; return new cmd(this.config); } diff --git a/packages/core/src/commands/registry.ts b/packages/core/src/commands/registry.ts new file mode 100644 index 000000000..b4cc7fd20 --- /dev/null +++ b/packages/core/src/commands/registry.ts @@ -0,0 +1,73 @@ +import type { ObjectAny } from '../common'; +import type { CommandPublicOptions } from './registryHelpers'; +import type CommandAbstract from './view/CommandAbstract'; +import type { + CommandConstructor, + CommandFunction, + CommandObject, +} from './view/CommandAbstract'; +import type { FullscreenCommandRegistryRun, FullscreenCommandRegistryStop } from './view/Fullscreen'; + +type CommandPublicHandler = (...args: any[]) => any; +type CommandRegistryEntry = TId extends keyof TRegistry + ? TRegistry[TId] extends CommandPublicHandler + ? TRegistry[TId] + : CommandPublicHandler + : CommandPublicHandler; + +export interface CommandRegistryRun extends FullscreenCommandRegistryRun {} + +export interface CommandRegistryStop extends FullscreenCommandRegistryStop {} + +export type CommandRunKnownId = Extract; +export type CommandStopKnownId = Extract; +export type CommandKnownId = Extract; + +export type CommandRunPublicFn = CommandRegistryEntry; +export type CommandStopPublicFn = CommandRegistryEntry; + +export type CommandRunArgs = TId extends keyof CommandRegistryRun + ? Parameters> + : [options?: any]; +export type CommandStopArgs = TId extends keyof CommandRegistryStop + ? Parameters> + : [options?: any]; + +export type CommandRunOptions = TId extends keyof CommandRegistryRun + ? CommandPublicOptions> + : any; +export type CommandStopOptions = TId extends keyof CommandRegistryStop + ? CommandPublicOptions> + : any; + +export type CommandRunResult = TId extends keyof CommandRegistryRun + ? ReturnType> + : any; +export type CommandStopResult = TId extends keyof CommandRegistryStop + ? ReturnType> + : any; + +export type CommandFunctionById = CommandFunction, CommandRunResult>; +export type CommandObjectById = CommandObject< + CommandRunOptions, + T, + CommandStopOptions, + CommandRunResult, + CommandStopResult +>; +export type CommandConstructorById = CommandConstructor< + CommandRunOptions, + CommandStopOptions, + CommandRunResult, + CommandStopResult +>; +export type CommandDefinitionById = + | CommandFunctionById + | CommandObjectById + | CommandConstructorById; +export type CommandInstanceById = CommandAbstract< + CommandRunOptions, + CommandStopOptions, + CommandRunResult, + CommandStopResult +>; diff --git a/packages/core/src/commands/registryHelpers.ts b/packages/core/src/commands/registryHelpers.ts new file mode 100644 index 000000000..cc8ebd62d --- /dev/null +++ b/packages/core/src/commands/registryHelpers.ts @@ -0,0 +1,9 @@ +type CommandPublicHandler = (...args: any[]) => any; + +export type CommandPublicFnFromHandler = T extends (editor: any, sender: any, ...args: infer P) => infer R + ? (...args: P) => R + : never; + +export type CommandPublicOptions = Parameters extends [] + ? undefined + : Parameters[0]; diff --git a/packages/core/src/commands/view/CommandAbstract.ts b/packages/core/src/commands/view/CommandAbstract.ts index 1f1e78961..49506734d 100644 --- a/packages/core/src/commands/view/CommandAbstract.ts +++ b/packages/core/src/commands/view/CommandAbstract.ts @@ -4,28 +4,63 @@ import Editor from '../../editor'; import EditorModel from '../../editor/model/Editor'; import CommandsEvents, { type CommandCallEventData, type CommandEventData } from '../types'; -interface ICommand { - run?: CommandAbstract['run']; - stop?: CommandAbstract['stop']; +interface ICommand { + run?: CommandAbstract['run']; + stop?: CommandAbstract['stop']; id?: string; + noStop?: boolean; + initialize?: unknown; [key: string]: unknown; } -export type CommandFunction = CommandAbstract['run']; +export type CommandFunction = CommandAbstract< + TRunOptions, + any, + TRunResult, + any +>['run']; + +export interface CommandConstructor< + TRunOptions = any, + TStopOptions = TRunOptions, + TRunResult = any, + TStopResult = any, +> { + new (o: any): CommandAbstract; + prototype: CommandAbstract; +} -export type Command = CommandObject | CommandFunction; +export type Command = CommandObject | CommandFunction | CommandConstructor; +export type CommandStored = CommandConstructor | CommandAbstract; export type CommandOptions = Record; -export type CommandObject = ICommand & +export type CommandObject< + TRunOptions = any, + T extends ObjectAny = {}, + TStopOptions = TRunOptions, + TRunResult = any, + TStopResult = any, +> = ICommand & T & - ThisType>; - -export function defineCommand(def: CommandObject) { + ThisType>; + +export function defineCommand< + TRunOptions = any, + T extends ObjectAny = {}, + TStopOptions = TRunOptions, + TRunResult = any, + TStopResult = any, +>(def: CommandObject) { return def; } -export default class CommandAbstract extends Model { +export default class CommandAbstract< + TRunOptions = any, + TStopOptions = TRunOptions, + TRunResult = any, + TStopResult = any, +> extends Model { config: any; em: EditorModel; pfx: string; @@ -109,8 +144,9 @@ export default class CommandAbstract extends Model { * @param {Object} [options={}] Options * @private * */ - callRun(editor: Editor, options: any = {}) { + callRun(editor: Editor, opts: TRunOptions = {} as TRunOptions) { const { id } = this; + const options = opts as CommandOptions; editor.trigger(`${CommandsEvents.runBeforeCommand}${id}`, { options }); if (options.abort) { @@ -119,7 +155,7 @@ export default class CommandAbstract extends Model { } const sender = options.sender || editor; - const result = this.run(editor, sender, options); + const result = this.run(editor, sender, options as TRunOptions); const data: CommandEventData = { id, result, options }; const dataCall: CommandCallEventData = { ...data, type: 'run' }; @@ -141,11 +177,12 @@ export default class CommandAbstract extends Model { * @param {Object} [options={}] Options * @private * */ - callStop(editor: Editor, options: any = {}) { + callStop(editor: Editor, opts: TStopOptions = {} as TStopOptions) { const { id } = this; + const options = opts as CommandOptions; const sender = options.sender || editor; editor.trigger(`${CommandsEvents.stopBeforeCommand}${id}`, { options }); - const result = this.stop(editor, sender, options); + const result = this.stop(editor, sender, options as TStopOptions); const data: CommandEventData = { id, result, options }; const dataCall: CommandCallEventData = { ...data, type: 'stop' }; delete editor.Commands.active[id]; @@ -169,7 +206,9 @@ export default class CommandAbstract extends Model { * @param {Object} sender Button sender * @private * */ - run(em: Editor, sender: any, options: O) {} + run(em: Editor, sender: any, options: TRunOptions): TRunResult { + return undefined as TRunResult; + } /** * Method that stop command @@ -177,5 +216,7 @@ export default class CommandAbstract extends Model { * @param {Object} sender Button sender * @private * */ - stop(em: Editor, sender: any, options: O) {} + stop(em: Editor, sender: any, options: TStopOptions): TStopResult { + return undefined as TStopResult; + } } diff --git a/packages/core/src/commands/view/Fullscreen.ts b/packages/core/src/commands/view/Fullscreen.ts index 105a065d2..7c41456ea 100644 --- a/packages/core/src/commands/view/Fullscreen.ts +++ b/packages/core/src/commands/view/Fullscreen.ts @@ -1,48 +1,86 @@ import { isElement } from 'underscore'; -import { CommandObject } from './CommandAbstract'; +import type { CommandPublicFnFromHandler } from '../registryHelpers'; +import CommandAbstract from './CommandAbstract'; + +export interface CommandFullscreenOptions { + target?: HTMLElement | string; +} + +export interface FullscreenCommandRegistryRun { + 'core:fullscreen': CommandPublicFnFromHandler; + fullscreen: CommandPublicFnFromHandler; +} + +export interface FullscreenCommandRegistryStop { + 'core:fullscreen': CommandPublicFnFromHandler; + fullscreen: CommandPublicFnFromHandler; +} + +interface DocumentWithFullscreen extends Document { + webkitFullscreenElement?: Element | null; + mozFullScreenElement?: Element | null; + webkitExitFullscreen?: () => void; + mozCancelFullScreen?: () => void; + msExitFullscreen?: () => void; +} + +export default class CommandFullscreen extends CommandAbstract< + CommandFullscreenOptions, + CommandFullscreenOptions, + void, + void +> { + sender: any; + fullscreenChangeEvent?: string; + fullscreenChangeHandler?: EventListener; -export default { /** * Check if fullscreen mode is enabled * @return {Boolean} */ isEnabled() { - const d = document; - // @ts-ignore - if (d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement) { - return true; - } - return false; - }, + const d = document as DocumentWithFullscreen; + return !!(d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement); + } /** * Enable fullscreen mode and return browser prefix * @param {HTMLElement} el * @return {string} */ - enable(el: any) { + enable(el?: HTMLElement | null) { let pfx = ''; - if (el.requestFullscreen) { - el.requestFullscreen(); - } else if (el.webkitRequestFullscreen) { + if (!el) { + return pfx; + } + + const target = el as HTMLElement & { + webkitRequestFullscreen?: () => void; + mozRequestFullScreen?: () => void; + msRequestFullscreen?: () => void; + }; + + if (target.requestFullscreen) { + target.requestFullscreen(); + } else if (target.webkitRequestFullscreen) { pfx = 'webkit'; - el.webkitRequestFullscreen(); - } else if (el.mozRequestFullScreen) { + target.webkitRequestFullscreen(); + } else if (target.mozRequestFullScreen) { pfx = 'moz'; - el.mozRequestFullScreen(); - } else if (el.msRequestFullscreen) { - el.msRequestFullscreen(); + target.mozRequestFullScreen(); + } else if (target.msRequestFullscreen) { + target.msRequestFullscreen(); } return pfx; - }, + } /** * Disable fullscreen mode */ disable() { - const d: any = document; + const d = document as DocumentWithFullscreen; if (this.isEnabled()) { if (d.exitFullscreen) d.exitFullscreen(); @@ -50,32 +88,39 @@ export default { else if (d.mozCancelFullScreen) d.mozCancelFullScreen(); else if (d.msExitFullscreen) d.msExitFullscreen(); } - }, + } - /** - * Triggered when the state of the fullscreen is changed. Inside detects if - * it's enabled - * @param {strinf} pfx Browser prefix - * @param {Event} e - */ - fsChanged(pfx: string) { + onFullscreenChange() { if (!this.isEnabled()) { this.stopCommand({ sender: this.sender }); - document.removeEventListener(`${pfx || ''}fullscreenchange`, this.fsChanged); } - }, + } - run(editor, sender, opts = {}) { + run(editor: any, sender: any, opts: CommandFullscreenOptions = {}) { this.sender = sender; const { target } = opts; - const targetEl = isElement(target) ? target : document.querySelector(target!); + const targetEl = isElement(target) + ? target + : typeof target === 'string' + ? document.querySelector(target) + : undefined; const pfx = this.enable(targetEl || editor.getContainer()); - this.fsChanged = this.fsChanged.bind(this, pfx); - document.addEventListener(pfx + 'fullscreenchange', this.fsChanged); - }, + this.fullscreenChangeEvent = `${pfx}fullscreenchange`; + this.fullscreenChangeHandler = this.onFullscreenChange.bind(this); + document.addEventListener(this.fullscreenChangeEvent, this.fullscreenChangeHandler); + } + + stop(_editor: any, sender: any, _opts: CommandFullscreenOptions = {}) { + if (sender && sender.set) { + sender.set('active', false); + } + + if (this.fullscreenChangeEvent && this.fullscreenChangeHandler) { + document.removeEventListener(this.fullscreenChangeEvent, this.fullscreenChangeHandler); + this.fullscreenChangeEvent = undefined; + this.fullscreenChangeHandler = undefined; + } - stop(editor, sender) { - if (sender && sender.set) sender.set('active', false); this.disable(); - }, -} as CommandObject<{ target?: HTMLElement | string }, { [k: string]: any }>; + } +} diff --git a/packages/core/src/editor/index.ts b/packages/core/src/editor/index.ts index 6dd1e9259..8074f02f4 100644 --- a/packages/core/src/editor/index.ts +++ b/packages/core/src/editor/index.ts @@ -48,6 +48,7 @@ import BlockManager from '../block_manager'; import CanvasModule from '../canvas'; import CodeManagerModule from '../code_manager'; import CommandsModule from '../commands'; +import type { CommandRunArgs, CommandRunResult, CommandStopArgs, CommandStopResult } from '../commands/registry'; import { AddOptions, EventHandler } from '../common'; import CssComposer from '../css_composer'; import CssRule from '../css_composer/model/CssRule'; @@ -498,8 +499,8 @@ export default class Editor implements IBaseModule { * @example * editor.runCommand('myCommand', {someValue: 1}); */ - runCommand(id: string, options: Record = {}) { - return this.Commands.run(id, options); + runCommand(id: TId, ...args: CommandRunArgs): CommandRunResult { + return this.Commands.run(id, ...(args as any)) as CommandRunResult; } /** @@ -510,8 +511,8 @@ export default class Editor implements IBaseModule { * @example * editor.stopCommand('myCommand', {someValue: 1}); */ - stopCommand(id: string, options: Record = {}) { - return this.Commands.stop(id, options); + stopCommand(id: TId, ...args: CommandStopArgs): CommandStopResult { + return this.Commands.stop(id, ...(args as any)) as CommandStopResult; } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 49098d19b..581d2af83 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -98,6 +98,7 @@ export const grapesjs = { */ export type { CategoryProperties as BlockCategoryProperties } from './abstract/ModuleCategory'; export type { ComponentDragEventProps } from './commands/view/ComponentDrag'; +export type { CommandRegistryRun, CommandRegistryStop } from './commands/registry'; // Exports for TS export type { default as Asset } from './asset_manager/model/Asset'; diff --git a/packages/core/test/specs/commands/view/Fullscreen.ts b/packages/core/test/specs/commands/view/Fullscreen.ts new file mode 100644 index 000000000..f86dbf3ff --- /dev/null +++ b/packages/core/test/specs/commands/view/Fullscreen.ts @@ -0,0 +1,58 @@ +import CommandFullscreen from '../../../../src/commands/view/Fullscreen'; +import Editor from '../../../../src/editor'; + +describe('Fullscreen command', () => { + let editor: Editor; + let container: HTMLElement & { requestFullscreen: jest.Mock }; + let isFullscreen: boolean; + let exitFullscreen: jest.Mock; + + beforeEach(() => { + isFullscreen = false; + container = document.createElement('div') as HTMLElement & { requestFullscreen: jest.Mock }; + container.requestFullscreen = jest.fn(() => { + isFullscreen = true; + }); + exitFullscreen = jest.fn(() => { + isFullscreen = false; + }); + + Object.defineProperty(document, 'fullscreenElement', { + configurable: true, + get: () => (isFullscreen ? container : null), + }); + + Object.defineProperty(document, 'exitFullscreen', { + configurable: true, + value: exitFullscreen, + }); + + editor = new Editor({ el: container } as any); + }); + + afterEach(() => { + editor.destroy(); + }); + + test('runs from canonical and legacy ids', () => { + const addSpy = jest.spyOn(document, 'addEventListener'); + const removeSpy = jest.spyOn(document, 'removeEventListener'); + + expect(editor.Commands.get('core:fullscreen')).toBeInstanceOf(CommandFullscreen); + expect(editor.Commands.get('fullscreen')).toBeInstanceOf(CommandFullscreen); + + editor.runCommand('core:fullscreen'); + expect(container.requestFullscreen).toHaveBeenCalledTimes(1); + expect(addSpy).toHaveBeenCalledWith('fullscreenchange', expect.any(Function)); + + editor.stopCommand('core:fullscreen'); + expect(exitFullscreen).toHaveBeenCalledTimes(1); + expect(removeSpy).toHaveBeenCalledWith('fullscreenchange', expect.any(Function)); + + editor.runCommand('fullscreen', { target: container }); + expect(container.requestFullscreen).toHaveBeenCalledTimes(2); + + editor.stopCommand('fullscreen'); + expect(exitFullscreen).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/core/test/types/commandRegistries.ts b/packages/core/test/types/commandRegistries.ts new file mode 100644 index 000000000..d3e1533f1 --- /dev/null +++ b/packages/core/test/types/commandRegistries.ts @@ -0,0 +1,86 @@ +import type { Editor } from 'grapesjs'; + +interface MyCommandOptions { + value: number; +} + +interface MyCommandResult { + done: boolean; +} + +interface MyCommandStopOptions { + reason: string; +} + +declare module 'grapesjs' { + interface CommandRegistryRun { + 'my:command': (options: MyCommandOptions) => MyCommandResult; + 'my:stateless': () => number; + } + + interface CommandRegistryStop { + 'my:command': (options: MyCommandStopOptions) => void; + } +} + +const editor = {} as Editor; + +const fullscreenResult: void = editor.runCommand('core:fullscreen'); +editor.runCommand('fullscreen', { target: document.body }); +// @ts-expect-error Fullscreen target must be an element or selector string +editor.runCommand('core:fullscreen', { target: 1 }); + +const customResult: MyCommandResult = editor.runCommand('my:command', { value: 1 }); +customResult.done; +editor.stopCommand('my:command', { reason: 'done' }); +// @ts-expect-error Missing required run options +editor.runCommand('my:command'); +// @ts-expect-error Stop options do not match the registry +editor.stopCommand('my:command', { value: 1 }); + +const statelessResult: number = editor.runCommand('my:stateless'); +statelessResult.toFixed(); +// @ts-expect-error Stateless commands should not accept options +editor.runCommand('my:stateless', {}); + +editor.Commands.add('my:command', { + run(_editor, _sender, options) { + options.value.toFixed(); + return { done: true }; + }, + stop(_editor, _sender, options) { + options.reason.toUpperCase(); + }, +}); + +editor.Commands.add('my:command', (_editor, _sender, options) => { + options.value.toFixed(); + return { done: true }; +}); + +editor.Commands.add('my:stateless', () => 1); + +editor.Commands.add('my:command', { + run(_editor, _sender, options) { + // @ts-expect-error The command run options should come from the registry + options.reason.toUpperCase(); + return { done: true }; + }, +}); + +editor.Commands.config.defaultOptions = { + 'my:command': { + run(options) { + return { ...options, value: options.value + 1 }; + }, + stop(options) { + return { ...options, reason: options.reason.toUpperCase() }; + }, + }, + 'core:fullscreen': { + run(options) { + return { ...options, target: options?.target ?? '.app' }; + }, + }, +}; +editor.Commands.config.defaultOptions['my:command']?.run?.({ value: 1 }); diff --git a/packages/core/test/types/tsconfig.json b/packages/core/test/types/tsconfig.json new file mode 100644 index 000000000..6d327d2d3 --- /dev/null +++ b/packages/core/test/types/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "strict": true, + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "noEmit": true, + "baseUrl": "../..", + "paths": { + "grapesjs": ["dist/index.d.ts"] + } + }, + "files": ["commandRegistries.ts"] +}