UNPKG

cheetah-grid

Version:

Cheetah Grid is a high performance grid engine that works on canvas

364 lines (352 loc) 9.87 kB
import type { CellAddress, ColumnMenuItemOption, ColumnTypeAPI, EventListenerId, InlineMenuEditorOption, LayoutObjectId, ListGridAPI, SimpleColumnMenuItemOption, } from "../../ts-types"; import type { GridInternal, InputEditorState } from "../../ts-types-internal"; import { array, cellEquals, event, isPromise, obj, then, } from "../../internal/utils"; import { isDisabledRecord, isReadOnlyRecord } from "./action-utils"; import { DG_EVENT_TYPE } from "../../core/DG_EVENT_TYPE"; import { Editor } from "./Editor"; import { InlineMenuElement } from "./internal/InlineMenuElement"; import { MenuColumn } from "../type"; import type { RangePasteContext } from "./BaseAction"; import { getInlineMenuEditorStateId } from "../../internal/symbolManager"; import { normalizeToFn } from "../../internal/menu-items"; const _ = getInlineMenuEditorStateId(); function getState<T>(grid: GridInternal<T>): InputEditorState { let state = grid[_]; if (!state) { state = {}; obj.setReadonly(grid, _, state); } return state; } // eslint-disable-next-line @typescript-eslint/no-explicit-any let globalElement: InlineMenuElement<any> | null = null; let bindGridCount = 0; function attachMenu<T>( grid: ListGridAPI<T>, cell: CellAddress, editor: InlineMenuEditor<T>, value: string, record: T | undefined ): void { const state = getState(grid); if (!globalElement) { globalElement = new InlineMenuElement(); } if (!state.element) { state.element = globalElement; bindGridCount++; grid.addDisposable({ dispose() { bindGridCount--; if (!bindGridCount) { globalElement?.dispose(); globalElement = null; state.element = null; } }, }); } globalElement.attach(grid, editor, cell.col, cell.row, value, record); } function detachMenu(gridFocus?: boolean): void { if (globalElement) { globalElement.detach(gridFocus); } } const KEY_ENTER = 13; const KEY_F2 = 113; export class InlineMenuEditor<T> extends Editor<T> { private _classList?: string | string[]; private _options: (record: T | undefined) => ColumnMenuItemOption[]; constructor(option: InlineMenuEditorOption<T> = {}) { super(option); this._classList = option.classList; this._options = normalizeToFn(option.options); } dispose(): void { // noop } get classList(): string[] | undefined { if (!this._classList) { return undefined; } return Array.isArray(this._classList) ? this._classList : [this._classList]; } set classList(classList: string[] | undefined) { this._classList = classList; } get options(): (record: T | undefined) => ColumnMenuItemOption[] { return this._options; } set options(options: (record: T | undefined) => ColumnMenuItemOption[]) { this._options = normalizeToFn(options); } clone(): InlineMenuEditor<T> { return new InlineMenuEditor(this); } onChangeDisabledInternal(): void { // cancel input detachMenu(true); } onChangeReadOnlyInternal(): void { // cancel input detachMenu(true); } bindGridEvent( grid: GridInternal<T>, cellId: LayoutObjectId ): EventListenerId[] { const open = (cell: CellAddress): boolean => { if ( isReadOnlyRecord(this.readOnly, grid, cell.row) || isDisabledRecord(this.disabled, grid, cell.row) ) { return false; } grid.doGetCellValue(cell.col, cell.row, (value) => { const record = grid.getRowRecord(cell.row); if (isPromise(record)) { return; } attachMenu(grid, cell, this, value, record); }); return true; }; function isTarget(col: number, row: number): boolean { return grid.getLayoutCellId(col, row) === cellId; } return [ grid.listen(DG_EVENT_TYPE.CLICK_CELL, (cell) => { if (!isTarget(cell.col, cell.row)) { return; } open({ col: cell.col, row: cell.row, }); }), grid.listen(DG_EVENT_TYPE.KEYDOWN, (e) => { if (e.keyCode !== KEY_F2 && e.keyCode !== KEY_ENTER) { return; } const sel = grid.selection.select; if (!isTarget(sel.col, sel.row)) { return; } if ( open({ col: sel.col, row: sel.row, }) ) { e.stopCellMoving(); } }), grid.listen(DG_EVENT_TYPE.SELECTED_CELL, (_e) => { detachMenu(); }), grid.listen(DG_EVENT_TYPE.SCROLL, () => { detachMenu(true); }), // mouse move grid.listen(DG_EVENT_TYPE.MOUSEOVER_CELL, (e) => { if (!isTarget(e.col, e.row)) { return; } if ( isReadOnlyRecord(this.readOnly, grid, e.row) || isDisabledRecord(this.disabled, grid, e.row) ) { return; } grid.getElement().style.cursor = "pointer"; }), grid.listen(DG_EVENT_TYPE.MOUSEMOVE_CELL, (e) => { if (!isTarget(e.col, e.row)) { return; } if ( isReadOnlyRecord(this.readOnly, grid, e.row) || isDisabledRecord(this.disabled, grid, e.row) ) { return; } if (!grid.getElement().style.cursor) { grid.getElement().style.cursor = "pointer"; } }), grid.listen(DG_EVENT_TYPE.MOUSEOUT_CELL, (e) => { if (!isTarget(e.col, e.row)) { return; } grid.getElement().style.cursor = ""; }), // paste value grid.listen(DG_EVENT_TYPE.PASTE_CELL, (e) => { if (e.multi) { // ignore multi cell values return; } const selectionRange = grid.selection.range; if (!cellEquals(selectionRange.start, selectionRange.end)) { // ignore multi paste values return; } if (!isTarget(e.col, e.row)) { return; } if ( isReadOnlyRecord(this.readOnly, grid, e.row) || isDisabledRecord(this.disabled, grid, e.row) ) { return; } const record = grid.getRowRecord(e.row); if (isPromise(record)) { return; } const pasteOpt = this._pasteDataToOptionValue( e.normalizeValue, grid, e, record ); if (pasteOpt) { event.cancel(e.event); then( grid.doChangeValue(e.col, e.row, () => pasteOpt.value), () => { const range = grid.getCellRange(e.col, e.row); grid.invalidateCellRange(range); } ); } else { grid.fireListeners("rejected_paste_values", { detail: [ { col: e.col, row: e.row, record, define: grid.getColumnDefine(e.col, e.row), pasteValue: e.normalizeValue, }, ], }); } }), ]; } onPasteCellRangeBox( grid: ListGridAPI<T>, cell: CellAddress, value: string, context: RangePasteContext ): void { if ( isReadOnlyRecord(this.readOnly, grid, cell.row) || isDisabledRecord(this.disabled, grid, cell.row) ) { return; } const record = grid.getRowRecord(cell.row); if (isPromise(record)) { return; } const pasteOpt = this._pasteDataToOptionValue(value, grid, cell, record); if (pasteOpt) { grid.doChangeValue(cell.col, cell.row, () => pasteOpt.value); } else { // unknown context.reject(); } } onDeleteCellRangeBox(grid: ListGridAPI<T>, cell: CellAddress): void { if ( isReadOnlyRecord(this.readOnly, grid, cell.row) || isDisabledRecord(this.disabled, grid, cell.row) ) { return; } const record = grid.getRowRecord(cell.row); if (isPromise(record)) { return; } const pasteOpt = this._pasteDataToOptionValue("", grid, cell, record); if (pasteOpt) { grid.doChangeValue(cell.col, cell.row, () => pasteOpt.value); } } private _pasteDataToOptionValue( value: string, grid: ListGridAPI<T>, cell: CellAddress, record: T | undefined ): SimpleColumnMenuItemOption | undefined { const options = this._options(record); const pasteOpt = _textToOptionValue(value, options); if (pasteOpt) { return pasteOpt; } const columnType = grid.getColumnType(cell.col, cell.row); if (hasOptions(columnType)) { // Find with caption. const pasteValue = normalizePasteValueStr(value); const captionOpt = array.find( columnType.options, (opt) => normalizePasteValueStr(opt.label) === pasteValue ); if (captionOpt) { return _textToOptionValue(captionOpt.value, options); } } return undefined; } } function _textToOptionValue( value: string, options: SimpleColumnMenuItemOption[] ): SimpleColumnMenuItemOption | undefined { const pasteValue = normalizePasteValueStr(value); const pasteOpt = array.find( options, (opt) => normalizePasteValueStr(opt.value) === pasteValue ); if (pasteOpt) { return pasteOpt; } return undefined; } // eslint-disable-next-line @typescript-eslint/no-explicit-any function normalizePasteValueStr(value: any): string { if (value == null) { return ""; } return `${value}`.trim(); } // eslint-disable-next-line @typescript-eslint/no-explicit-any function hasOptions(columnType: ColumnTypeAPI): columnType is MenuColumn<any> { if (columnType instanceof MenuColumn) { return true; } // eslint-disable-next-line @typescript-eslint/no-explicit-any if (Array.isArray((columnType as any).options)) { return true; } return false; }