UNPKG

slickgrid

Version:

A lightning fast JavaScript grid/spreadsheet

4 lines 1.38 MB
{ "version": 3, "sources": ["../../src/slick.core.ts", "../../src/controls/slick.columnmenu.ts", "../../src/controls/slick.columnpicker.ts", "../../src/controls/slick.gridmenu.ts", "../../src/controls/slick.pager.ts", "../../src/models/fieldType.enum.ts", "../../src/models/sortDirectionNumber.enum.ts", "../../src/plugins/slick.autotooltips.ts", "../../src/plugins/slick.cellcopymanager.ts", "../../src/plugins/slick.cellexternalcopymanager.ts", "../../src/plugins/slick.cellmenu.ts", "../../src/plugins/slick.cellrangedecorator.ts", "../../src/slick.interactions.ts", "../../src/plugins/slick.cellrangeselector.ts", "../../src/plugins/slick.cellselectionmodel.ts", "../../src/plugins/slick.checkboxselectcolumn.ts", "../../src/plugins/slick.contextmenu.ts", "../../src/plugins/slick.crossgridrowmovemanager.ts", "../../src/plugins/slick.customtooltip.ts", "../../src/plugins/slick.draggablegrouping.ts", "../../src/plugins/slick.headerbuttons.ts", "../../src/plugins/slick.headermenu.ts", "../../src/plugins/slick.hybridselectionmodel.ts", "../../src/plugins/slick.resizer.ts", "../../src/plugins/slick.rowdetailview.ts", "../../src/plugins/slick.rowmovemanager.ts", "../../src/plugins/slick.rowselectionmodel.ts", "../../src/plugins/slick.state.ts", "../../src/slick.compositeeditor.ts", "../../src/slick.groupitemmetadataprovider.ts", "../../src/slick.dataview.ts", "../../src/slick.editors.ts", "../../src/slick.formatters.ts", "../../src/slick.grid.ts", "../../src/slick.remotemodel-yahoo.ts", "../../src/slick.remotemodel.ts"], "sourcesContent": ["/**\n * Contains core SlickGrid classes.\n * @module Core\n * @namespace Slick\n */\n\nimport type {\n AnyFunction,\n CSSStyleDeclarationWritable,\n EditController,\n ElementEventListener,\n Handler,\n InferDOMType,\n MergeTypes,\n DragRange\n} from './models/index.js';\nimport type { SlickGrid } from './slick.grid.js';\n\nexport interface BasePubSub {\n publish<ArgType = any>(_eventName: string | any, _data?: ArgType): any;\n subscribe<ArgType = any>(_eventName: string | Function, _callback: (data: ArgType) => void): any;\n}\n\n/**\n * An event object for passing data to event handlers and letting them control propagation.\n * <p>This is pretty much identical to how W3C and jQuery implement events.</p>\n * @class EventData\n * @constructor\n */\nexport class SlickEventData<ArgType = any> {\n protected _isPropagationStopped = false;\n protected _isImmediatePropagationStopped = false;\n protected _isDefaultPrevented = false;\n protected returnValues: string[] = [];\n protected returnValue: any = undefined;\n protected _eventTarget?: EventTarget | null;\n protected nativeEvent?: Event | null;\n protected arguments_?: ArgType;\n\n // public props that can be optionally pulled from the provided Event in constructor\n // they are all optional props because it really depends on the type of Event provided (KeyboardEvent, MouseEvent, ...)\n readonly altKey?: boolean;\n readonly ctrlKey?: boolean;\n readonly metaKey?: boolean;\n readonly shiftKey?: boolean;\n readonly key?: string;\n readonly keyCode?: number;\n readonly clientX?: number;\n readonly clientY?: number;\n readonly offsetX?: number;\n readonly offsetY?: number;\n readonly pageX?: number;\n readonly pageY?: number;\n readonly bubbles?: boolean;\n readonly target?: HTMLElement;\n readonly type?: string;\n readonly which?: number;\n readonly x?: number;\n readonly y?: number;\n\n get defaultPrevented() {\n return this._isDefaultPrevented;\n }\n\n constructor(protected event?: Event | null, protected args?: ArgType) {\n this.nativeEvent = event;\n this.arguments_ = args;\n\n // when we already have an event, we want to keep some of the event properties\n // looping through some props is the only way to keep and sync these properties to the returned EventData\n if (event) {\n [\n 'altKey', 'ctrlKey', 'metaKey', 'shiftKey', 'key', 'keyCode',\n 'clientX', 'clientY', 'offsetX', 'offsetY', 'pageX', 'pageY',\n 'bubbles', 'target', 'type', 'which', 'x', 'y'\n ].forEach(key => (this as any)[key] = event[key as keyof Event]);\n }\n this._eventTarget = this.nativeEvent ? this.nativeEvent.target : undefined;\n }\n\n /**\n * Stops event from propagating up the DOM tree.\n * @method stopPropagation\n */\n stopPropagation() {\n this._isPropagationStopped = true;\n this.nativeEvent?.stopPropagation();\n }\n\n /**\n * Returns whether stopPropagation was called on this event object.\n * @method isPropagationStopped\n * @return {Boolean}\n */\n isPropagationStopped() {\n return this._isPropagationStopped;\n }\n\n /**\n * Prevents the rest of the handlers from being executed.\n * @method stopImmediatePropagation\n */\n stopImmediatePropagation() {\n this._isImmediatePropagationStopped = true;\n if (this.nativeEvent) {\n this.nativeEvent.stopImmediatePropagation();\n }\n };\n\n /**\n * Returns whether stopImmediatePropagation was called on this event object.\\\n * @method isImmediatePropagationStopped\n * @return {Boolean}\n */\n isImmediatePropagationStopped() {\n return this._isImmediatePropagationStopped;\n };\n\n getNativeEvent<E extends Event>() {\n return this.nativeEvent as E;\n }\n\n preventDefault() {\n if (this.nativeEvent) {\n this.nativeEvent.preventDefault();\n }\n this._isDefaultPrevented = true;\n }\n\n isDefaultPrevented() {\n if (this.nativeEvent) {\n return this.nativeEvent.defaultPrevented;\n }\n return this._isDefaultPrevented;\n }\n\n addReturnValue(value: any) {\n this.returnValues.push(value);\n if (this.returnValue === undefined && value !== undefined) {\n this.returnValue = value;\n }\n }\n\n getReturnValue() {\n return this.returnValue;\n }\n\n getArguments() {\n return this.arguments_;\n }\n}\n\n/**\n * A simple publisher-subscriber implementation.\n * @class Event\n * @constructor\n */\nexport class SlickEvent<ArgType = any> {\n protected _handlers: Handler<ArgType>[] = [];\n protected _pubSubService?: BasePubSub;\n\n get subscriberCount() {\n return this._handlers.length;\n }\n\n /**\n * Constructor\n * @param {String} [eventName] - event name that could be used for dispatching CustomEvent (when enabled)\n * @param {BasePubSub} [pubSubService] - event name that could be used for dispatching CustomEvent (when enabled)\n */\n constructor(protected readonly eventName?: string, protected readonly pubSub?: BasePubSub) {\n this._pubSubService = pubSub;\n }\n\n /**\n * Adds an event handler to be called when the event is fired.\n * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>\n * object the event was fired with.<p>\n * @method subscribe\n * @param {Function} fn - Event handler.\n */\n subscribe(fn: Handler<ArgType>) {\n this._handlers.push(fn);\n }\n\n /**\n * Removes an event handler added with <code>subscribe(fn)</code>.\n * @method unsubscribe\n * @param {Function} [fn] - Event handler to be removed.\n */\n unsubscribe(fn?: Handler<ArgType>) {\n for (let i = this._handlers.length - 1; i >= 0; i--) {\n if (this._handlers[i] === fn) {\n this._handlers.splice(i, 1);\n }\n }\n }\n\n /**\n * Fires an event notifying all subscribers.\n * @method notify\n * @param {Object} args Additional data object to be passed to all handlers.\n * @param {EventData} [event] - An <code>EventData</code> object to be passed to all handlers.\n * For DOM events, an existing W3C event object can be passed in.\n * @param {Object} [scope] - The scope (\"this\") within which the handler will be executed.\n * If not specified, the scope will be set to the <code>Event</code> instance.\n */\n notify(args: ArgType, evt?: SlickEventData<ArgType> | Event | MergeTypes<SlickEventData<ArgType>, Event> | null, scope?: any) {\n const sed: SlickEventData = evt instanceof SlickEventData\n ? evt\n : new SlickEventData(evt, args);\n scope = scope || this;\n\n for (let i = 0; i < this._handlers.length && !(sed.isPropagationStopped() || sed.isImmediatePropagationStopped()); i++) {\n const returnValue = this._handlers[i].call(scope, sed, args);\n sed.addReturnValue(returnValue);\n }\n\n // user can optionally add a global PubSub Service which makes it easy to publish/subscribe to events\n if (typeof this._pubSubService?.publish === 'function' && this.eventName) {\n const ret = this._pubSubService.publish<{ args: ArgType; eventData?: SlickEventData<ArgType>; nativeEvent?: Event; }>(this.eventName, { args, eventData: sed });\n sed.addReturnValue(ret);\n }\n return sed;\n }\n\n setPubSubService(pubSub: BasePubSub) {\n this._pubSubService = pubSub;\n }\n}\n\nexport class SlickEventHandler {\n protected handlers: Array<{ event: SlickEvent; handler: Handler<any>; }> = [];\n\n subscribe<T = any>(event: SlickEvent<T>, handler: Handler<T>) {\n this.handlers.push({ event, handler });\n event.subscribe(handler);\n\n return this as SlickEventHandler; // allow chaining\n }\n\n unsubscribe<T = any>(event: SlickEvent<T>, handler: Handler<T>) {\n let i = this.handlers.length;\n while (i--) {\n if (this.handlers[i].event === event &&\n this.handlers[i].handler === handler) {\n this.handlers.splice(i, 1);\n event.unsubscribe(handler);\n return;\n }\n }\n\n return this as SlickEventHandler; // allow chaining\n }\n\n unsubscribeAll() {\n let i = this.handlers.length;\n while (i--) {\n this.handlers[i].event.unsubscribe(this.handlers[i].handler);\n }\n this.handlers = [];\n\n return this as SlickEventHandler; // allow chaining\n }\n}\n\n/**\n * A structure containing a range of cells.\n * @class Range\n * @constructor\n * @param fromRow {Integer} Starting row.\n * @param fromCell {Integer} Starting cell.\n * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.\n * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.\n */\nexport class SlickRange {\n fromRow: number;\n fromCell: number;\n toCell: number;\n toRow: number;\n\n constructor(fromRow: number, fromCell: number, toRow?: number, toCell?: number) {\n if (toRow === undefined && toCell === undefined) {\n toRow = fromRow;\n toCell = fromCell;\n }\n\n /**\n * @property fromRow\n * @type {Integer}\n */\n this.fromRow = Math.min(fromRow, toRow as number);\n\n /**\n * @property fromCell\n * @type {Integer}\n */\n this.fromCell = Math.min(fromCell, toCell as number);\n\n /**\n * @property toCell\n * @type {Integer}\n */\n this.toCell = Math.max(fromCell, toCell as number);\n\n /**\n * @property toRow\n * @type {Integer}\n */\n this.toRow = Math.max(fromRow, toRow as number);\n }\n\n\n /**\n * Returns whether a range represents a single row.\n * @method isSingleRow\n * @return {Boolean}\n */\n isSingleRow() {\n return this.fromRow === this.toRow;\n }\n\n /**\n * Returns whether a range represents a single cell.\n * @method isSingleCell\n * @return {Boolean}\n */\n isSingleCell() {\n return this.fromRow === this.toRow && this.fromCell === this.toCell;\n }\n\n /**\n * Row Count.\n * @method rowCount\n * @return {Number}\n */\n rowCount() {\n return this.toRow - this.fromRow + 1;\n }\n\n /**\n * Cell Count.\n * @method cellCount\n * @return {Number}\n */\n cellCount() {\n return this.toCell - this.fromCell + 1;\n }\n\n /**\n * Returns whether a range contains a given cell.\n * @method contains\n * @param row {Integer}\n * @param cell {Integer}\n * @return {Boolean}\n */\n contains(row: number, cell: number) {\n return row >= this.fromRow && row <= this.toRow &&\n cell >= this.fromCell && cell <= this.toCell;\n }\n\n /**\n * Returns a readable representation of a range.\n * @method toString\n * @return {String}\n */\n toString() {\n if (this.isSingleCell()) {\n return `(${this.fromRow}:${this.fromCell})`;\n }\n else {\n return `(${this.fromRow}:${this.fromCell} - ${this.toRow}:${this.toCell})`;\n }\n };\n}\n\n/**\n * A structure containing a range of cells to copy to.\n * @class SlickCopyRange\n * @constructor\n * @param fromRow {Integer} Starting row.\n * @param fromCell {Integer} Starting cell.\n * @param rowCount {Integer} Row Count.\n * @param cellCount {Integer} Cell Count.\n */\nexport class SlickCopyRange {\n fromRow: number;\n fromCell: number;\n rowCount: number;\n cellCount: number;\n\n constructor(fromRow: number, fromCell: number, rowCount: number, cellCount: number) {\n this.fromRow = fromRow;\n this.fromCell = fromCell;\n this.rowCount = rowCount;\n this.cellCount = cellCount;\n }\n\n}\n\n/**\n * Create a handle element for Excel style drag-replace\n * @class DragExtendHandle\n * @constructor\n * @param gridUid {String} string UID of parent grid\n */\nexport class SlickDragExtendHandle {\n id: string;\n cssClass = 'slick-drag-replace-handle';\n\n constructor(gridUid: string) {\n this.id = `${gridUid}_drag_replace_handle`;\n }\n\n removeEl() {\n document.getElementById(this.id)?.remove();\n }\n\n createEl(activeCellNode: any) {\n if (activeCellNode) {\n const dragReplaceEl = document.createElement(\"div\");\n dragReplaceEl.classList.add(\"slick-drag-replace-handle\");\n dragReplaceEl.id = this.id;\n activeCellNode.appendChild(dragReplaceEl);\n }\n }\n}\n\n/**\n * A base class that all special / non-data rows (like Group and GroupTotals) derive from.\n * @class NonDataItem\n * @constructor\n */\nexport class SlickNonDataItem {\n __nonDataRow = true;\n}\n\n\n/**\n * Information about a group of rows.\n * @class Group\n * @extends Slick.NonDataItem\n * @constructor\n */\nexport class SlickGroup extends SlickNonDataItem {\n __group = true;\n\n /**\n * Grouping level, starting with 0.\n * @property level\n * @type {Number}\n */\n level = 0;\n\n /**\n * Number of rows in the group.\n * @property count\n * @type {Integer}\n */\n count = 0;\n\n /**\n * Grouping value.\n * @property value\n * @type {Object}\n */\n value = null;\n\n /**\n * Formatted display value of the group.\n * @property title\n * @type {String}\n */\n title: string | null = null;\n\n /**\n * Whether a group is collapsed.\n * @property collapsed\n * @type {Boolean}\n */\n collapsed: boolean | number = false;\n\n /**\n * Whether a group selection checkbox is checked.\n * @property selectChecked\n * @type {Boolean}\n */\n selectChecked = false;\n\n /**\n * GroupTotals, if any.\n * @property totals\n * @type {GroupTotals}\n */\n totals: SlickGroupTotals = null as any;\n\n /**\n * Rows that are part of the group.\n * @property rows\n * @type {Array}\n */\n rows: number[] = [];\n\n /**\n * Sub-groups that are part of the group.\n * @property groups\n * @type {Array}\n */\n groups: any[] = null as any;\n\n /**\n * A unique key used to identify the group. This key can be used in calls to DataView\n * collapseGroup() or expandGroup().\n * @property groupingKey\n * @type {Object}\n */\n groupingKey: any = null;\n\n constructor() {\n super();\n }\n /**\n * Compares two Group instances.\n * @method equals\n * @return {Boolean}\n * @param group {Group} Group instance to compare to.\n */\n equals(group: SlickGroup): boolean {\n return this.value === group.value &&\n this.count === group.count &&\n this.collapsed === group.collapsed &&\n this.title === group.title;\n };\n}\n\n/**\n * Information about group totals.\n * An instance of GroupTotals will be created for each totals row and passed to the aggregators\n * so that they can store arbitrary data in it. That data can later be accessed by group totals\n * formatters during the display.\n * @class GroupTotals\n * @extends Slick.NonDataItem\n * @constructor\n */\nexport class SlickGroupTotals extends SlickNonDataItem {\n __groupTotals = true;\n\n /**\n * Parent Group.\n * @param group\n * @type {Group}\n */\n group: SlickGroup = null as any;\n\n /**\n * Whether the totals have been fully initialized / calculated.\n * Will be set to false for lazy-calculated group totals.\n * @param initialized\n * @type {Boolean}\n */\n initialized = false;\n\n constructor() {\n super();\n }\n}\n\n/**\n * A locking helper to track the active edit controller and ensure that only a single controller\n * can be active at a time. This prevents a whole class of state and validation synchronization\n * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress\n * and attempt a commit or cancel before proceeding.\n * @class EditorLock\n * @constructor\n */\nexport class SlickEditorLock {\n activeEditController: any = null;\n\n /**\n * Returns true if a specified edit controller is active (has the edit lock).\n * If the parameter is not specified, returns true if any edit controller is active.\n * @method isActive\n * @param editController {EditController}\n * @return {Boolean}\n */\n isActive(editController?: EditController): boolean {\n return (editController ? this.activeEditController === editController : this.activeEditController !== null);\n };\n\n /**\n * Sets the specified edit controller as the active edit controller (acquire edit lock).\n * If another edit controller is already active, and exception will be throw new Error(.\n * @method activate\n * @param editController {EditController} edit controller acquiring the lock\n */\n activate(editController: EditController) {\n if (editController === this.activeEditController) { // already activated?\n return;\n }\n if (this.activeEditController !== null) {\n throw new Error(`Slick.EditorLock.activate: an editController is still active, can't activate another editController`);\n }\n if (!editController.commitCurrentEdit) {\n throw new Error('Slick.EditorLock.activate: editController must implement .commitCurrentEdit()');\n }\n if (!editController.cancelCurrentEdit) {\n throw new Error('Slick.EditorLock.activate: editController must implement .cancelCurrentEdit()');\n }\n this.activeEditController = editController;\n };\n\n /**\n * Unsets the specified edit controller as the active edit controller (release edit lock).\n * If the specified edit controller is not the active one, an exception will be throw new Error(.\n * @method deactivate\n * @param editController {EditController} edit controller releasing the lock\n */\n deactivate(editController: EditController) {\n if (!this.activeEditController) {\n return;\n }\n if (this.activeEditController !== editController) {\n throw new Error('Slick.EditorLock.deactivate: specified editController is not the currently active one');\n }\n this.activeEditController = null;\n };\n\n /**\n * Attempts to commit the current edit by calling \"commitCurrentEdit\" method on the active edit\n * controller and returns whether the commit attempt was successful (commit may fail due to validation\n * errors, etc.). Edit controller's \"commitCurrentEdit\" must return true if the commit has succeeded\n * and false otherwise. If no edit controller is active, returns true.\n * @method commitCurrentEdit\n * @return {Boolean}\n */\n commitCurrentEdit(): boolean {\n return (this.activeEditController ? this.activeEditController.commitCurrentEdit() : true);\n };\n\n /**\n * Attempts to cancel the current edit by calling \"cancelCurrentEdit\" method on the active edit\n * controller and returns whether the edit was successfully cancelled. If no edit controller is\n * active, returns true.\n * @method cancelCurrentEdit\n * @return {Boolean}\n */\n cancelCurrentEdit(): boolean {\n return (this.activeEditController ? this.activeEditController.cancelCurrentEdit() : true);\n };\n}\n\nfunction regexSanitizer(dirtyHtml: string) {\n return dirtyHtml.replace(/(\\b)(on[a-z]+)(\\s*)=|javascript:([^>]*)[^>]*|(<\\s*)(\\/*)script([<>]*).*(<\\s*)(\\/*)script(>*)|(&lt;)(\\/*)(script|script defer)(.*)(&gt;|&gt;\">)/gi, '');\n}\n\n/**\n * A simple binding event service to keep track of all JavaScript events with callback listeners,\n * it allows us to unbind event(s) and their listener(s) by calling a simple unbind method call.\n * Unbinding is a necessary step to make sure that all event listeners are removed to avoid memory leaks when destroing the grid\n */\nexport class BindingEventService {\n protected _boundedEvents: ElementEventListener[] = [];\n\n getBoundedEvents() {\n return this._boundedEvents;\n }\n\n destroy() {\n this.unbindAll();\n }\n\n /** Bind an event listener to any element */\n bind(element: Element | Window, eventName: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions, groupName = '') {\n if (element) {\n element.addEventListener(eventName, listener, options);\n this._boundedEvents.push({ element, eventName, listener, groupName });\n }\n }\n\n /** Unbind all will remove every every event handlers that were bounded earlier */\n unbind(element: Element | Window, eventName: string, listener: EventListenerOrEventListenerObject) {\n if (element?.removeEventListener) {\n element.removeEventListener(eventName, listener);\n }\n }\n\n unbindByEventName(element: Element | Window, eventName: string) {\n const boundedEvent = this._boundedEvents.find(e => e.element === element && e.eventName === eventName);\n if (boundedEvent) {\n this.unbind(boundedEvent.element, boundedEvent.eventName, boundedEvent.listener);\n }\n }\n\n /**\n * Unbind all event listeners that were bounded, optionally provide a group name to unbind all listeners assigned to that specific group only.\n */\n unbindAll(groupName?: string | string[]) {\n if (groupName) {\n const groupNames = Array.isArray(groupName) ? groupName : [groupName];\n\n // unbind only the bounded event with a specific group\n // Note: we need to loop in reverse order to avoid array reindexing (causing index offset) after a splice is called\n for (let i = this._boundedEvents.length - 1; i >= 0; --i) {\n const boundedEvent = this._boundedEvents[i];\n if (groupNames.some(g => g === boundedEvent.groupName)) {\n const { element, eventName, listener } = boundedEvent;\n this.unbind(element, eventName, listener);\n this._boundedEvents.splice(i, 1);\n }\n }\n } else {\n // unbind everything\n while (this._boundedEvents.length > 0) {\n const boundedEvent = this._boundedEvents.pop() as ElementEventListener;\n const { element, eventName, listener } = boundedEvent;\n this.unbind(element, eventName, listener);\n }\n }\n }\n}\n\nexport class Utils {\n // jQuery's extend\n private static getProto = Object.getPrototypeOf;\n private static class2type: any = {};\n private static toString = Utils.class2type.toString;\n private static hasOwn = Utils.class2type.hasOwnProperty;\n private static fnToString = Utils.hasOwn.toString;\n private static ObjectFunctionString = Utils.fnToString.call(Object);\n public static storage = {\n // https://stackoverflow.com/questions/29222027/vanilla-alternative-to-jquery-data-function-any-native-javascript-alternati\n _storage: new WeakMap(),\n // eslint-disable-next-line object-shorthand\n put: function (element: any, key: string, obj: any) {\n if (!this._storage.has(element)) {\n this._storage.set(element, new Map());\n }\n this._storage.get(element).set(key, obj);\n },\n // eslint-disable-next-line object-shorthand\n get: function (element: any, key: string) {\n const el = this._storage.get(element);\n if (el) {\n return el.get(key);\n }\n return null;\n },\n // eslint-disable-next-line object-shorthand\n remove: function (element: any, key: string) {\n const ret = this._storage.get(element).delete(key);\n if (!(this._storage.get(element).size === 0)) {\n this._storage.delete(element);\n }\n return ret;\n }\n };\n\n public static isFunction(obj: any) {\n return typeof obj === 'function' && typeof obj.nodeType !== 'number' && typeof obj.item !== 'function';\n }\n\n public static isPlainObject(obj: any) {\n if (!obj || Utils.toString.call(obj) !== '[object Object]') {\n return false;\n }\n\n const proto = Utils.getProto(obj);\n if (!proto) {\n return true;\n }\n const Ctor = Utils.hasOwn.call(proto, 'constructor') && proto.constructor;\n return typeof Ctor === 'function' && Utils.fnToString.call(Ctor) === Utils.ObjectFunctionString;\n }\n\n public static calculateAvailableSpace(element: HTMLElement) {\n let bottom = 0, top = 0, left = 0, right = 0;\n\n const windowHeight = window.innerHeight || 0;\n const windowWidth = window.innerWidth || 0;\n const scrollPosition = Utils.windowScrollPosition();\n const pageScrollTop = scrollPosition.top;\n const pageScrollLeft = scrollPosition.left;\n const elmOffset = Utils.offset(element);\n\n if (elmOffset) {\n const elementOffsetTop = elmOffset.top || 0;\n const elementOffsetLeft = elmOffset.left || 0;\n top = elementOffsetTop - pageScrollTop;\n bottom = windowHeight - (elementOffsetTop - pageScrollTop);\n left = elementOffsetLeft - pageScrollLeft;\n right = windowWidth - (elementOffsetLeft - pageScrollLeft);\n }\n\n return { top, bottom, left, right };\n }\n\n public static extend<T = any>(...args: any[]): T {\n let options, name, src, copy, copyIsArray, clone,\n target = args[0],\n i = 1,\n deep = false;\n const length = args.length;\n\n if (typeof target === 'boolean') {\n deep = target;\n target = args[i] || {};\n i++;\n } else {\n target = target || {};\n }\n if (typeof target !== 'object' && !Utils.isFunction(target)) {\n target = {};\n }\n if (i === length) {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n target = this;\n i--;\n }\n for (; i < length; i++) {\n if (Utils.isDefined(options = args[i])) {\n for (name in options) {\n copy = options[name];\n if (name === '__proto__' || target === copy) {\n continue;\n }\n if (deep && copy && (Utils.isPlainObject(copy) ||\n (copyIsArray = Array.isArray(copy)))) {\n src = target[name];\n if (copyIsArray && !Array.isArray(src)) {\n clone = [];\n } else if (!copyIsArray && !Utils.isPlainObject(src)) {\n clone = {};\n } else {\n clone = src;\n }\n copyIsArray = false;\n target[name] = Utils.extend(deep, clone, copy);\n } else if (copy !== undefined) {\n target[name] = copy;\n }\n }\n }\n }\n return target as T;\n }\n\n /**\n * Create a DOM Element with any optional attributes or properties.\n * It will only accept valid DOM element properties that `createElement` would accept.\n * For example: `createDomElement('div', { className: 'my-css-class' })`,\n * for style or dataset you need to use nested object `{ style: { display: 'none' }}\n * The last argument is to optionally append the created element to a parent container element.\n * @param {String} tagName - html tag\n * @param {Object} options - element properties\n * @param {[HTMLElement]} appendToParent - parent element to append to\n */\n public static createDomElement<T extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap[T]>(\n tagName: T,\n elementOptions?: null | { [P in K]: InferDOMType<HTMLElementTagNameMap[T][P]> },\n appendToParent?: Element\n ): HTMLElementTagNameMap[T] {\n const elm = document.createElement<T>(tagName);\n\n if (elementOptions) {\n Object.keys(elementOptions).forEach((elmOptionKey) => {\n if (elmOptionKey === 'innerHTML') {\n console.warn(`[SlickGrid] For better CSP (Content Security Policy) support, do not use \"innerHTML\" directly in \"createDomElement('${tagName}', { innerHTML: 'some html'})\"` +\n `, it is better as separate assignment: \"const elm = createDomElement('span'); elm.innerHTML = 'some html';\"`);\n }\n\n const elmValue = elementOptions[elmOptionKey as keyof typeof elementOptions];\n if (typeof elmValue === 'object') {\n Object.assign(elm[elmOptionKey as K] as object, elmValue);\n } else {\n elm[elmOptionKey as K] = (elementOptions as any)[elmOptionKey as keyof typeof elementOptions];\n }\n });\n }\n if (appendToParent?.appendChild) {\n appendToParent.appendChild(elm);\n }\n return elm;\n }\n\n /**\n * From any input provided, return the HTML string (when a string is provided, it will be returned \"as is\" but when it's a number it will be converted to string)\n * When detecting HTMLElement/DocumentFragment, we can also specify which HTML type to retrieve innerHTML or outerHTML.\n * We can get the HTML by looping through all fragment `childNodes`\n * @param {DocumentFragment | HTMLElement | string | number} input\n * @param {'innerHTML' | 'outerHTML'} [type] - when the input is a DocumentFragment or HTMLElement, which type of HTML do you want to return? 'innerHTML' or 'outerHTML'\n * @returns {String}\n */\n public static getHtmlStringOutput(input: DocumentFragment | HTMLElement | string | number, type: 'innerHTML' | 'outerHTML' = 'innerHTML'): string {\n if (input instanceof DocumentFragment) {\n // a DocumentFragment doesn't have innerHTML/outerHTML, but we can loop through all children and concatenate them all to an HTML string\n return [].map.call(input.childNodes, (x: HTMLElement) => x[type]).join('') || input.textContent || '';\n } else if (input instanceof HTMLElement) {\n return input[type];\n }\n return String(input); // reaching this line means it's already a string (or number) so just return it as string\n }\n\n public static emptyElement<T extends Element = Element>(element?: T | null): T | undefined | null {\n while (element?.firstChild) {\n element.removeChild(element.firstChild);\n }\n return element;\n }\n\n /**\n * Accepts string containing the class or space-separated list of classes, and\n * returns list of individual classes.\n * Method properly takes into account extra whitespaces in the `className`\n * e.g.: \" class1 class2 \" => will result in `['class1', 'class2']`.\n * @param {String} className - space separated list of class names\n */\n public static classNameToList(className = ''): string[] {\n return className.split(' ').filter(cls => cls);\n }\n\n public static innerSize(elm: HTMLElement, type: 'height' | 'width') {\n let size = 0;\n\n if (elm) {\n const clientSize = type === 'height' ? 'clientHeight' : 'clientWidth';\n const sides = type === 'height' ? ['top', 'bottom'] : ['left', 'right'];\n size = elm[clientSize];\n for (const side of sides) {\n const sideSize = (parseFloat(Utils.getElementProp(elm, `padding-${side}`) || '') || 0);\n size -= sideSize;\n }\n }\n return size;\n }\n\n public static isDefined<T>(value: T | undefined | null): value is T {\n return <T>value !== undefined && <T>value !== null && <T>value !== '';\n }\n\n public static getElementProp(elm: HTMLElement & { getComputedStyle?: () => CSSStyleDeclaration }, property: string) {\n if (elm?.getComputedStyle) {\n return window.getComputedStyle(elm, null).getPropertyValue(property);\n }\n return null;\n }\n\n /**\n * Get the function details (param & body) of a function.\n * It supports regular function and also ES6 arrow functions\n * @param {Function} fn - function to analyze\n * @param {Boolean} [addReturn] - when using ES6 function as single liner, we could add the missing `return ...`\n * @returns\n */\n public static getFunctionDetails(fn: AnyFunction, addReturn = true) {\n let isAsyncFn = false;\n\n const getFunctionBody = (func: AnyFunction) => {\n const fnStr = func.toString();\n isAsyncFn = fnStr.includes('async ');\n\n // when fn is one liner arrow fn returning an object in brackets e.g. `() => ({ hello: 'world' })`\n if ((fnStr.replaceAll(' ', '').includes('=>({'))) {\n const matches = fnStr.match(/(({.*}))/g) || [];\n return matches.length >= 1 ? `return ${matches[0]!.trimStart()}` : fnStr;\n }\n const isOneLinerArrowFn = (!fnStr.includes('{') && fnStr.includes('=>'));\n const body = fnStr.substring(\n (fnStr.indexOf('{') + 1) || (fnStr.indexOf('=>') + 2),\n fnStr.includes('}') ? fnStr.lastIndexOf('}') : fnStr.length\n );\n if (addReturn && isOneLinerArrowFn && !body.startsWith('return')) {\n return 'return ' + body.trimStart(); // add the `return ...` to the body for ES6 arrow fn\n }\n return body;\n };\n\n const getFunctionParams = (func: AnyFunction): string[] => {\n const STRIP_COMMENTS = /(\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/)|(\\s*=[^,)]*(('(?:\\\\'|[^'\\r\\n])*')|(\"(?:\\\\\"|[^\"\\r\\n])*\"))|(\\s*=[^,)]*))/mg;\n const ARG_NAMES = /([^\\s,]+)/g;\n const fnStr = func.toString().replace(STRIP_COMMENTS, '');\n return fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARG_NAMES) ?? [];\n };\n\n return {\n params: getFunctionParams(fn),\n body: getFunctionBody(fn),\n isAsync: isAsyncFn,\n };\n }\n\n public static insertAfterElement(referenceNode: HTMLElement, newNode: HTMLElement) {\n referenceNode.parentNode?.insertBefore(newNode, referenceNode.nextSibling);\n }\n\n public static isEmptyObject(obj: any) {\n if (obj === null || obj === undefined) {\n return true;\n }\n return Object.entries(obj).length === 0;\n }\n\n public static noop() { }\n\n public static offset(el: HTMLElement | null) {\n if (!el || !el.getBoundingClientRect) {\n return undefined;\n }\n const box = el.getBoundingClientRect();\n const docElem = document.documentElement;\n\n return {\n top: box.top + window.pageYOffset - docElem.clientTop,\n left: box.left + window.pageXOffset - docElem.clientLeft\n };\n }\n\n public static windowScrollPosition() {\n return {\n left: window.pageXOffset || document.documentElement.scrollLeft || 0,\n top: window.pageYOffset || document.documentElement.scrollTop || 0,\n };\n }\n\n public static width(el: HTMLElement, value?: number | string): number | void {\n if (!el || !el.getBoundingClientRect) { return; }\n if (value === undefined) {\n return el.getBoundingClientRect().width;\n }\n Utils.setStyleSize(el, 'width', value);\n }\n\n public static height(el: HTMLElement, value?: number | string): number | void {\n if (!el) { return; }\n if (value === undefined) {\n return el.getBoundingClientRect().height;\n }\n Utils.setStyleSize(el, 'height', value);\n }\n\n public static setStyleSize(el: HTMLElement, style: string, val?: number | string | Function) {\n if (typeof val === 'function') {\n val = val();\n } else if (typeof val === 'string') {\n el.style[style as CSSStyleDeclarationWritable] = val;\n } else {\n el.style[style as CSSStyleDeclarationWritable] = val + 'px';\n }\n }\n\n public static contains(parent: HTMLElement, child: HTMLElement) {\n if (!parent || !child) {\n return false;\n }\n\n const parentList = Utils.parents(child);\n return !parentList.every((p) => {\n if (parent === p) {\n return false;\n }\n return true;\n });\n }\n\n public static isHidden(el: HTMLElement) {\n return el.offsetWidth === 0 && el.offsetHeight === 0;\n }\n\n public static parents(el: HTMLElement | ParentNode, selector?: string) {\n const parents: Array<HTMLElement | ParentNode> = [];\n const visible = selector === ':visible';\n const hidden = selector === ':hidden';\n\n while ((el = el.parentNode as ParentNode) && el !== document) {\n if (!el || !el.parentNode) {\n break;\n }\n if (hidden) {\n if (Utils.isHidden(el as HTMLElement)) {\n parents.push(el);\n }\n } else if (visible) {\n if (!Utils.isHidden(el as HTMLElement)) {\n parents.push(el);\n }\n } else if (!selector || (el as any).matches(selector)) {\n parents.push(el);\n }\n }\n return parents;\n }\n\n public static toFloat(value: string | number) {\n const x = parseFloat(value as string);\n if (isNaN(x)) {\n return 0;\n }\n return x;\n }\n\n public static show(el: HTMLElement | HTMLElement[], type = '') {\n if (Array.isArray(el)) {\n el.forEach((e) => e.style.display = type);\n } else {\n el.style.display = type;\n }\n }\n\n public static hide(el: HTMLElement | HTMLElement[]) {\n if (Array.isArray(el)) {\n el.forEach((e) => e.style.display = 'none');\n } else {\n el.style.display = 'none';\n }\n }\n\n public static slideUp(el: HTMLElement | HTMLElement[], callback: Function) {\n return Utils.slideAnimation(el, 'slideUp', callback);\n }\n\n public static slideDown(el: HTMLElement | HTMLElement[], callback: Function) {\n return Utils.slideAnimation(el, 'slideDown', callback);\n }\n\n public static slideAnimation(el: HTMLElement | HTMLElement[], slideDirection: 'slideDown' | 'slideUp', callback: Function) {\n if ((window as any).jQuery !== undefined) {\n (window as any).jQuery(el)[slideDirection]('fast', callback);\n return;\n }\n (slideDirection === 'slideUp') ? Utils.hide(el) : Utils.show(el);\n callback();\n }\n\n public static applyDefaults(targetObj: any, srcObj: any) {\n if (typeof srcObj === 'object') {\n Object.keys(srcObj).forEach(key => {\n if (srcObj.hasOwnProperty(key) && !targetObj.hasOwnProperty(key)) {\n targetObj[key] = srcObj[key];\n }\n });\n }\n }\n\n /**\n * User could optionally add PubSub Service to SlickEvent\n * When it is defined then a SlickEvent `notify()` call will also dispatch it by using the PubSub publish() method\n * @param {BasePubSub} [pubSubService]\n * @param {*} scope\n */\n public static addSlickEventPubSubWhenDefined<T = any>(pubSub?: BasePubSub, scope?: T) {\n if (pubSub) {\n for (const prop in scope) {\n if (scope[prop] instanceof SlickEvent && typeof (scope[prop] as SlickEvent).setPubSubService === 'function') {\n (scope[prop] as SlickEvent).setPubSubService(pubSub);\n }\n }\n }\n }\n}\n\nexport class SelectionUtils {\n\n // |---0----|---1----|---2----|---3----|---4----|---5----|\n // 0 | | | | ^ | | |\n // |--------|--------|--------|--------|--------|--------|\n // 1 | | | | | | |\n // |--------|--------|--------|--------|--------|--------|\n // 2 | | | 1 | 2 | > h | |\n // |--------|--------|--------|--------|--------|--------|\n // 3 | < | | 4 | 5 x| > h | > |\n // |--------|--------|--------|--------|--------|--------|\n // 4 | | | > v | > v | > v | |\n // |--------|--------|--------|--------|--------|--------|\n // 5 | | | | v | | |\n // |--------|--------|--------|--------|--------|--------|\n //\n // original range (1,2,4,5) expanded one cell to right and down\n // '> h' indicates horizontal target copy area\n // '> v' indicates vertical target copy area\n // note bottom right (corner) cell is considered part of vertical copy area\n\n public static normaliseDragRange(rawRange: DragRange) {\n // depending how the range is created (drag up/down) the start row/cell may be\n // greater or less thatn the end row/cell. Create a guaranteed left/down\n // progressive range (ie. start row/cell < end row/cell)\n\n const rtn: DragRange = {\n start: {\n row: (rawRange.end.row ?? 0) > (rawRange.start.row ?? 0) ? rawRange.start.row : rawRange.end.row,\n cell: (rawRange.end.cell ?? 0) > (rawRange.start.cell ?? 0) ? rawRange.start.cell : rawRange.end.cell\n },\n end: {\n row: (rawRange.end.row ?? 0) > (rawRange.start.row ?? 0) ? rawRange.end.row : rawRange.start.row,\n cell: (rawRange.end.cell ?? 0) > (rawRange.start.cell ?? 0) ? rawRange.end.cell : rawRange.start.cell\n }\n };\n rtn.rowCount = (rtn.end.row ?? 0) - (rtn.start.row ?? 0) + 1;\n rtn.cellCount = (rtn.end.cell ?? 0) - (rtn.start.cell ?? 0) + 1;\n\n rtn.wasDraggedUp = (rawRange.end.row ?? 0) < (rawRange.start.row ?? 0);\n rtn.wasDraggedLeft = (rawRange.end.row ?? 0) < (rawRange.start.row ?? 0);\n\n return rtn;\n }\n\n public static copyRangeIsLarger(baseRange: SlickRange, copyToRange: SlickRange): boolean {\n return copyToRange.fromRow < baseRange.fromRow\n || copyToRange.fromCell < baseRange.fromCell\n || copyToRange.toRow > baseRange.toRow\n || copyToRange.toCell > baseRange.toCell\n ;\n }\n\n public static normalRangeOppositeCellFromCopy(normalisedDragRange: DragRange, targetCell: { row: number, cell: number }): { row: number, cell: number } {\n const row = targetCell.row < (normalisedDragRange.end.row || 0)\n ? (normalisedDragRange.end.row || 0)\n : (normalisedDragRange.start.row || 0)\n ;\n const cell = targetCell.cell < (normalisedDragRange.end.cell || 0)\n ? (normalisedDragRange.end.cell || 0)\n : (normalisedDragRange.start.cell || 0)\n ;\n return { row, cell };\n }\n\n // copy to range above or below - includes corner space target range\n public static verticalTargetRange(baseRange: SlickRange, copyToRange: SlickRange) {\n const copyUp = copyToRange.fromRow < baseRange.fromRow;\n const copyDown = copyToRange.toRow > baseRange.toRow;\n if (!copyUp && !copyDown) {\n return null;\n }\n let rtn;\n if (copyUp) {\n rtn = new Range(copyToRange.fromRow, copyToRange.fromCell, baseRange.fromRow - 1, baseRange.toCell);\n } else {\n rtn = new Range(baseRange.toRow + 1, copyToRange.fromCell, copyToRange.toRow, baseRange.toCell);\n }\n return rtn;\n }\n\n // copy to range left or right - excludes corner space target range\n public static horizontalTargetRange(baseRange: SlickRange, copyToRange: SlickRange) {\n const copyLeft = copyToRange.fromCell < baseRange.fromCell;\n const copyRight = copyToRange.toCell > baseRange.toCell;\n if (!copyLeft && !copyRight) {\n return null;\n }\n let rtn;\n if (copyLeft) {\n rtn = new Range(baseRange.fromRow, copyToRange.fromCell, baseRange.toRow, baseRange.fromCell - 1);\n } else {\n rtn = new Range(baseRange.fromRow, baseRange.toCell + 1, baseRange.toRow, copyToRange.toCell);\n }\n return rtn;\n }\n\n // copy to corner space target range\n public static cornerTargetRange(baseRange: SlickRange, copyToRange: SlickRange) {\n const copyUp = copyToRange.fromRow < baseRange.fromRow;\n const copyDown = copyToRange.toRow > baseRange.toRow;\n const copyLeft = copyToRange.fromCell < baseRange.fromCell;\n const copyRight = copyToRange.toCell > baseRange.toCell;\n if ((!copyLeft && !copyRight) || (!copyUp && !copyDown)) {\n return null;\n }\n let rtn;\n if (copyLeft) {\n if (copyUp) {\n rtn = new Range(copyToRange.fromRow, copyToRange.fromCell, baseRange.fromRow - 1, baseRange.fromCell - 1);\n } else {\n rtn = new Range(baseRange.toRow + 1, copyToRange.fromCell, copyToRange.toRow, baseRange.fromCell - 1);\n }\n } else {\n if (copyUp) {\n rtn = new Range(copyToRange.fromRow, baseRange.toCell + 1, baseRange.fromRow - 1, copyToRange.toCell);\n } else {\n rtn = new Range(baseRange.toRow + 1, baseRange.toCell + 1, copyToRange.toRow, copyToRange.toCell);\n }\n }\n return rtn;\n }\n\n public static copyCellsToTargetRange(baseRange: SlickRange, targetRange: SlickRange, grid: SlickGrid) {\n let fromRowOffset = 0, fromCellOffset = 0;\n const columns = grid.getVisibleColumns();\n const options = grid.getOptions();\n\n for (let i = 0; i < targetRange.rowCount(); i++) {\n const toRow = grid.getDataItem(targetRange.fromRow + i);\n const fromRow = grid.getDataItem(baseRange.fromRow + fromRowOffset);\n fromCellOffset = 0;\n\n for (let j = 0; j < targetRange.cellCount(); j++) {\n const toColDef = columns[targetRange.fromCell + j];\n const fromColDef = columns[baseRange.fromCell + fromCellOffset];\n\n if (!toColDef.hidden && !fromColDef.hidden) {\n let val = fromRow[fromColDef.field];\n if (options.dataItemColumnValueExtractor) {\n val = options.dataItemColumnValueExtractor(fromRow, fromColDef);\n }\n toRow[toColDef.field] = val;\n }\n\n fromCellOffset++;\n if (fromCellOffset >= baseRange.cellCount()) { fromCellOffset = 0; }\n }\n\n fromRowOffset++;\n if (fromRowOffset >= baseRange.rowCount()) { fromRowOffset = 0; }\n }\n }\n}\n\nexport const SlickGlobalEditorLock = new SlickEditorLock();\n\n// export Slick namespace on both global & window objects\nconst SlickCore = {\n Event: SlickEvent,\n EventData: SlickEventData,\n EventHandler: SlickEventHandler,\n Range: SlickRange,\n CopyRange: SlickCopyRange,\n DragExtendHandle: SlickDragExtendHandle,\n NonDataRow: SlickNonDataItem,\n Group: SlickGroup,\n GroupTotals: SlickGroupTotals,\n EditorLock: SlickEditorLock,\n RegexSanitizer: regexSanitizer,\n\n /**\n * A global singleton editor lock.\n * @class GlobalEditorLock\n * @static\n * @constructor\n */\n GlobalEditorLock: SlickGlobalEditorLock,\n\n keyCode: {\n SPACE: 8,\n BACKSPACE: 8,\n DELETE: 46,\n DOWN: 40,\n END: 35,\n ENTER: 13,\n ESCAPE: 27,\n HOME: 36,\n INSERT: 45,\n LEFT: 37,\n PAGE_DOWN: 34,\n PAGE_UP: 33,\n RIGHT: 39,\n TAB: 9,\n UP: 38,\n A: 65\n },\n preClickClassName: 'slick-edit-preclick',\n\n GridAutosizeColsMode: {\n None: 'NOA',\n LegacyOff: 'LOF',\n LegacyForceFit: 'LFF',\n IgnoreViewport: 'IGV',\n FitColsToViewport: 'FCV',\n FitViewportToCols: 'FVC'\n },\n\n 'ColAutosizeMode': {\n Locked: 'LCK',\n Guide: 'GUI',\n Content: 'CON',\n ContentExpandOnly: 'CXO',\n ContentIntelligent: 'CTI'\n },\n\n 'RowSelectionMode': {\n FirstRow: 'FS1',\n FirstNRows: 'FSN',\n AllRows: 'ALL',\n LastRow: 'LS1'\n },\n\n 'CellSelectionMode': {\n Select: \"SEL\",\n Replace: \"REP\"\n },\n\n 'ValueFilterMode': {\n None: 'NONE',\n DeDuplicate: 'DEDP',\n GetGreatestAndSub: 'GR8T',\n GetLongestTextAndSub: 'LNSB',\n GetLongestText: 'LNSC'\n },\n\n WidthEvalMode: {\n Auto: 'AUTO',\n TextOnly: 'CANV',\n HTML: 'HTML'\n }\n};\n\nexport const {\n EditorLock, Event, EventData, EventHandler, Group, GroupTotals, NonDataRow, Range, CopyRange, DragExtendHandle,\n RegexSanitizer, GlobalEditorLock, keyCode, preClickClassName, GridAutosizeColsMode, ColAutosizeMode,\n RowSelectionMode, CellSelectionMode, ValueFilterMode, WidthEvalMode\n} = SlickCore;\n\n// also add to global object when exist\nif (IIFE_ONLY && typeof global !== 'undefined' && window.Slick) {\n global.Slick = window.Slick;\n}\n", "import { BindingEventService as BindingEventService_, Event as SlickEvent_, type SlickEventData, Utils as Utils_ } from '../slick.core.js';\nimport type { Column, ColumnPickerOption, DOMMouseOrTouchEvent, GridOption, OnColumnsChangedArgs } from '../models/index.js';\nimport type { SlickGrid } from '../slick.grid.js';\n\n// for (iife) load Slick methods from global Slick object, or use imports for (esm)\nconst BindingEventService = IIFE_ONLY ? Slick.BindingEventService : BindingEventService_;\nconst SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\n\n/***\n * A control to add a Column Picker (right+click on any column header to reveal the column picker)\n * NOTE: this a simplified and updated version of slick.columnpicker.js\n *\n * USAGE:\n *\n * Add the slick.columnpicker.(js|css) files and register it with the grid.\n *\n * Available options, by defining a columnPicker object:\n *\n * let options = {\n * enableCellNavigation: true,\n * columnPicker: {\n * columnTitle: \"Columns\", // default to empty string\n *\n * // the last 2 checkboxes titles\n * hideForceFitButton: false, // show/hide checkbox near the end \"Force Fit Columns\" (default:false)\n * hideSyncResizeButton: false, // show/hide checkbox near the end \"Synchronous Resize\" (default:false)\n * forceFitTitle: \"Force fit columns\", // default to \"Force fit columns\"\n * headerColumnValueExtractor: \"Extract the column label\" // default to column.name\n * syncResizeTitle: \"Synchronous resize\", // default to \"Synchronous resize\"\n * }\n * };\n */\n\nexport class SlickColumnMenu {\n // --\n // public API\n onColumnsChanged = new SlickEvent<OnColumnsChangedArgs>