import { Cells, type CellStyle, type Color, Area, DOMContext } from '../../treb-base-types/src/index'; import type { TextPart, Cell, ICellAddress, CellValue, ImportedSheetData, IArea, Table, TableTheme, Theme } from '../../treb-base-types/src/index'; import type { FreezePane, SerializedSheet, ScrollOffset } from './sheet_types'; import type { SerializeOptions } from './serialize_options'; import type { GridSelection } from './sheet_selection'; import { Annotation } from './annotation'; import type { ConditionalFormatList } from './conditional_format'; import type { DataValidation } from './data-validation'; export declare class Sheet { static base_id: number; static readonly default_sheet_name = "Sheet1"; /** * adding verbose flag so we can figure out who is publishing * (and stop -- part of the ExecCommand switchover) */ /** * in the old model, we had a concept of "default" style properties. we then * used that object for theming: we would set default properties when the theme * changed. * * the problem is that if there are multiple instances on a single page, with * different themes, they would clash. * * so the new concept is to have a default property set per instance, managed * by the grid instance. any sheets that are loaded in/created by grid will * get a reference to that property set, and grid can update it as desired. * * because it's a reference, it should be constant. * FIXME: move to model... */ readonly default_style_properties: CellStyle; annotations: Annotation[]; freeze: FreezePane; /** testing */ visible: boolean; /** standard width (FIXME: static?) */ default_column_width: number; /** standard height (FIXME: static?) */ default_row_height: number; /** cells data */ readonly cells: Cells; /** * selection. moved to sheet to preserve selections in multiple sheets. * this instance should just be used to populate the actual selection, * not used as a reference. */ selection: GridSelection; /** * cache scroll offset for flipping between sheets. should this be * persisted? (...) */ scroll_offset: ScrollOffset; /** * named ranges: name -> area * FIXME: this needs to move to an outer container, otherwise we * may get conflicts w/ multiple sheets. unless we want to allow that... */ name: string; tab_color?: Color; background_image?: string; protected _image: HTMLImageElement | undefined; /** * set this flag when we need to update conditional formats even * if they are not dirty (generally when one is deleted) */ flush_conditional_formats: boolean; get image(): HTMLImageElement | undefined; /** * @internal */ conditional_formats: ConditionalFormatList; /** * @internal */ data_validation: DataValidation[]; /** * @internal * * testing, not serialized atm */ outline: number[] | undefined; /** internal ID */ private id_; private row_height_map; private column_width_; /** * optionally, custom row headers (instead of 1...2...3...) * FIXME: should maybe be a function instead? * FIXME: why is this any type? just sloppiness? */ private row_headers; /** * optionally, custom column headers (instead of A...B...C...) * FIXME: should maybe be a function instead? * FIXME: why is this any type? just sloppiness? */ private column_headers; /** size of header */ private row_header_width; /** size of header */ private column_header_height; private style_map; private style_json_map; private sheet_style; private row_styles; private column_styles; private row_pattern; private cell_style; /** * applied conditional formats are stored them in this array; * they will be stacked on top of cell style when rendering. * conditional formats have top priority. [FIXME: what about tables?] */ private conditional_format_cache; /** * this is a list of cells we formatted on the last pass, so we can * compare when applying conditional formats . * * update: using areas */ private conditional_format_checklist; get header_offset(): { x: number; y: number; }; /** accessor: now just a wrapper for the call on cells */ get rows(): number; /** accessor: now just a wrapper for the call on cells */ get columns(): number; get id(): number; set id(id: number); /** * constructor is now protected. use a factory method (Blank or FromJSON). */ protected constructor(theme_style_properties: CellStyle); static Reset(): void; /** * factory method creates a new sheet */ static Blank(style_defaults: CellStyle, name?: string, rows?: number, columns?: number, theme?: Theme): Sheet; /** * update old-style alignment constants to the new symbolic values. * updates in place. */ static UpdateStyle(properties: CellStyle): void; /** * deserialize json representation. returns new instance or updates * passed instance. * * FIXME: why not make this an instance method, always call on new instance? * * @param hints UpdateHints supports partial deserialization/replacement * if we know there are only minor changes (as part of undo/redo, probably) */ static FromJSON(json: string | Partial, style_defaults: CellStyle, sheet?: Sheet): Sheet; /** add a data validation. */ AddValidation(validation: DataValidation): void; /** * remove validations from area. must be an exact match (FIXME). * if there are multiple areas, only remove the matching area. */ RemoveValidations(area: IArea): void; /** return data validation(s) that apply to a given address */ GetValidation(address: ICellAddress): DataValidation[]; Activate(DOM: DOMContext): void; MergeCells(area: Area): void; UnmergeCells(area: Area): void; /** * FIXME: this is called in the ctor, which made sense when sheets * were more ephemeral. now that we update a single instance, rather * than create new instances, we lose this behavior. we should call * this when we change sheet style. * * actually this should just move to theme, no? as long as sheet has * a reference to theme. for headless instances that would just use * theme defaults, which should be appropriate. * * I guess the original idea was that sheet style might be used to * base row height on sheet font size? not sure if that's how it actually * plays out, since this is only called in the ctor (or equivalent) or when * theme is updated (but not when sheet style is updated). might need some * thought. * */ UpdateDefaultRowHeight(theme: Theme, scale?: number): void; /** * deprecated (or give me a reason to keep it) * KEEP IT: just maintain flexibility, it has very low cost */ SetRowHeaders(headers: CellValue[]): void; /** * deprecated (or give me a reason to keep it) * KEEP IT: just maintain flexibility, it has very low cost */ SetColumnHeaders(headers: CellValue[]): void; /** * deprecated * KEEP IT: just maintain flexibility, it has very low cost */ RowHeader(row: number): string | number; /** * deprecated * KEEP IT: just maintain flexibility, it has very low cost * (we did drop the multiple rows, though) */ ColumnHeader(column: number): string; GetRowHeight(row: number): number; SetRowHeight(row: number, height: number): number; GetColumnWidth(column: number): number; SetColumnWidth(column: number, width: number): number; /** * returns set of properties in B that differ from A. returns * property values from B. * * this is the function I could never get to work inline for * CellStyle -- not sure why it works better with a generic * function (although the partial here is new, so maybe it's that?) * * seems to be related to * https://github.com/microsoft/TypeScript/pull/30769 * */ Delta(A: T, B: T): Partial; /** * updates cell styles. flushes cached style. * * @param delta merge with existing properties (we will win conflicts) * @param inline this is part of another operation, don't do any undo/state updates */ UpdateCellStyle(address: ICellAddress, properties: CellStyle, delta?: boolean): void; /** * invalidate sets the "render dirty" flag on cells, whether there * is any change or not. we are currently using it to force rendering * when border/background changes, and we need to handle bleed into * neighboring cells. */ Invalidate(area: Area): void; /** * * @param area * @param style * @param delta * @param render LEGACY PARAMETER NOT USED */ UpdateAreaStyle(area?: Area, style?: CellStyle, delta?: boolean): void; /** * checks if the given cell has been assigned a specific style, either for * the cell itself, or for row and column. */ HasCellStyle(address: ICellAddress): boolean; /** * returns the next non-hidden column. so if you are column C (2) and columns * D, E, and F are hidden, then it will return 6 (G). */ NextVisibleColumn(column: number): number; /** * @see NextVisibleColumn * because this one goes left, it may return -1 meaning you are at the left edge */ PreviousVisibleColumn(column: number): number; /** * @see NextVisibleColumn */ NextVisibleRow(row: number): number; /** * @see PreviousVisibleColumn */ PreviousVisibleRow(row: number): number; /** * if this cell is part of a table, get row information -- is this * an alternate row, is it the header, is it the last (visible) row * * @param table * @param row * @returns */ TableRow(table: Table, row: number): { alternate?: boolean; header?: boolean; last?: boolean; totals?: boolean; }; /** * returns style properties for cells surrounding this cell, * mapped like a number pad: * * +---+---+---+ * | 7 | 8 | 9 | * +---+---+---+ * | 4 | X | 6 | * +---+---+---+ * | 1 | 2 | 3 | * +---+---+---+ * * presuming you already have X (5). this is called by renderer, we * move it here so we can inline the next/previous loops. * */ SurroundingStyle(address: ICellAddress, table?: TableTheme): CellStyle[]; /** * get style only. as noted in the comment to `CellData` there used to be * no case where this was useful without calculated value as well; but we * now have a case: fixing borders by checking neighboring cells. (testing). * * switching from null to undefined as "missing" type * * UPDATE: this is a convenient place to do table formatting. table * formatting is complicated because it's variable; it depends on row * visibility so we can't cache it. this is a good spot because we're * already calling this function when doing border rendering; we can call * it separately, if necessary, when rendering cells. * * table formats are applied on top of cell formats, after compositing, * and we don't preserve the style. * */ CellStyleData(address: ICellAddress, default_table_theme?: TableTheme): CellStyle | undefined; /** * accessor to get cell style without row pattern -- for cut/copy * @param address */ GetCopyStyle(address: ICellAddress): CellStyle; /** * wrapper for getting all relevant render data. * TODO: merge in "FormattedValue". restructure data so we don't have * two caches (formatted and calculated). * * NOTE: we removed "GetCellStyle" in favor of this function. the rationale * is that there are no reasonable cases where someone looks up the style * without that being a next step to (or in reasonable proximity to) * rendering. so it's reasonable to call this function even if it's in * advance of rendering. * * NOTE: that applies to the "GetCellFormula" and "GetCellValue" functions * as well -- so remove those too. * * NOTE: actually GetCellFormula resolves array formulae, so maybe not -- * or the caller needs to check. * */ CellData(address: ICellAddress): Cell; /** * format number using passed format; gets the actual format object * and calls method. returns a string or array of text parts * (@see treb-format). */ FormatNumber(value: CellValue, format?: string): string | TextPart[]; /** * the only place this is called is in a method that shows/hides headers; * it sets the size either to 1 (hidden) or undefined, which uses the * defaults here. that suggests we should have a show/hide method instead. * * @param row_header_width * @param column_header_height */ SetHeaderSize(row_header_width?: number, column_header_height?: number): void; /** returns the style properties for a given style index */ GetStyle(index: number): CellStyle; /** * * @param before_row insert before * @param count number to insert */ InsertRows(before_row?: number, count?: number): boolean; /** * see InsertRow for details */ InsertColumns(before_column?: number, count?: number): boolean; /** clear cells in area */ ClearArea(area: Area): void; SetAreaValues2(area: Area, values: CellValue | CellValue[][]): void; /** * set the area as an array formula, based in the top-left cell */ SetArrayValue(area: Area, value: CellValue): void; /** * set a single value in a single cell */ SetCellValue(address: ICellAddress, value: CellValue): void; /** * FIXME: does not need to be in sheet * * @param headers_only - only return tables if the cell is in the * header (first) row. useful if you only want to worry about headers. */ TablesFromArea(area: IArea | ICellAddress, headers_only?: boolean): Table[]; /** * returns the area bounding actual content * (i.e. flattening "entire row/column/sheet") * * FIXME: this does not clamp to actual cells... why not? * FIXME: so now we are (optionally) clamping end; should clamp start, too * * @param clamp -- new parameter will optionally clamp to actual sheet size */ RealArea(area: Area, clamp?: boolean): Area; /** specialization */ GetCellStyle(area: ICellAddress, apply_theme?: boolean): CellStyle; /** specialization */ GetCellStyle(area: IArea, apply_theme?: boolean): CellStyle[][]; /** extra specialization */ GetCellStyle(area: K, apply_theme?: boolean): CellStyle | CellStyle[][]; FormattedCellValue(address: ICellAddress): CellValue; GetFormattedRange(from: ICellAddress, to?: ICellAddress): CellValue | CellValue[][]; /** * get all styles used in the sheet. this is used to populate color * and number format lists in the toolbar. we used to just serialize * the document and use that, but that's absurdly wasteful. for this * application we don't even need composites. * * although, this is a bit dangerous because you could (in theory) * modify the results in place. so maybe we should either duplicate or * just return the requested data... */ NumberFormatsAndColors(color_map: Record, number_format_map: Record): void; CompressCellStyles(data: number[][]): { row: number; column: number; ref: number; rows?: number; }[]; /** * generates serializable object. given the new data semantics this * has to change a bit. here is what we are storing: * * all style data (sheet, row/column, alternate and cell) * raw value for cell * array head for arrays * row height and column width arrays * * because we have sparse arrays, we convert them to flat objects first. */ toJSON(options?: SerializeOptions): SerializedSheet; /** flushes ALL rendered styles and caches. made public for theme API */ FlushCellStyles(): void; ImportData(data: ImportedSheetData): void; /** * figure out the last row/column of the annotation. this * might set it to 0/0 if there's no rect, just make sure * that it gets cleared on layout changes. */ protected CalculateAnnotationExtent(annotation: Annotation): void; /** * update style properties. merge by default. * * this method will reverse-override properties, meaning if you have set (for * example) a cell style to bold, then you set the whole sheet to unbold, we * expect that the unbold style will control. instead of explicitly setting * the cell style, we go up the chain and remove any matching properties. */ private UpdateSheetStyle; /** * updates row properties. reverse-overrides cells (@see UpdateSheetStyle). * * we also need to ensure that the desired effect takes hold, meaning if * there's an overriding column property (columns have priority), we will * need to update the cell property to match the desired output. */ private UpdateRowStyle; /** * updates column properties. reverse-overrides cells (@see UpdateSheetStyle). */ private UpdateColumnStyle; BleedFlush(area: IArea): void; FlushConditionalFormats(): void; /** * this version combines flushing the cache with building it, using * the application flag in the format objects. * * this function was set up to support comparing the two lists and * only flushing style if necessary; but that turns out to be so * much additional work that I'm not sure it's preferable to just * repaint. need to test. * * ...we're also probably looping unecessarily. since we're using * those leaf nodes we can probably check if the state changed, and * it not, skip the loop pass. I think we'd need to identify or map * the applications though (meaning use a stack that matches the list * of formats). or you could even recheck everything if one of them * changed, you'd still probably save a lot in cases where nothing * changed. * */ ApplyConditionalFormats(): void; private ConditionalFormatForCell; /** * generates the composite style for the given cell. this * should only be used to generate a cache of styles (Q: really? PERF?) * * the "apply_cell_style" parameter is used for testing when pruning. we * want to check what happens if the cell style is not applied; if nothing * happens, then we can drop the cell style (or the property in the style). */ private CompositeStyleForCell; /** * can we use the rendered JSON as a key, instead? */ private GetStyleIndex; }