import { Wordgard } from 'wordgard/editor'; import { Transaction, GardState } from 'wordgard/state'; import { PhraseSet } from 'wordgard/phrases'; import { Mark, Plot, Node, Schema, Pos, ChangeSet } from 'wordgard/doc'; /** A command is a function that takes an editor and an additional parameter, and either... - returns `false` to indicate that it does not apply to the current editor state - performs its action as a side effect and returns `true` - returns a {@link state.Transaction.Spec transaction spec} that should be dispatched as its effect This formulation is chosen to cover both side-effecting commands (whose effect may not even directly affect the editor—a command may just open a dialog or change some editor-external state) _and_ {@link Command.Pure commands} implemented as pure functions from state to transaction. Extensions can register additional handlers for a command, which will be called in order of precedence (until one returns true) when the command is {@link Command.dispatch dispatched}. Commands are recognized by function identity. So, for example, the `enter` command is both the tag used to indicate invocation of an enter press and the function that implements the default behavior for this action. */ type Command = (target: Wordgard, param: Param) => boolean | Transaction.Spec; declare namespace Command { /** `Command.Pure` is a subtype of `Command` that relies only on the editor state, and performs no imperative effects. When implementing such a command function, it can be useful to tag it with this type, so that it can be invoked without a full editor component for testing or for use in a context where there is no editor. Note that invoking a command function directly will not activate custom {@link Command.handler handlers}. */ type Pure = (target: { state: GardState; }, param: Param) => false | Transaction.Spec; /** Create an extension that adds a handler for the given {@link Command command}. */ function handler(command: Command, handler: Command): GardState.Extension; /** Bind a command with a parameter. The only thing you can do with a bound command is to {@link Command.dispatch dispatch} it. */ function bind(command: Command, param: Param): Command.Bound; /** Opaque type used for {@link Command.bind bound} commands. */ type Bound = { readonly tag: unique symbol; }; /** Apply a command to the given editor view. When passing a non-{@link Command.bind bound} command with a parameter, the parameter has to be passed as second argument. */ function dispatch(wg: Wordgard, command: Command | Command.Bound): boolean; function dispatch(wg: Wordgard, command: Command, param: Param): boolean; } declare namespace Menu { /** Editor menus are structured as trees, with item groups and submenus as internal nodes, and buttons and custom controls as leaf nodes. */ type Item = Group | Submenu | Button | CustomControl; namespace Item { /** Generic configuration fields supported by all menu items. */ interface Spec { /** When given and returning false, this item should be hidden from the menu. Should be used sparingly, to avoid the menu constantly flickering and changing size as the user is editing. */ select?: (state: GardState) => boolean; /** When given and returning false, this item is disabled, which means it looks faded and cannot be interacted with. */ enable?: (state: GardState) => boolean; /** By default, state predicates (`select`, `enable`, and `active`) are re-checked whenever the document or selection changes. If an item is sensitive to other aspects of the state, provide a test here that returns `true` for transactions that might affect the item state. */ updateFor?: (tr: Transaction) => boolean; /** The item's parent. See {@link Menu.resolve} for information on how menus are linked up. */ parent?: Group | Submenu; /** Determines the order of elements in the parent. Should be a number between 0 and 100. Defaults to 100. */ rank?: number; /** A description to associate with the item, used for hover tooltips and screen-reader text. If the item has a textual label, this will default to that label when not given. */ description?: PhraseSet.Ref | string; } /** Base class for menu items, storing the fields specified in {@link Menu.Item.Spec}. */ class Base { select: ((state: GardState) => boolean) | undefined; enable: ((state: GardState) => boolean) | undefined; updateFor: ((tr: Transaction) => boolean) | undefined; parent: Group | Submenu | undefined; rank: number; description: PhraseSet.Ref | string | undefined; /** Menu items can be used as editor extensions to include them in a configuration. */ extension: GardState.Extension; } /** The facet used to add menu items to a configuration. Used by the items' extensions to register them, and by menu implementations to find available items. */ const source: GardState.Facet; /** A resolved menu consists of buttons, custom controls, submenus, and spacers, which are represented by the string literal `"|"`. */ type Resolved = Button | CustomControl | "|" | Submenu.Resolved; } /** Labels are used by buttons and submenus to determine what they look like. They may either be textual (a string or reference to a phrase), or an icon, which is expressed an SVG path string that draws the icon inside a 100-by-100 space. The `directional` flag indicates that the icon should be mirrored vertically in a right-to-left editor. */ type Label = string | PhraseSet.Ref | { icon: string; directional?: boolean; }; /** A menu button runs a command when activated. See the {@link Menu.Button.Spec spec type} for the meaning of the fields. */ class Button extends Item.Base { /** The configuration object used to create this button. */ readonly spec: Button.Spec; label: Label; run: Command.Bound | Command; active: ((state: GardState) => boolean) | undefined; private constructor(); /** Define a menu button. */ static define(spec: Button.Spec): Button; } namespace Button { interface Spec extends Item.Spec { /** The command to run when the user activates the button. */ run: Command.Bound | Command; /** When this returns true, the button is highlighted as active. This can be used to show, for example, that a mark is active at the cursor, or that a block type matches the block around the current selection. Also used to automatically select a label for a {@link Menu.Submenu submenu}. */ active?: (state: GardState) => boolean; /** The label to show on this button. */ label: Label; } /** Creates a menu button that toggles an inline mark via {@link Menu.Button.toggleMark}, and is shown as active when either that mark is part of the marks associated with the current cursor, or the selection covers only content with that mark. */ function toggleMark(config: { mark: Mark; parent?: Menu.Group | Menu.Submenu; rank?: number; description?: PhraseSet.Ref; label: Menu.Label; }): Button; } /** Custom controls are similar to buttons, in that they can be part of the menu and receive focus through menu navigation, but they manage their own DOM. This can be used for elements like color pickers that should be displayed inside of the menu but support user interaction more complex than a button. */ class CustomControl extends Item.Base { /** The configuration object used to create this control. */ readonly spec: CustomControl.Spec; /** See {@link Menu.CustomControl.Spec.render}. */ render: (wg: Wordgard, done: () => void) => { dom: HTMLElement; focus?: HTMLElement; }; /** See {@link Menu.CustomControl.Spec.setEnabled}. */ setEnabled: ((dom: Element, enabled: boolean) => void) | undefined; private constructor(); /** Define a custom menu item. */ static define(spec: CustomControl.Spec): CustomControl; } namespace CustomControl { interface Spec extends Item.Spec { /** The function that renders the actual control. The `dom` property on the returned object will be displayed in the menu. If `focus` is provided, that is used as the element to put focus on. If not, `dom` is used. The control should call the `done` function when it decides it is closed or activated, so that any submenu above it knows to close, and focus can be moved back to the editor if appropriate. */ render: (wg: Wordgard, done: () => void) => { dom: HTMLElement; focus?: HTMLElement; }; /** If the control supports {@link Menu.Item.Spec.enable disabling}, this function will be called when the enabled state changes, and should update the control to show this. */ setEnabled?: (focus: Element, enabled: boolean) => void; } } /** Groups are used to organize sets of menu items together. The {@link Menu.Group.top top-level menu} is a group, but groups may appear at any level, so that items with similar roles can attach themselves to them in order to appear next to each other. See the {@link Menu.Group.Spec spec type} for the meaning of the class's fields. */ class Group { /** The configuration object used to create this group. */ readonly spec: Group.Spec; margin: boolean; parent: Group | Submenu | undefined; rank: number; content: readonly (Item | "...")[] | undefined; overflow: { at: number; wrap?: Submenu; } | undefined; /** Menu groups count as extensions. */ extension: GardState.Extension; private constructor(); /** Define a menu group. */ static define(spec?: Group.Spec): Group; /** Create a template for this group. */ template(...content: (Template | Item | "...")[]): Template; } namespace Group { /** Options used to configure a menu group. */ interface Spec { /** When set to true, leave a bit of space between this group and adjacent items. */ margin?: boolean; /** The group's parent item, if any. */ parent?: Group | Submenu; /** The group's rank within its parent. */ rank?: number; /** Default content for this group. Usually you don't need this, as you let parent links from the content items determine what goes in the group. See the {@link Menu.resolve menu resolution} system. */ content?: readonly (Item | "...")[]; /** If given when, during resolution, the group contains more than `at` items, wrap items `at - 1` and up in a submenu. You may optionally provide submenu object to specify the look of the submenu, or let it default to showing three vertical dots. */ overflow?: { at: number; wrap?: Submenu; }; } /** The top-level menu. When you don't provide a custom menu template, this is the starting point from which the menu will be resolved. Parent of most other groups. */ const top: Group; /** Editing commands. Holds items like the history undo/redo buttons. */ const commands: Group; /** Inline style items. Will, by default, contain buttons to create emphasized text, links, and so on. */ const inline: Group; /** Group for block-related items. Holds things like list toggles and text alignment. */ const block: Group; /** Group for inserting elements into the document, such as images or tables. */ const insert: Group; } /** A submenu is a menu item that, when activated, shows the menu items that are nested under it. See the {@link Menu.Submenu.spec spec type} for the meaning of the class fields. */ class Submenu extends Item.Base { /** The configuration object used to define this submenu. */ readonly spec: Submenu.Spec; label: Label | undefined; defaultLabel: Label | undefined; arrow: boolean; width: number | undefined; content: readonly (Item | "...")[] | undefined; private constructor(); /** Define a submenu. */ static define(spec: Submenu.Spec): Submenu; /** Create a template item for this submenu. */ template(...content: (Template | Item | "...")[]): Template; } namespace Submenu { /** The options that can be passed to a submenu. */ interface Spec extends Item.Spec { /** The label to show for the submenu. When not given, the submenu will look for the first {@link Menu.Button.Spec.active active} item in its children, and use that child's label, or fall back to `defaultLabel`. */ label?: Label; /** Fallback label when no regular label is given and there are no active children. */ defaultLabel?: Label; /** Whether to show an arrow on the submenu button to indicate that it can be expanded. Defaults to true. */ arrow?: boolean; /** A base with for the submenu button, in CSS `ch` units. Can be useful when the menu uses a dynamic textual label, and you want to prevent it from changing size as its label changes. */ width?: number; /** An optional default content. See the {@link Menu.resolve resolution system}. */ content?: readonly (Item | "...")[]; } /** A resolved submenu, part of the output of {@link Menu.resolve}. */ class Resolved { /** The submenu item. */ readonly item: Submenu; /** The items inside the submenu. */ readonly content: readonly Item.Resolved[]; private constructor(); } /** The submenu to select textblock type. Used to switch between, for example, regular paragraphs and headings */ const textblockStyle: Submenu; } /** Templates are used to explicitly choose (part of) your menu structure, rather than letting the resolution algorithm build one from your configuration. See {@link Menu.resolve}, {@link Menu.Group.template `Group.template`}, and {@link Menu.Submenu.template `Submenu.template`}. */ class Template { private tag; private constructor(); } /** Given a set of menu items and optionally a template, this function will resolve a concrete menu tree. To do this, it goes through the template (which defaults to just the {@link Menu.Group.top top group}), filling in open spaces (represented as the string literal `"..."`) with any items provided that have the group or submenu as parent. The idea is to combine a top-down (the template) and bottom-up (the items, which typically come from an editor {@link Menu.Item.source configuration}) in a way that allows the user to figure out a balance between manually specifying their menu and just using whatever is in the configuration. Items that are used explicitly in a template will not be used again implicitly. Items included in the `suppress` parameter will be ignored. When a submenu or group specifies default content, this will only be used when the template does not specify its own content for the item. */ function resolve(items: readonly Item[], template?: Template | readonly Template[], suppress?: readonly Item[]): readonly Item.Resolved[]; } /** This command handles text input. To selectively override the behavior of text input, provide a handler that, when the conditions that it requires apply, handles the input and returns true. `userEvent` will generally be one of `"input.type"`, `"input.type.compose"` (text inserted as part as a composition), or `"input.type.compose.start"` (initial text created by a started composition). */ declare const insertText: Command.Pure<{ from: number; to: number; insert: string; userEvent: string; }>; /** Command to insert a line break. The default handler will, if the schema defines a {@link Node.Role.LineBreak line break} node and the selection's parent node allows that, insert such a node. Otherwise, in nodes marked as {@link Plot.Spec.preserveWhitespace whitespace-preserving}, this will insert a line break. */ declare const insertLineBreak: Command.Pure; /** The command that handles enter presses. The default handler will, if the selection is not in an inline context, insert an empty default textblock in its position. Otherwise it first tries {@link enterInCode}, then {@link liftEmptyBlock}, and finally {@link splitTextblock}. */ declare const enter: Command.Pure; /** Delete the selection, or the unit after or before the selection. If that unit is the start or end of a textblock, this will try to join that textblock to the next one. Otherwise, if it is a character or leaf node, that is deleted. If none of that is possible and the cursor is in an empty textblock, this will delete the textblock. When deleting backward at the start of a list item that has a sibling before it, this command will try to join those list items. */ declare const deleteUnit: Command.Pure<"forward" | "backward">; /** Delete the selection, or the word next to it. Will behave like {@link deleteUnit}, except that, when deleting text, it will delete an entire word. */ declare const deleteWord: Command.Pure<"forward" | "backward">; /** Delete to the end or start of the line. Stops at line wrapping points. */ declare const deleteToLineEnd: Command<"forward" | "backward">; /** Command similar to forward `deleteToLineEnd` for macOS's Ctrl-k binding. If there's no content left on the line, this will delete the node (generally a line break) after the cursor of, if at end of textblock, join it to the next textblock. Content deleted by this command is added to a ‘kill buffer’, and can be reinserted with {@link yankKilled}. Multiple kill actions in sequence will accumulate the deleted content in the buffer. Doing anything else and then running this command again will reset the buffer to hold only the newly killed content. */ declare const killToLineEnd: Command; /** Insert the content killed by the most recent {@link killToLineEnd} command (or sequence thereof) at the cursor position. */ declare const yankKilled: Command; /** Delete the selection, or if that is empty, the line around the cursor. */ declare const deleteLine: Command; /** Swap the characters before and after the cursor. */ declare const transposeChars: Command.Pure; /** Set the type of the textblock(s) around the selection to the given tag. */ declare const setTextblockType: Command.Pure; /** Try to unwrap blocks around the selection. The second argument, if given, indicates what kind of wrapping plots may be removed. Returns null when no unwrapping is possible. */ declare const unwrapBlock: Command.Pure; /** Try to wrap selected textblocks in the given wrapper. Will return null if no wrapping is possible. */ declare const wrapBlock: Command.Pure; /** If the selection is in a block of the given type, unwap it. Otherwise, try to wrap the selected blocks in such a tag. */ declare const toggleBlock: Command.Pure; /** Toggle the given mark. If there is no selection, it is added to the cursor's active marks, or removed if it is already in there. Otherwise, if any selected content allows for the mark to be added, it is added. If not, remove the mark from the selection. */ declare const toggleMark: Command.Pure; /** Toggle emphasis. The default implementation uses the {@link Emphasis} mark. */ declare const toggleEmphasis: Command.Pure; /** Toggle strong emphasis. The default implementation uses the {@link Strong} mark. */ declare const toggleStrong: Command.Pure; /** Toggle underlining. The default implementation uses the {@link Underline} mark. */ declare const toggleUnderline: Command.Pure; /** Set the selected textblocks to the given alignment. `"left"` and `"right"` will be normalized to `"start"` or `"end"` depending on the editor's text direction. The default implementation uses the {@link Alignment} mark. */ declare const setAlignment: Command.Pure; /** Set the text direction for the selected textblocks. `null` will remove an explicit direction mark, defaulting the blocks back to the editor's base direction. The default implementation uses the {@link Direction} mark. */ declare const setDirection: Command.Pure; /** Toggle list wrapping with the given list tag for the selected blocks. */ declare const toggleList: Command.Pure; /** Returns true when all selected textblocks are wrapped in a list of the given type. */ declare const listIsActive: (listTag: Plot.Tag) => (state: GardState) => boolean; /** Move the selection head one unit (text cluster, atomic node, or node boundary) in the indicated direction. When `extend` is true, keep the selection anchor in place. The default handler moves visually through bidirectional text, so when going left, the motion will go back in left-to-right text, and forward in right-to-left text. */ declare const moveByUnit: Command.Pure<{ dir: "left" | "right" | "forward" | "backward"; extend?: boolean; }>; /** Move the selection head one word in the indicated direction. Keep the anchor in place if `extend` is true. The default handler moves visually. */ declare const moveByWord: Command.Pure<{ dir: "left" | "right"; extend?: boolean; }>; /** Move the selection head one line up or down. When `extend` is true, keep the anchor in place. */ declare const moveByLine: Command<{ dir: "up" | "down"; extend?: boolean; }>; /** Move the selection head one page up or down. Extend the selection when the `extend` flag is true. */ declare const moveByPage: Command<{ dir: "up" | "down"; extend?: boolean; }>; /** Move to the indicated side of the current line. Will stop at line wrap points. `"left"` and `"right"` will be interpreted based on the editor's text direction. */ declare const moveToLineSide: Command<{ dir: "left" | "right" | "forward" | "backward"; extend?: boolean; }>; /** Move to the start or end of the textblock that has the selection head. */ declare const moveToTextblockSide: Command<{ dir: "left" | "right" | "forward" | "backward"; extend?: boolean; }>; /** Move to the start or end of the document. */ declare const moveToDocSide: Command.Pure<{ side: "start" | "end"; extend?: boolean; }>; /** Select the entire document. */ declare const selectAll: Command.Pure; /** Undo an edit. Does not have a default handler, but a handler is added by the history extension. */ declare const undo: Command; /** Redo an edit. Does not have a default handler. */ declare const redo: Command; /** If the cursor is in an empty textblock that can be lifted out of a parent, return a transaction that does this. */ declare function liftEmptyBlock(state: GardState): Transaction.Spec | false; /** If the selection is in a {@link Plot.Spec.preserveWhitespace `preserveWhitespace`} textblock, replace the selected content with a {@link Schema.lineBreak line break}. If the selection additionally is a cursor on an otherwise empty line after a blank line, split or trunctate (if at end) the parent textblock and create a blank default text block in place of the empty line. */ declare function enterInCode(state: GardState): Transaction.Spec | false; /** Split the textblock at the cursor position, if any. If the textblock is the first child of a list item, also split that item, unless `splitListItem` is false. */ declare function splitTextblock(state: GardState, splitListItem?: boolean): Transaction.Spec | false; /** Returns a transaction that deletes the selection, or false if the selection is empty. */ declare function deleteSelection(state: GardState): Transaction.Spec | false; /** If the cursor is inside an empty plot, return a transaction that deletes the entire plot. `dir` determines which way the cursor moves after the deletion. */ declare function deleteEmptyPlot(state: GardState, dir?: -1 | 1): Transaction.Spec | false; /** If the cursor is at the start of a textblock that can be joined to a textblock before it, return a transaction to performs this join. */ declare function joinBackward(state: GardState): Transaction.Spec | false; /** If the cursor is at the start of a list item that has another item before it, return a transaction that joins those two items. */ declare function joinListItems(state: GardState): Transaction.Spec | false; /** If the cursor is at the end of a textblock that can be joined to the textblock after it, return a transaction that performs this join. */ declare function joinForward(state: GardState): Transaction.Spec | false; /** Create a transaction that deletes the atomic node or text cluster/word in front of the cursor, if possible. */ declare function deleteBackward(state: GardState, word?: boolean): Transaction.Spec | false; /** Return a transaction that deletes the element (text cluster or leaf node) after the cursor, if any. */ declare function deleteForward(state: GardState, word?: boolean): Transaction.Spec | false; /** Get an array of all textblocks that contain part of the selection. */ declare function selectedTextblocks(state: GardState): Pos.Plot[]; /** Remove all content in `node` that is not allowed to appear in `type`. */ declare function clearNonFitting(schema: Schema, node: Pos.Plot, type: Plot.Type): ChangeSet.Spec; /** Find a way to wrap the blocks betwen `from` and `to` in a node with the given tag. Returns precise start and end positions where a wrap is possible, or null if none is possible. */ declare function findWrappable(from: Pos, to: Pos, wrapper: Plot.Tag): { from: Pos; to: Pos; } | null; /** Wrap the given range in the given wrapper tag. The caller is responsible for verifying that this is actually a valid wrapping. It is recommended to use {@link findWrappable} for finding wrap positions in non-trivial situations. */ declare function wrapBlockRange(range: { from: Pos; to: Pos; }, wrapper: Plot.Tag): ChangeSet.Spec[]; /** Find the set of block nodes around the given range that match the predicate (if any) and can be unwrapped, meaning their content gets moved out to a parent node. */ declare function findUnwrappable(schema: Schema, from: Pos, to: Pos, query?: Node.Query): Pos.Plot[] | null; /** Unwrap the given block node, or the node's children between `from` and `to`. */ declare function doUnwrapBlock(block: Pos.Plot, from?: number, to?: number): ChangeSet.Spec; /** Join two adjacent (only separated by first a sequence of block end tokens and then a sequence of block open tokens) block plots. */ declare function joinBlocks(before: Pos.Plot, after: Pos.Plot): ChangeSet.Spec; /** Query whether the given range has any content to which the given mark or mark type could be added. */ declare function canAddMarkInRange(doc: Plot.Doc, from: number, to: number, mark: Mark | Mark.Type): boolean; /** Post-process the given transaction spec to check for any block boundaries touched by the changes in it that can be {@link Plot.Spec.autoJoin auto-joined}. If any are found, the spec is updated to perform those joins. */ declare function autoJoinBlocks(state: GardState, tr: Transaction.Spec): Transaction.Spec; export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, enterInCode, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, killToLineEnd, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange, yankKilled };