slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
1,018 lines (916 loc) • 359 kB
text/typescript
// @ts-ignore
import type { SortableEvent, SortableInstance, SortableOptions } from 'sortablejs';
import type {
AutoSize,
CellPosition,
CellViewportRange,
Column,
ColumnMetadata,
ColumnSort,
CssStyleHash,
CSSStyleDeclarationWritable,
CustomDataView,
DOMEvent,
DragPosition,
DragRowMove,
Editor,
EditorArguments,
EditorConstructor,
EditController,
Formatter,
FormatterOverrideCallback,
FormatterResultObject,
FormatterResultWithHtml,
FormatterResultWithText,
GridOption as BaseGridOption,
InteractionBase,
ItemMetadata,
MenuCommandItemCallbackArgs,
MultiColumnSort,
OnActivateChangedOptionsEventArgs,
OnActiveCellChangedEventArgs,
OnAddNewRowEventArgs,
OnAfterSetColumnsEventArgs,
OnAutosizeColumnsEventArgs,
OnBeforeUpdateColumnsEventArgs,
OnBeforeAppendCellEventArgs,
OnBeforeCellEditorDestroyEventArgs,
OnBeforeColumnsResizeEventArgs,
OnBeforeEditCellEventArgs,
OnBeforeHeaderCellDestroyEventArgs,
OnBeforeHeaderRowCellDestroyEventArgs,
OnBeforeFooterRowCellDestroyEventArgs,
OnBeforeSetColumnsEventArgs,
OnCellChangeEventArgs,
OnCellCssStylesChangedEventArgs,
OnClickEventArgs,
OnColumnsDragEventArgs,
OnColumnsReorderedEventArgs,
OnColumnsResizedEventArgs,
OnColumnsResizeDblClickEventArgs,
OnCompositeEditorChangeEventArgs,
OnDblClickEventArgs,
OnFooterContextMenuEventArgs,
OnFooterRowCellRenderedEventArgs,
OnHeaderCellRenderedEventArgs,
OnFooterClickEventArgs,
OnHeaderClickEventArgs,
OnHeaderContextMenuEventArgs,
OnHeaderMouseEventArgs,
OnHeaderRowCellRenderedEventArgs,
OnKeyDownEventArgs,
OnPreHeaderContextMenuEventArgs,
OnPreHeaderClickEventArgs,
OnRenderedEventArgs,
OnSelectedRowsChangedEventArgs,
OnSetOptionsEventArgs,
OnScrollEventArgs,
OnValidationErrorEventArgs,
OnDragReplaceCellsEventArgs,
PagingInfo,
RowInfo,
SelectionModel,
SingleColumnSort,
SlickGridModel,
SlickPlugin,
} from './models/index.js';
import {
type BasePubSub,
BindingEventService as BindingEventService_,
ColAutosizeMode as ColAutosizeMode_,
GlobalEditorLock as GlobalEditorLock_,
GridAutosizeColsMode as GridAutosizeColsMode_,
keyCode as keyCode_,
preClickClassName as preClickClassName_,
RowSelectionMode as RowSelectionMode_,
CellSelectionMode as CellSelectionMode_,
type SlickEditorLock,
SlickEvent as SlickEvent_,
SlickEventData as SlickEventData_,
SlickRange as SlickRange_,
Utils as Utils_,
SelectionUtils as SelectionUtils_,
ValueFilterMode as ValueFilterMode_,
WidthEvalMode as WidthEvalMode_,
DragExtendHandle as DragExtendHandle_,
} from './slick.core.js';
import { Draggable as Draggable_, MouseWheel as MouseWheel_, Resizable as Resizable_ } from './slick.interactions.js';
// for (iife) load Slick methods from global Slick object, or use imports for (esm)
const BindingEventService = IIFE_ONLY ? Slick.BindingEventService : BindingEventService_;
const ColAutosizeMode = IIFE_ONLY ? Slick.ColAutosizeMode : ColAutosizeMode_;
const SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;
const SlickEventData = IIFE_ONLY ? Slick.EventData : SlickEventData_;
const GlobalEditorLock = IIFE_ONLY ? Slick.GlobalEditorLock : GlobalEditorLock_;
const GridAutosizeColsMode = IIFE_ONLY ? Slick.GridAutosizeColsMode : GridAutosizeColsMode_;
const keyCode = IIFE_ONLY ? Slick.keyCode : keyCode_;
const preClickClassName = IIFE_ONLY ? Slick.preClickClassName : preClickClassName_;
const SlickRange = IIFE_ONLY ? Slick.Range : SlickRange_;
const RowSelectionMode = IIFE_ONLY ? Slick.RowSelectionMode : RowSelectionMode_;
const CellSelectionMode = IIFE_ONLY ? Slick.CellSelectionMode : CellSelectionMode_;
const ValueFilterMode = IIFE_ONLY ? Slick.ValueFilterMode : ValueFilterMode_;
const Utils = IIFE_ONLY ? Slick.Utils : Utils_;
const SelectionUtils = IIFE_ONLY ? Slick.SelectionUtils : SelectionUtils_;
const WidthEvalMode = IIFE_ONLY ? Slick.WidthEvalMode : WidthEvalMode_;
const Draggable = IIFE_ONLY ? Slick.Draggable : Draggable_;
const MouseWheel = IIFE_ONLY ? Slick.MouseWheel : MouseWheel_;
const Resizable = IIFE_ONLY ? Slick.Resizable : Resizable_;
const DragExtendHandle = IIFE_ONLY ? Slick.DragExtendHandle : DragExtendHandle_;
/**
* @license
* (c) 2009-present Michael Leibman
* michael{dot}leibman{at}gmail{dot}com
* http://github.com/mleibman/slickgrid
*
* Distributed under MIT license.
* All rights reserved.
*
* SlickGrid v5.18.0
*
* NOTES:
* Cell/row DOM manipulations are done directly bypassing JS DOM manipulation methods.
* This increases the speed dramatically, but can only be done safely because there are no event handlers
* or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy()
* and do proper cleanup.
*/
//////////////////////////////////////////////////////////////////////////////////////////////
// SlickGrid class implementation (available as SlickGrid)
interface RowCaching {
rowNode: HTMLElement[] | null,
cellColSpans: Array<number | '*'>;
cellNodesByColumnIdx: HTMLElement[];
cellRenderQueue: any[];
}
export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O extends BaseGridOption<C> = BaseGridOption<C>> {
//////////////////////////////////////////////////////////////////////////////////////////////
// Public API
slickGridVersion = '5.18.0';
/** optional grid state clientId */
cid = '';
// Events
onActiveCellChanged: SlickEvent_<OnActiveCellChangedEventArgs>;
onActiveCellPositionChanged: SlickEvent_<{ grid: SlickGrid; }>;
onAddNewRow: SlickEvent_<OnAddNewRowEventArgs>;
onAfterSetColumns: SlickEvent_<OnAfterSetColumnsEventArgs>;
onAutosizeColumns: SlickEvent_<OnAutosizeColumnsEventArgs>;
onBeforeAppendCell: SlickEvent_<OnBeforeAppendCellEventArgs>;
onBeforeCellEditorDestroy: SlickEvent_<OnBeforeCellEditorDestroyEventArgs>;
onBeforeColumnsResize: SlickEvent_<OnBeforeColumnsResizeEventArgs>;
onBeforeDestroy: SlickEvent_<{ grid: SlickGrid; }>;
onBeforeEditCell: SlickEvent_<OnBeforeEditCellEventArgs>;
onBeforeFooterRowCellDestroy: SlickEvent_<OnBeforeFooterRowCellDestroyEventArgs>;
onBeforeHeaderCellDestroy: SlickEvent_<OnBeforeHeaderCellDestroyEventArgs>;
onBeforeHeaderRowCellDestroy: SlickEvent_<OnBeforeHeaderRowCellDestroyEventArgs>;
onBeforeRemoveCachedRow: SlickEvent_<{ row: number; grid: SlickGrid }>;
onBeforeSetColumns: SlickEvent_<OnBeforeSetColumnsEventArgs>;
onBeforeSort: SlickEvent_<SingleColumnSort | MultiColumnSort>;
onBeforeUpdateColumns: SlickEvent_<OnBeforeUpdateColumnsEventArgs>;
onCellChange: SlickEvent_<OnCellChangeEventArgs>;
onCellCssStylesChanged: SlickEvent_<OnCellCssStylesChangedEventArgs>;
onClick: SlickEvent_<OnClickEventArgs>;
onColumnsReordered: SlickEvent_<OnColumnsReorderedEventArgs>;
onColumnsDrag: SlickEvent_<OnColumnsDragEventArgs>;
onColumnsResized: SlickEvent_<OnColumnsResizedEventArgs>;
onColumnsResizeDblClick: SlickEvent_<OnColumnsResizeDblClickEventArgs>;
onCompositeEditorChange: SlickEvent_<OnCompositeEditorChangeEventArgs>;
onContextMenu: SlickEvent_<MenuCommandItemCallbackArgs>;
onDrag: SlickEvent_<DragRowMove>;
onDblClick: SlickEvent_<OnDblClickEventArgs>;
onDragInit: SlickEvent_<DragRowMove>;
onDragStart: SlickEvent_<DragRowMove>;
onDragEnd: SlickEvent_<DragRowMove>;
onFooterClick: SlickEvent_<OnFooterClickEventArgs>;
onFooterContextMenu: SlickEvent_<OnFooterContextMenuEventArgs>;
onFooterRowCellRendered: SlickEvent_<OnFooterRowCellRenderedEventArgs>;
onHeaderCellRendered: SlickEvent_<OnHeaderCellRenderedEventArgs>;
onHeaderClick: SlickEvent_<OnHeaderClickEventArgs>;
onHeaderContextMenu: SlickEvent_<OnHeaderContextMenuEventArgs>;
onHeaderMouseEnter: SlickEvent_<OnHeaderMouseEventArgs>;
onHeaderMouseLeave: SlickEvent_<OnHeaderMouseEventArgs>;
onHeaderRowCellRendered: SlickEvent_<OnHeaderRowCellRenderedEventArgs>;
onHeaderRowMouseEnter: SlickEvent_<OnHeaderMouseEventArgs>;
onHeaderRowMouseLeave: SlickEvent_<OnHeaderMouseEventArgs>;
onPreHeaderContextMenu: SlickEvent_<OnPreHeaderContextMenuEventArgs>;
onPreHeaderClick: SlickEvent_<OnPreHeaderClickEventArgs>;
onKeyDown: SlickEvent_<OnKeyDownEventArgs>;
onMouseEnter: SlickEvent_<OnHeaderMouseEventArgs>;
onMouseLeave: SlickEvent_<OnHeaderMouseEventArgs>;
onRendered: SlickEvent_<OnRenderedEventArgs>;
onScroll: SlickEvent_<OnScrollEventArgs>;
onSelectedRowsChanged: SlickEvent_<OnSelectedRowsChangedEventArgs>;
onSetOptions: SlickEvent_<OnSetOptionsEventArgs>;
onActivateChangedOptions: SlickEvent_<OnActivateChangedOptionsEventArgs>;
onSort: SlickEvent_<SingleColumnSort | MultiColumnSort>;
onValidationError: SlickEvent_<OnValidationErrorEventArgs>;
onViewportChanged: SlickEvent_<{ grid: SlickGrid; }>;
onDragReplaceCells: SlickEvent_<OnDragReplaceCellsEventArgs>;
// ---
// protected variables
// shared across all grids on the page
protected scrollbarDimensions?: { height: number; width: number; };
protected maxSupportedCssHeight!: number; // browser's breaking point
protected canvas: HTMLCanvasElement | null = null;
protected canvas_context: CanvasRenderingContext2D | null = null;
// settings
protected _options!: O;
protected _defaults: BaseGridOption = {
invalidColumnFreezePickerCallback: (error) => alert(error),
invalidColumnFreezeWidthCallback: (error) => alert(error),
invalidColumnFreezeWidthMessage:
'[SlickGrid] You are trying to freeze/pin more columns than the grid can support. ' +
'Make sure to have less columns pinned (on the left) than the actual visible grid width.',
invalidColumnFreezePickerMessage:
'[SlickGrid] Action not allowed and aborted, you need to have at least one or more column on the right section of the column freeze/pining. ' +
'You could alternatively "Unfreeze all the columns" before trying again.',
alwaysShowVerticalScroll: false,
alwaysAllowHorizontalScroll: false,
explicitInitialization: false,
rowHeight: 25,
defaultColumnWidth: 80,
enableHtmlRendering: true,
enableAddRow: false,
leaveSpaceForNewRows: false,
editable: false,
autoEdit: true,
autoEditNewRow: true,
autoCommitEdit: false,
suppressActiveCellChangeOnEdit: false,
enableCellNavigation: true,
enableColumnReorder: true,
unorderableColumnCssClass: 'unorderable',
asyncEditorLoading: false,
asyncEditorLoadDelay: 100,
forceFitColumns: false,
enableAsyncPostRender: false,
asyncPostRenderDelay: 50,
enableAsyncPostRenderCleanup: false,
asyncPostRenderCleanupDelay: 40,
auto: false,
nonce: '',
editorLock: GlobalEditorLock,
showColumnHeader: true,
showHeaderRow: false,
headerRowHeight: 25,
createFooterRow: false,
showFooterRow: false,
footerRowHeight: 25,
createPreHeaderPanel: false,
createTopHeaderPanel: false,
showPreHeaderPanel: false,
showTopHeaderPanel: false,
preHeaderPanelHeight: 25,
showTopPanel: false,
topPanelHeight: 25,
preHeaderPanelWidth: 'auto', // mostly useful for Draggable Grouping dropzone to take full width
topHeaderPanelHeight: 25,
topHeaderPanelWidth: 'auto', // mostly useful for Draggable Grouping dropzone to take full width
formatterFactory: null,
editorFactory: null,
cellFlashingCssClass: 'flashing',
rowHighlightCssClass: 'highlight-animate',
rowHighlightDuration: 400,
selectedCellCssClass: 'selected',
multiSelect: true,
enableCellRowSpan: false,
enableTextSelectionOnCells: false,
dataItemColumnValueExtractor: null,
frozenBottom: false,
frozenColumn: -1,
frozenRow: -1,
frozenRightViewportMinWidth: 100,
throwWhenFrozenNotAllViewable: false,
fullWidthRows: false,
multiColumnSort: false,
numberedMultiColumnSort: false,
tristateMultiColumnSort: false,
sortColNumberInSeparateSpan: false,
defaultFormatter: this.defaultFormatter,
forceSyncScrolling: false,
addNewRowCssClass: 'new-row',
preserveCopiedSelectionOnPaste: false,
preventDragFromKeys: ['ctrlKey', 'metaKey'],
showCellSelection: true,
viewportClass: undefined,
minRowBuffer: 3,
emulatePagingWhenScrolling: true, // when scrolling off bottom of viewport, place new row at top of viewport
editorCellNavOnLRKeys: false,
enableMouseWheelScrollHandler: true,
doPaging: true,
autosizeColsMode: GridAutosizeColsMode.LegacyOff,
autosizeColPaddingPx: 4,
rowTopOffsetRenderType: 'top',
scrollRenderThrottling: 10,
autosizeTextAvgToMWidthRatio: 0.75,
viewportSwitchToScrollModeWidthPercent: undefined,
viewportMinWidthPx: undefined,
viewportMaxWidthPx: undefined,
suppressCssChangesOnHiddenInit: false,
ffMaxSupportedCssHeight: 6000000,
maxSupportedCssHeight: 1000000000,
maxPartialRowSpanRemap: 5000,
sanitizer: undefined, // sanitize function, built in basic sanitizer is: Slick.RegexSanitizer(dirtyHtml)
logSanitizedHtml: false, // log to console when sanitised - recommend true for testing of dev and production
mixinDefaults: true,
shadowRoot: undefined
};
protected _columnDefaults = {
name: '',
headerCssClass: null,
defaultSortAsc: true,
focusable: true,
hidden: false,
minWidth: 30,
maxWidth: undefined,
rerenderOnResize: false,
reorderable: true,
resizable: true,
sortable: false,
selectable: true,
} as Partial<C>;
protected _columnAutosizeDefaults: AutoSize = {
ignoreHeaderText: false,
colValueArray: undefined,
allowAddlPercent: undefined,
formatterOverride: undefined,
autosizeMode: ColAutosizeMode.ContentIntelligent,
rowSelectionModeOnInit: undefined,
rowSelectionMode: RowSelectionMode.FirstNRows,
rowSelectionCount: 100,
valueFilterMode: ValueFilterMode.None,
widthEvalMode: WidthEvalMode.Auto,
sizeToRemaining: undefined,
widthPx: undefined,
contentSizePx: 0,
headerWidthPx: 0,
colDataTypeOf: undefined
};
protected _columnResizeTimer?: number;
protected _executionBlockTimer?: number;
protected _flashCellTimer?: number;
protected _highlightRowTimer?: number;
// scroller
protected th!: number; // virtual height
protected h!: number; // real scrollable height
protected ph!: number; // page height
protected n!: number; // number of pages
protected cj!: number; // "jumpiness" coefficient
protected page = 0; // current page
protected offset = 0; // current page offset
protected vScrollDir = 1;
protected _bindingEventService = new BindingEventService();
protected initialized = false;
protected _container!: HTMLElement;
protected uid = `slickgrid_${Math.round(1000000 * Math.random())}`;
protected dragReplaceEl = new DragExtendHandle(this.uid);
protected _focusSink!: HTMLDivElement;
protected _focusSink2!: HTMLDivElement;
protected _groupHeaders: HTMLDivElement[] = [];
protected _headerScroller: HTMLDivElement[] = [];
protected _headers: HTMLDivElement[] = [];
protected _headerRows!: HTMLDivElement[];
protected _headerRowScroller!: HTMLDivElement[];
protected _headerRowSpacerL!: HTMLDivElement;
protected _headerRowSpacerR!: HTMLDivElement;
protected _footerRow!: HTMLDivElement[];
protected _footerRowScroller!: HTMLDivElement[];
protected _footerRowSpacerL!: HTMLDivElement;
protected _footerRowSpacerR!: HTMLDivElement;
protected _preHeaderPanel!: HTMLDivElement;
protected _preHeaderPanelScroller!: HTMLDivElement;
protected _preHeaderPanelSpacer!: HTMLDivElement;
protected _preHeaderPanelR!: HTMLDivElement;
protected _preHeaderPanelScrollerR!: HTMLDivElement;
protected _preHeaderPanelSpacerR!: HTMLDivElement;
protected _topHeaderPanel!: HTMLDivElement;
protected _topHeaderPanelScroller!: HTMLDivElement;
protected _topHeaderPanelSpacer!: HTMLDivElement;
protected _topPanelScrollers!: HTMLDivElement[];
protected _topPanels!: HTMLDivElement[];
protected _viewport!: HTMLDivElement[];
protected _canvas!: HTMLDivElement[];
protected _style?: HTMLStyleElement;
protected _boundAncestors: HTMLElement[] = [];
protected stylesheet?: { cssRules: Array<{ selectorText: string; }>; rules: Array<{ selectorText: string; }>; } | null;
protected columnCssRulesL?: Array<{ selectorText: string; }>;
protected columnCssRulesR?: Array<{ selectorText: string; }>;
protected viewportH = 0;
protected viewportW = 0;
protected canvasWidth = 0;
protected canvasWidthL = 0;
protected canvasWidthR = 0;
protected headersWidth = 0;
protected headersWidthL = 0;
protected headersWidthR = 0;
protected viewportHasHScroll = false;
protected viewportHasVScroll = false;
protected headerColumnWidthDiff = 0;
protected headerColumnHeightDiff = 0; // border+padding
protected cellWidthDiff = 0;
protected cellHeightDiff = 0;
protected absoluteColumnMinWidth!: number;
protected hasFrozenRows = false;
protected frozenRowsHeight = 0;
protected actualFrozenRow = -1;
protected _prevFrozenColumnIdx = -1;
/** flag to indicate if invalid frozen alert has been shown already or not? This is to avoid showing it more than once */
protected _invalidfrozenAlerted = false;
protected paneTopH = 0;
protected paneBottomH = 0;
protected viewportTopH = 0;
protected viewportBottomH = 0;
protected topPanelH = 0;
protected headerRowH = 0;
protected footerRowH = 0;
protected tabbingDirection = 1;
protected _activeCanvasNode!: HTMLDivElement;
protected _activeViewportNode!: HTMLDivElement;
protected activePosX!: number;
protected activePosY!: number;
protected activeRow!: number;
protected activeCell!: number;
protected selectionBottomRow!: number;
protected selectionRightCell!: number;
protected activeCellNode: HTMLDivElement | null = null;
protected currentEditor: Editor | null = null;
protected serializedEditorValue: any;
protected editController?: EditController;
protected _prevDataLength = 0;
protected _prevInvalidatedRowsCount = 0;
protected _rowSpanIsCached = false;
protected _colsWithRowSpanCache: { [colIdx: number]: Set<string> } = {};
protected rowsCache: Record<number, RowCaching> = {};
protected renderedRows = 0;
protected numVisibleRows = 0;
protected prevScrollTop = 0;
protected scrollHeight = 0;
protected scrollTop = 0;
protected lastRenderedScrollTop = 0;
protected lastRenderedScrollLeft = 0;
protected prevScrollLeft = 0;
protected scrollLeft = 0;
protected selectionModel?: SelectionModel;
protected selectedRows: number[] = [];
protected selectedRanges: SlickRange_[] = [];
protected plugins: SlickPlugin[] = [];
protected cellCssClasses: CssStyleHash = {};
protected columnsById: Record<string, number> = {};
protected sortColumns: ColumnSort[] = [];
protected columnPosLeft: number[] = [];
protected columnPosRight: number[] = [];
protected pagingActive = false;
protected pagingIsLastPage = false;
protected scrollThrottle!: { enqueue: () => void; dequeue: () => void; };
// async call handles
protected h_editorLoader?: number;
protected h_postrender?: number;
protected h_postrenderCleanup?: number;
protected postProcessedRows: any = {};
protected postProcessToRow: number = null as any;
protected postProcessFromRow: number = null as any;
protected postProcessedCleanupQueue: Array<{
actionType: string;
groupId: number;
node: HTMLElement | HTMLElement[];
columnIdx?: number;
rowIdx?: number;
}> = [];
protected postProcessgroupId = 0;
// perf counters
protected counter_rows_rendered = 0;
protected counter_rows_removed = 0;
protected _paneHeaderL!: HTMLDivElement;
protected _paneHeaderR!: HTMLDivElement;
protected _paneTopL!: HTMLDivElement;
protected _paneTopR!: HTMLDivElement;
protected _paneBottomL!: HTMLDivElement;
protected _paneBottomR!: HTMLDivElement;
protected _headerScrollerL!: HTMLDivElement;
protected _headerScrollerR!: HTMLDivElement;
protected _headerL!: HTMLDivElement;
protected _headerR!: HTMLDivElement;
protected _groupHeadersL!: HTMLDivElement;
protected _groupHeadersR!: HTMLDivElement;
protected _headerRowScrollerL!: HTMLDivElement;
protected _headerRowScrollerR!: HTMLDivElement;
protected _footerRowScrollerL!: HTMLDivElement;
protected _footerRowScrollerR!: HTMLDivElement;
protected _headerRowL!: HTMLDivElement;
protected _headerRowR!: HTMLDivElement;
protected _footerRowL!: HTMLDivElement;
protected _footerRowR!: HTMLDivElement;
protected _topPanelScrollerL!: HTMLDivElement;
protected _topPanelScrollerR!: HTMLDivElement;
protected _topPanelL!: HTMLDivElement;
protected _topPanelR!: HTMLDivElement;
protected _viewportTopL!: HTMLDivElement;
protected _viewportTopR!: HTMLDivElement;
protected _viewportBottomL!: HTMLDivElement;
protected _viewportBottomR!: HTMLDivElement;
protected _canvasTopL!: HTMLDivElement;
protected _canvasTopR!: HTMLDivElement;
protected _canvasBottomL!: HTMLDivElement;
protected _canvasBottomR!: HTMLDivElement;
protected _viewportScrollContainerX!: HTMLDivElement;
protected _viewportScrollContainerY!: HTMLDivElement;
protected _headerScrollContainer!: HTMLDivElement;
protected _headerRowScrollContainer!: HTMLDivElement;
protected _footerRowScrollContainer!: HTMLDivElement;
// store css attributes if display:none is active in container or parent
protected cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' };
protected _hiddenParents: HTMLElement[] = [];
protected oldProps: Array<Partial<CSSStyleDeclaration>> = [];
protected enforceFrozenRowHeightRecalc = false;
protected columnResizeDragging = false;
protected slickDraggableInstance: InteractionBase | null = null;
protected slickMouseWheelInstances: Array<InteractionBase> = [];
protected slickResizableInstances: Array<InteractionBase> = [];
protected sortableSideLeftInstance?: SortableInstance;
protected sortableSideRightInstance?: SortableInstance;
protected logMessageCount = 0;
protected logMessageMaxCount = 30;
protected _pubSubService?: BasePubSub;
/**
* Creates a new instance of the grid.
* @class SlickGrid
* @constructor
* @param {Node} container - Container node to create the grid in.
* @param {Array|Object} data - An array of objects for databinding or an external DataView.
* @param {Array<C>} columns - An array of column definitions.
* @param {Object} [options] - Grid Options
* @param {Object} [externalPubSub] - optional External PubSub Service to use by SlickEvent
**/
constructor(protected readonly container: HTMLElement | string, protected data: CustomDataView<TData> | TData[], protected columns: C[], options: Partial<O>, protected readonly externalPubSub?: BasePubSub) {
this._container = typeof this.container === 'string'
? document.querySelector(this.container) as HTMLDivElement
: this.container;
if (!this._container) {
throw new Error(`SlickGrid requires a valid container, ${this.container} does not exist in the DOM.`);
}
this._pubSubService = externalPubSub;
this.onActiveCellChanged = new SlickEvent<OnActiveCellChangedEventArgs>('onActiveCellChanged', externalPubSub);
this.onActiveCellPositionChanged = new SlickEvent<{ grid: SlickGrid; }>('onActiveCellPositionChanged', externalPubSub);
this.onAddNewRow = new SlickEvent<OnAddNewRowEventArgs>('onAddNewRow', externalPubSub);
this.onAfterSetColumns = new SlickEvent<OnAfterSetColumnsEventArgs>('onAfterSetColumns', externalPubSub);
this.onAutosizeColumns = new SlickEvent<OnAutosizeColumnsEventArgs>('onAutosizeColumns', externalPubSub);
this.onBeforeAppendCell = new SlickEvent<OnBeforeAppendCellEventArgs>('onBeforeAppendCell', externalPubSub);
this.onBeforeCellEditorDestroy = new SlickEvent<OnBeforeCellEditorDestroyEventArgs>('onBeforeCellEditorDestroy', externalPubSub);
this.onBeforeColumnsResize = new SlickEvent<OnBeforeColumnsResizeEventArgs>('onBeforeColumnsResize', externalPubSub);
this.onBeforeDestroy = new SlickEvent<{ grid: SlickGrid; }>('onBeforeDestroy', externalPubSub);
this.onBeforeEditCell = new SlickEvent<OnBeforeEditCellEventArgs>('onBeforeEditCell', externalPubSub);
this.onBeforeFooterRowCellDestroy = new SlickEvent<OnBeforeFooterRowCellDestroyEventArgs>('onBeforeFooterRowCellDestroy', externalPubSub);
this.onBeforeHeaderCellDestroy = new SlickEvent<OnBeforeHeaderCellDestroyEventArgs>('onBeforeHeaderCellDestroy', externalPubSub);
this.onBeforeHeaderRowCellDestroy = new SlickEvent<OnBeforeHeaderRowCellDestroyEventArgs>('onBeforeHeaderRowCellDestroy', externalPubSub);
this.onBeforeRemoveCachedRow = new SlickEvent<{ row: number; grid: SlickGrid }>('onRowRemovedFromCache', externalPubSub);
this.onBeforeSetColumns = new SlickEvent<OnBeforeSetColumnsEventArgs>('onBeforeSetColumns', externalPubSub);
this.onBeforeSort = new SlickEvent<SingleColumnSort | MultiColumnSort>('onBeforeSort', externalPubSub);
this.onBeforeUpdateColumns = new SlickEvent<OnBeforeUpdateColumnsEventArgs>('onBeforeUpdateColumns', externalPubSub);
this.onCellChange = new SlickEvent<OnCellChangeEventArgs>('onCellChange', externalPubSub);
this.onCellCssStylesChanged = new SlickEvent<OnCellCssStylesChangedEventArgs>('onCellCssStylesChanged', externalPubSub);
this.onClick = new SlickEvent<OnClickEventArgs>('onClick', externalPubSub);
this.onColumnsReordered = new SlickEvent<OnColumnsReorderedEventArgs>('onColumnsReordered', externalPubSub);
this.onColumnsDrag = new SlickEvent<OnColumnsDragEventArgs>('onColumnsDrag', externalPubSub);
this.onColumnsResized = new SlickEvent<OnColumnsResizedEventArgs>('onColumnsResized', externalPubSub);
this.onColumnsResizeDblClick = new SlickEvent<OnColumnsResizeDblClickEventArgs>('onColumnsResizeDblClick', externalPubSub);
this.onCompositeEditorChange = new SlickEvent<OnCompositeEditorChangeEventArgs>('onCompositeEditorChange', externalPubSub);
this.onContextMenu = new SlickEvent<MenuCommandItemCallbackArgs>('onContextMenu', externalPubSub);
this.onDrag = new SlickEvent<DragRowMove>('onDrag', externalPubSub);
this.onDblClick = new SlickEvent<OnDblClickEventArgs>('onDblClick', externalPubSub);
this.onDragInit = new SlickEvent<DragRowMove>('onDragInit', externalPubSub);
this.onDragStart = new SlickEvent<DragRowMove>('onDragStart', externalPubSub);
this.onDragEnd = new SlickEvent<DragRowMove>('onDragEnd', externalPubSub);
this.onFooterClick = new SlickEvent<OnFooterClickEventArgs>('onFooterClick', externalPubSub);
this.onFooterContextMenu = new SlickEvent<OnFooterContextMenuEventArgs>('onFooterContextMenu', externalPubSub);
this.onFooterRowCellRendered = new SlickEvent<OnFooterRowCellRenderedEventArgs>('onFooterRowCellRendered', externalPubSub);
this.onHeaderCellRendered = new SlickEvent<OnHeaderCellRenderedEventArgs>('onHeaderCellRendered', externalPubSub);
this.onHeaderClick = new SlickEvent<OnHeaderClickEventArgs>('onHeaderClick', externalPubSub);
this.onHeaderContextMenu = new SlickEvent<OnHeaderContextMenuEventArgs>('onHeaderContextMenu', externalPubSub);
this.onHeaderMouseEnter = new SlickEvent<OnHeaderMouseEventArgs>('onHeaderMouseEnter', externalPubSub);
this.onHeaderMouseLeave = new SlickEvent<OnHeaderMouseEventArgs>('onHeaderMouseLeave', externalPubSub);
this.onHeaderRowCellRendered = new SlickEvent<OnHeaderRowCellRenderedEventArgs>('onHeaderRowCellRendered', externalPubSub);
this.onHeaderRowMouseEnter = new SlickEvent<OnHeaderMouseEventArgs>('onHeaderRowMouseEnter', externalPubSub);
this.onHeaderRowMouseLeave = new SlickEvent<OnHeaderMouseEventArgs>('onHeaderRowMouseLeave', externalPubSub);
this.onPreHeaderClick = new SlickEvent<OnPreHeaderClickEventArgs>('onPreHeaderClick', externalPubSub);
this.onPreHeaderContextMenu = new SlickEvent<OnPreHeaderContextMenuEventArgs>('onPreHeaderContextMenu', externalPubSub);
this.onKeyDown = new SlickEvent<OnKeyDownEventArgs>('onKeyDown', externalPubSub);
this.onMouseEnter = new SlickEvent<OnHeaderMouseEventArgs>('onMouseEnter', externalPubSub);
this.onMouseLeave = new SlickEvent<OnHeaderMouseEventArgs>('onMouseLeave', externalPubSub);
this.onRendered = new SlickEvent<OnRenderedEventArgs>('onRendered', externalPubSub);
this.onScroll = new SlickEvent<OnScrollEventArgs>('onScroll', externalPubSub);
this.onSelectedRowsChanged = new SlickEvent<OnSelectedRowsChangedEventArgs>('onSelectedRowsChanged', externalPubSub);
this.onSetOptions = new SlickEvent<OnSetOptionsEventArgs>('onSetOptions', externalPubSub);
this.onActivateChangedOptions = new SlickEvent<OnActivateChangedOptionsEventArgs>('onActivateChangedOptions', externalPubSub);
this.onSort = new SlickEvent<SingleColumnSort | MultiColumnSort>('onSort', externalPubSub);
this.onValidationError = new SlickEvent<OnValidationErrorEventArgs>('onValidationError', externalPubSub);
this.onViewportChanged = new SlickEvent<{ grid: SlickGrid; }>('onViewportChanged', externalPubSub);
this.onDragReplaceCells = new SlickEvent<OnDragReplaceCellsEventArgs>('onDragReplaceCells', externalPubSub);
this.initialize(options);
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Grid and Dom Initialisation
//////////////////////////////////////////////////////////////////////////////////////////////
/** Initializes the grid. */
init() {
this.finishInitialization();
}
/**
* Processes the provided grid options (mixing in default settings as needed),
* validates required modules (for example, ensuring Sortable.js is loaded if column reordering is enabled),
* and creates all necessary DOM elements for the grid (including header containers, viewports, canvases, panels, etc.).
* It also caches CSS if the container or its ancestors are hidden and calls finish.
*
* @param {Partial<O>} options - Partial grid options to be applied during initialization.
*/
protected initialize(options: Partial<O>) {
// calculate these only once and share between grid instances
if (options?.mixinDefaults) {
// use provided options and then assign defaults
if (!this._options) { this._options = options as O; }
Utils.applyDefaults(this._options, this._defaults);
} else {
this._options = Utils.extend<O>(true, {}, this._defaults, options);
}
this.scrollThrottle = this.actionThrottle(this.render.bind(this), this._options.scrollRenderThrottling as number);
this.maxSupportedCssHeight = this.maxSupportedCssHeight || this.getMaxSupportedCssHeight();
this.validateAndEnforceOptions();
this._columnDefaults.width = this._options.defaultColumnWidth;
this._prevFrozenColumnIdx = this.getFrozenColumnIdx();
if (!this._options.suppressCssChangesOnHiddenInit) {
this.cacheCssForHiddenInit();
}
this.updateColumnProps();
// validate loaded JavaScript modules against requested options
if (this._options.enableColumnReorder && (!Sortable || !Sortable.create)) {
throw new Error('SlickGrid requires Sortable.js module to be loaded');
}
this.editController = {
commitCurrentEdit: this.commitCurrentEdit.bind(this),
cancelCurrentEdit: this.cancelCurrentEdit.bind(this),
};
Utils.emptyElement(this._container);
this._container.style.outline = String(0);
this._container.classList.add(this.uid);
this._container.classList.add('ui-widget');
this._container.setAttribute('role', 'grid');
const containerStyles = window.getComputedStyle(this._container);
if (!(/relative|absolute|fixed/).test(containerStyles.position)) {
this._container.style.position = 'relative';
}
this._focusSink = Utils.createDomElement('div', { tabIndex: 0, style: { position: 'fixed', width: '0px', height: '0px', top: '0px', left: '0px', outline: '0px' } }, this._container);
if (this._options.createTopHeaderPanel) {
this._topHeaderPanelScroller = Utils.createDomElement('div', { className: 'slick-topheader-panel slick-state-default', style: { overflow: 'hidden', position: 'relative' } }, this._container);
this._topHeaderPanelScroller.appendChild(document.createElement('div'));
this._topHeaderPanel = Utils.createDomElement('div', null, this._topHeaderPanelScroller);
this._topHeaderPanelSpacer = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._topHeaderPanelScroller);
if (!this._options.showTopHeaderPanel) {
Utils.hide(this._topHeaderPanelScroller);
}
}
// Containers used for scrolling frozen columns and rows
this._paneHeaderL = Utils.createDomElement('div', { className: 'slick-pane slick-pane-header slick-pane-left', tabIndex: 0 }, this._container);
this._paneHeaderR = Utils.createDomElement('div', { className: 'slick-pane slick-pane-header slick-pane-right', tabIndex: 0 }, this._container);
this._paneTopL = Utils.createDomElement('div', { className: 'slick-pane slick-pane-top slick-pane-left', tabIndex: 0 }, this._container);
this._paneTopR = Utils.createDomElement('div', { className: 'slick-pane slick-pane-top slick-pane-right', tabIndex: 0 }, this._container);
this._paneBottomL = Utils.createDomElement('div', { className: 'slick-pane slick-pane-bottom slick-pane-left', tabIndex: 0 }, this._container);
this._paneBottomR = Utils.createDomElement('div', { className: 'slick-pane slick-pane-bottom slick-pane-right', tabIndex: 0 }, this._container);
if (this._options.createPreHeaderPanel) {
this._preHeaderPanelScroller = Utils.createDomElement('div', { className: 'slick-preheader-panel ui-state-default slick-state-default', style: { overflow: 'hidden', position: 'relative' } }, this._paneHeaderL);
this._preHeaderPanelScroller.appendChild(document.createElement('div'));
this._preHeaderPanel = Utils.createDomElement('div', null, this._preHeaderPanelScroller);
this._preHeaderPanelSpacer = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._preHeaderPanelScroller);
this._preHeaderPanelScrollerR = Utils.createDomElement('div', { className: 'slick-preheader-panel ui-state-default slick-state-default', style: { overflow: 'hidden', position: 'relative' } }, this._paneHeaderR);
this._preHeaderPanelR = Utils.createDomElement('div', null, this._preHeaderPanelScrollerR);
this._preHeaderPanelSpacerR = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._preHeaderPanelScrollerR);
if (!this._options.showPreHeaderPanel) {
Utils.hide(this._preHeaderPanelScroller);
Utils.hide(this._preHeaderPanelScrollerR);
}
}
// Append the header scroller containers
this._headerScrollerL = Utils.createDomElement('div', { className: 'slick-header ui-state-default slick-state-default slick-header-left' }, this._paneHeaderL);
this._headerScrollerR = Utils.createDomElement('div', { className: 'slick-header ui-state-default slick-state-default slick-header-right' }, this._paneHeaderR);
// Cache the header scroller containers
this._headerScroller.push(this._headerScrollerL);
this._headerScroller.push(this._headerScrollerR);
// Append the columnn containers to the headers
this._headerL = Utils.createDomElement('div', { className: 'slick-header-columns slick-header-columns-left', role: 'row', style: { left: '-1000px' } }, this._headerScrollerL);
this._headerR = Utils.createDomElement('div', { className: 'slick-header-columns slick-header-columns-right', role: 'row', style: { left: '-1000px' } }, this._headerScrollerR);
// Cache the header columns
this._headers = [this._headerL, this._headerR];
this._headerRowScrollerL = Utils.createDomElement('div', { className: 'slick-headerrow ui-state-default slick-state-default' }, this._paneTopL);
this._headerRowScrollerR = Utils.createDomElement('div', { className: 'slick-headerrow ui-state-default slick-state-default' }, this._paneTopR);
this._headerRowScroller = [this._headerRowScrollerL, this._headerRowScrollerR];
this._headerRowSpacerL = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._headerRowScrollerL);
this._headerRowSpacerR = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._headerRowScrollerR);
this._headerRowL = Utils.createDomElement('div', { className: 'slick-headerrow-columns slick-headerrow-columns-left' }, this._headerRowScrollerL);
this._headerRowR = Utils.createDomElement('div', { className: 'slick-headerrow-columns slick-headerrow-columns-right' }, this._headerRowScrollerR);
this._headerRows = [this._headerRowL, this._headerRowR];
// Append the top panel scroller
this._topPanelScrollerL = Utils.createDomElement('div', { className: 'slick-top-panel-scroller ui-state-default slick-state-default' }, this._paneTopL);
this._topPanelScrollerR = Utils.createDomElement('div', { className: 'slick-top-panel-scroller ui-state-default slick-state-default' }, this._paneTopR);
this._topPanelScrollers = [this._topPanelScrollerL, this._topPanelScrollerR];
// Append the top panel
this._topPanelL = Utils.createDomElement('div', { className: 'slick-top-panel', style: { width: '10000px' } }, this._topPanelScrollerL);
this._topPanelR = Utils.createDomElement('div', { className: 'slick-top-panel', style: { width: '10000px' } }, this._topPanelScrollerR);
this._topPanels = [this._topPanelL, this._topPanelR];
if (!this._options.showColumnHeader) {
this._headerScroller.forEach((el) => {
Utils.hide(el);
});
}
if (!this._options.showTopPanel) {
this._topPanelScrollers.forEach((scroller) => {
Utils.hide(scroller);
});
}
if (!this._options.showHeaderRow) {
this._headerRowScroller.forEach((scroller) => {
Utils.hide(scroller);
});
}
// Append the viewport containers
this._viewportTopL = Utils.createDomElement('div', { className: 'slick-viewport slick-viewport-top slick-viewport-left', tabIndex: 0 }, this._paneTopL);
this._viewportTopR = Utils.createDomElement('div', { className: 'slick-viewport slick-viewport-top slick-viewport-right', tabIndex: 0 }, this._paneTopR);
this._viewportBottomL = Utils.createDomElement('div', { className: 'slick-viewport slick-viewport-bottom slick-viewport-left', tabIndex: 0 }, this._paneBottomL);
this._viewportBottomR = Utils.createDomElement('div', { className: 'slick-viewport slick-viewport-bottom slick-viewport-right', tabIndex: 0 }, this._paneBottomR);
// Cache the viewports
this._viewport = [this._viewportTopL, this._viewportTopR, this._viewportBottomL, this._viewportBottomR];
if (this._options.viewportClass) {
this._viewport.forEach((view) => {
view.classList.add(...Utils.classNameToList((this._options.viewportClass)));
});
}
// Default the active viewport to the top left
this._activeViewportNode = this._viewportTopL;
// Append the canvas containers
this._canvasTopL = Utils.createDomElement('div', { className: 'grid-canvas grid-canvas-top grid-canvas-left', tabIndex: 0 }, this._viewportTopL);
this._canvasTopR = Utils.createDomElement('div', { className: 'grid-canvas grid-canvas-top grid-canvas-right', tabIndex: 0 }, this._viewportTopR);
this._canvasBottomL = Utils.createDomElement('div', { className: 'grid-canvas grid-canvas-bottom grid-canvas-left', tabIndex: 0 }, this._viewportBottomL);
this._canvasBottomR = Utils.createDomElement('div', { className: 'grid-canvas grid-canvas-bottom grid-canvas-right', tabIndex: 0 }, this._viewportBottomR);
// Cache the canvases
this._canvas = [this._canvasTopL, this._canvasTopR, this._canvasBottomL, this._canvasBottomR];
this.scrollbarDimensions = this.scrollbarDimensions || this.measureScrollbar();
const canvasWithScrollbarWidth = this.getCanvasWidth() + this.scrollbarDimensions.width;
// Default the active canvas to the top left
this._activeCanvasNode = this._canvasTopL;
// top-header
if (this._topHeaderPanelSpacer) {
Utils.width(this._topHeaderPanelSpacer, canvasWithScrollbarWidth);
}
// pre-header
if (this._preHeaderPanelSpacer) {
Utils.width(this._preHeaderPanelSpacer, canvasWithScrollbarWidth);
}
this._headers.forEach((el) => {
Utils.width(el, this.getHeadersWidth());
});
Utils.width(this._headerRowSpacerL, canvasWithScrollbarWidth);
Utils.width(this._headerRowSpacerR, canvasWithScrollbarWidth);
// footer Row
if (this._options.createFooterRow) {
this._footerRowScrollerR = Utils.createDomElement('div', { className: 'slick-footerrow ui-state-default slick-state-default' }, this._paneTopR);
this._footerRowScrollerL = Utils.createDomElement('div', { className: 'slick-footerrow ui-state-default slick-state-default' }, this._paneTopL);
this._footerRowScroller = [this._footerRowScrollerL, this._footerRowScrollerR];
this._footerRowSpacerL = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._footerRowScrollerL);
Utils.width(this._footerRowSpacerL, canvasWithScrollbarWidth);
this._footerRowSpacerR = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._footerRowScrollerR);
Utils.width(this._footerRowSpacerR, canvasWithScrollbarWidth);
this._footerRowL = Utils.createDomElement('div', { className: 'slick-footerrow-columns slick-footerrow-columns-left' }, this._footerRowScrollerL);
this._footerRowR = Utils.createDomElement('div', { className: 'slick-footerrow-columns slick-footerrow-columns-right' }, this._footerRowScrollerR);
this._footerRow = [this._footerRowL, this._footerRowR];
if (!this._options.showFooterRow) {
this._footerRowScroller.forEach((scroller) => {
Utils.hide(scroller);
});
}
}
this._focusSink2 = this._focusSink.cloneNode(true) as HTMLDivElement;
this._container.appendChild(this._focusSink2);
if (!this._options.explicitInitialization) {
this.finishInitialization();
}
}
/**
* Completes grid initialisation by calculating viewport dimensions, measuring cell padding and border differences,
* disabling text selection (except on editable inputs), setting frozen options and pane visibility,
* updating column caches, creating column headers and footers, setting up column sorting,
* creating CSS rules, binding ancestor scroll events, and binding various event handlers
* (e.g. for scrolling, mouse, keyboard, drag-and-drop).
* It also starts up any asynchronous post–render processing if enabled.
*/
protected finishInitialization() {
if (!this.initialized) {
this.initialized = true;
this.getViewportWidth();
this.getViewportHeight();
// header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
// calculate the diff so we can set consistent sizes
this.measureCellPaddingAndBorder();
this.disableSelection(this._headers); // disable all text selection in header (including input and textarea)
if (!this._options.enableTextSelectionOnCells) {
// disable text selection in grid cells except in input and textarea elements
this._viewport.forEach((view) => {
this._bindingEventService.bind(view, 'selectstart', (event) => {
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
return;
}
event.preventDefault();
});
});
}
this.setFrozenOptions();
this.setPaneFrozenClasses();
this.setPaneVisibility();
this.setScroller();
this.setOverflow();
this.updateColumnCaches();
this.createColumnHeaders();
this.createColumnFooter();
this.setupColumnSort();
this.createCssRules();
this.resizeCanvas();
this.bindAncestorScrollEvents();
this._bindingEventService.bind(this._container, 'resize', this.resizeCanvas.bind(this));
this._viewport.forEach((view) => {
this._bindingEventService.bind(view, 'scroll', this.handleScroll.bind(this));
});
if (this._options.enableMouseWheelScrollHandler) {
this._viewport.forEach((view) => {
this.slickMouseWheelInstances.push(MouseWheel({
element: view,
onMouseWheel: this.handleMouseWheel.bind(this)
}));
});
}
this._headerScroller.forEach((el) => {
this._bindingEventService.bind(el, 'contextmenu', this.handleHeaderContextMenu.bind(this) as EventListener);
this._bindingEventService.bind(el, 'click', this.handleHeaderClick.bind(this) as EventListener);
});
this._headerRowScroller.forEach((scroller) => {
this._bindingEventService.bind(scroller, 'scroll', this.handleHeaderRowScroll.bind(this) as EventListener);
});
if (this._options.createFooterRow) {
this._footerRow.forEach((footer) => {
this._bindingEventService.bind(footer, 'contextmenu', this.handleFooterContextMenu.bind(this) as EventListener);
this._bindingEventService.bind(footer, 'click', this.handleFooterClick.bind(this) as EventListener);
});
this._footerRowScroller.forEach((scroller) => {
this._bindingEventService.bind(scroller, 'scroll', this.handleFooterRowScroll.bind(this) as EventListener);
});
}
if (this._options.createTopHeaderPanel) {
this._bindingEventService.bind(this._topHeaderPanelScroller, 'scroll', this.handleTopHeaderPanelScroll.bind(this) as EventListener);
}
if (this._options.createPreHeaderPanel) {
this._bindingEventService.bind(this._preHeaderPanelScroller, 'scroll', this.handlePreHeaderPanelScroll.bind(this) as EventListener);
this._bindingEventService.bind(this._preHeaderPanelScroller, 'contextmenu', this.handlePreHeaderContextMenu.bind(this) as EventListener);
this._bindingEventService.bind(this._preHeaderPanelScrollerR, 'contextmenu', this.handlePreHeaderContextMenu.bind(this) as EventListener);
this._bindingEventService.bind(this._preHeaderPanelScroller, 'click', this.handlePreHeaderClick.bind(this) as EventListener);
this._bindingEventService.bind(this._preHeaderPanelScrollerR, 'click', this.handlePreHeaderClick.bind(this) as EventListener);
}
this._bindingEventService.bind(this._focusSink, 'keydown', this.handleKeyDown.bind(this) as EventListener);
this._bindingEventService.bind(this._focusSink2, 'keydown', this.handleKeyDown.bind(this) as EventListener);
this._canvas.forEach((element) => {
this._bindingEventService.bind(element, 'keydown', this.handleKeyDown.bind(this) as EventListener);
this._bindingEventService.bind(element, 'click', this.handleClick.bind(this) as EventListener);
this._bindingEventService.bind(element, 'dblclick', this.handleDblClick.bind(this) as EventListener);
this._bindingEventService.bind(element, 'contextmenu', this.handleContextMenu.bind(this) as EventListener);
this._bindingEventService.bind(element, 'mouseover', this.handleCellMouseOver.bind(this) as EventListener);
this._bindingEventService.bind(element, 'mouseout', this.handleCellMouseOut.bind(this) as EventListener);
});
if (Draggable) {
this.slickDraggableInstance = Draggable({
containerElement: this._container,
allowDragFrom: `div.slick-cell, div.slick-cell *, div.${this.dragReplaceEl.cssClass}`,
dragFromClassDetectArr: [{ tag: 'dragReplaceHandle', id: this.dragReplaceEl.id }],
// the slick cell parent must always contain `.dnd` and/or `.cell-reorder` class to be identified as draggable
allowDragFromClosest: 'div.slick-cell.dnd, div.slick-cell.cell-reorder',
preventDragFromKeys: this._options.preventDragFromKeys,
onDragInit: this.handleDragInit.bind(this),
onDragStart: this.handleDragStart.bind(this),
onDrag: this.handleDrag.bind(this),
onDragEnd: this.handleDragEnd.bind(this)
});
}
if (!this._options.suppressCssChangesOnHiddenInit) {
this.restoreCssFromHiddenInit();
}
}
}
/**
* Finds all container ancestors/parents (including the grid container itself) that are hidden (i.e. have display:none)
* and temporarily applies visible CSS properties (absolute positioning, hidden visibility, block display)
* so that dimensions can be measured correctly.
* It stores the original CSS properties in an internal array for later restoration.
*
* Related to issue: https://github.com/6pac/SlickGrid/issues/568 */
cacheCssForHiddenInit() {
this._hiddenParents = Utils.parents(this._container, ':hidden') as HTMLElement[];
this.oldProps = [];
this._hiddenParents.forEach(el => {
const old: Partial<CSSStyleDeclaration> = {};
Object.keys(this.cssShow).forEach(name => {
if (this.cssShow) {
old[name as any] = el.style[name as 'position' | 'visibility' | 'display'];
el.style[name as any] = this.cssShow[name as 'position' | 'visibility' | 'display'];
}
});
this