UNPKG

slickgrid

Version:

A lightning fast JavaScript grid/spreadsheet

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