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 };