/** * attempting (at least partial) rewrite of editor. better support of * external editors, and a little cleaner behavior for context highlighting. * * I didn't want to handle spellcheck, but we're setting a flag reflecting * whether it's a formula; so we probably should do it. * * we are specifically NOT handling the following: * * - enter key * * subclasses or callers can handle those. * * --- * * NOTE: external editors might run in a different realm (in the js meaning * of that term). so we don't necessarily want to use the spreadsheet's context * for everything. this is going to be extremely confusing. * */ import { Area, type ICellAddress, type Cell } from '../../../treb-base-types/src/index'; import type { ParseResult } from '../../../treb-parser/src/index'; import { Parser } from '../../../treb-parser/src/index'; import type { DataModel, ViewModel } from '../../../treb-data-model/src/index'; import type { Autocomplete, AutocompleteResult } from './autocomplete'; import { EventSource } from '../../../treb-utils/src/index'; import { type AutocompleteExecResult, AutocompleteMatcher } from './autocomplete_matcher'; export interface UpdateTextOptions { rewrite_addresses: boolean; validate_addresses: boolean; canonicalize_functions: boolean; format_only: boolean; toll_events: boolean; } type GenericEventListener = (event: Event) => unknown; /** event on commit, either enter or tab */ export interface FormulaEditorCommitEvent { type: 'commit'; value?: string; /** * true if commiting an array. note that if the cell _is_ an array, * and you commit as !array, that should be an error. */ array?: boolean; /** * for the formula editor, the event won't bubble so we can't handle * it with the normal event handler -- so use the passed event to */ event?: KeyboardEvent; } /** event on discard -- escape */ export interface FormulaEditorDiscardEvent { type: 'discard'; } /** event on end select state, reset selection */ export interface FormulaEditorEndSelectionEvent { type: 'end-selection'; } /** event on text update: need to update sheet dependencies */ export interface FormulaEditorUpdateEvent { type: 'update'; text?: string; cell?: Cell; dependencies?: Area[]; } export interface StartEditingEvent { type: 'start-editing'; editor?: string; } export interface StopEditingEvent { type: 'stop-editing'; editor?: string; } /** discriminated union */ export type FormulaEditorEvent = StopEditingEvent | StartEditingEvent | FormulaEditorUpdateEvent | FormulaEditorCommitEvent | FormulaEditorDiscardEvent | FormulaEditorEndSelectionEvent; export interface NodeDescriptor { /** the contenteditable node */ node: HTMLElement; /** list of references in this node */ references?: Area[]; /** listeners we attached, so we can clean up */ listeners?: Map, GenericEventListener>; /** last-known text, to avoid unecessary styling */ formatted_text?: string; /** check (not sure if we still need this) length of html content */ check?: number; } export declare class Editor extends EventSource { model: DataModel; view: ViewModel; autocomplete?: Autocomplete | undefined; protected static readonly FormulaChars: string[]; /** * the current edit cell. in the event we're editing a merged or * array cell, this might be different than the actual target address. */ active_cell?: Cell; /** matcher. passed in by owner. should move to constructor arguments */ autocomplete_matcher?: AutocompleteMatcher; /** the containing node, used for layout */ protected container_node?: HTMLElement; /** * this is the node we are currently editing. it's possible we are not * editing any cell, but just formatting. this one sends events and is * the target for inserting addresses. */ protected active_editor?: NodeDescriptor; /** * all nodes that are involved with this editor. we format all of them, * and if you edit one we might switch the colors in the others as * references change. */ protected nodes: NodeDescriptor[]; /** * address of cell we're editing, if we're editing a cell */ target_address?: ICellAddress; /** * assume we're editing a formula. this is for the external editor. * if we switch the formula bar to inherit from this class, it should * be false. */ protected assume_formula: boolean; /** * this flag indicates we're editing a formula, which starts with `=`. */ protected text_formula: boolean; /** * this has changed -- we don't have an internal field. instead we'll * check when called. it's slightly more expensive but should be * relatively rare. */ get selecting(): boolean; /** internal. not sure why we have a shadow property. */ protected composite_dependencies: Area[]; /** accessor */ get dependencies(): Area[]; /** reference to model parser */ parser: Parser; constructor(model: DataModel, view: ViewModel, autocomplete?: Autocomplete | undefined); FocusEditor(): void; /** * add an event listener to the node. these are stored so we can remove * them later if the node is disconnected. * * listeners moved to node descriptors so we can have multiple sets. */ protected RegisterListener(descriptor: NodeDescriptor, key: K, handler: (event: HTMLElementEventMap[K]) => unknown): void; protected SelectAll(node: HTMLElement): void; protected SetCaret(start: { node: ChildNode; offset: number; }, end?: { node: ChildNode; offset: number; }): void; /** * */ InsertReference(reference: string, toggle_reference_type?: boolean, typed_reference_list?: string[]): "" | undefined; /** * this method does three things: * * (1) builds a flat list of references across all nodes * (2) applies colors to formatted references * (3) sends an event (if necessary, or forced) * * that's fine, but it needs a new name. * */ protected UpdateColors(force_event?: boolean, toll_update?: boolean): void; /** * get a list of all references in the text (actually in the parse result, * since we have that). stores the list in the node descriptor (and in * the node dataset). * * returns a list of the references in parse result mapped to normalized * address labels. those can be used to identify identical references when * we highlight later. * * @param parse_result * @returns */ protected UpdateDependencies(descriptor: NodeDescriptor, parse_result: ParseResult): Map; /** * store the set of references, and store in the node dataset for * external clients. * * @param descriptor * @param references * @param options */ protected UpdateReferences(descriptor: NodeDescriptor, references?: Area[]): void; /** * reformat text to highlight, which involves tinkering with * node structure. we're probably doing this more than necessary; * we might consider editing the existing structure, rather than * throwing it away every time. * */ protected UpdateText(descriptor: NodeDescriptor, options?: Partial): void; protected NodeAtIndex(index: number): Node | undefined; protected AcceptAutocomplete(ac_result: AutocompleteResult): void; /** called when there's AC data to display (or tooltip) */ protected Autocomplete(data: AutocompleteExecResult, target_node?: Node): void; /** * this version gets substrings to both selection points. * * @param node * @returns [substring to start of selection, substring to end of selection] */ protected SubstringToCaret2(node: HTMLElement, force?: boolean): [string, string]; } export {};