/** * Types for grid-editor's public API: the GridEditor class, its methods, its * settings, and its events and their payloads. * * The plugin contract - the handle a plugin's factory gets - is not typed * here; docs/plugins.md describes it. A plugin's factory is typed loosely as * a function of that handle. */ /** A breakpoint key. */ export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; /** A view: a breakpoint, or every one at once. */ export type View = Breakpoint | 'all'; /** A column's size: units, Bootstrap's `col` (`'equal'`) or `col-auto` (`'auto'`). */ export type ColumnSize = number | 'equal' | 'auto'; /** A row's columns-per-row layout: row-cols per breakpoint, and how many columns. */ export interface RowColsLayout { row_cols: Partial>; columns?: number; } /** What a row is made of: sizes, or a columns-per-row layout. */ export type RowLayout = ColumnSize[] | RowColsLayout; /** An element, or a selector for the first element it matches. */ export type Target = Element | string; /** Where a created node goes. The first one given wins; none leaves it detached. */ export interface Placement { appendTo?: Target; prependTo?: Target; insertAfter?: Target; insertBefore?: Target; } /** A host tool in a drawer: row_tools, col_tools and the rest. */ export interface HostTool { title?: string; className?: string; iconClass?: string; /** A click handler, or handlers by event name. `this` is the tool. */ on?: ((this: HTMLElement, event: Event) => void) | Record void>; } /** A preset class toggle on a settings panel. */ export interface PresetClass { label: string; cssClass: string; title?: string; } /** A content filter: run on init and deinit with the canvas. A string names a function on window. */ /** A section of the inline-style plugin's accordion. */ export type InlineStyleSectionKey = 'size' | 'spacing' | 'border' | 'background' | 'text' | 'typography' | 'display' | 'flex' | 'position' | 'custom'; /** A section on or off, or on with some of its properties and without its catalog. */ export type InlineStyleSection = boolean | { properties?: string[]; catalog?: boolean }; /** * A tabs container's variant: its strip's style, width and alignment, and * whether it is laid out vertically - always, or from a breakpoint up. */ export interface TabsOptions { variant?: 'tabs' | 'pills' | 'underline'; width?: 'natural' | 'fill' | 'justified'; /** Only with the natural width, and horizontal. */ align?: 'start' | 'center' | 'end'; vertical?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; } /** createContainer('tabs', …): how many tabs, their labels, and the variant. */ export interface CreateTabsOptions extends Placement, TabsOptions { tabs?: number; labels?: string[]; } /** * A carousel's options, which are Bootstrap's classes and attributes: arrows, * indicators, fade, dark theme, autoplay and its interval, and the pause, * wrap, keyboard and touch behaviours. All on or off unless said, as * Bootstrap has them. */ export interface CarouselOptions { /** Previous and next arrows. Default true. */ controls?: boolean; /** One indicator a slide. Default true. */ indicators?: boolean; /** Slides fade into each other instead of sliding. Default false. */ fade?: boolean; /** `data-bs-theme="dark"`. Default false. */ dark?: boolean; /** Autoplay: false, `'carousel'` on load, `'true'` after the first interaction. Default false. */ ride?: false | 'carousel' | 'true'; /** Milliseconds, 1000 or more, between slides; null is Bootstrap's own. Default null. */ interval?: number | null; /** Pauses while the pointer is over it. Default true. */ pause?: boolean; /** Goes from the last slide to the first. Default true. */ wrap?: boolean; /** Answers the arrow keys. Default true. */ keyboard?: boolean; /** Answers a swipe. Default true. */ touch?: boolean; /** How many slides a new one has, 1 or more. Default 2. */ slides?: number; } /** createContainer('carousel', …): the options, and how many slides. */ export interface CreateCarouselOptions extends Placement, CarouselOptions {} /** * The offcanvas plugin's options, the ones an offcanvas made new has, as the * offcanvas setting gives them and createContainer overrides them: where it * comes from, its backdrop, whether the page scrolls behind it, Esc, the * breakpoint from which it is content and not a panel, and the dark theme. */ export interface OffcanvasOptions { /** The edge it slides in from. Default `'start'`. */ placement?: 'start' | 'end' | 'top' | 'bottom'; /** A backdrop behind it: `true`, `false`, or `'static'`, which does not close it on a click. Default true. */ backdrop?: boolean | 'static'; /** The page can scroll while it is open. Default false. */ scroll?: boolean; /** Esc closes it. Default true. */ keyboard?: boolean; /** From this breakpoint up it is content in the page, and its trigger is hidden; null is a panel at every size. Default null. */ responsive?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | null; /** `data-bs-theme="dark"`. Default false. */ dark?: boolean; } /** createContainer('offcanvas', …): the options, its title and its trigger. */ export interface CreateOffcanvasOptions extends Placement, OffcanvasOptions { title?: string; /** false makes none, for a trigger the page writes itself. Default true. */ trigger?: boolean; trigger_label?: string; } /** What a file browser is told about the field it was opened from. */ export interface InlineStyleBrowseContext { node: HTMLElement; property: string; /** What the field shows: the url, or the value as it is when it is not one. */ value: string; } /** * The host's file browser. It answers with `done(url)`, or with a promise of * the url: the first answer counts. Nothing, `null` or an empty text leaves * the field as it was. */ export type InlineStyleBrowse = ( done: (url?: string | null) => void, context: InlineStyleBrowseContext, ) => void | Promise; /** The inline-style plugin's settings. */ export interface InlineStyleOptions { /** Every section is on unless it is turned off here. */ sections?: Partial>; /** The spacing section: the values offered, and what 0 to 5 come to - for the gaps too. */ spacing?: { values?: string[]; scale?: string[] }; /** The eye that hides and shows a node, the display family's: false leaves it out of the drawers. */ visibility?: { drawer?: boolean }; /** A file browser for the url fields: with it they have a button that opens it. */ browse?: InlineStyleBrowse; } /** What the sidebar shows while nothing is selected, instead of its message. */ export interface SidebarContent { /** Text, not html. Default the sidebar's own title. */ title?: string; body: Element; } export interface SidebarSettings { /** Called whenever the sidebar is left with nothing selected. Null gives the message. */ empty?: ((ge: GridEditor) => SidebarContent | null) | null; } export type CustomFilter = ((canvas: HTMLElement, isInit: boolean) => void) | string; /** The settings. Every one is optional. */ export interface GridEditorOptions { new_row_layouts?: RowLayout[]; row_classes?: PresetClass[]; col_classes?: PresetClass[]; element_classes?: PresetClass[]; container_classes?: PresetClass[]; pane_classes?: PresetClass[]; text_classes?: PresetClass[]; row_tools?: HostTool[]; col_tools?: HostTool[]; element_tools?: HostTool[]; container_tools?: HostTool[]; tab_tools?: HostTool[]; accordion_tools?: HostTool[]; /** Host tools on carousel slide drawers. */ carousel_tools?: HostTool[]; text_tools?: HostTool[]; drag_handle?: 'tool' | 'drawer'; toolbar_drag?: 'auto' | boolean; /** The add buttons that don't fit on the toolbar's line: behind a more button, or onto another line. Default 'menu'. */ toolbar_overflow?: 'menu' | 'wrap'; /** The drawers' tools that don't fit on one line: behind a more tool, or onto more lines. Default 'menu'. */ drawer_overflow?: 'menu' | 'wrap'; /** The add buttons in categories - rows, content, and a plugin's own - one shown at a time, chosen with tabs. Default false. */ toolbar_groups?: boolean; /** A click on any node with settings - a row, a column, a container, a text - makes it the target: where the toolbar's buttons add, and what the sidebar shows. Default false, and true with settings_panel 'sidebar', which throws if it is given false. */ active_target?: boolean; /** The plugins to use, of any kind; null is every one loaded. */ plugins?: string[] | null; /** The text editors offered, in order. */ content_types?: string[]; row_cols?: boolean; utilities?: Record; /** The inline-style plugin's sections, and the options of the utilities it carries. */ inline_style?: InlineStyleOptions; /** The tabs plugin: the variant of the tabs containers made new. */ tabs?: TabsOptions; /** The carousel plugin: the options of the carousels made new. */ carousel?: CarouselOptions; /** The offcanvas plugin: the options of the offcanvases made new. */ offcanvas?: OffcanvasOptions; elements?: { enabled?: boolean | 'auto'; selector?: string; auto?: boolean; types?: ElementType[] }; /** The autosave plugin: what is edited, kept in the browser's storage as it changes. */ autosave?: AutosaveOptions; /** The clipboard plugin. `shortcuts`: Ctrl+C and Ctrl+V on the active target; default active_target. */ clipboard?: { shortcuts?: boolean }; /** The history plugin: undo and redo. */ history?: HistoryOptions; custom_filter?: CustomFilter | CustomFilter[] | ''; valid_col_sizes?: ColumnSize[]; valid_col_offsets?: number[]; add_column?: { size?: ColumnSize | null; picker?: boolean; delay?: number }; layout_modes?: View[]; default_view?: View; /** enabled is the handle on the column's edge, tools the narrower and wider tools in its drawer. */ resize?: { enabled?: boolean; tools?: boolean; handles?: string; balance?: 'next' | false }; /** tools is the indent tools in a column's drawer. */ indent?: { tools?: boolean }; /** A textarea whose html the canvas starts with. */ source_textarea?: Target | ''; edit_source?: boolean; locale?: string; locale_strings?: Record; callbacks?: Partial; confirm_delete?: boolean; /** Where a node's settings open. 'sidebar' is a panel that stays on the right while editing, showing the active target's; one editor per page has it. It turns active_target on, and the constructor throws a TypeError if active_target is false. */ settings_panel?: 'offcanvas' | 'popover' | 'modal' | 'inline' | 'sidebar'; /** The 'sidebar' panel. */ sidebar?: SidebarSettings; drag?: { delay?: number; touch_delay?: number; threshold?: number; animation?: number; scroll?: boolean }; tinymce?: { config?: Record }; ckeditor?: { config?: Record }; summernote?: { config?: Record }; [plugin: string]: unknown; } /** The autosave plugin's options. */ export interface AutosaveOptions { /** Saving from the start; the methods turn it on and off. Default true. */ enabled?: boolean; /** localStorage or sessionStorage. Default 'local'. */ storage?: 'local' | 'session'; /** Where the draft is kept. Default 'grideditor.autosave:' + the page's path + '#' + the canvas's id. One per document on a page that loads several. */ key?: string | null; /** Milliseconds after the last change. Default 1000. */ delay?: number; /** Milliseconds a draft is offered for; null, the default, for ever. */ maxAge?: number | null; } /** The history plugin's options. */ export interface HistoryOptions { /** Ctrl+Z, and Ctrl+Y or Ctrl+Shift+Z (⌘ on a Mac). Default true. */ shortcuts?: boolean; /** The undo and redo buttons on the right of the toolbar. Default true. */ buttons?: boolean; /** The steps that can be undone. Default 50. */ max?: number; /** Milliseconds after the last change that make a step. Default 500. */ delay?: number; } /** A draft the autosave plugin kept. */ export interface AutosaveDraft { html: string; /** When, as an ISO 8601 date. */ savedAt: string; } export interface GetHtmlOptions { /** Read the canvas without it leaving editing: open texts and settings panels stay open. */ keepEditing?: boolean; } export interface AutosavePayload { canvas: HTMLElement; html: string; savedAt: string; source: 'change' | 'pagehide' | 'api' | 'destroy'; } export interface RestoreDraftPayload { canvas: HTMLElement; html: string; savedAt: string; } export interface AutosaveErrorPayload { canvas: HTMLElement; error: unknown; } export interface HistoryPayload { canvas: HTMLElement; /** The markup the canvas went to. */ html: string; source: 'shortcut' | 'tool' | 'api'; } export interface HistoryChangePayload { canvas: HTMLElement; canUndo: boolean; canRedo: boolean; } /** Where a node was, or went: its parent and its index among the blocks there. */ export interface Position { parent: HTMLElement; index: number; } /** What a breakpoint said before the all view's write took it off. */ export interface Cleared { breakpoint: Breakpoint; value: string | number; } /** What every notification carries. */ export interface Payload { /** What the node is: row, column, text, plain, element, tab, accordion-item, carousel-item, a container's type… */ kind: string; node: HTMLElement; /** Where the node is going, or coming from on a delete; null while it is detached. */ parent: HTMLElement | null; canvas: HTMLElement; breakpoint: View; source: 'api' | 'tool' | 'dragdrop' | 'paste' | 'cut' | 'panel' | 'shortcut' | string; /** A pane inside a container. */ container?: HTMLElement; } /** A node was copied, or cut: with the clipboard plugin, `source` is 'tool' or 'shortcut', and `cut` says whether the node is leaving. */ export interface CopyPayload extends Payload { cut: boolean; } export interface MovePayload extends Payload { from: Position; to?: Position; } export interface ResizePayload extends Payload { from: ColumnSize | null; to: ColumnSize | null; cleared?: Cleared[]; } export interface IndentPayload extends Payload { from: number; to: number; cleared?: Cleared[]; } export interface UtilityPayload extends Payload { family: string; from: string | null; to: string | null; tiers: Breakpoint[]; cleared: Cleared[]; } export interface ConvertPayload extends Payload { from: 'plain'; to: string; } export interface EditHtmlPayload extends Omit { node: HTMLElement | null; /** Every element written, which may be several. */ nodes?: HTMLElement[]; from: string; to: string; } export interface PopupOrphanPayload extends Payload { missing: string; } export interface OffcanvasOrphanPayload extends Payload { missing: string; } export interface ViewChangePayload { canvas: HTMLElement; breakpoint: View; from: View; to: View; } /** The active target changed: the node the toolbar adds to and the sidebar shows - any with settings - or null for the canvas. */ export interface TargetChangePayload { canvas: HTMLElement; target: HTMLElement | null; from: HTMLElement | null; } /** The preview button was pressed (true) or let go (false). */ export interface PreviewChangePayload { canvas: HTMLElement; previewing: boolean; } /** The events, by name, with their payloads. Adding fires a specific name and the generic one. */ export interface GridEditorEventMap { 'grideditor:before-add': Payload; 'grideditor:after-add': Payload; 'grideditor:before-add-row': Payload; 'grideditor:after-add-row': Payload; 'grideditor:before-add-column': Payload; 'grideditor:after-add-column': Payload; 'grideditor:before-add-text': Payload; 'grideditor:after-add-text': Payload; 'grideditor:before-add-element': Payload; 'grideditor:after-add-element': Payload; 'grideditor:before-add-container': Payload; 'grideditor:after-add-container': Payload; 'grideditor:before-add-tab': Payload; 'grideditor:after-add-tab': Payload; 'grideditor:before-add-accordion-item': Payload; 'grideditor:after-add-accordion-item': Payload; 'grideditor:before-add-carousel-item': Payload; 'grideditor:after-add-carousel-item': Payload; 'grideditor:before-add-section': Payload; 'grideditor:after-add-section': Payload; 'grideditor:before-delete': Payload; 'grideditor:after-delete': Payload; 'grideditor:before-move': MovePayload; 'grideditor:after-move': MovePayload; 'grideditor:before-resize': ResizePayload; 'grideditor:after-resize': ResizePayload; 'grideditor:before-indent': IndentPayload; 'grideditor:after-indent': IndentPayload; 'grideditor:before-utility': UtilityPayload; 'grideditor:after-utility': UtilityPayload; 'grideditor:before-convert': ConvertPayload; 'grideditor:after-convert': ConvertPayload; 'grideditor:before-edit-html': EditHtmlPayload; 'grideditor:after-edit-html': EditHtmlPayload; 'grideditor:after-copy': CopyPayload; 'grideditor:popup-orphan': PopupOrphanPayload; 'grideditor:offcanvas-orphan': OffcanvasOrphanPayload; 'grideditor:view-change': ViewChangePayload; 'grideditor:preview-change': PreviewChangePayload; 'grideditor:target-change': TargetChangePayload; 'grideditor:after-autosave': AutosavePayload; 'grideditor:after-restore-draft': RestoreDraftPayload; 'grideditor:autosave-error': AutosaveErrorPayload; 'grideditor:after-undo': HistoryPayload; 'grideditor:after-redo': HistoryPayload; 'grideditor:history-change': HistoryChangePayload; } /** An event the editor dispatches on its canvas: a CustomEvent whose detail is the payload. */ export type GridEditorEvent = CustomEvent; /** 'grideditor:before-add-row' is the callback before_add_row. */ type CallbackName = K extends `grideditor:${infer Name}` ? Snake : never; type Snake = S extends `${infer Head}-${infer Tail}` ? `${Head}_${Snake}` : S; /** The callbacks setting: the events by another route. Returning false cancels a before-*. */ export type GridEditorCallbacks = { [K in keyof GridEditorEventMap as CallbackName]: (payload: GridEditorEventMap[K]) => boolean | void; }; /** A plugin's factory: called once per editor with the handle in docs/plugins.md. */ /** An element the toolbar offers, from elements.types. */ export interface ElementType { /** What it is: its data-ge-element. */ type: string; /** Its markup: html, or a function called for each one made that returns html or a node. */ html: string | (() => string | Node); /** Its button's label, as it is. */ label?: string; /** Its button's label, a locale key: wins over label, and follows setLocale. */ labelKey?: string; /** The button shows this icon alone, with the label as its title. */ iconClass?: string; /** Its toolbar_groups category. Default 'elements'. */ group?: string; } export type PluginFactory = ((ge: any) => Record) & { always?: boolean }; export interface CreateColumnOptions extends Placement { offset?: number; /** Html, or a node: a text of the first editor offered, or plain content with none. */ content?: string | Node; } export interface CreateTextOptions extends Placement { content?: string | Node; } export interface CreateElementOptions extends Placement { type?: string; label?: string; } export interface CreateSectionOptions extends Placement { width?: 'fixed' | 'fluid' | Breakpoint; rows?: RowLayout[]; } export interface PaneOptions { label?: string; [option: string]: unknown; } /** An editor on a canvas element. */ export declare class GridEditor { /** * An editor on `target`. An element carries one at most: asked for a * second, this gives back the first, with its options, and warns once. * Throws a TypeError when `target` is neither an element nor a selector * that matches one. */ constructor(target: Target, options?: GridEditorOptions); /** The same as `new GridEditor(target, options)`. */ static create(target: Target, options?: GridEditorOptions): GridEditor; /** The editor on `target`, or null. */ static get(target: Target | null | undefined): GridEditor | null; static containers: Record; static features: Record; static utilities: Record; static texts: Record; static locales: Record>; /** A string from the locale `settings.locale` names, English when it has none. */ static t(settings: { locale?: string; locale_strings?: Record }, key: string, params?: Record): string; /** SortableJS, for a page that imports it rather than loading it as window.Sortable. */ static Sortable: unknown; /** Bootstrap's javascript (only its Modal is used), for a page with no window.bootstrap. */ static bootstrap: { Modal?: unknown } | null; static version: string; /** The canvas element. */ readonly canvas: HTMLElement; /** A read-only copy of the settings the editor runs with. */ readonly settings: Readonly; /** The html to save: no drawers, no editor classes, no inline styles. keepEditing reads it without the canvas leaving editing. */ getHtml(options?: GetHtmlOptions): string; /** getHtml without grid-editor's marking, for publishing. It cannot be edited again as it was. */ getPlainHtml(options?: GetHtmlOptions): string; /** Run the editing pass over the canvas again, after markup was put in. */ init(): this; /** Take the editing furniture off, leaving the markup. */ deinit(): this; /** deinit(), then init(). */ reset(): this; /** Take the editor off: the markup stays, everything else goes. */ destroy(): this; changeView(view: View): this; /** The node the toolbar adds to, and the sidebar shows, or null: any node with settings, a row, a column, a container, a text. Null without active_target, which settings_panel 'sidebar' turns on. */ getActiveTarget(): HTMLElement | null; /** Make a node with settings, or the first a selector matches, the one the toolbar adds to; null for none. */ setActiveTarget(target: HTMLElement | string | null): this; getView(): View; setLocale(code: string): this; createRow(layout?: RowLayout, options?: Placement): HTMLElement | null; createColumn(size?: ColumnSize | null, options?: CreateColumnOptions): HTMLElement | null; createText(type?: string, options?: CreateTextOptions): HTMLElement | null; createText(options: CreateTextOptions): HTMLElement | null; createElement(content: string | Node | ArrayLike, options?: CreateElementOptions): HTMLElement | null; createSection(options?: CreateSectionOptions): HTMLElement | null; createContainer(type: 'tabs', options?: CreateTabsOptions): HTMLElement | null; createContainer(type: 'carousel', options?: CreateCarouselOptions): HTMLElement | null; createContainer(type: 'offcanvas', options?: CreateOffcanvasOptions): HTMLElement | null; createContainer(type: string, options?: Placement & Record): HTMLElement | null; addTab(container: Target, options?: PaneOptions): HTMLElement | null; addAccordionItem(container: Target, options?: PaneOptions): HTMLElement | null; /** A pane added to a container of any type that has panes; a carousel's takes `interval`. Null for one that has none. */ addPane(container: Target, options?: PaneOptions & { interval?: number }): HTMLElement | null; /** A utility's value on a node in a view (the current one by default), or null. */ getUtility(node: Target, family: string, view?: View): string | null; /** Write one through the events; null is inherit. False when canceled or nothing changed. */ setUtility(node: Target, family: string, value: string | number | null, view?: View): boolean; /** The autosave plugin's, when it is loaded: false when the browser gives it no storage. */ enableAutosave?(): boolean; disableAutosave?(): boolean; /** Save now, if it changed: true when it was written. */ saveDraft?(): boolean; getDraft?(): AutosaveDraft | null; clearDraft?(): boolean; /** The history plugin's, when it is loaded: true when it went to another step. */ undo?(): boolean; redo?(): boolean; /** Whether there is a step to go back to, a change not yet a step counting. */ canUndo?(): boolean; canRedo?(): boolean; /** What there is now becomes the first step, with nothing before or after it. */ clearHistory?(): boolean; } export default GridEditor; declare global { interface Window { GridEditor: typeof GridEditor; } interface HTMLElementEventMap extends GridEditorEventMap_ {} } /** The events on any element, so canvas.addEventListener('grideditor:...') is typed. */ type GridEditorEventMap_ = { [K in keyof GridEditorEventMap]: CustomEvent; };