{ "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(_eventName: string | any, _data?: ArgType): any;\r\n subscribe(_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 *

This is pretty much identical to how W3C and jQuery implement events.

\r\n * @class EventData\r\n * @constructor\r\n */\r\nexport class SlickEventData {\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() {\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 {\r\n protected _handlers: Handler[] = [];\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 *

Event handler will receive two arguments - an EventData and the data\r\n * object the event was fired with.

\r\n * @method subscribe\r\n * @param {Function} fn - Event handler.\r\n */\r\n subscribe(fn: Handler) {\r\n this._handlers.push(fn);\r\n }\r\n\r\n /**\r\n * Removes an event handler added with subscribe(fn).\r\n * @method unsubscribe\r\n * @param {Function} [fn] - Event handler to be removed.\r\n */\r\n unsubscribe(fn?: Handler) {\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 EventData 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 Event instance.\r\n */\r\n notify(args: ArgType, evt?: SlickEventData | Event | MergeTypes, 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; 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; }> = [];\r\n\r\n subscribe(event: SlickEvent, handler: Handler) {\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(event: SlickEvent, handler: Handler) {\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 fromRow.\r\n * @param toCell {Integer} Optional. Ending cell. Defaults to fromCell.\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(>*)|(<)(\\/*)(script|script defer)(.*)(>|>\">)/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(...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(\r\n tagName: T,\r\n elementOptions?: null | { [P in K]: InferDOMType },\r\n appendToParent?: Element\r\n ): HTMLElementTagNameMap[T] {\r\n const elm = document.createElement(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(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(value: T | undefined | null): value is T {\r\n return value !== undefined && value !== null && 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 = [];\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(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('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) {\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.createElement('input');\r\n forceFitCheckboxElm.type = 'checkbox';\r\n forceFitCheckboxElm.id = `${this._gridUid}colpicker-forcefit`;\r\n forceFitCheckboxElm.dataset.option = 'autoresize';\r\n liElm.appendChild(forceFitCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}colpicker-forcefit`;\r\n labelElm.textContent = forceFitTitle || '';\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().forceFitColumns) {\r\n forceFitCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n if (!this._options.columnPicker?.hideSyncResizeButton) {\r\n const syncResizeTitle = this._options.columnPicker?.syncResizeTitle || this._options.syncResizeTitle;\r\n\r\n const liElm = document.createElement('li');\r\n liElm.ariaLabel = syncResizeTitle || '';\r\n this._listElm.appendChild(liElm);\r\n\r\n const syncResizeCheckboxElm = document.createElement('input');\r\n syncResizeCheckboxElm.type = 'checkbox';\r\n syncResizeCheckboxElm.id = `${this._gridUid}colpicker-syncresize`;\r\n syncResizeCheckboxElm.dataset.option = 'syncresize';\r\n liElm.appendChild(syncResizeCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}colpicker-syncresize`;\r\n labelElm.textContent = syncResizeTitle || '';\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().syncColumnCellResize) {\r\n syncResizeCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n this.repositionMenu(e);\r\n }\r\n\r\n repositionMenu(event: DOMMouseOrTouchEvent | SlickEventData) {\r\n const targetEvent = (event as TouchEvent)?.touches?.[0] || event;\r\n this._menuElm.style.top = `${targetEvent.pageY - 10}px`;\r\n this._menuElm.style.left = `${targetEvent.pageX - 10}px`;\r\n this._menuElm.style.maxHeight = `${window.innerHeight - targetEvent.clientY}px`;\r\n this._menuElm.style.display = 'block';\r\n this._menuElm.setAttribute('aria-expanded', 'true');\r\n this._menuElm.appendChild(this._listElm);\r\n }\r\n\r\n updateColumnOrder() {\r\n // Because columns can be reordered, we have to update the `columns`\r\n // to reflect the new order, however we can't just take `grid.getColumns()`,\r\n // as it does not include columns currently hidden by the picker.\r\n // We create a new `columns` structure by leaving currently-hidden\r\n // columns in their original ordinal position and interleaving the results\r\n // of the current column sort.\r\n const current = this.grid.getColumns().slice(0);\r\n const ordered = new Array(this.columns.length);\r\n for (let i = 0; i < ordered.length; i++) {\r\n if (this.grid.getColumnIndex(this.columns[i].id) === undefined) {\r\n // If the column doesn't return a value from getColumnIndex,\r\n // it is hidden. Leave it in this position.\r\n ordered[i] = this.columns[i];\r\n } else {\r\n // Otherwise, grab the next visible column.\r\n ordered[i] = current.shift();\r\n }\r\n }\r\n this.columns = ordered;\r\n }\r\n\r\n /** Update the Titles of each sections (command, customTitle, ...) */\r\n updateAllTitles(pickerOptions: { columnTitle: string; }) {\r\n this.grid.applyHtmlCode(this._columnTitleElm, pickerOptions.columnTitle);\r\n }\r\n\r\n updateColumn(e: DOMMouseOrTouchEvent) {\r\n if (e.target.dataset.option === 'autoresize') {\r\n // when calling setOptions, it will resize with ALL Columns (even the hidden ones)\r\n // we can avoid this problem by keeping a reference to the visibleColumns before setOptions and then setColumns after\r\n const previousVisibleColumns = this.getVisibleColumns();\r\n const isChecked = e.target.checked;\r\n this.grid.setOptions({ forceFitColumns: isChecked });\r\n this.grid.setColumns(previousVisibleColumns);\r\n return;\r\n }\r\n\r\n if (e.target.dataset.option === 'syncresize') {\r\n if (e.target.checked) {\r\n this.grid.setOptions({ syncColumnCellResize: true });\r\n } else {\r\n this.grid.setOptions({ syncColumnCellResize: false });\r\n }\r\n return;\r\n }\r\n\r\n if (e.target.type === 'checkbox') {\r\n const isChecked = e.target.checked;\r\n const columnId = e.target.dataset.columnid || '';\r\n const visibleColumns: Column[] = [];\r\n this._columnCheckboxes.forEach((columnCheckbox, idx) => {\r\n if (this.columns[idx].hidden !== undefined) { this.columns[idx].hidden = !columnCheckbox.checked; }\r\n if (columnCheckbox.checked) {\r\n visibleColumns.push(this.columns[idx]);\r\n }\r\n });\r\n\r\n if (!visibleColumns.length) {\r\n e.target.checked = true;\r\n return;\r\n }\r\n\r\n this.grid.setColumns(visibleColumns);\r\n this.onColumnsChanged.notify({ columnId, showing: isChecked, allColumns: this.columns, columns: this.columns, visibleColumns, grid: this.grid });\r\n }\r\n }\r\n\r\n /** @deprecated because of a typo @use `setColumnVisibility()` instead */\r\n setColumnVisibiliy(idxOrId: number | string, show: boolean) {\r\n this.setColumnVisibility(idxOrId, show);\r\n }\r\n\r\n setColumnVisibility(idxOrId: number | string, show: boolean) {\r\n const idx = typeof idxOrId === 'number' ? idxOrId : this.getColumnIndexbyId(idxOrId);\r\n let visibleColumns: Column[] = this.getVisibleColumns();\r\n const col = this.columns[idx];\r\n if (show) {\r\n col.hidden = false;\r\n visibleColumns.splice(idx, 0, col);\r\n } else {\r\n const newVisibleColumns: Column[] = [];\r\n for (let i = 0; i < visibleColumns.length; i++) {\r\n if (visibleColumns[i].id !== col.id) { newVisibleColumns.push(visibleColumns[i]); }\r\n }\r\n visibleColumns = newVisibleColumns;\r\n }\r\n\r\n this.grid.setColumns(visibleColumns);\r\n this.onColumnsChanged.notify({ columnId: col.id, showing: show, allColumns: this.columns, columns: this.columns, visibleColumns, grid: this.grid });\r\n }\r\n\r\n getAllColumns() {\r\n return this.columns;\r\n }\r\n\r\n getColumnbyId(id: number | string) {\r\n for (let i = 0; i < this.columns.length; i++) {\r\n if (this.columns[i].id === id) { return this.columns[i]; }\r\n }\r\n return null;\r\n }\r\n\r\n getColumnIndexbyId(id: number | string) {\r\n for (let i = 0; i < this.columns.length; i++) {\r\n if (this.columns[i].id === id) { return i; }\r\n }\r\n return -1;\r\n }\r\n\r\n /** visible columns, we can simply get them directly from the grid */\r\n getVisibleColumns() {\r\n return this.grid.getColumns();\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n window.Slick.Controls = window.Slick.Controls || {};\r\n window.Slick.Controls.ColumnPicker = SlickColumnMenu;\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 is the old 'complex' column pciker that hides columns by removing them from the grid\r\n * for a more modern version that uses the column.hidden property and is a lot simpler, use slick.columnmenu.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 SlickColumnPicker {\r\n // --\r\n // public API\r\n onColumnsChanged = new SlickEvent('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 _gridOptions: 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, gridOptions: GridOption) {\r\n this._gridUid = grid.getUID();\r\n this._gridOptions = Utils.extend({}, this._defaults, gridOptions);\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.onColumnsReordered.subscribe(this.updateColumnOrder.bind(this));\r\n grid.onHeaderContextMenu.subscribe(this.handleHeaderContextMenu.bind(this));\r\n grid.onPreHeaderContextMenu.subscribe((e) => {\r\n if (['slick-column-name', 'slick-header-column'].some(className => e.target?.classList.contains(className))) {\r\n this.handleHeaderContextMenu(e); // open picker only when preheader has column groups\r\n }\r\n });\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._gridOptions.columnPickerTitle || (this._gridOptions.columnPicker?.columnTitle)) {\r\n const columnTitle = this._gridOptions.columnPickerTitle || this._gridOptions.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.onPreHeaderContextMenu.unsubscribe(this.handleHeaderContextMenu.bind(this));\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 protected handleBodyMouseDown(e: DOMMouseOrTouchEvent) {\r\n if ((this._menuElm !== e.target && !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 protected 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._gridOptions?.columnPicker?.headerColumnValueExtractor)\r\n ? this._gridOptions.columnPicker.headerColumnValueExtractor(this.columns[i], this._gridOptions)\r\n : this._defaults.headerColumnValueExtractor!(this.columns[i], this._gridOptions);\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._gridOptions.columnPicker && (!this._gridOptions.columnPicker.hideForceFitButton || !this._gridOptions.columnPicker.hideSyncResizeButton)) {\r\n this._listElm.appendChild(document.createElement('hr'));\r\n }\r\n\r\n if (!(this._gridOptions.columnPicker?.hideForceFitButton)) {\r\n const forceFitTitle = this._gridOptions.columnPicker?.forceFitTitle || this._gridOptions.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.createElement('input');\r\n forceFitCheckboxElm.type = 'checkbox';\r\n forceFitCheckboxElm.id = `${this._gridUid}colpicker-forcefit`;\r\n forceFitCheckboxElm.dataset.option = 'autoresize';\r\n liElm.appendChild(forceFitCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}colpicker-forcefit`;\r\n labelElm.textContent = forceFitTitle || '';\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().forceFitColumns) {\r\n forceFitCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n if (!(this._gridOptions.columnPicker?.hideSyncResizeButton)) {\r\n const syncResizeTitle = (this._gridOptions.columnPicker?.syncResizeTitle) || this._gridOptions.syncResizeTitle;\r\n\r\n const liElm = document.createElement('li');\r\n liElm.ariaLabel = syncResizeTitle || '';\r\n this._listElm.appendChild(liElm);\r\n\r\n const syncResizeCheckboxElm = document.createElement('input');\r\n syncResizeCheckboxElm.type = 'checkbox';\r\n syncResizeCheckboxElm.id = `${this._gridUid}colpicker-syncresize`;\r\n syncResizeCheckboxElm.dataset.option = 'syncresize';\r\n liElm.appendChild(syncResizeCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}colpicker-syncresize`;\r\n labelElm.textContent = syncResizeTitle || '';\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().syncColumnCellResize) {\r\n syncResizeCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n this.repositionMenu(e);\r\n }\r\n\r\n protected repositionMenu(event: DOMMouseOrTouchEvent | SlickEventData) {\r\n const targetEvent: MouseEvent | Touch = (event as TouchEvent)?.touches?.[0] ?? event;\r\n if (this._menuElm) {\r\n this._menuElm.style.display = 'block';\r\n\r\n // auto-positioned menu left/right by available position\r\n const gridPos = this.grid.getGridPosition();\r\n const menuWidth = this._menuElm.clientWidth || 0;\r\n let menuOffsetLeft = targetEvent.pageX || 0;\r\n if (gridPos?.width && (menuOffsetLeft + menuWidth >= gridPos.width)) {\r\n menuOffsetLeft = menuOffsetLeft - menuWidth;\r\n }\r\n\r\n this._menuElm.style.top = `${targetEvent.pageY - 10}px`;\r\n this._menuElm.style.left = `${menuOffsetLeft}px`;\r\n this._menuElm.style.maxHeight = `${window.innerHeight - targetEvent.clientY}px`;\r\n this._menuElm.setAttribute('aria-expanded', 'true');\r\n this._menuElm.appendChild(this._listElm);\r\n }\r\n }\r\n\r\n protected updateColumnOrder() {\r\n // Because columns can be reordered, we have to update the `columns`\r\n // to reflect the new order, however we can't just take `grid.getColumns()`,\r\n // as it does not include columns currently hidden by the picker.\r\n // We create a new `columns` structure by leaving currently-hidden\r\n // columns in their original ordinal position and interleaving the results\r\n // of the current column sort.\r\n const current = this.grid.getColumns().slice(0);\r\n const ordered = new Array(this.columns.length);\r\n for (let i = 0; i < ordered.length; i++) {\r\n if (this.grid.getColumnIndex(this.columns[i].id) === undefined) {\r\n // If the column doesn't return a value from getColumnIndex,\r\n // it is hidden. Leave it in this position.\r\n ordered[i] = this.columns[i];\r\n } else {\r\n // Otherwise, grab the next visible column.\r\n ordered[i] = current.shift();\r\n }\r\n }\r\n this.columns = ordered;\r\n }\r\n\r\n /** Update the Titles of each sections (command, customTitle, ...) */\r\n updateAllTitles(pickerOptions: { columnTitle: string; }) {\r\n this.grid.applyHtmlCode(this._columnTitleElm, pickerOptions.columnTitle);\r\n }\r\n\r\n protected updateColumn(e: DOMMouseOrTouchEvent) {\r\n if (e.target.dataset.option === 'autoresize') {\r\n // when calling setOptions, it will resize with ALL Columns (even the hidden ones)\r\n // we can avoid this problem by keeping a reference to the visibleColumns before setOptions and then setColumns after\r\n const previousVisibleColumns = this.getVisibleColumns();\r\n const isChecked: boolean = e.target.checked || false;\r\n this.grid.setOptions({ forceFitColumns: isChecked });\r\n this.grid.setColumns(previousVisibleColumns);\r\n return;\r\n }\r\n\r\n if (e.target.dataset.option === 'syncresize') {\r\n if (e.target.checked) {\r\n this.grid.setOptions({ syncColumnCellResize: true });\r\n } else {\r\n this.grid.setOptions({ syncColumnCellResize: false });\r\n }\r\n return;\r\n }\r\n\r\n if (e.target.type === 'checkbox') {\r\n const isChecked = e.target.checked;\r\n const columnId = e.target.dataset.columnid || '';\r\n const visibleColumns: Column[] = [];\r\n this._columnCheckboxes.forEach((columnCheckbox, idx) => {\r\n if (this.columns[idx].hidden !== undefined) { this.columns[idx].hidden = !columnCheckbox.checked; }\r\n if (columnCheckbox.checked) {\r\n visibleColumns.push(this.columns[idx]);\r\n }\r\n });\r\n\r\n if (!visibleColumns.length) {\r\n e.target.checked = true;\r\n return;\r\n }\r\n\r\n this.grid.setColumns(visibleColumns);\r\n this.onColumnsChanged.notify({ columnId, showing: isChecked, allColumns: this.columns, columns: this.columns, visibleColumns, grid: this.grid });\r\n }\r\n }\r\n\r\n /** @deprecated because of a typo @use `setColumnVisibility()` instead */\r\n setColumnVisibiliy(idxOrId: number | string, show: boolean) {\r\n this.setColumnVisibility(idxOrId, show);\r\n }\r\n\r\n setColumnVisibility(idxOrId: number | string, show: boolean) {\r\n const idx = typeof idxOrId === 'number' ? idxOrId : this.getColumnIndexbyId(idxOrId);\r\n let visibleColumns = this.getVisibleColumns();\r\n const col = this.columns[idx];\r\n if (show) {\r\n col.hidden = false;\r\n visibleColumns.splice(idx, 0, col);\r\n } else {\r\n const newVisibleColumns: Column[] = [];\r\n for (let i = 0; i < visibleColumns.length; i++) {\r\n if (visibleColumns[i].id !== col.id) { newVisibleColumns.push(visibleColumns[i]); }\r\n }\r\n visibleColumns = newVisibleColumns;\r\n }\r\n\r\n this.grid.setColumns(visibleColumns);\r\n this.onColumnsChanged.notify({ columnId: col.id, showing: show, allColumns: this.columns, columns: this.columns, visibleColumns, grid: this.grid });\r\n }\r\n\r\n getAllColumns() {\r\n return this.columns;\r\n }\r\n\r\n getColumnbyId(id: number | string) {\r\n for (let i = 0; i < this.columns.length; i++) {\r\n if (this.columns[i].id === id) { return this.columns[i]; }\r\n }\r\n return null;\r\n }\r\n\r\n getColumnIndexbyId(id: number | string) {\r\n for (let i = 0; i < this.columns.length; i++) {\r\n if (this.columns[i].id === id) { return i; }\r\n }\r\n return -1;\r\n }\r\n\r\n /** visible columns, we can simply get them directly from the grid */\r\n getVisibleColumns() {\r\n return this.grid.getColumns();\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n window.Slick.Controls = window.Slick.Controls || {};\r\n window.Slick.Controls.ColumnPicker = SlickColumnPicker;\r\n}\r\n", "import type {\r\n Column,\r\n DOMMouseOrTouchEvent,\r\n GridMenuCommandItemCallbackArgs,\r\n GridMenuEventWithElementCallbackArgs,\r\n GridMenuItem,\r\n GridMenuOption,\r\n GridOption,\r\n MenuCommandItem,\r\n onGridMenuColumnsChangedCallbackArgs\r\n} from '../models/index.js';\r\nimport { BindingEventService as BindingEventService_, SlickEvent as SlickEvent_, Utils as Utils_ } from '../slick.core.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 Grid Menu (hambuger menu on top-right of the grid)\r\n *\r\n * USAGE:\r\n *\r\n * Add the slick.gridmenu.(js|css) files and register it with the grid.\r\n *\r\n * To specify a menu in a column header, extend the column definition like so:\r\n * let gridMenuControl = new Slick.Controls.GridMenu(columns, grid, options);\r\n *\r\n * Available grid options, by defining a gridMenu object:\r\n *\r\n * let options = {\r\n * enableCellNavigation: true,\r\n * gridMenu: {\r\n * commandTitle: \"Command List\", // default to empty string\r\n * columnTitle: \"Columns\", // default to empty string\r\n * iconImage: \"some-image.png\", // this is the Grid Menu icon (hamburger icon)\r\n * iconCssClass: \"fa fa-bars\", // you can provide iconImage OR iconCssClass\r\n * leaveOpen: false, // do we want to leave the Grid Menu open after a command execution? (false by default)\r\n * menuWidth: 18, // width (icon) that will be use to resize the column header container (18 by default)\r\n * contentMinWidth: 0,\t\t\t\t\t\t\t // defaults to 0 (auto), minimum width of grid menu content (command, column list)\r\n * marginBottom: 15, // defaults to 15, margin to use at the bottom of the grid when using max-height (default)\r\n * resizeOnShowHeaderRow: false, // false by default\r\n * showButton: true, // true by default - it allows the user to control if the\r\n * // default gridMenu button (located on the top right corner by default CSS)\r\n * // should be created or omitted\r\n * useClickToRepositionMenu: true, // true by default\r\n *\r\n * // the last 2 checkboxes titles\r\n * hideForceFitButton: false, // show/hide checkbox near the end \"Force Fit Columns\"\r\n * hideSyncResizeButton: false, // show/hide checkbox near the end \"Synchronous Resize\"\r\n * forceFitTitle: \"Force fit columns\", // default to \"Force fit columns\"\r\n * syncResizeTitle: \"Synchronous resize\", // default to \"Synchronous resize\"\r\n *\r\n * commandItems: [\r\n * {\r\n * // command menu item options\r\n * },\r\n * {\r\n * // command menu item options\r\n * }\r\n * ]\r\n * }\r\n * };\r\n *\r\n *\r\n * Available menu options:\r\n * hideForceFitButton: Hide the \"Force fit columns\" button (defaults to false)\r\n * hideSyncResizeButton: Hide the \"Synchronous resize\" button (defaults to false)\r\n * forceFitTitle: Text of the title \"Force fit columns\"\r\n * contentMinWidth:\t\t\t\t\t\tminimum width of grid menu content (command, column list), defaults to 0 (auto)\r\n * height: Height of the Grid Menu content, when provided it will be used instead of the max-height (defaults to undefined)\r\n * menuWidth: Grid menu button width (defaults to 18)\r\n * resizeOnShowHeaderRow: Do we want to resize on the show header row event\r\n * syncResizeTitle: Text of the title \"Synchronous resize\"\r\n * useClickToRepositionMenu: Use the Click offset to reposition the Grid Menu (defaults to true), when set to False it will use the icon offset to reposition the grid menu\r\n * menuUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling the menu from being usable (must be combined with a custom formatter)\r\n * marginBottom: Margin to use at the bottom of the grid menu, only in effect when height is undefined (defaults to 15)\r\n * subItemChevronClass: CSS class that can be added on the right side of a sub-item parent (typically a chevron-right icon)\r\n * subMenuOpenByEvent: defaults to \"mouseover\", what event type shoud we use to open sub-menu(s), 2 options are available: \"mouseover\" or \"click\"\r\n *\r\n * Available command menu item options:\r\n * action: Optionally define a callback function that gets executed when item is chosen (and/or use the onCommand event)\r\n * title: Menu item text.\r\n * divider: Whether the current item is a divider, not an actual command.\r\n * disabled: Whether the item/command is disabled.\r\n * hidden: Whether the item/command is hidden.\r\n * tooltip: Item tooltip.\r\n * command: A command identifier to be passed to the onCommand event handlers.\r\n * cssClass: A CSS class to be added to the menu item container.\r\n * iconCssClass: A CSS class to be added to the menu item icon.\r\n * iconImage: A url to the icon image.\r\n * textCssClass: A CSS class to be added to the menu item text.\r\n * subMenuTitle: Optional sub-menu title that will shows up when sub-menu commmands/options list is opened\r\n * subMenuTitleCssClass: Optional sub-menu title CSS class to use with `subMenuTitle`\r\n * itemVisibilityOverride: Callback method that user can override the default behavior of showing/hiding an item from the list\r\n * itemUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling an item from the list\r\n *\r\n *\r\n * The plugin exposes the following events:\r\n *\r\n * onAfterMenuShow: Fired after the menu is shown. You can customize the menu or dismiss it by returning false.\r\n * * ONLY works with a JS event (as per slick.core code), so we cannot notify when it's a button event (when grid menu is attached to an external button, not the hamburger menu)\r\n * Event args:\r\n * grid: Reference to the grid.\r\n * column: Column definition.\r\n * menu: Menu options. Note that you can change the menu items here.\r\n *\r\n * onBeforeMenuShow: Fired before the menu is shown. You can customize the menu or dismiss it by returning false.\r\n * * ONLY works with a JS event (as per slick.core code), so we cannot notify when it's a button event (when grid menu is attached to an external button, not the hamburger menu)\r\n * Event args:\r\n * grid: Reference to the grid.\r\n * column: Column definition.\r\n * menu: Menu options. Note that you can change the menu items here.\r\n *\r\n * onMenuClose: Fired when the menu is closing.\r\n * Event args:\r\n * grid: Reference to the grid.\r\n * column: Column definition.\r\n * menu: Menu options. Note that you can change the menu items here.\r\n *\r\n * onCommand: Fired on menu item click for buttons with 'command' specified.\r\n * Event args:\r\n * grid: Reference to the grid.\r\n * column: Column definition.\r\n * command: Button command identified.\r\n * button: Button options. Note that you can change the button options in your\r\n * event handler, and the column header will be automatically updated to\r\n * reflect them. This is useful if you want to implement something like a\r\n * toggle button.\r\n */\r\n\r\nexport class SlickGridMenu {\r\n // --\r\n // public API\r\n onAfterMenuShow = new SlickEvent('onAfterMenuShow');\r\n onBeforeMenuShow = new SlickEvent('onBeforeMenuShow');\r\n onMenuClose = new SlickEvent('onMenuClose');\r\n onCommand = new SlickEvent('onCommand');\r\n onColumnsChanged = new SlickEvent('onColumnsChanged');\r\n\r\n // --\r\n // protected props\r\n protected _bindingEventService: BindingEventService_;\r\n protected _gridOptions: GridOption;\r\n protected _gridUid: string;\r\n protected _isMenuOpen = false;\r\n protected _columnCheckboxes: HTMLInputElement[] = [];\r\n protected _columnTitleElm!: HTMLElement;\r\n protected _commandTitleElm!: HTMLElement;\r\n protected _commandListElm!: HTMLDivElement;\r\n protected _headerElm: HTMLDivElement | null = null;\r\n protected _listElm!: HTMLElement;\r\n protected _buttonElm!: HTMLElement;\r\n protected _menuElm!: HTMLElement;\r\n protected _subMenuParentId = '';\r\n protected _gridMenuOptions: GridMenuOption | null = null;\r\n protected _defaults: GridMenuOption = {\r\n showButton: true,\r\n hideForceFitButton: false,\r\n hideSyncResizeButton: false,\r\n forceFitTitle: 'Force fit columns',\r\n marginBottom: 15,\r\n menuWidth: 18,\r\n contentMinWidth: 0,\r\n resizeOnShowHeaderRow: false,\r\n subMenuOpenByEvent: 'mouseover',\r\n syncResizeTitle: 'Synchronous resize',\r\n useClickToRepositionMenu: true,\r\n headerColumnValueExtractor: (columnDef: Column) => Utils.getHtmlStringOutput(columnDef.name || '', 'innerHTML'),\r\n };\r\n\r\n constructor(protected columns: Column[], protected readonly grid: SlickGrid, gridOptions: GridOption) {\r\n this._gridUid = grid.getUID();\r\n this._gridOptions = gridOptions;\r\n this._gridMenuOptions = Utils.extend({}, this._defaults, gridOptions.gridMenu);\r\n this._bindingEventService = new BindingEventService();\r\n\r\n // when a grid optionally changes from a regular grid to a frozen grid, we need to destroy & recreate the grid menu\r\n // we do this change because the Grid Menu is on the left container for a regular grid, it is however on the right container for a frozen grid\r\n grid.onSetOptions.subscribe((_e, args) => {\r\n if (args && args.optionsBefore && args.optionsAfter) {\r\n const switchedFromRegularToFrozen = args.optionsBefore.frozenColumn! >= 0 && args.optionsAfter.frozenColumn === -1;\r\n const switchedFromFrozenToRegular = args.optionsBefore.frozenColumn === -1 && args.optionsAfter.frozenColumn! >= 0;\r\n if (switchedFromRegularToFrozen || switchedFromFrozenToRegular) {\r\n this.recreateGridMenu();\r\n }\r\n }\r\n });\r\n this.init(this.grid);\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n this._gridOptions = grid.getOptions();\r\n Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);\r\n this.createGridMenu();\r\n\r\n if (this._gridMenuOptions?.customItems || this._gridMenuOptions?.customTitle) {\r\n console.warn('[SlickGrid] Grid Menu \"customItems\" and \"customTitle\" were deprecated to align with other Menu plugins, please use \"commandItems\" and \"commandTitle\" instead.');\r\n }\r\n\r\n // subscribe to the grid, when it's destroyed, we should also destroy the Grid Menu\r\n grid.onBeforeDestroy.subscribe(this.destroy.bind(this));\r\n }\r\n\r\n setOptions(newOptions: GridMenuOption) {\r\n this._gridMenuOptions = Utils.extend({}, this._gridMenuOptions, newOptions);\r\n }\r\n\r\n protected createGridMenu() {\r\n const gridMenuWidth = (this._gridMenuOptions?.menuWidth) || this._defaults.menuWidth;\r\n if (this._gridOptions && this._gridOptions.hasOwnProperty('frozenColumn') && this._gridOptions.frozenColumn! >= 0) {\r\n this._headerElm = document.querySelector(`.${this._gridUid} .slick-header-right`);\r\n } else {\r\n this._headerElm = document.querySelector(`.${this._gridUid} .slick-header-left`);\r\n }\r\n this._headerElm!.style.width = `calc(100% - ${gridMenuWidth}px)`;\r\n\r\n // if header row is enabled, we need to resize its width also\r\n const enableResizeHeaderRow = (Utils.isDefined(this._gridMenuOptions?.resizeOnShowHeaderRow)) ? this._gridMenuOptions!.resizeOnShowHeaderRow : this._defaults.resizeOnShowHeaderRow;\r\n if (enableResizeHeaderRow && this._gridOptions.showHeaderRow) {\r\n const headerRow = document.querySelector(`.${this._gridUid}.slick-headerrow`);\r\n if (headerRow) {\r\n headerRow.style.width = `calc(100% - ${gridMenuWidth}px)`;\r\n }\r\n }\r\n\r\n const showButton = (this._gridMenuOptions?.showButton !== undefined) ? this._gridMenuOptions.showButton : this._defaults.showButton;\r\n if (showButton) {\r\n this._buttonElm = document.createElement('button');\r\n this._buttonElm.className = 'slick-gridmenu-button';\r\n this._buttonElm.ariaLabel = 'Grid Menu';\r\n\r\n if (this._gridMenuOptions?.iconCssClass) {\r\n this._buttonElm.classList.add(...Utils.classNameToList(this._gridMenuOptions.iconCssClass));\r\n } else {\r\n const iconImageElm = document.createElement('img');\r\n iconImageElm.src = (this._gridMenuOptions?.iconImage) ? this._gridMenuOptions.iconImage : '../images/drag-handle.png';\r\n this._buttonElm.appendChild(iconImageElm);\r\n }\r\n\r\n // add the grid menu button in the preheader (when exists) or always in the column header (default)\r\n const buttonContainerTarget = this._gridMenuOptions?.iconButtonContainer === 'preheader' ? 'firstChild' : 'lastChild';\r\n this._headerElm!.parentElement!.insertBefore(this._buttonElm, this._headerElm!.parentElement![buttonContainerTarget]);\r\n\r\n // add on click handler for the Grid Menu itself\r\n this._bindingEventService.bind(this._buttonElm, 'click', this.showGridMenu.bind(this) as EventListener);\r\n }\r\n\r\n this._menuElm = this.createMenu(0);\r\n this.populateColumnPicker();\r\n document.body.appendChild(this._menuElm);\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 /** Create the menu or sub-menu(s) but without the column picker which is a separate single process */\r\n createMenu(level = 0, item?: GridMenuItem | MenuCommandItem | 'divider') {\r\n // create a new cell menu\r\n const maxHeight = isNaN(this._gridMenuOptions?.maxHeight as number) ? this._gridMenuOptions?.maxHeight : `${this._gridMenuOptions?.maxHeight ?? 0}px`;\r\n const width = isNaN(this._gridMenuOptions?.width as number) ? this._gridMenuOptions?.width : `${this._gridMenuOptions?.maxWidth ?? 0}px`;\r\n\r\n // to avoid having multiple sub-menu trees opened,\r\n // we need to somehow keep trace of which parent menu the tree belongs to\r\n // and we should keep ref of only the first sub-menu parent, we can use the command name (remove any whitespaces though)\r\n const subMenuCommand = (item as GridMenuItem)?.command;\r\n let subMenuId = (level === 1 && subMenuCommand) ? subMenuCommand.replaceAll(' ', '') : '';\r\n if (subMenuId) {\r\n this._subMenuParentId = subMenuId;\r\n }\r\n if (level > 1) {\r\n subMenuId = this._subMenuParentId;\r\n }\r\n\r\n const menuClasses = `slick-gridmenu slick-menu-level-${level} ${this._gridUid}`;\r\n const bodyMenuElm = document.body.querySelector(`.slick-gridmenu.slick-menu-level-${level}${this.getGridUidSelector()}`);\r\n\r\n // return menu/sub-menu if it's already opened unless we are on different sub-menu tree if so close them all\r\n if (bodyMenuElm) {\r\n if (bodyMenuElm.dataset.subMenuParent === subMenuId) {\r\n return bodyMenuElm;\r\n }\r\n this.destroySubMenus();\r\n }\r\n\r\n const menuElm = document.createElement('div');\r\n menuElm.role = 'menu';\r\n menuElm.className = menuClasses;\r\n if (level > 0) {\r\n menuElm.classList.add('slick-submenu');\r\n if (subMenuId) {\r\n menuElm.dataset.subMenuParent = subMenuId;\r\n }\r\n }\r\n menuElm.ariaLabel = level > 1 ? 'SubMenu' : 'Grid Menu';\r\n\r\n if (width) {\r\n menuElm.style.width = width as string;\r\n }\r\n if (maxHeight) {\r\n menuElm.style.maxHeight = maxHeight as string;\r\n }\r\n\r\n menuElm.style.display = 'none';\r\n\r\n let closeButtonElm: HTMLButtonElement | null = null;\r\n if (level === 0) {\r\n closeButtonElm = document.createElement('button');\r\n closeButtonElm.type = 'button';\r\n closeButtonElm.className = 'close';\r\n closeButtonElm.dataset.dismiss = 'slick-gridmenu';\r\n closeButtonElm.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 closeButtonElm.appendChild(spanCloseElm);\r\n menuElm.appendChild(closeButtonElm);\r\n }\r\n\r\n // -- Command List section\r\n this._commandListElm = document.createElement('div');\r\n this._commandListElm.className = `slick-gridmenu-custom slick-gridmenu-command-list slick-menu-level-${level}`;\r\n this._commandListElm.role = 'menu';\r\n menuElm.appendChild(this._commandListElm);\r\n\r\n const commandItems =\r\n (item as GridMenuItem)?.commandItems\r\n ?? (item as GridMenuItem)?.customItems\r\n ?? this._gridMenuOptions?.commandItems\r\n ?? this._gridMenuOptions?.customItems\r\n ?? [];\r\n\r\n if (commandItems.length > 0) {\r\n\r\n // when creating sub-menu add its sub-menu title when exists\r\n if (item && level > 0) {\r\n this.addSubMenuTitleWhenExists(item, this._commandListElm); // add sub-menu title when exists\r\n }\r\n }\r\n this.populateCommandsMenu(commandItems, this._commandListElm, { grid: this.grid, level });\r\n\r\n // increment level for possible next sub-menus if exists\r\n level++;\r\n\r\n return menuElm;\r\n }\r\n\r\n /** Destroy the plugin by unsubscribing every events & also delete the menu DOM elements */\r\n destroy() {\r\n this.onAfterMenuShow.unsubscribe();\r\n this.onBeforeMenuShow.unsubscribe();\r\n this.onMenuClose.unsubscribe();\r\n this.onCommand.unsubscribe();\r\n this.onColumnsChanged.unsubscribe();\r\n this.grid.onColumnsReordered.unsubscribe(this.updateColumnOrder.bind(this));\r\n this.grid.onBeforeDestroy.unsubscribe();\r\n this.grid.onSetOptions.unsubscribe();\r\n this._bindingEventService.unbindAll();\r\n this._menuElm?.remove();\r\n this.deleteMenu();\r\n }\r\n\r\n /** Delete the menu DOM element but without unsubscribing any events */\r\n deleteMenu() {\r\n this._bindingEventService.unbindAll();\r\n const gridMenuElm = document.querySelector(`div.slick-gridmenu.${this._gridUid}`);\r\n if (gridMenuElm) {\r\n gridMenuElm.style.display = 'none';\r\n }\r\n if (this._headerElm) {\r\n // put back original width (fixes width and frozen+gridMenu on left header)\r\n this._headerElm.style.width = '100%';\r\n }\r\n this._buttonElm?.remove();\r\n this._menuElm?.remove();\r\n }\r\n\r\n /** Close and destroy all previously opened sub-menus */\r\n destroySubMenus() {\r\n this._bindingEventService.unbindAll('sub-menu');\r\n document.querySelectorAll(`.slick-gridmenu.slick-submenu${this.getGridUidSelector()}`)\r\n .forEach(subElm => subElm.remove());\r\n }\r\n\r\n /** Construct the custom command menu items. */\r\n protected populateCommandsMenu(commandItems: Array, commandListElm: HTMLElement, args: { grid: SlickGrid, level: number }) {\r\n // user could pass a title on top of the custom section\r\n const level = args?.level || 0;\r\n const isSubMenu = level > 0;\r\n if (!isSubMenu && (this._gridMenuOptions?.commandTitle || this._gridMenuOptions?.customTitle)) {\r\n this._commandTitleElm = document.createElement('div');\r\n this._commandTitleElm.className = 'title';\r\n this.grid.applyHtmlCode(this._commandTitleElm, this.grid.sanitizeHtmlString((this._gridMenuOptions.commandTitle || this._gridMenuOptions.customTitle) as string));\r\n commandListElm.appendChild(this._commandTitleElm);\r\n }\r\n\r\n for (let i = 0, ln = commandItems.length; i < ln; i++) {\r\n let addClickListener = true;\r\n const item = commandItems[i];\r\n const callbackArgs = {\r\n grid: this.grid,\r\n menu: this._menuElm,\r\n columns: this.columns,\r\n visibleColumns: this.getVisibleColumns()\r\n };\r\n\r\n // run each override functions to know if the item is visible and usable\r\n const isItemVisible = this.runOverrideFunctionWhenExists((item as GridMenuItem).itemVisibilityOverride, callbackArgs);\r\n const isItemUsable = this.runOverrideFunctionWhenExists((item as GridMenuItem).itemUsabilityOverride, callbackArgs);\r\n\r\n // if the result is not visible then there's no need to go further\r\n if (!isItemVisible) {\r\n continue;\r\n }\r\n\r\n // when the override is defined, we need to use its result to update the disabled property\r\n // so that \"handleMenuItemClick\" has the correct flag and won't trigger a command clicked event\r\n if (Object.prototype.hasOwnProperty.call(item, 'itemUsabilityOverride')) {\r\n (item as GridMenuItem).disabled = isItemUsable ? false : true;\r\n }\r\n\r\n const liElm = document.createElement('div');\r\n liElm.className = 'slick-gridmenu-item';\r\n liElm.role = 'menuitem';\r\n\r\n if ((item as GridMenuItem).divider || item === 'divider') {\r\n liElm.classList.add('slick-gridmenu-item-divider');\r\n addClickListener = false;\r\n }\r\n if ((item as GridMenuItem).disabled) {\r\n liElm.classList.add('slick-gridmenu-item-disabled');\r\n }\r\n\r\n if ((item as GridMenuItem).hidden) {\r\n liElm.classList.add('slick-gridmenu-item-hidden');\r\n }\r\n\r\n if ((item as GridMenuItem).cssClass) {\r\n liElm.classList.add(...Utils.classNameToList((item as GridMenuItem).cssClass));\r\n }\r\n\r\n if ((item as GridMenuItem).tooltip) {\r\n liElm.title = (item as GridMenuItem).tooltip || '';\r\n }\r\n\r\n const iconElm = document.createElement('div');\r\n iconElm.className = 'slick-gridmenu-icon';\r\n\r\n liElm.appendChild(iconElm);\r\n\r\n if ((item as GridMenuItem).iconCssClass) {\r\n iconElm.classList.add(...Utils.classNameToList((item as GridMenuItem).iconCssClass));\r\n }\r\n\r\n if ((item as GridMenuItem).iconImage) {\r\n iconElm.style.backgroundImage = `url(${(item as GridMenuItem).iconImage})`;\r\n }\r\n\r\n const textElm = document.createElement('span');\r\n textElm.className = 'slick-gridmenu-content';\r\n this.grid.applyHtmlCode(textElm, this.grid.sanitizeHtmlString((item as GridMenuItem).title || ''));\r\n\r\n liElm.appendChild(textElm);\r\n\r\n if ((item as GridMenuItem).textCssClass) {\r\n textElm.classList.add(...Utils.classNameToList((item as GridMenuItem).textCssClass));\r\n }\r\n\r\n commandListElm.appendChild(liElm);\r\n\r\n if (addClickListener) {\r\n const eventGroup = isSubMenu ? 'sub-menu' : 'parent-menu';\r\n this._bindingEventService.bind(liElm, 'click', this.handleMenuItemClick.bind(this, item, level) as EventListener, undefined, eventGroup);\r\n }\r\n\r\n // optionally open sub-menu(s) by mouseover\r\n if (this._gridMenuOptions?.subMenuOpenByEvent === 'mouseover') {\r\n this._bindingEventService.bind(liElm, 'mouseover', ((e: DOMMouseOrTouchEvent) => {\r\n if ((item as GridMenuItem).commandItems || (item as GridMenuItem).customItems) {\r\n this.repositionSubMenu(item, level, e);\r\n } else if (!isSubMenu) {\r\n this.destroySubMenus();\r\n }\r\n }) as EventListener);\r\n }\r\n\r\n // the option/command item could be a sub-menu if it has another list of commands/options\r\n if ((item as GridMenuItem).commandItems || (item as GridMenuItem).customItems) {\r\n const chevronElm = document.createElement('span');\r\n chevronElm.className = 'sub-item-chevron';\r\n if (this._gridMenuOptions?.subItemChevronClass) {\r\n chevronElm.classList.add(...Utils.classNameToList(this._gridMenuOptions.subItemChevronClass));\r\n } else {\r\n chevronElm.textContent = '\u2B9E'; // \u2B9E or \u25B8\r\n }\r\n\r\n liElm.classList.add('slick-submenu-item');\r\n liElm.appendChild(chevronElm);\r\n continue;\r\n }\r\n }\r\n }\r\n\r\n /** Build the column picker, the code comes almost untouched from the file \"slick.columnpicker.js\" */\r\n protected populateColumnPicker() {\r\n this.grid.onColumnsReordered.subscribe(this.updateColumnOrder.bind(this));\r\n\r\n // user could pass a title on top of the columns list\r\n if (this._gridMenuOptions?.columnTitle) {\r\n this._columnTitleElm = document.createElement('div');\r\n this._columnTitleElm.className = 'title';\r\n this.grid.applyHtmlCode(this._columnTitleElm, this.grid.sanitizeHtmlString(this._gridMenuOptions.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 this._listElm = document.createElement('span');\r\n this._listElm.className = 'slick-gridmenu-list';\r\n this._listElm.role = 'menu';\r\n }\r\n\r\n /** Delete and then Recreate the Grid Menu (for example when we switch from regular to a frozen grid) */\r\n recreateGridMenu() {\r\n this.deleteMenu();\r\n this.init(this.grid);\r\n }\r\n\r\n showGridMenu(e: DOMMouseOrTouchEvent) {\r\n const targetEvent = e.touches ? e.touches[0] : e;\r\n e.preventDefault();\r\n\r\n // empty both the picker list & the command list\r\n Utils.emptyElement(this._listElm);\r\n Utils.emptyElement(this._commandListElm);\r\n\r\n const commandItems = this._gridMenuOptions?.commandItems ?? this._gridMenuOptions?.customItems ?? [];\r\n this.populateCommandsMenu(commandItems, this._commandListElm, { grid: this.grid, level: 0 });\r\n this.updateColumnOrder();\r\n this._columnCheckboxes = [];\r\n\r\n const callbackArgs = {\r\n grid: this.grid,\r\n menu: this._menuElm,\r\n allColumns: this.columns,\r\n visibleColumns: this.getVisibleColumns()\r\n };\r\n\r\n // run the override function (when defined), if the result is false it won't go further\r\n if (this._gridMenuOptions && !this.runOverrideFunctionWhenExists(this._gridMenuOptions.menuUsabilityOverride, callbackArgs)) {\r\n return;\r\n }\r\n\r\n // notify of the onBeforeMenuShow only works when\r\n // this mean that we cannot notify when the grid menu is attach to a button event\r\n if (typeof e.stopPropagation === 'function') {\r\n if (this.onBeforeMenuShow.notify(callbackArgs, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n }\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 excludeCssClass = this.columns[i].excludeFromGridMenu ? 'hidden' : '';\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\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}-gridmenu-colpicker-${columnId}`;\r\n checkboxElm.dataset.columnid = String(this.columns[i].id);\r\n liElm.appendChild(checkboxElm);\r\n\r\n if (Utils.isDefined(this.grid.getColumnIndex(this.columns[i].id)) && !this.columns[i].hidden) {\r\n checkboxElm.checked = true;\r\n }\r\n\r\n this._columnCheckboxes.push(checkboxElm);\r\n\r\n // get the column label from the picker value extractor (user can optionally provide a custom extractor)\r\n columnLabel = (this._gridMenuOptions?.headerColumnValueExtractor)\r\n ? this._gridMenuOptions.headerColumnValueExtractor(this.columns[i], this._gridOptions)\r\n : this._defaults.headerColumnValueExtractor!(this.columns[i]);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}-gridmenu-colpicker-${columnId}`;\r\n this.grid.applyHtmlCode(labelElm, this.grid.sanitizeHtmlString(Utils.getHtmlStringOutput(columnLabel || '')));\r\n liElm.appendChild(labelElm);\r\n this._listElm.appendChild(liElm);\r\n }\r\n\r\n if (this._gridMenuOptions && (!this._gridMenuOptions.hideForceFitButton || !this._gridMenuOptions.hideSyncResizeButton)) {\r\n this._listElm.appendChild(document.createElement('hr'));\r\n }\r\n\r\n if (!(this._gridMenuOptions?.hideForceFitButton)) {\r\n const forceFitTitle = (this._gridMenuOptions?.forceFitTitle) || this._defaults.forceFitTitle as string;\r\n\r\n const liElm = document.createElement('li');\r\n liElm.ariaLabel = forceFitTitle;\r\n liElm.role = 'menuitem';\r\n this._listElm.appendChild(liElm);\r\n\r\n const forceFitCheckboxElm = document.createElement('input');\r\n forceFitCheckboxElm.type = 'checkbox';\r\n forceFitCheckboxElm.id = `${this._gridUid}-gridmenu-colpicker-forcefit`;\r\n forceFitCheckboxElm.dataset.option = 'autoresize';\r\n liElm.appendChild(forceFitCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}-gridmenu-colpicker-forcefit`;\r\n labelElm.textContent = forceFitTitle;\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().forceFitColumns) {\r\n forceFitCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n if (!(this._gridMenuOptions?.hideSyncResizeButton)) {\r\n const syncResizeTitle = (this._gridMenuOptions?.syncResizeTitle) || this._defaults.syncResizeTitle as string;\r\n\r\n const liElm = document.createElement('li');\r\n liElm.ariaLabel = syncResizeTitle;\r\n this._listElm.appendChild(liElm);\r\n\r\n const syncResizeCheckboxElm = document.createElement('input');\r\n syncResizeCheckboxElm.type = 'checkbox';\r\n syncResizeCheckboxElm.id = `${this._gridUid}-gridmenu-colpicker-syncresize`;\r\n syncResizeCheckboxElm.dataset.option = 'syncresize';\r\n liElm.appendChild(syncResizeCheckboxElm);\r\n\r\n const labelElm = document.createElement('label');\r\n labelElm.htmlFor = `${this._gridUid}-gridmenu-colpicker-syncresize`;\r\n labelElm.textContent = syncResizeTitle;\r\n liElm.appendChild(labelElm);\r\n\r\n if (this.grid.getOptions().syncColumnCellResize) {\r\n syncResizeCheckboxElm.checked = true;\r\n }\r\n }\r\n\r\n let buttonElm = (e.target.nodeName === 'BUTTON' ? e.target : e.target.querySelector('button')) as HTMLButtonElement; // get button element\r\n if (!buttonElm) {\r\n buttonElm = e.target.parentElement as HTMLButtonElement; // external grid menu might fall in this last case if wrapped in a span/div\r\n }\r\n\r\n // we need to display the menu to properly calculate its width but we can however make it invisible\r\n this._menuElm.style.display = 'block';\r\n this._menuElm.style.opacity = '0';\r\n\r\n this.repositionMenu(e, this._menuElm, buttonElm);\r\n\r\n // set \"height\" when defined OR ELSE use the \"max-height\" with available window size and optional margin bottom\r\n const menuMarginBottom = (this._gridMenuOptions?.marginBottom !== undefined) ? this._gridMenuOptions.marginBottom : this._defaults.marginBottom as number;\r\n if (this._gridMenuOptions?.height !== undefined) {\r\n this._menuElm.style.height = `${this._gridMenuOptions.height}px`;\r\n } else {\r\n this._menuElm.style.maxHeight = `${window.innerHeight - targetEvent.clientY - menuMarginBottom}px`;\r\n }\r\n\r\n this._menuElm.style.display = 'block';\r\n this._menuElm.style.opacity = '1'; // restore menu visibility\r\n this._menuElm.appendChild(this._listElm);\r\n this._isMenuOpen = true;\r\n\r\n if (typeof e.stopPropagation === 'function') {\r\n if (this.onAfterMenuShow.notify(callbackArgs, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n }\r\n }\r\n\r\n protected getGridUidSelector() {\r\n const gridUid = this.grid.getUID() || '';\r\n return gridUid ? `.${gridUid}` : '';\r\n }\r\n\r\n protected handleBodyMouseDown(e: DOMMouseOrTouchEvent) {\r\n // did we click inside the menu or any of its sub-menu(s)\r\n let isMenuClicked = false;\r\n if (this._menuElm?.contains(e.target)) {\r\n isMenuClicked = true;\r\n }\r\n if (!isMenuClicked) {\r\n document\r\n .querySelectorAll(`.slick-gridmenu.slick-submenu${this.getGridUidSelector()}`)\r\n .forEach(subElm => {\r\n if (subElm.contains(e.target)) {\r\n isMenuClicked = true;\r\n }\r\n });\r\n }\r\n\r\n if ((this._menuElm !== e.target && !isMenuClicked && !e.defaultPrevented && this._isMenuOpen) || e.target.className === 'close') {\r\n this.hideMenu(e);\r\n }\r\n }\r\n\r\n protected handleMenuItemClick(item: GridMenuItem | MenuCommandItem | 'divider', level = 0, e: DOMMouseOrTouchEvent) {\r\n if (item !== 'divider' && !item.disabled && !item.divider) {\r\n const command = item.command || '';\r\n\r\n if (Utils.isDefined(command) && !item.commandItems && !(item as GridMenuItem).customItems) {\r\n const callbackArgs: GridMenuCommandItemCallbackArgs = {\r\n grid: this.grid,\r\n command,\r\n item,\r\n allColumns: this.columns,\r\n visibleColumns: this.getVisibleColumns()\r\n };\r\n this.onCommand.notify(callbackArgs, e, this);\r\n\r\n // execute action callback when defined\r\n if (typeof item.action === 'function') {\r\n (item as MenuCommandItem).action!.call(this, e, callbackArgs);\r\n }\r\n\r\n // does the user want to leave open the Grid Menu after executing a command?\r\n const leaveOpen = !!(this._gridMenuOptions?.leaveOpen);\r\n if (!leaveOpen && !e.defaultPrevented) {\r\n this.hideMenu(e);\r\n }\r\n\r\n // Stop propagation so that it doesn't register as a header click event.\r\n e.preventDefault();\r\n e.stopPropagation();\r\n } else if (item.commandItems || (item as GridMenuItem).customItems) {\r\n this.repositionSubMenu(item, level, e);\r\n } else {\r\n this.destroySubMenus();\r\n }\r\n }\r\n }\r\n\r\n hideMenu(e: DOMMouseOrTouchEvent) {\r\n if (this._menuElm) {\r\n const callbackArgs = {\r\n grid: this.grid,\r\n menu: this._menuElm,\r\n allColumns: this.columns,\r\n visibleColumns: this.getVisibleColumns()\r\n };\r\n if (this._isMenuOpen && this.onMenuClose.notify(callbackArgs, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n this._isMenuOpen = false;\r\n Utils.hide(this._menuElm);\r\n }\r\n this.destroySubMenus();\r\n }\r\n\r\n /** Update the Titles of each sections (command, commandTitle, ...) */\r\n updateAllTitles(gridMenuOptions: GridMenuOption) {\r\n if (this._commandTitleElm) {\r\n this.grid.applyHtmlCode(this._commandTitleElm, this.grid.sanitizeHtmlString(gridMenuOptions.commandTitle || gridMenuOptions.customTitle || ''));\r\n }\r\n if (this._columnTitleElm) {\r\n this.grid.applyHtmlCode(this._columnTitleElm, this.grid.sanitizeHtmlString(gridMenuOptions.columnTitle || ''));\r\n }\r\n }\r\n\r\n protected addSubMenuTitleWhenExists(item: GridMenuItem | MenuCommandItem | 'divider', commandOrOptionMenu: HTMLDivElement) {\r\n if (item !== 'divider' && item?.subMenuTitle) {\r\n const subMenuTitleElm = document.createElement('div');\r\n subMenuTitleElm.className = 'slick-menu-title';\r\n subMenuTitleElm.textContent = item.subMenuTitle as string;\r\n const subMenuTitleClass = item.subMenuTitleCssClass as string;\r\n if (subMenuTitleClass) {\r\n subMenuTitleElm.classList.add(...Utils.classNameToList(subMenuTitleClass));\r\n }\r\n\r\n commandOrOptionMenu.appendChild(subMenuTitleElm);\r\n }\r\n }\r\n\r\n protected repositionSubMenu(item: GridMenuItem | MenuCommandItem | 'divider', level: number, e: DOMMouseOrTouchEvent) {\r\n // when we're clicking a grid cell OR our last menu type (command/option) differs then we know that we need to start fresh and close any sub-menus that might still be open\r\n if (e.target.classList.contains('slick-cell')) {\r\n this.destroySubMenus();\r\n }\r\n\r\n // creating sub-menu, we'll also pass level & the item object since we might have \"subMenuTitle\" to show\r\n const subMenuElm = this.createMenu(level + 1, item);\r\n subMenuElm.style.display = 'block';\r\n document.body.appendChild(subMenuElm);\r\n this.repositionMenu(e, subMenuElm);\r\n }\r\n\r\n /**\r\n * Reposition the menu drop (up/down) and the side (left/right)\r\n * @param {*} event\r\n */\r\n protected repositionMenu(e: DOMMouseOrTouchEvent, menuElm: HTMLElement, buttonElm?: HTMLButtonElement) {\r\n const targetEvent = e.touches ? e.touches[0] : e;\r\n const isSubMenu = menuElm.classList.contains('slick-submenu');\r\n const parentElm = isSubMenu\r\n ? e.target.closest('.slick-gridmenu-item') as HTMLDivElement\r\n : targetEvent.target as HTMLElement;\r\n\r\n const menuIconOffset = Utils.offset(buttonElm || this._buttonElm); // get button offset position\r\n const menuWidth = menuElm.offsetWidth;\r\n const useClickToRepositionMenu = (this._gridMenuOptions?.useClickToRepositionMenu !== undefined) ? this._gridMenuOptions.useClickToRepositionMenu : this._defaults.useClickToRepositionMenu;\r\n const contentMinWidth = (this._gridMenuOptions?.contentMinWidth) ? this._gridMenuOptions.contentMinWidth : this._defaults.contentMinWidth as number;\r\n const currentMenuWidth = (contentMinWidth > menuWidth) ? contentMinWidth : menuWidth + 5;\r\n let menuOffsetTop = (useClickToRepositionMenu && targetEvent.pageY > 0) ? targetEvent.pageY : menuIconOffset!.top + 10;\r\n let menuOffsetLeft = (useClickToRepositionMenu && targetEvent.pageX > 0) ? targetEvent.pageX : menuIconOffset!.left + 10;\r\n\r\n if (isSubMenu && parentElm) {\r\n const parentOffset = Utils.offset(parentElm);\r\n menuOffsetLeft = parentOffset?.left ?? 0;\r\n menuOffsetTop = parentOffset?.top ?? 0;\r\n const gridPos = this.grid.getGridPosition();\r\n let subMenuPosCalc = menuOffsetLeft + Number(menuWidth); // calculate coordinate at caller element far right\r\n if (isSubMenu) {\r\n subMenuPosCalc += parentElm.clientWidth;\r\n }\r\n const browserWidth = document.documentElement.clientWidth;\r\n const dropSide = (subMenuPosCalc >= gridPos.width || subMenuPosCalc >= browserWidth) ? 'left' : 'right';\r\n if (dropSide === 'left') {\r\n menuElm.classList.remove('dropright');\r\n menuElm.classList.add('dropleft');\r\n menuOffsetLeft -= menuWidth;\r\n } else {\r\n menuElm.classList.remove('dropleft');\r\n menuElm.classList.add('dropright');\r\n if (isSubMenu) {\r\n menuOffsetLeft += parentElm.offsetWidth;\r\n }\r\n }\r\n } else {\r\n menuOffsetTop += 10;\r\n menuOffsetLeft = menuOffsetLeft - currentMenuWidth + 10;\r\n }\r\n\r\n menuElm.style.top = `${menuOffsetTop}px`;\r\n menuElm.style.left = `${menuOffsetLeft}px`;\r\n\r\n if (contentMinWidth > 0) {\r\n this._menuElm.style.minWidth = `${contentMinWidth}px`;\r\n }\r\n }\r\n\r\n protected updateColumnOrder() {\r\n // Because columns can be reordered, we have to update the `columns`\r\n // to reflect the new order, however we can't just take `grid.getColumns()`,\r\n // as it does not include columns currently hidden by the picker.\r\n // We create a new `columns` structure by leaving currently-hidden\r\n // columns in their original ordinal position and interleaving the results\r\n // of the current column sort.\r\n const current = this.grid.getColumns().slice(0);\r\n const ordered = new Array(this.columns.length);\r\n for (let i = 0; i < ordered.length; i++) {\r\n if (this.grid.getColumnIndex(this.columns[i].id) === undefined) {\r\n // If the column doesn't return a value from getColumnIndex,\r\n // it is hidden. Leave it in this position.\r\n ordered[i] = this.columns[i];\r\n } else {\r\n // Otherwise, grab the next visible column.\r\n ordered[i] = current.shift();\r\n }\r\n }\r\n this.columns = ordered;\r\n }\r\n\r\n protected updateColumn(e: DOMMouseOrTouchEvent) {\r\n if (e.target.dataset.option === 'autoresize') {\r\n // when calling setOptions, it will resize with ALL Columns (even the hidden ones)\r\n // we can avoid this problem by keeping a reference to the visibleColumns before setOptions and then setColumns after\r\n const previousVisibleColumns = this.getVisibleColumns();\r\n const isChecked = e.target.checked;\r\n this.grid.setOptions({ forceFitColumns: isChecked });\r\n this.grid.setColumns(previousVisibleColumns);\r\n return;\r\n }\r\n\r\n if (e.target.dataset.option === 'syncresize') {\r\n this.grid.setOptions({ syncColumnCellResize: !!(e.target.checked) });\r\n return;\r\n }\r\n\r\n if (e.target.type === 'checkbox') {\r\n const isChecked = e.target.checked;\r\n const columnId = e.target.dataset.columnid || '';\r\n const visibleColumns: Column[] = [];\r\n this._columnCheckboxes.forEach((columnCheckbox, idx) => {\r\n if (columnCheckbox.checked) {\r\n if (this.columns[idx].hidden) { this.columns[idx].hidden = false; }\r\n visibleColumns.push(this.columns[idx]);\r\n }\r\n });\r\n\r\n if (!visibleColumns.length) {\r\n e.target.checked = true;\r\n return;\r\n }\r\n\r\n const callbackArgs = {\r\n columnId,\r\n showing: isChecked,\r\n grid: this.grid,\r\n allColumns: this.columns,\r\n columns: visibleColumns,\r\n visibleColumns: this.getVisibleColumns()\r\n };\r\n this.grid.setColumns(visibleColumns);\r\n this.onColumnsChanged.notify(callbackArgs, e, this);\r\n }\r\n }\r\n\r\n getAllColumns() {\r\n return this.columns;\r\n }\r\n\r\n /** visible columns, we can simply get them directly from the grid */\r\n getVisibleColumns() {\r\n return this.grid.getColumns();\r\n }\r\n\r\n /**\r\n * Method that user can pass to override the default behavior.\r\n * In order word, user can choose or an item is (usable/visible/enable) by providing his own logic.\r\n * @param overrideFn: override function callback\r\n * @param args: multiple arguments provided to the override (cell, row, columnDef, dataContext, grid)\r\n */\r\n protected runOverrideFunctionWhenExists(overrideFn: ((args: any) => boolean) | undefined, args: T): boolean {\r\n if (typeof overrideFn === 'function') {\r\n return overrideFn.call(this, args);\r\n }\r\n return true;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n window.Slick.Controls = window.Slick.Controls || {};\r\n window.Slick.Controls.GridMenu = SlickGridMenu;\r\n}\r\n", "import type { PagingInfo } from '../models/index.js';\r\nimport { BindingEventService as BindingEventService_, SlickGlobalEditorLock as SlickGlobalEditorLock_, Utils as Utils_ } from '../slick.core.js';\r\nimport type { SlickDataView } from '../slick.dataview.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 SlickGlobalEditorLock = IIFE_ONLY ? Slick.GlobalEditorLock : SlickGlobalEditorLock_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\nexport interface GridPagerOption {\r\n showAllText: string;\r\n showPageText: string;\r\n showCountText: string;\r\n showCount: boolean;\r\n pagingOptions: Array<{ data: number; name: string; ariaLabel: string; }>;\r\n showPageSizes: boolean;\r\n}\r\n\r\nexport class SlickGridPager {\r\n // --\r\n // public API\r\n\r\n // --\r\n // protected props\r\n protected _container: HTMLElement; // the container might be a string, a jQuery object or a native element\r\n protected _statusElm!: HTMLElement;\r\n protected _bindingEventService: BindingEventService_;\r\n protected _options: GridPagerOption;\r\n protected _defaults: GridPagerOption = {\r\n showAllText: 'Showing all {rowCount} rows',\r\n showPageText: 'Showing page {pageNum} of {pageCount}',\r\n showCountText: 'From {countBegin} to {countEnd} of {rowCount} rows',\r\n showCount: false,\r\n pagingOptions: [\r\n { data: 0, name: 'All', ariaLabel: 'Show All Pages' },\r\n { data: -1, name: 'Auto', ariaLabel: 'Auto Page Size' },\r\n { data: 25, name: '25', ariaLabel: 'Show 25 rows per page' },\r\n { data: 50, name: '50', ariaLabel: 'Show 50 rows per page' },\r\n { data: 100, name: '100', ariaLabel: 'Show 100 rows per page' },\r\n ],\r\n showPageSizes: false\r\n };\r\n\r\n constructor(protected readonly dataView: SlickDataView, protected readonly grid: SlickGrid, selectorOrElm: HTMLElement | string, options?: Partial) {\r\n this._container = this.getContainerElement(selectorOrElm) as HTMLElement;\r\n this._options = Utils.extend(true, {}, this._defaults, options);\r\n this._bindingEventService = new BindingEventService();\r\n this.init();\r\n }\r\n\r\n init() {\r\n this.constructPagerUI();\r\n this.updatePager(this.dataView.getPagingInfo());\r\n this.dataView.onPagingInfoChanged.subscribe((_e, pagingInfo) => {\r\n this.updatePager(pagingInfo);\r\n });\r\n }\r\n\r\n /** Destroy function when element is destroyed */\r\n destroy() {\r\n this.setPageSize(0);\r\n this._bindingEventService.unbindAll();\r\n Utils.emptyElement(this._container);\r\n }\r\n\r\n protected getNavState() {\r\n const cannotLeaveEditMode = !SlickGlobalEditorLock.commitCurrentEdit();\r\n const pagingInfo = this.dataView.getPagingInfo();\r\n const lastPage = pagingInfo.totalPages - 1;\r\n\r\n return {\r\n canGotoFirst: !cannotLeaveEditMode && pagingInfo.pageSize !== 0 && pagingInfo.pageNum > 0,\r\n canGotoLast: !cannotLeaveEditMode && pagingInfo.pageSize !== 0 && pagingInfo.pageNum !== lastPage,\r\n canGotoPrev: !cannotLeaveEditMode && pagingInfo.pageSize !== 0 && pagingInfo.pageNum > 0,\r\n canGotoNext: !cannotLeaveEditMode && pagingInfo.pageSize !== 0 && pagingInfo.pageNum < lastPage,\r\n pagingInfo\r\n };\r\n }\r\n\r\n protected setPageSize(n: number) {\r\n this.dataView.setRefreshHints({\r\n isFilterUnchanged: true\r\n });\r\n this.dataView.setPagingOptions({ pageSize: n });\r\n }\r\n\r\n protected gotoFirst() {\r\n if (this.getNavState().canGotoFirst) {\r\n this.dataView.setPagingOptions({ pageNum: 0 });\r\n }\r\n }\r\n\r\n protected gotoLast() {\r\n const state = this.getNavState();\r\n if (state.canGotoLast) {\r\n this.dataView.setPagingOptions({ pageNum: state.pagingInfo.totalPages - 1 });\r\n }\r\n }\r\n\r\n protected gotoPrev() {\r\n const state = this.getNavState();\r\n if (state.canGotoPrev) {\r\n this.dataView.setPagingOptions({ pageNum: state.pagingInfo.pageNum - 1 });\r\n }\r\n }\r\n\r\n protected gotoNext() {\r\n const state = this.getNavState();\r\n if (state.canGotoNext) {\r\n this.dataView.setPagingOptions({ pageNum: state.pagingInfo.pageNum + 1 });\r\n }\r\n }\r\n\r\n protected getContainerElement(selectorOrElm: object | HTMLElement | string) {\r\n // the container might be a string, a jQuery object or a native element\r\n return typeof selectorOrElm === 'string'\r\n ? document.querySelector(selectorOrElm)\r\n : typeof selectorOrElm === 'object' && (selectorOrElm as any)[0]\r\n ? (selectorOrElm as any)[0] as HTMLElement\r\n : selectorOrElm;\r\n }\r\n\r\n protected constructPagerUI() {\r\n // the container might be a string, a jQuery object or a native element\r\n const container = this.getContainerElement(this._container) as HTMLElement | HTMLElement[];\r\n if (!container || ((container as any).jquery && !(container as HTMLElement[])[0])) { return; }\r\n\r\n const navElm = document.createElement('span');\r\n navElm.className = 'slick-pager-nav';\r\n\r\n const settingsElm = document.createElement('span');\r\n settingsElm.className = 'slick-pager-settings';\r\n\r\n this._statusElm = document.createElement('span');\r\n this._statusElm.className = 'slick-pager-status';\r\n\r\n const pagerSettingsElm = document.createElement('span');\r\n pagerSettingsElm.className = 'slick-pager-settings-expanded';\r\n pagerSettingsElm.textContent = 'Show: ';\r\n\r\n for (let o = 0; o < this._options.pagingOptions.length; o++) {\r\n const p = this._options.pagingOptions[o];\r\n\r\n const anchorElm = document.createElement('a');\r\n anchorElm.textContent = p.name;\r\n anchorElm.ariaLabel = p.ariaLabel;\r\n anchorElm.dataset.val = String(p.data);\r\n pagerSettingsElm.appendChild(anchorElm);\r\n\r\n this._bindingEventService.bind(anchorElm, 'click', ((e: any) => {\r\n const pagesize = e.target.dataset.val;\r\n if (pagesize !== undefined) {\r\n if (Number(pagesize) === -1) {\r\n const vp = this.grid.getViewport();\r\n this.setPageSize(vp.bottom - vp.top);\r\n } else {\r\n this.setPageSize(parseInt(pagesize));\r\n }\r\n }\r\n }));\r\n }\r\n\r\n pagerSettingsElm.style.display = this._options.showPageSizes ? 'block' : 'none';\r\n\r\n settingsElm.appendChild(pagerSettingsElm);\r\n\r\n // light bulb icon\r\n const displayPaginationContainer = document.createElement('span');\r\n const displayIconElm = document.createElement('span');\r\n displayPaginationContainer.className = 'sgi-container';\r\n displayIconElm.ariaLabel = 'Show Pagination Options';\r\n displayIconElm.role = 'button';\r\n displayIconElm.className = 'sgi sgi-lightbulb';\r\n displayPaginationContainer.appendChild(displayIconElm);\r\n\r\n this._bindingEventService.bind(displayIconElm, 'click', () => {\r\n const styleDisplay = pagerSettingsElm.style.display;\r\n pagerSettingsElm.style.display = styleDisplay === 'none' ? 'inline-flex' : 'none';\r\n });\r\n settingsElm.appendChild(displayPaginationContainer);\r\n\r\n const pageButtons = [\r\n { key: 'start', ariaLabel: 'First Page', callback: this.gotoFirst },\r\n { key: 'left', ariaLabel: 'Previous Page', callback: this.gotoPrev },\r\n { key: 'right', ariaLabel: 'Next Page', callback: this.gotoNext },\r\n { key: 'end', ariaLabel: 'Last Page', callback: this.gotoLast },\r\n ];\r\n\r\n pageButtons.forEach(pageBtn => {\r\n const iconElm = document.createElement('span');\r\n iconElm.className = 'sgi-container';\r\n\r\n const innerIconElm = document.createElement('span');\r\n innerIconElm.role = 'button';\r\n innerIconElm.ariaLabel = pageBtn.ariaLabel;\r\n innerIconElm.className = `sgi sgi-chevron-${pageBtn.key}`;\r\n this._bindingEventService.bind(innerIconElm, 'click', pageBtn.callback.bind(this));\r\n\r\n iconElm.appendChild(innerIconElm);\r\n navElm.appendChild(iconElm);\r\n });\r\n\r\n const slickPagerElm = document.createElement('div');\r\n slickPagerElm.className = 'slick-pager';\r\n\r\n slickPagerElm.appendChild(navElm);\r\n slickPagerElm.appendChild(this._statusElm);\r\n slickPagerElm.appendChild(settingsElm);\r\n\r\n (container as HTMLElement).appendChild(slickPagerElm);\r\n }\r\n\r\n protected updatePager(pagingInfo: PagingInfo) {\r\n if (!this._container || ((this._container as any).jquery && !(this._container as any)[0])) { return; }\r\n const state = this.getNavState();\r\n\r\n // remove disabled class on all icons\r\n this._container.querySelectorAll('.slick-pager-nav span')\r\n .forEach(pagerIcon => pagerIcon.classList.remove('sgi-state-disabled'));\r\n\r\n // add back disabled class to only necessary icons\r\n if (!state.canGotoFirst) {\r\n this._container!.querySelector('.sgi-chevron-start')?.classList.add('sgi-state-disabled');\r\n }\r\n if (!state.canGotoLast) {\r\n this._container!.querySelector('.sgi-chevron-end')?.classList.add('sgi-state-disabled');\r\n }\r\n if (!state.canGotoNext) {\r\n this._container!.querySelector('.sgi-chevron-right')?.classList.add('sgi-state-disabled');\r\n }\r\n if (!state.canGotoPrev) {\r\n this._container!.querySelector('.sgi-chevron-left')?.classList.add('sgi-state-disabled');\r\n }\r\n\r\n if (pagingInfo.pageSize === 0) {\r\n this._statusElm.textContent = (this._options.showAllText.replace('{rowCount}', pagingInfo.totalRows + '').replace('{pageCount}', pagingInfo.totalPages + ''));\r\n } else {\r\n this._statusElm.textContent = (this._options.showPageText.replace('{pageNum}', pagingInfo.pageNum + 1 + '').replace('{pageCount}', pagingInfo.totalPages + ''));\r\n }\r\n\r\n if (this._options.showCount && pagingInfo.pageSize !== 0) {\r\n const pageBegin = pagingInfo.pageNum * pagingInfo.pageSize;\r\n let currentText = this._statusElm.textContent;\r\n\r\n if (currentText) {\r\n currentText += ' - ';\r\n }\r\n\r\n this._statusElm.textContent =\r\n currentText +\r\n this._options.showCountText\r\n .replace('{rowCount}', String(pagingInfo.totalRows))\r\n .replace('{countBegin}', String(pageBegin + 1))\r\n .replace('{countEnd}', String(Math.min(pageBegin + pagingInfo.pageSize, pagingInfo.totalRows)));\r\n }\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n window.Slick.Controls = window.Slick.Controls || {};\r\n window.Slick.Controls.Pager = SlickGridPager;\r\n}\r\n\r\n", "export const FieldType = {\n /** unknown type */\n unknown: 'unknown',\n\n /** string type */\n string: 'string',\n\n /** boolean type (true/false) */\n boolean: 'boolean',\n\n /** integer number type (1,2,99) */\n integer: 'integer',\n\n /** float number (with decimal) type */\n float: 'float',\n\n /** number includes Integer and Float */\n number: 'number',\n\n /** new Date(), javascript Date object */\n date: 'date',\n\n /** Format: 'YYYY-MM-DD' <=> 2001-02-28 */\n dateIso: 'dateIso',\n\n /** Format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' <=> 2001-02-28T14:00:00.123Z */\n dateUtc: 'dateUtc',\n\n /** new Date(), javacript Date Object with Time */\n dateTime: 'dateTime',\n\n /** Format: 'YYYY-MM-DD HH:mm:ss' <=> 2001-02-28 14:01:01 */\n dateTimeIso: 'dateTimeIso',\n\n /** Format: 'YYYY-MM-DD hh:mm:ss a' <=> 2001-02-28 11:01:01 pm */\n dateTimeIsoAmPm: 'dateTimeIsoAmPm',\n\n /** Format: 'YYYY-MM-DD hh:mm:ss A' <=> 2001-02-28 11:01:01 PM */\n dateTimeIsoAM_PM: 'dateTimeIsoAM_PM',\n\n /** Format: 'YYYY-MM-DD HH:mm' <=> 2001-02-28 14:01 */\n dateTimeShortIso: 'dateTimeShortIso',\n\n /** Format (Euro): 'DD/MM/YYYY' <=> 28/02/2001 */\n dateEuro: 'dateEuro',\n\n /** Format (Euro): 'D/M/YY' <=> 28/2/12 */\n dateEuroShort: 'dateEuroShort',\n\n /** Format (Euro): 'DD/MM/YYYY HH:mm' <=> 28/02/2001 13:01 */\n dateTimeShortEuro: 'dateTimeShortEuro',\n\n /** Format (Euro): 'DD/MM/YYYY HH:mm:ss' <=> 02/28/2001 13:01:01 */\n dateTimeEuro: 'dateTimeEuro',\n\n /** Format (Euro): 'DD/MM/YYYY hh:mm:ss a' <=> 28/02/2001 11:01:01 pm */\n dateTimeEuroAmPm: 'dateTimeEuroAmPm',\n\n /** Format (Euro): 'DD/MM/YYYY hh:mm:ss A' <=> 28/02/2001 11:01:01 PM */\n dateTimeEuroAM_PM: 'dateTimeEuroAM_PM',\n\n /** Format (Euro): 'D/M/YY H:m:s' <=> 28/2/14 14:1:2 */\n dateTimeEuroShort: 'dateTimeEuroShort',\n\n /** Format (Euro): 'D/M/YY h:m:s a' <=> 28/2/14 1:2:10 pm */\n dateTimeEuroShortAmPm: 'dateTimeEuroShortAmPm',\n\n /** Format (Euro): 'D/M/YY h:m:s A' <=> 28/2/14 14:1:1 PM */\n dateTimeEuroShortAM_PM: 'dateTimeEuroShortAM_PM',\n\n /** Format: 'MM/DD/YYYY' <=> 02/28/2001 */\n dateUs: 'dateUs',\n\n /** Format: 'M/D/YY' <=> 2/28/12 */\n dateUsShort: 'dateUsShort',\n\n /** Format: 'MM/DD/YYYY HH:mm' <=> 02/28/2001 13:01 */\n dateTimeShortUs: 'dateTimeShortUs',\n\n /** Format: 'MM/DD/YYYY HH:mm:ss' <=> 02/28/2001 13:01:01 */\n dateTimeUs: 'dateTimeUs',\n\n /** Format: 'MM/DD/YYYY hh:mm:ss a' <=> 02/28/2001 11:01:01 pm */\n dateTimeUsAmPm: 'dateTimeUsAmPm',\n\n /** Format: 'MM/DD/YYYY hh:mm:ss A' <=> 02/28/2001 11:01:01 PM */\n dateTimeUsAM_PM: 'dateTimeUsAM_PM',\n\n /** Format: 'M/D/YY H:m:s' <=> 2/28/14 14:1:2 */\n dateTimeUsShort: 'dateTimeUsShort',\n\n /** Format: 'M/D/YY h:m:s a' <=> 2/28/14 1:2:10 pm */\n dateTimeUsShortAmPm: 'dateTimeUsShortAmPm',\n\n /** Format: 'M/D/YY h:m:s A' <=> 2/28/14 14:1:1 PM */\n dateTimeUsShortAM_PM: 'dateTimeUsShortAM_PM',\n\n /** complex object with various properties */\n object: 'object',\n\n /** password text string */\n password: 'password',\n\n /** alias to string */\n text: 'text',\n\n /** readonly text string */\n readonly: 'readonly',\n} as const;\n", "export enum SortDirectionNumber {\n asc = 1,\n desc = -1,\n neutral = 0,\n}\n", "import type { AutoTooltipOption, Column, SlickPlugin } from '../models/index.js';\r\nimport { type SlickEventData, Utils as Utils_ } from '../slick.core.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 Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\n/**\r\n * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content.\r\n */\r\nexport class SlickAutoTooltips implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'AutoTooltips' as const;\r\n\r\n // --\r\n // protected props\r\n protected _grid!: SlickGrid;\r\n protected _options?: AutoTooltipOption;\r\n protected _defaults: AutoTooltipOption = {\r\n enableForCells: true,\r\n enableForHeaderCells: false,\r\n maxToolTipLength: undefined,\r\n replaceExisting: true\r\n };\r\n\r\n /**\r\n * Constructor of the SlickGrid 3rd party plugin, it can optionally receive options\r\n * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells\r\n * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells\r\n * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip\r\n * @param {boolean} [options.replaceExisting=null] - Allow preventing custom tooltips from being overwritten by auto tooltips\r\n */\r\n constructor(options?: AutoTooltipOption) {\r\n this._options = Utils.extend(true, {}, this._defaults, options);\r\n }\r\n\r\n /**\r\n * Initialize plugin.\r\n */\r\n init(grid: SlickGrid) {\r\n this._grid = grid;\r\n if (this._options?.enableForCells) {\r\n this._grid.onMouseEnter.subscribe(this.handleMouseEnter.bind(this));\r\n }\r\n if (this._options?.enableForHeaderCells) {\r\n this._grid.onHeaderMouseEnter.subscribe(this.handleHeaderMouseEnter.bind(this));\r\n }\r\n }\r\n\r\n /**\r\n * Destroy plugin.\r\n */\r\n destroy() {\r\n if (this._options?.enableForCells) {\r\n this._grid.onMouseEnter.unsubscribe(this.handleMouseEnter.bind(this));\r\n }\r\n if (this._options?.enableForHeaderCells) {\r\n this._grid.onHeaderMouseEnter.unsubscribe(this.handleHeaderMouseEnter.bind(this));\r\n }\r\n }\r\n\r\n /**\r\n * Handle mouse entering grid cell to add/remove tooltip.\r\n * @param {SlickEventData} event - The event\r\n */\r\n protected handleMouseEnter(event: SlickEventData) {\r\n const cell = this._grid.getCellFromEvent(event);\r\n if (cell) {\r\n let node: HTMLElement | null = this._grid.getCellNode(cell.row, cell.cell);\r\n let text;\r\n if (this._options && node && (!node.title || this._options?.replaceExisting)) {\r\n if (node.clientWidth < node.scrollWidth) {\r\n text = node.textContent?.trim() ?? '';\r\n if (this._options?.maxToolTipLength && text.length > this._options?.maxToolTipLength) {\r\n text = text.substring(0, this._options.maxToolTipLength - 3) + '...';\r\n }\r\n } else {\r\n text = '';\r\n }\r\n node.title = text;\r\n }\r\n node = null;\r\n }\r\n }\r\n\r\n /**\r\n * Handle mouse entering header cell to add/remove tooltip.\r\n * @param {SlickEventData} event - The event\r\n * @param {object} args.column - The column definition\r\n */\r\n protected handleHeaderMouseEnter(event: SlickEventData, args: { column: Column; }) {\r\n const column = args.column;\r\n let node: HTMLDivElement | null;\r\n const targetElm = (event.target as HTMLDivElement);\r\n\r\n if (targetElm) {\r\n node = targetElm.closest('.slick-header-column');\r\n if (node && !(column?.toolTip)) {\r\n const titleVal = (targetElm.clientWidth < node.clientWidth) ? column?.name ?? '' : '';\r\n node.title = Utils.getHtmlStringOutput(titleVal, 'innerHTML');\r\n }\r\n }\r\n node = null;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n AutoTooltips: SlickAutoTooltips\r\n }\r\n });\r\n}\r\n", "import type { CssStyleHash, SlickPlugin } from '../models/index.js';\r\nimport { keyCode as keyCode_, SlickEvent as SlickEvent_, type SlickEventData, Utils as Utils_, type SlickRange } from '../slick.core.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 keyCode = IIFE_ONLY ? Slick.keyCode : keyCode_;\r\nconst SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\n/**\r\n * This manager enables users to copy/paste cell data\r\n */\r\nexport class SlickCellCopyManager implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CellCopyManager' as const;\r\n onCopyCells = new SlickEvent<{ ranges: SlickRange[] | null; }>('onCopyCells');\r\n onCopyCancelled = new SlickEvent<{ ranges: SlickRange[] | null; }>('onCopyCancelled');\r\n onPasteCells = new SlickEvent<{ from: SlickRange[] | undefined; to: SlickRange[] | undefined; }>('onPasteCells');\r\n\r\n // --\r\n // protected props\r\n protected _grid!: SlickGrid;\r\n protected _copiedRanges?: SlickRange[] | null = null;\r\n\r\n init(grid: SlickGrid) {\r\n this._grid = grid;\r\n Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);\r\n this._grid.onKeyDown.subscribe(this.handleKeyDown.bind(this));\r\n }\r\n\r\n destroy() {\r\n this._grid.onKeyDown.unsubscribe(this.handleKeyDown.bind(this));\r\n }\r\n\r\n protected handleKeyDown(e: SlickEventData) {\r\n let ranges: SlickRange[] | undefined;\r\n if (!this._grid.getEditorLock().isActive()) {\r\n if (e.which === keyCode.ESCAPE) {\r\n if (this._copiedRanges) {\r\n e.preventDefault();\r\n this.clearCopySelection();\r\n this.onCopyCancelled.notify({ ranges: this._copiedRanges });\r\n this._copiedRanges = null;\r\n }\r\n }\r\n\r\n if (e.which === 67 && (e.ctrlKey || e.metaKey)) {\r\n ranges = this._grid.getSelectionModel()?.getSelectedRanges() ?? [];\r\n if (ranges.length !== 0) {\r\n e.preventDefault();\r\n this._copiedRanges = ranges;\r\n this.markCopySelection(ranges);\r\n this.onCopyCells.notify({ ranges });\r\n }\r\n }\r\n\r\n if (e.which === 86 && (e.ctrlKey || e.metaKey)) {\r\n if (this._copiedRanges) {\r\n e.preventDefault();\r\n ranges = this._grid.getSelectionModel()?.getSelectedRanges();\r\n this.onPasteCells.notify({ from: this._copiedRanges, to: ranges });\r\n if (!this._grid.getOptions().preserveCopiedSelectionOnPaste) {\r\n this.clearCopySelection();\r\n this._copiedRanges = null;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n protected markCopySelection(ranges: SlickRange[]) {\r\n const columns = this._grid.getColumns();\r\n const hash: CssStyleHash = {};\r\n for (let i = 0; i < ranges.length; i++) {\r\n for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {\r\n hash[j] = {};\r\n for (let k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {\r\n hash[j][columns[k].id] = 'copied';\r\n }\r\n }\r\n }\r\n this._grid.setCellCssStyles('copy-manager', hash);\r\n }\r\n\r\n protected clearCopySelection() {\r\n this._grid.removeCellCssStyles('copy-manager');\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n CellCopyManager: SlickCellCopyManager\r\n }\r\n });\r\n}\r\n", "import type { Column, CssStyleHash, ExcelCopyBufferOption, ExternalCopyClipCommand, SlickPlugin } from '../models/index.js';\r\nimport type { SlickGrid } from '../slick.grid.js';\r\nimport { SlickEvent as SlickEvent_, type SlickEventData, SlickRange as SlickRange_, Utils as Utils_ } from '../slick.core.js';\r\n\r\n// for (iife) load Slick methods from global Slick object, or use imports for (esm)\r\nconst SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;\r\nconst SlickRange = IIFE_ONLY ? Slick.Range : SlickRange_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\nconst CLEAR_COPY_SELECTION_DELAY = 2000;\r\nconst CLIPBOARD_PASTE_DELAY = 100;\r\n\r\n/***\r\n This manager enables users to copy/paste data from/to an external Spreadsheet application\r\n such as MS-Excel\u00AE or OpenOffice-Spreadsheet.\r\n\r\n Since it is not possible to access directly the clipboard in javascript, the plugin uses\r\n a trick to do it's job. After detecting the keystroke, we dynamically create a textarea\r\n where the browser copies/pastes the serialized data.\r\n\r\n options:\r\n copiedCellStyle : sets the css className used for copied cells. default : \"copied\"\r\n copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : \"copy-manager\"\r\n dataItemColumnValueExtractor : option to specify a custom column value extractor function\r\n dataItemColumnValueSetter : option to specify a custom column value setter function\r\n clipboardCommandHandler : option to specify a custom handler for paste actions\r\n includeHeaderWhenCopying : set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard\r\n bodyElement: option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog.\r\n onCopyInit: optional handler to run when copy action initializes\r\n onCopySuccess: optional handler to run when copy action is complete\r\n newRowCreator: function to add rows to table if paste overflows bottom of table, if this function is not provided new rows will be ignored.\r\n readOnlyMode: suppresses paste\r\n headerColumnValueExtractor : option to specify a custom column header value extractor function\r\n*/\r\nexport class SlickCellExternalCopyManager implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CellExternalCopyManager' as const;\r\n onCopyCells = new SlickEvent<{ ranges: SlickRange_[]; }>('onCopyCells');\r\n onCopyCancelled = new SlickEvent<{ ranges: SlickRange_[]; }>('onCopyCancelled');\r\n onPasteCells = new SlickEvent<{ ranges: SlickRange_[]; }>('onPasteCells');\r\n\r\n // --\r\n // protected props\r\n protected _grid!: SlickGrid;\r\n protected _bodyElement: HTMLElement;\r\n protected _copiedRanges: SlickRange_[] | null = null;\r\n protected _clearCopyTI?: number;\r\n protected _copiedCellStyle: string;\r\n protected _copiedCellStyleLayerKey: string;\r\n protected _onCopyInit?: () => void;\r\n protected _onCopySuccess?: (rowCount: number) => void;\r\n protected _options: ExcelCopyBufferOption;\r\n\r\n protected keyCodes = {\r\n 'C': 67,\r\n 'V': 86,\r\n 'ESC': 27,\r\n 'INSERT': 45\r\n };\r\n\r\n constructor(options: ExcelCopyBufferOption) {\r\n this._options = options || {};\r\n this._copiedCellStyleLayerKey = this._options.copiedCellStyleLayerKey || 'copy-manager';\r\n this._copiedCellStyle = this._options.copiedCellStyle || 'copied';\r\n this._bodyElement = this._options.bodyElement || document.body;\r\n this._onCopyInit = this._options.onCopyInit || undefined;\r\n this._onCopySuccess = this._options.onCopySuccess || undefined;\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n this._grid = grid;\r\n Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);\r\n this._grid.onKeyDown.subscribe(this.handleKeyDown.bind(this));\r\n\r\n // we need a cell selection model\r\n const cellSelectionModel = grid.getSelectionModel();\r\n if (!cellSelectionModel) {\r\n throw new Error('Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())');\r\n }\r\n // we give focus on the grid when a selection is done on it.\r\n // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work\r\n cellSelectionModel.onSelectedRangesChanged.subscribe(() => {\r\n if (!this._grid.getEditorLock().isActive()) {\r\n this._grid.focus();\r\n }\r\n });\r\n }\r\n\r\n destroy() {\r\n this._grid.onKeyDown.unsubscribe(this.handleKeyDown.bind(this));\r\n }\r\n\r\n protected getHeaderValueForColumn(columnDef: Column): string {\r\n if (this._options.headerColumnValueExtractor) {\r\n const val = Utils.getHtmlStringOutput(this._options.headerColumnValueExtractor(columnDef));\r\n if (val) {\r\n return val;\r\n }\r\n }\r\n\r\n return Utils.getHtmlStringOutput(columnDef.name || '');\r\n }\r\n\r\n protected getDataItemValueForColumn(item: any, columnDef: Column, event: SlickEventData): string {\r\n if (typeof this._options.dataItemColumnValueExtractor === 'function') {\r\n const val = this._options.dataItemColumnValueExtractor(item, columnDef) as string | null;\r\n if (val) {\r\n return val;\r\n }\r\n }\r\n\r\n let retVal = '';\r\n\r\n // if a custom getter is not defined, we call serializeValue of the editor to serialize\r\n if (columnDef?.editor) {\r\n const tmpP = document.createElement('p');\r\n const editor = new (columnDef.editor as any)({\r\n container: tmpP, // a dummy container\r\n column: columnDef,\r\n event,\r\n position: { top: 0, left: 0 }, // a dummy position required by some editors\r\n grid: this._grid,\r\n });\r\n editor.loadValue(item);\r\n retVal = editor.serializeValue();\r\n editor.destroy();\r\n tmpP.remove();\r\n } else {\r\n retVal = item[columnDef.field || ''];\r\n }\r\n\r\n return retVal;\r\n }\r\n\r\n protected setDataItemValueForColumn(item: any, columnDef: Column, value: string): null | string | void {\r\n if (columnDef.denyPaste) {\r\n return null;\r\n }\r\n\r\n if (this._options.dataItemColumnValueSetter) {\r\n return this._options.dataItemColumnValueSetter(item, columnDef, value) as string;\r\n }\r\n\r\n // if a custom setter is not defined, we call applyValue of the editor to unserialize\r\n if (columnDef.editor) {\r\n const tmpDiv = document.createElement('div');\r\n const editor = new (columnDef.editor as any)({\r\n container: tmpDiv, // a dummy container\r\n column: columnDef,\r\n position: { top: 0, left: 0 }, // a dummy position required by some editors\r\n grid: this._grid\r\n });\r\n editor.loadValue(item);\r\n editor.applyValue(item, value);\r\n editor.destroy();\r\n tmpDiv.remove();\r\n } else {\r\n item[columnDef.field] = value;\r\n }\r\n }\r\n\r\n\r\n protected _createTextBox(innerText: string) {\r\n const scrollPos = document.documentElement.scrollTop || document.body.scrollTop;\r\n const ta = document.createElement('textarea');\r\n ta.style.position = 'absolute';\r\n ta.style.opacity = '0';\r\n ta.value = innerText;\r\n ta.style.top = `${scrollPos}px`;\r\n this._bodyElement.appendChild(ta);\r\n ta.select();\r\n\r\n return ta;\r\n }\r\n\r\n protected _decodeTabularData(grid: SlickGrid, ta: HTMLTextAreaElement) {\r\n const columns = grid.getColumns();\r\n const clipText = ta.value;\r\n const clipRows = clipText.split(/[\\n\\f\\r]/);\r\n // trim trailing CR if present\r\n if (clipRows[clipRows.length - 1] === '') {\r\n clipRows.pop();\r\n }\r\n\r\n let j = 0;\r\n const clippedRange: any[] = [];\r\n\r\n this._bodyElement.removeChild(ta);\r\n for (let i = 0; i < clipRows.length; i++) {\r\n if (clipRows[i] !== '') {\r\n clippedRange[j++] = clipRows[i].split('\\t');\r\n } else {\r\n clippedRange[j++] = [''];\r\n }\r\n }\r\n const selectedCell = grid.getActiveCell();\r\n const ranges = grid.getSelectionModel()?.getSelectedRanges();\r\n const selectedRange = ranges && ranges.length ? ranges[0] : null; // pick only one selection\r\n let activeRow: number;\r\n let activeCell: number;\r\n\r\n if (selectedRange) {\r\n activeRow = selectedRange.fromRow;\r\n activeCell = selectedRange.fromCell;\r\n } else if (selectedCell) {\r\n activeRow = selectedCell.row;\r\n activeCell = selectedCell.cell;\r\n } else {\r\n // we don't know where to paste\r\n return;\r\n }\r\n\r\n let oneCellToMultiple = false;\r\n let destH = clippedRange.length;\r\n let destW = clippedRange.length ? clippedRange[0].length : 0;\r\n if (clippedRange.length === 1 && clippedRange[0].length === 1 && selectedRange) {\r\n oneCellToMultiple = true;\r\n destH = selectedRange.toRow - selectedRange.fromRow + 1;\r\n destW = selectedRange.toCell - selectedRange.fromCell + 1;\r\n }\r\n const availableRows = (grid.getData() as any[]).length - (activeRow || 0);\r\n let addRows = 0;\r\n\r\n // ignore new rows if we don't have a \"newRowCreator\"\r\n if (availableRows < destH && typeof this._options.newRowCreator === 'function') {\r\n const d = grid.getData();\r\n for (addRows = 1; addRows <= destH - availableRows; addRows++) {\r\n d.push({});\r\n }\r\n grid.setData(d);\r\n grid.render();\r\n }\r\n\r\n const overflowsBottomOfGrid = (activeRow || 0) + destH > grid.getDataLength();\r\n if (this._options.newRowCreator && overflowsBottomOfGrid) {\r\n const newRowsNeeded = (activeRow || 0) + destH - grid.getDataLength();\r\n this._options.newRowCreator(newRowsNeeded);\r\n }\r\n\r\n const clipCommand: ExternalCopyClipCommand = {\r\n isClipboardCommand: true,\r\n clippedRange,\r\n oldValues: [],\r\n cellExternalCopyManager: this,\r\n _options: this._options,\r\n setDataItemValueForColumn: this.setDataItemValueForColumn.bind(this),\r\n markCopySelection: this.markCopySelection.bind(this),\r\n oneCellToMultiple,\r\n activeRow,\r\n activeCell,\r\n destH,\r\n destW,\r\n maxDestY: grid.getDataLength(),\r\n maxDestX: grid.getColumns().length,\r\n h: 0,\r\n w: 0,\r\n execute: () => {\r\n clipCommand.h = 0;\r\n for (let y = 0; y < clipCommand.destH; y++) {\r\n clipCommand.oldValues[y] = [];\r\n clipCommand.w = 0;\r\n clipCommand.h++;\r\n let xOffset = 0; // the x offset for hidden col\r\n\r\n for (let x = 0; x < clipCommand.destW; x++) {\r\n const desty = activeRow + y;\r\n const destx = activeCell + x;\r\n const column = columns[destx];\r\n\r\n // paste on hidden column will be skipped, but we need to paste 1 cell further on X axis\r\n // we'll increase our X and increase the offset`\r\n if (column.hidden) {\r\n clipCommand.destW++;\r\n xOffset++;\r\n continue;\r\n }\r\n clipCommand.w++;\r\n\r\n if (desty < clipCommand.maxDestY && destx < clipCommand.maxDestX) {\r\n const dt = grid.getDataItem(desty);\r\n\r\n clipCommand.oldValues[y][x - xOffset] = dt[column['field']];\r\n if (oneCellToMultiple) {\r\n clipCommand.setDataItemValueForColumn(dt, column, clippedRange[0][0]);\r\n } else {\r\n clipCommand.setDataItemValueForColumn(dt, column, clippedRange[y] ? clippedRange[y][x - xOffset] : '');\r\n }\r\n grid.updateCell(desty, destx);\r\n grid.onCellChange.notify({\r\n row: desty,\r\n cell: destx,\r\n item: dt,\r\n grid,\r\n column: {} as Column\r\n });\r\n }\r\n }\r\n }\r\n\r\n const bRange = new SlickRange(\r\n activeRow,\r\n activeCell,\r\n activeRow + clipCommand.h - 1,\r\n activeCell + clipCommand.w - 1\r\n );\r\n\r\n this.markCopySelection([bRange]);\r\n grid.getSelectionModel()?.setSelectedRanges([bRange]);\r\n this.onPasteCells.notify({ ranges: [bRange] });\r\n },\r\n undo: () => {\r\n for (let y = 0; y < clipCommand.destH; y++) {\r\n for (let x = 0; x < clipCommand.destW; x++) {\r\n const desty = activeRow + y;\r\n const destx = activeCell + x;\r\n\r\n if (desty < clipCommand.maxDestY && destx < clipCommand.maxDestX) {\r\n const dt = grid.getDataItem(desty);\r\n if (oneCellToMultiple) {\r\n clipCommand.setDataItemValueForColumn(dt, columns[destx], clipCommand.oldValues[0][0]);\r\n } else {\r\n clipCommand.setDataItemValueForColumn(dt, columns[destx], clipCommand.oldValues[y][x]);\r\n }\r\n grid.updateCell(desty, destx);\r\n grid.onCellChange.notify({\r\n row: desty,\r\n cell: destx,\r\n item: dt,\r\n grid,\r\n column: {} as Column\r\n });\r\n }\r\n }\r\n }\r\n\r\n const bRange = new SlickRange(\r\n activeRow,\r\n activeCell,\r\n activeRow + clipCommand.h - 1,\r\n activeCell + clipCommand.w - 1\r\n );\r\n\r\n this.markCopySelection([bRange]);\r\n grid.getSelectionModel()?.setSelectedRanges([bRange]);\r\n if (typeof this._options.onPasteCells === 'function') {\r\n this.onPasteCells.notify({ ranges: [bRange] });\r\n }\r\n\r\n if (addRows > 1) {\r\n const d = grid.getData();\r\n for (; addRows > 1; addRows--) {\r\n d.splice(d.length - 1, 1);\r\n }\r\n grid.setData(d);\r\n grid.render();\r\n }\r\n }\r\n };\r\n\r\n if (typeof this._options.clipboardCommandHandler === 'function') {\r\n this._options.clipboardCommandHandler(clipCommand);\r\n } else {\r\n clipCommand.execute();\r\n }\r\n }\r\n\r\n protected handleKeyDown(e: SlickEventData): boolean | void {\r\n let ranges: SlickRange_[];\r\n if (!this._grid.getEditorLock().isActive() || this._grid.getOptions().autoEdit) {\r\n if (e.which === this.keyCodes.ESC) {\r\n if (this._copiedRanges) {\r\n e.preventDefault();\r\n this.clearCopySelection();\r\n this.onCopyCancelled.notify({ ranges: this._copiedRanges });\r\n this._copiedRanges = null;\r\n }\r\n }\r\n\r\n if ((e.which === this.keyCodes.C || e.which === this.keyCodes.INSERT) && (e.ctrlKey || e.metaKey) && !e.shiftKey) { // CTRL+C or CTRL+INS\r\n if (typeof this._onCopyInit === 'function') {\r\n this._onCopyInit.call(this);\r\n }\r\n ranges = this._grid.getSelectionModel()?.getSelectedRanges() ?? [];\r\n if (ranges.length !== 0) {\r\n this._copiedRanges = ranges;\r\n this.markCopySelection(ranges);\r\n this.onCopyCells.notify({ ranges });\r\n\r\n const columns = this._grid.getColumns();\r\n let clipText = '';\r\n\r\n for (let rg = 0; rg < ranges.length; rg++) {\r\n const range = ranges[rg];\r\n const clipTextRows: string[] = [];\r\n for (let i = range.fromRow; i < range.toRow + 1; i++) {\r\n const clipTextCells: string[] = [];\r\n const dt = this._grid.getDataItem(i);\r\n\r\n if (clipTextRows.length === 0 && this._options.includeHeaderWhenCopying) {\r\n const clipTextHeaders: string[] = [];\r\n for (let j = range.fromCell; j < range.toCell + 1; j++) {\r\n const colName: string = columns[j].name instanceof HTMLElement\r\n ? (columns[j].name as HTMLElement).innerHTML\r\n : columns[j].name as string;\r\n if (colName.length > 0 && !columns[j].hidden) {\r\n clipTextHeaders.push(this.getHeaderValueForColumn(columns[j]) || '');\r\n }\r\n }\r\n clipTextRows.push(clipTextHeaders.join('\\t'));\r\n }\r\n\r\n for (let j = range.fromCell; j < range.toCell + 1; j++) {\r\n const colName: string = columns[j].name instanceof HTMLElement\r\n ? (columns[j].name as HTMLElement).innerHTML\r\n : columns[j].name as string;\r\n if (colName.length > 0 && !columns[j].hidden) {\r\n clipTextCells.push(this.getDataItemValueForColumn(dt, columns[j], e));\r\n }\r\n }\r\n clipTextRows.push(clipTextCells.join('\\t'));\r\n }\r\n clipText += clipTextRows.join('\\r\\n') + '\\r\\n';\r\n }\r\n\r\n if ((window as any).clipboardData) {\r\n (window as any).clipboardData.setData('Text', clipText);\r\n return true;\r\n }\r\n else {\r\n const focusEl = document.activeElement as HTMLElement;\r\n const ta = this._createTextBox(clipText);\r\n ta.focus();\r\n\r\n window.setTimeout(() => {\r\n this._bodyElement.removeChild(ta);\r\n // restore focus when possible\r\n focusEl\r\n ? focusEl.focus()\r\n : console.log('No element to restore focus to after copy?');\r\n }, this._options?.clipboardPasteDelay ?? CLIPBOARD_PASTE_DELAY);\r\n\r\n if (typeof this._onCopySuccess === 'function') {\r\n let rowCount = 0;\r\n // If it's cell selection, use the toRow/fromRow fields\r\n if (ranges.length === 1) {\r\n rowCount = (ranges[0].toRow + 1) - ranges[0].fromRow;\r\n } else {\r\n rowCount = ranges.length;\r\n }\r\n this._onCopySuccess(rowCount);\r\n }\r\n\r\n return false;\r\n }\r\n }\r\n }\r\n\r\n if (!this._options.readOnlyMode && (\r\n (e.which === this.keyCodes.V && (e.ctrlKey || e.metaKey) && !e.shiftKey)\r\n || (e.which === this.keyCodes.INSERT && e.shiftKey && !e.ctrlKey)\r\n )) { // CTRL+V or Shift+INS\r\n const focusEl = document.activeElement as HTMLElement;\r\n const ta = this._createTextBox('');\r\n window.setTimeout(() => {\r\n this._decodeTabularData(this._grid, ta);\r\n // restore focus when possible\r\n focusEl?.focus();\r\n }, this._options?.clipboardPasteDelay ?? CLIPBOARD_PASTE_DELAY);\r\n return false;\r\n }\r\n }\r\n }\r\n\r\n protected markCopySelection(ranges: SlickRange_[]) {\r\n this.clearCopySelection();\r\n\r\n const columns = this._grid.getColumns();\r\n const hash: CssStyleHash = {};\r\n for (let i = 0; i < ranges.length; i++) {\r\n for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {\r\n hash[j] = {};\r\n for (let k = ranges[i].fromCell; k <= ranges[i].toCell && k < columns.length; k++) {\r\n hash[j][columns[k].id] = this._copiedCellStyle;\r\n }\r\n }\r\n }\r\n this._grid.setCellCssStyles(this._copiedCellStyleLayerKey, hash);\r\n window.clearTimeout(this._clearCopyTI);\r\n this._clearCopyTI = window.setTimeout(() => {\r\n this.clearCopySelection();\r\n }, this._options?.clearCopySelectionDelay || CLEAR_COPY_SELECTION_DELAY);\r\n }\r\n\r\n clearCopySelection() {\r\n this._grid.removeCellCssStyles(this._copiedCellStyleLayerKey);\r\n }\r\n\r\n setIncludeHeaderWhenCopying(includeHeaderWhenCopying: boolean) {\r\n this._options.includeHeaderWhenCopying = includeHeaderWhenCopying;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n CellExternalCopyManager: SlickCellExternalCopyManager\r\n }\r\n });\r\n}\r\n", "import {\r\n BindingEventService as BindingEventService_,\r\n SlickEvent as SlickEvent_,\r\n SlickEventData as SlickEventData_,\r\n SlickEventHandler as SlickEventHandler_,\r\n Utils as Utils_\r\n} from '../slick.core.js';\r\nimport type {\r\n CellMenuOption,\r\n Column,\r\n DOMMouseOrTouchEvent,\r\n GridOption,\r\n MenuCommandItem,\r\n MenuCommandItemCallbackArgs,\r\n MenuFromCellCallbackArgs,\r\n MenuOptionItem,\r\n MenuOptionItemCallbackArgs,\r\n MenuType,\r\n SlickPlugin\r\n} 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 SlickEventData = IIFE_ONLY ? Slick.EventData : SlickEventData_;\r\nconst EventHandler = IIFE_ONLY ? Slick.EventHandler : SlickEventHandler_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\n/**\r\n * A plugin to add Menu on a Cell click (click on the cell that has the cellMenu object defined)\r\n * The \"cellMenu\" is defined in a Column Definition object\r\n * Similar to the ContextMenu plugin (could be used in combo),\r\n * except that it subscribes to the cell \"onClick\" event (regular mouse click or touch).\r\n *\r\n * A general use of this plugin is for an Action Dropdown Menu to do certain things on the row that was clicked\r\n * You can use it to change the cell data property through a list of Options AND/OR through a list of Commands.\r\n *\r\n * USAGE:\r\n *\r\n * Add the slick.cellMenu.(js|css) files and register it with the grid.\r\n *\r\n * To specify a menu in a column header, extend the column definition like so:\r\n * var cellMenuPlugin = new Slick.Plugins.CellMenu(columns, grid, options);\r\n *\r\n * Available cellMenu options, by defining a cellMenu object:\r\n *\r\n * var columns = [\r\n * {\r\n * id: \"action\", name: \"Action\", field: \"action\", formatter: fakeButtonFormatter,\r\n * cellMenu: {\r\n * optionTitle: \"Change Effort Driven\",\r\n * optionItems: [\r\n * { option: true, title: \"True\", iconCssClass: 'checkmark' },\r\n * { option: false, title: \"False\" }\r\n * ],\r\n * commandTitle: \"Commands\",\r\n * commandItems: [\r\n * { command: \"delete-row\", title: \"Delete Row\", iconCssClass: \"sgi sgi-close\", cssClass: 'bold', textCssClass: \"red\" },\r\n * { divider: true },\r\n * \"divider\" // you can pass \"divider\" as a string or an object\r\n * { command: \"help\", title: \"Help\", iconCssClass: \"icon-help\" },\r\n * { command: \"help\", title: \"Disabled Command\", disabled: true },\r\n * ],\r\n * }\r\n * }\r\n * ];\r\n *\r\n *\r\n * Available cellMenu properties:\r\n * commandTitle: Title of the Command section (optional)\r\n * commandItems: Array of Command item objects (command/title pair)\r\n * optionTitle: Title of the Option section (optional)\r\n * optionItems: Array of Options item objects (option/title pair)\r\n * hideCloseButton: Hide the Close button on top right (defaults to false)\r\n * hideCommandSection: Hide the Commands section even when the commandItems array is filled (defaults to false)\r\n * hideMenuOnScroll: Do we want to hide the Cell Menu when a scrolling event occurs (defaults to true)?\r\n * hideOptionSection: Hide the Options section even when the optionItems array is filled (defaults to false)\r\n * maxHeight: Maximum height that the drop menu will have, can be a number (250) or text (\"none\")\r\n * width: Width that the drop menu will have, can be a number (250) or text (defaults to \"auto\")\r\n * autoAdjustDrop: Auto-align dropup or dropdown menu to the left or right depending on grid viewport available space (defaults to true)\r\n * autoAdjustDropOffset: Optionally add an offset to the auto-align of the drop menu (defaults to 0)\r\n * autoAlignSide: Auto-align drop menu to the left or right depending on grid viewport available space (defaults to true)\r\n * autoAlignSideOffset: Optionally add an offset to the left/right side auto-align (defaults to 0)\r\n * menuUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling the menu from being usable (must be combined with a custom formatter)\r\n * subItemChevronClass: CSS class that can be added on the right side of a sub-item parent (typically a chevron-right icon)\r\n * subMenuOpenByEvent: defaults to \"mouseover\", what event type shoud we use to open sub-menu(s), 2 options are available: \"mouseover\" or \"click\"\r\n *\r\n *\r\n * Available menu Command/Option item properties:\r\n * action: Optionally define a callback function that gets executed when item is chosen (and/or use the onCommand event)\r\n * command: A command identifier to be passed to the onCommand event handlers (when using \"commandItems\").\r\n * option: An option to be passed to the onOptionSelected event handlers (when using \"optionItems\").\r\n * title: Menu item text label.\r\n * divider: Boolean which tells if the current item is a divider, not an actual command. You could also pass \"divider\" instead of an object\r\n * disabled: Whether the item/command is disabled.\r\n * hidden: Whether the item/command is hidden.\r\n * subMenuTitle: Optional sub-menu title that will shows up when sub-menu commmands/options list is opened\r\n * subMenuTitleCssClass: Optional sub-menu title CSS class to use with `subMenuTitle`\r\n * tooltip: Item tooltip.\r\n * cssClass: A CSS class to be added to the menu item container.\r\n * iconCssClass: A CSS class to be added to the menu item icon.\r\n * textCssClass: A CSS class to be added to the menu item text.\r\n * iconImage: A url to the icon image.\r\n * itemVisibilityOverride: Callback method that user can override the default behavior of showing/hiding an item from the list\r\n * itemUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling an item from the list\r\n *\r\n *\r\n * The plugin exposes the following events:\r\n *\r\n * onAfterMenuShow: Fired after the menu is shown. You can customize the menu or dismiss it by returning false.\r\n * Event args:\r\n * cell: Cell or column index\r\n * row: Row index\r\n * grid: Reference to the grid.\r\n *\r\n * onBeforeMenuShow: Fired before the menu is shown. You can customize the menu or dismiss it by returning false.\r\n * Event args:\r\n * cell: Cell or column index\r\n * row: Row index\r\n * grid: Reference to the grid.\r\n *\r\n * onBeforeMenuClose: Fired when the menu is closing.\r\n * Event args:\r\n * cell: Cell or column index\r\n * row: Row index\r\n * grid: Reference to the grid.\r\n * menu: Menu DOM element\r\n *\r\n * onCommand: Fired on menu option clicked from the Command items list\r\n * Event args:\r\n * cell: Cell or column index\r\n * row: Row index\r\n * grid: Reference to the grid.\r\n * command: Menu command identified.\r\n * item: Menu item selected\r\n * column: Cell Column definition\r\n * dataContext: Cell Data Context (data object)\r\n *\r\n * onOptionSelected: Fired on menu option clicked from the Option items list\r\n * Event args:\r\n * cell: Cell or column index\r\n * row: Row index\r\n * grid: Reference to the grid.\r\n * option: Menu option selected.\r\n * item: Menu item selected\r\n * column: Cell Column definition\r\n * dataContext: Cell Data Context (data object)\r\n *\r\n *\r\n * @param options {Object} Cell Menu Options\r\n * @class Slick.Plugins.CellMenu\r\n */\r\nexport class SlickCellMenu implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CellMenu' as const;\r\n onAfterMenuShow = new SlickEvent('onAfterMenuShow');\r\n onBeforeMenuShow = new SlickEvent('onBeforeMenuShow');\r\n onBeforeMenuClose = new SlickEvent('onBeforeMenuClose');\r\n onCommand = new SlickEvent('onCommand');\r\n onOptionSelected = new SlickEvent('onOptionSelected');\r\n\r\n // --\r\n // protected props\r\n protected _bindingEventService = new BindingEventService();\r\n protected _cellMenuProperties: CellMenuOption;\r\n protected _currentCell = -1;\r\n protected _currentRow = -1;\r\n protected _grid!: SlickGrid;\r\n protected _gridOptions!: GridOption;\r\n protected _gridUid = '';\r\n protected _handler = new EventHandler();\r\n protected _commandTitleElm?: HTMLSpanElement;\r\n protected _optionTitleElm?: HTMLSpanElement;\r\n protected _lastMenuTypeClicked = '';\r\n protected _menuElm?: HTMLDivElement | null;\r\n protected _subMenuParentId = '';\r\n protected _defaults: CellMenuOption = {\r\n autoAdjustDrop: true, // dropup/dropdown\r\n autoAlignSide: true, // left/right\r\n autoAdjustDropOffset: 0,\r\n autoAlignSideOffset: 0,\r\n hideMenuOnScroll: true,\r\n maxHeight: 'none',\r\n width: 'auto',\r\n subMenuOpenByEvent: 'mouseover',\r\n };\r\n\r\n constructor(optionProperties: Partial) {\r\n this._cellMenuProperties = Utils.extend({}, this._defaults, optionProperties);\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n this._grid = grid;\r\n this._gridOptions = grid.getOptions();\r\n Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);\r\n this._gridUid = grid?.getUID() || '';\r\n this._handler.subscribe(this._grid.onClick as any, this.handleCellClick.bind(this));\r\n if (this._cellMenuProperties.hideMenuOnScroll) {\r\n this._handler.subscribe(this._grid.onScroll, this.closeMenu.bind(this));\r\n }\r\n }\r\n\r\n setOptions(newOptions: Partial) {\r\n this._cellMenuProperties = Utils.extend({}, this._cellMenuProperties, newOptions);\r\n }\r\n\r\n destroy() {\r\n this.onAfterMenuShow.unsubscribe();\r\n this.onBeforeMenuShow.unsubscribe();\r\n this.onBeforeMenuClose.unsubscribe();\r\n this.onCommand.unsubscribe();\r\n this.onOptionSelected.unsubscribe();\r\n this._handler.unsubscribeAll();\r\n this._bindingEventService.unbindAll();\r\n this._menuElm?.remove();\r\n this._commandTitleElm = null as any;\r\n this._optionTitleElm = null as any;\r\n this._menuElm = null as any;\r\n }\r\n\r\n protected createParentMenu(e: DOMMouseOrTouchEvent) {\r\n const cell = this._grid.getCellFromEvent(e);\r\n this._currentCell = cell?.cell ?? 0;\r\n this._currentRow = cell?.row ?? 0;\r\n const columnDef = this._grid.getColumns()[this._currentCell];\r\n\r\n const commandItems = this._cellMenuProperties.commandItems || [];\r\n const optionItems = this._cellMenuProperties.optionItems || [];\r\n\r\n // make sure there's at least something to show before creating the Cell Menu\r\n if (!columnDef || !columnDef.cellMenu || (!commandItems.length && !optionItems.length)) {\r\n return;\r\n }\r\n\r\n // delete any prior Cell Menu\r\n this.closeMenu();\r\n\r\n // Let the user modify the menu or cancel altogether,\r\n // or provide alternative menu implementation.\r\n if (this.onBeforeMenuShow.notify({\r\n cell: this._currentCell,\r\n row: this._currentRow,\r\n grid: this._grid\r\n }, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n\r\n // create 1st parent menu container & reposition it\r\n this._menuElm = this.createMenu(commandItems, optionItems);\r\n this._menuElm.style.top = `${e.pageY + 5}px`;\r\n this._menuElm.style.left = `${e.pageX}px`;\r\n this._menuElm.style.display = 'block';\r\n document.body.appendChild(this._menuElm);\r\n\r\n if (this.onAfterMenuShow.notify({\r\n cell: this._currentCell,\r\n row: this._currentRow,\r\n grid: this._grid\r\n }, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n\r\n return this._menuElm;\r\n }\r\n\r\n /**\r\n * Create parent menu or sub-menu(s), a parent menu will start at level 0 while sub-menu(s) will be incremented\r\n * @param commandItems - array of optional commands or dividers\r\n * @param optionItems - array of optional options or dividers\r\n * @param level - menu level\r\n * @param item - command, option or divider\r\n * @returns menu DOM element\r\n */\r\n protected createMenu(commandItems: Array, optionItems: Array, level = 0, item?: MenuCommandItem | MenuOptionItem | 'divider') {\r\n const columnDef = this._grid.getColumns()[this._currentCell];\r\n const dataContext = this._grid.getDataItem(this._currentRow);\r\n\r\n // create a new cell menu\r\n const maxHeight = isNaN(this._cellMenuProperties.maxHeight as number) ? this._cellMenuProperties.maxHeight : `${this._cellMenuProperties.maxHeight ?? 0}px`;\r\n const width = isNaN(this._cellMenuProperties.width as number) ? this._cellMenuProperties.width : `${this._cellMenuProperties.maxWidth ?? 0}px`;\r\n\r\n // to avoid having multiple sub-menu trees opened,\r\n // we need to somehow keep trace of which parent menu the tree belongs to\r\n // and we should keep ref of only the first sub-menu parent, we can use the command name (remove any whitespaces though)\r\n const subMenuCommand = (item as MenuCommandItem)?.command;\r\n let subMenuId = (level === 1 && subMenuCommand) ? subMenuCommand.replaceAll(' ', '') : '';\r\n if (subMenuId) {\r\n this._subMenuParentId = subMenuId;\r\n }\r\n if (level > 1) {\r\n subMenuId = this._subMenuParentId;\r\n }\r\n\r\n const menuClasses = `slick-cell-menu slick-menu-level-${level} ${this._gridUid}`;\r\n const bodyMenuElm = document.body.querySelector(`.slick-cell-menu.slick-menu-level-${level}${this.getGridUidSelector()}`);\r\n\r\n // return menu/sub-menu if it's already opened unless we are on different sub-menu tree if so close them all\r\n if (bodyMenuElm) {\r\n if (bodyMenuElm.dataset.subMenuParent === subMenuId) {\r\n return bodyMenuElm;\r\n }\r\n this.destroySubMenus();\r\n }\r\n\r\n const menuElm = document.createElement('div');\r\n menuElm.className = menuClasses;\r\n if (level > 0) {\r\n menuElm.classList.add('slick-submenu');\r\n if (subMenuId) {\r\n menuElm.dataset.subMenuParent = subMenuId;\r\n }\r\n }\r\n menuElm.ariaLabel = level > 1 ? 'SubMenu' : 'Cell Menu';\r\n menuElm.role = 'menu';\r\n if (width) {\r\n menuElm.style.width = width as string;\r\n }\r\n if (maxHeight) {\r\n menuElm.style.maxHeight = maxHeight as string;\r\n }\r\n\r\n menuElm.style.display = 'none';\r\n\r\n let closeButtonElm: HTMLButtonElement | null = null;\r\n if (level === 0) {\r\n closeButtonElm = document.createElement('button');\r\n closeButtonElm.type = 'button';\r\n closeButtonElm.className = 'close';\r\n closeButtonElm.dataset.dismiss = 'slick-cell-menu';\r\n closeButtonElm.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 closeButtonElm.appendChild(spanCloseElm);\r\n }\r\n\r\n // -- Option List section\r\n if (!this._cellMenuProperties.hideOptionSection && optionItems.length > 0) {\r\n const optionMenuElm = document.createElement('div');\r\n optionMenuElm.className = 'slick-cell-menu-option-list';\r\n optionMenuElm.role = 'menu';\r\n\r\n // when creating sub-menu add its sub-menu title when exists\r\n if (item && level > 0) {\r\n this.addSubMenuTitleWhenExists(item, optionMenuElm); // add sub-menu title when exists\r\n }\r\n\r\n if (closeButtonElm && !this._cellMenuProperties.hideCloseButton) {\r\n this._bindingEventService.bind(closeButtonElm, 'click', this.handleCloseButtonClicked.bind(this) as EventListener);\r\n menuElm.appendChild(closeButtonElm);\r\n }\r\n menuElm.appendChild(optionMenuElm);\r\n\r\n this.populateCommandOrOptionItems(\r\n 'option',\r\n this._cellMenuProperties,\r\n optionMenuElm,\r\n optionItems,\r\n { cell: this._currentCell, row: this._currentRow, column: columnDef, dataContext, grid: this._grid, level }\r\n );\r\n }\r\n\r\n // -- Command List section\r\n if (!this._cellMenuProperties.hideCommandSection && commandItems.length > 0) {\r\n const commandMenuElm = document.createElement('div');\r\n commandMenuElm.className = 'slick-cell-menu-command-list';\r\n commandMenuElm.role = 'menu';\r\n\r\n // when creating sub-menu add its sub-menu title when exists\r\n if (item && level > 0) {\r\n this.addSubMenuTitleWhenExists(item, commandMenuElm); // add sub-menu title when exists\r\n }\r\n\r\n if (closeButtonElm && !this._cellMenuProperties.hideCloseButton && (optionItems.length === 0 || this._cellMenuProperties.hideOptionSection)) {\r\n this._bindingEventService.bind(closeButtonElm, 'click', this.handleCloseButtonClicked.bind(this) as EventListener);\r\n menuElm.appendChild(closeButtonElm);\r\n }\r\n menuElm.appendChild(commandMenuElm);\r\n\r\n this.populateCommandOrOptionItems(\r\n 'command',\r\n this._cellMenuProperties,\r\n commandMenuElm,\r\n commandItems,\r\n { cell: this._currentCell, row: this._currentRow, column: columnDef, dataContext, grid: this._grid, level }\r\n );\r\n }\r\n\r\n // increment level for possible next sub-menus if exists\r\n level++;\r\n\r\n return menuElm;\r\n }\r\n\r\n protected addSubMenuTitleWhenExists(item: MenuCommandItem | MenuOptionItem | 'divider', commandOrOptionMenu: HTMLDivElement) {\r\n if (item !== 'divider' && item?.subMenuTitle) {\r\n const subMenuTitleElm = document.createElement('div');\r\n subMenuTitleElm.className = 'slick-menu-title';\r\n subMenuTitleElm.textContent = item.subMenuTitle as string;\r\n const subMenuTitleClass = item.subMenuTitleCssClass as string;\r\n if (subMenuTitleClass) {\r\n subMenuTitleElm.classList.add(...Utils.classNameToList(subMenuTitleClass));\r\n }\r\n\r\n commandOrOptionMenu.appendChild(subMenuTitleElm);\r\n }\r\n }\r\n\r\n protected handleCloseButtonClicked(e: DOMMouseOrTouchEvent) {\r\n if (!e.defaultPrevented) {\r\n this.closeMenu(e);\r\n }\r\n }\r\n\r\n /** Close and destroy Cell Menu */\r\n closeMenu(e?: DOMMouseOrTouchEvent | SlickEventData_, args?: MenuFromCellCallbackArgs) {\r\n if (this._menuElm) {\r\n if (this.onBeforeMenuClose.notify({\r\n cell: args?.cell ?? 0,\r\n row: args?.row ?? 0,\r\n grid: this._grid,\r\n }, e, this).getReturnValue() === false) {\r\n return;\r\n }\r\n this._menuElm.remove();\r\n this._menuElm = null;\r\n }\r\n this.destroySubMenus();\r\n }\r\n\r\n /** Destroy all parent menus and any sub-menus */\r\n destroyAllMenus() {\r\n this.destroySubMenus();\r\n\r\n // remove all parent menu listeners before removing them from the DOM\r\n this._bindingEventService.unbindAll('parent-menu');\r\n document.querySelectorAll(`.slick-cell-menu${this.getGridUidSelector()}`)\r\n .forEach(subElm => subElm.remove());\r\n }\r\n\r\n /** Close and destroy all previously opened sub-menus */\r\n destroySubMenus() {\r\n this._bindingEventService.unbindAll('sub-menu');\r\n document.querySelectorAll(`.slick-cell-menu.slick-submenu${this.getGridUidSelector()}`)\r\n .forEach(subElm => subElm.remove());\r\n }\r\n\r\n protected repositionSubMenu(item: MenuCommandItem | MenuOptionItem | 'divider', type: MenuType, level: number, e: DOMMouseOrTouchEvent) {\r\n // when we're clicking a grid cell OR our last menu type (command/option) differs then we know that we need to start fresh and close any sub-menus that might still be open\r\n if (e.target.classList.contains('slick-cell') || this._lastMenuTypeClicked !== type) {\r\n this.destroySubMenus();\r\n }\r\n\r\n // creating sub-menu, we'll also pass level & the item object since we might have \"subMenuTitle\" to show\r\n const subMenuElm = this.createMenu((item as MenuCommandItem)?.commandItems || [], (item as MenuOptionItem)?.optionItems || [], level + 1, item);\r\n subMenuElm.style.display = 'block';\r\n document.body.appendChild(subMenuElm);\r\n this.repositionMenu(e, subMenuElm);\r\n }\r\n\r\n /**\r\n * Reposition the menu drop (up/down) and the side (left/right)\r\n * @param {*} event\r\n */\r\n repositionMenu(e: DOMMouseOrTouchEvent, menuElm: HTMLElement) {\r\n const isSubMenu = menuElm.classList.contains('slick-submenu');\r\n const parentElm = isSubMenu\r\n ? e.target.closest('.slick-cell-menu-item') as HTMLDivElement\r\n : e.target.closest('.slick-cell') as HTMLDivElement;\r\n\r\n if (menuElm && parentElm) {\r\n const parentOffset = Utils.offset(parentElm);\r\n let menuOffsetLeft = parentElm ? parentOffset?.left ?? 0 : e?.pageX ?? 0;\r\n let menuOffsetTop = parentElm ? parentOffset?.top ?? 0 : e?.pageY ?? 0;\r\n const parentCellWidth = parentElm?.offsetWidth || 0;\r\n const menuHeight = menuElm?.offsetHeight ?? 0;\r\n const menuWidth = Number(menuElm?.offsetWidth ?? this._cellMenuProperties.width ?? 0);\r\n const rowHeight = this._gridOptions.rowHeight;\r\n const dropOffset = Number(this._cellMenuProperties.autoAdjustDropOffset || 0);\r\n const sideOffset = Number(this._cellMenuProperties.autoAlignSideOffset || 0);\r\n\r\n // if autoAdjustDrop is enable, we first need to see what position the drop will be located (defaults to bottom)\r\n // without necessary toggling it's position just yet, we just want to know the future position for calculation\r\n if (this._cellMenuProperties.autoAdjustDrop) {\r\n // since we reposition menu below slick cell, we need to take it in consideration and do our calculation from that element\r\n const spaceBottom = Utils.calculateAvailableSpace(parentElm).bottom;\r\n const spaceTop = Utils.calculateAvailableSpace(parentElm).top;\r\n const spaceBottomRemaining = spaceBottom + dropOffset - rowHeight!;\r\n const spaceTopRemaining = spaceTop - dropOffset + rowHeight!;\r\n const dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom';\r\n if (dropPosition === 'top') {\r\n menuElm.classList.remove('dropdown');\r\n menuElm.classList.add('dropup');\r\n if (isSubMenu) {\r\n menuOffsetTop -= (menuHeight - dropOffset - parentElm.clientHeight);\r\n } else {\r\n menuOffsetTop -= menuHeight - dropOffset;\r\n }\r\n } else {\r\n menuElm.classList.remove('dropup');\r\n menuElm.classList.add('dropdown');\r\n if (isSubMenu) {\r\n menuOffsetTop += dropOffset;\r\n } else {\r\n menuOffsetTop += rowHeight! + dropOffset;\r\n }\r\n }\r\n }\r\n\r\n // when auto-align is set, it will calculate whether it has enough space in the viewport to show the drop menu on the right (default)\r\n // if there isn't enough space on the right, it will automatically align the drop menu to the left (defaults to the right)\r\n // to simulate an align left, we actually need to know the width of the drop menu\r\n if (this._cellMenuProperties.autoAlignSide) {\r\n const gridPos = this._grid.getGridPosition();\r\n let subMenuPosCalc = menuOffsetLeft + Number(menuWidth); // calculate coordinate at caller element far right\r\n if (isSubMenu) {\r\n subMenuPosCalc += parentElm.clientWidth;\r\n }\r\n const browserWidth = document.documentElement.clientWidth;\r\n const dropSide = (subMenuPosCalc >= gridPos.width || subMenuPosCalc >= browserWidth) ? 'left' : 'right';\r\n if (dropSide === 'left') {\r\n menuElm.classList.remove('dropright');\r\n menuElm.classList.add('dropleft');\r\n if (isSubMenu) {\r\n menuOffsetLeft -= menuWidth - sideOffset;\r\n } else {\r\n menuOffsetLeft -= menuWidth - parentCellWidth - sideOffset;\r\n }\r\n } else {\r\n menuElm.classList.remove('dropleft');\r\n menuElm.classList.add('dropright');\r\n if (isSubMenu) {\r\n menuOffsetLeft += sideOffset + parentElm.offsetWidth;\r\n } else {\r\n menuOffsetLeft += sideOffset;\r\n }\r\n }\r\n }\r\n\r\n // ready to reposition the menu\r\n menuElm.style.top = `${menuOffsetTop}px`;\r\n menuElm.style.left = `${menuOffsetLeft}px`;\r\n }\r\n }\r\n\r\n protected getGridUidSelector() {\r\n const gridUid = this._grid.getUID() || '';\r\n return gridUid ? `.${gridUid}` : '';\r\n }\r\n\r\n protected handleCellClick(evt: SlickEventData_ | DOMMouseOrTouchEvent, args: MenuCommandItemCallbackArgs) {\r\n this.destroyAllMenus(); // make there's only 1 parent menu opened at a time\r\n const e = (evt instanceof SlickEventData) ? evt.getNativeEvent>() : evt;\r\n const cell = this._grid.getCellFromEvent(e);\r\n\r\n if (cell) {\r\n const dataContext = this._grid.getDataItem(cell.row);\r\n const columnDef = this._grid.getColumns()[cell.cell];\r\n\r\n // prevent event from bubbling but only on column that has a cell menu defined\r\n if (columnDef?.cellMenu) {\r\n e.preventDefault();\r\n }\r\n\r\n // merge the cellMenu of the column definition with the default properties\r\n this._cellMenuProperties = Utils.extend({}, this._cellMenuProperties, columnDef.cellMenu);\r\n\r\n // run the override function (when defined), if the result is false it won't go further\r\n args = args || {};\r\n args.column = columnDef;\r\n args.dataContext = dataContext;\r\n args.grid = this._grid;\r\n if (!this.runOverrideFunctionWhenExists(this._cellMenuProperties.menuUsabilityOverride, args)) {\r\n return;\r\n }\r\n\r\n // create the DOM element\r\n this._menuElm = this.createParentMenu(e);\r\n\r\n // reposition the menu to where the user clicked\r\n if (this._menuElm) {\r\n this.repositionMenu(e, this._menuElm);\r\n this._menuElm.setAttribute('aria-expanded', 'true');\r\n this._menuElm.style.display = 'block';\r\n }\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 }\r\n\r\n /** When users click outside the Cell Menu, we will typically close the Cell Menu (and any sub-menus) */\r\n protected handleBodyMouseDown(e: DOMMouseOrTouchEvent) {\r\n // did we click inside the menu or any of its sub-menu(s)\r\n let isMenuClicked = false;\r\n if (this._menuElm?.contains(e.target)) {\r\n isMenuClicked = true;\r\n }\r\n if (!isMenuClicked) {\r\n document\r\n .querySelectorAll(`.slick-cell-menu.slick-submenu${this.getGridUidSelector()}`)\r\n .forEach(subElm => {\r\n if (subElm.contains(e.target)) {\r\n isMenuClicked = true;\r\n }\r\n });\r\n }\r\n\r\n if (this._menuElm !== e.target && !isMenuClicked && !e.defaultPrevented) {\r\n this.closeMenu(e, { cell: this._currentCell, row: this._currentRow, grid: this._grid });\r\n }\r\n }\r\n\r\n /** Build the Command Items section. */\r\n protected populateCommandOrOptionItems(\r\n itemType: MenuType,\r\n cellMenu: CellMenuOption,\r\n commandOrOptionMenuElm: HTMLElement,\r\n commandOrOptionItems: Array | Array,\r\n args: { cell: number, row: number, column: Column, dataContext: any, grid: SlickGrid, level: number }\r\n ) {\r\n if (!args || !commandOrOptionItems || !cellMenu) {\r\n return;\r\n }\r\n\r\n // user could pass a title on top of the Commands/Options section\r\n const level = args?.level || 0;\r\n const isSubMenu = level > 0;\r\n if (cellMenu?.[`${itemType}Title`] && !isSubMenu) {\r\n this[`_${itemType}TitleElm`] = document.createElement('div');\r\n this[`_${itemType}TitleElm`]!.className = 'slick-menu-title';\r\n this[`_${itemType}TitleElm`]!.textContent = cellMenu[`${itemType}Title`] as string;\r\n commandOrOptionMenuElm.appendChild(this[`_${itemType}TitleElm`]!);\r\n }\r\n\r\n for (let i = 0, ln = commandOrOptionItems.length; i < ln; i++) {\r\n let addClickListener = true;\r\n const item = commandOrOptionItems[i];\r\n\r\n // run each override functions to know if the item is visible and usable\r\n const isItemVisible = this.runOverrideFunctionWhenExists((item as MenuCommandItem | MenuOptionItem).itemVisibilityOverride, args);\r\n const isItemUsable = this.runOverrideFunctionWhenExists((item as MenuCommandItem | MenuOptionItem).itemUsabilityOverride, args);\r\n\r\n // if the result is not visible then there's no need to go further\r\n if (!isItemVisible) {\r\n continue;\r\n }\r\n\r\n // when the override is defined, we need to use its result to update the disabled property\r\n // so that \"handleMenuItemClick\" has the correct flag and won't trigger a command/option clicked event\r\n if (Object.prototype.hasOwnProperty.call(item, 'itemUsabilityOverride')) {\r\n (item as MenuCommandItem | MenuOptionItem).disabled = isItemUsable ? false : true;\r\n }\r\n\r\n const liElm = document.createElement('div');\r\n liElm.className = 'slick-cell-menu-item';\r\n liElm.role = 'menuitem';\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).divider || item === 'divider') {\r\n liElm.classList.add('slick-cell-menu-item-divider');\r\n addClickListener = false;\r\n }\r\n\r\n // if the item is disabled then add the disabled css class\r\n if ((item as MenuCommandItem | MenuOptionItem).disabled || !isItemUsable) {\r\n liElm.classList.add('slick-cell-menu-item-disabled');\r\n }\r\n\r\n // if the item is hidden then add the hidden css class\r\n if ((item as MenuCommandItem | MenuOptionItem).hidden) {\r\n liElm.classList.add('slick-cell-menu-item-hidden');\r\n }\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).cssClass) {\r\n liElm.classList.add(...Utils.classNameToList((item as MenuCommandItem | MenuOptionItem).cssClass));\r\n }\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).tooltip) {\r\n liElm.title = (item as MenuCommandItem | MenuOptionItem).tooltip || '';\r\n }\r\n\r\n const iconElm = document.createElement('div');\r\n iconElm.className = 'slick-cell-menu-icon';\r\n\r\n liElm.appendChild(iconElm);\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).iconCssClass) {\r\n iconElm.classList.add(...Utils.classNameToList((item as MenuCommandItem | MenuOptionItem).iconCssClass));\r\n }\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).iconImage) {\r\n iconElm.style.backgroundImage = `url(${(item as MenuCommandItem | MenuOptionItem).iconImage})`;\r\n }\r\n\r\n const textElm = document.createElement('span');\r\n textElm.className = 'slick-cell-menu-content';\r\n textElm.textContent = (item as MenuCommandItem | MenuOptionItem).title || '';\r\n\r\n liElm.appendChild(textElm);\r\n\r\n if ((item as MenuCommandItem | MenuOptionItem).textCssClass) {\r\n textElm.classList.add(...Utils.classNameToList((item as MenuCommandItem | MenuOptionItem).textCssClass));\r\n }\r\n\r\n commandOrOptionMenuElm.appendChild(liElm);\r\n\r\n if (addClickListener) {\r\n const eventGroup = isSubMenu ? 'sub-menu' : 'parent-menu';\r\n this._bindingEventService.bind(liElm, 'click', this.handleMenuItemClick.bind(this, item, itemType, level) as EventListener, undefined, eventGroup);\r\n }\r\n\r\n // optionally open sub-menu(s) by mouseover\r\n if (this._cellMenuProperties.subMenuOpenByEvent === 'mouseover') {\r\n this._bindingEventService.bind(liElm, 'mouseover', ((e: DOMMouseOrTouchEvent) => {\r\n if ((item as MenuCommandItem).commandItems || (item as MenuOptionItem).optionItems) {\r\n this.repositionSubMenu(item, itemType, level, e);\r\n this._lastMenuTypeClicked = itemType;\r\n } else if (!isSubMenu) {\r\n this.destroySubMenus();\r\n }\r\n }) as EventListener);\r\n }\r\n\r\n // the option/command item could be a sub-menu if it has another list of commands/options\r\n if ((item as MenuCommandItem).commandItems || (item as MenuOptionItem).optionItems) {\r\n const chevronElm = document.createElement('span');\r\n chevronElm.className = 'sub-item-chevron';\r\n if (this._cellMenuProperties.subItemChevronClass) {\r\n chevronElm.classList.add(...Utils.classNameToList(this._cellMenuProperties.subItemChevronClass));\r\n } else {\r\n chevronElm.textContent = '\u2B9E'; // \u2B9E or \u25B8\r\n }\r\n\r\n liElm.classList.add('slick-submenu-item');\r\n liElm.appendChild(chevronElm);\r\n continue;\r\n }\r\n }\r\n }\r\n\r\n protected handleMenuItemClick(item: MenuCommandItem | MenuOptionItem | 'divider', type: MenuType, level = 0, e: DOMMouseOrTouchEvent) {\r\n if ((item as never)?.[type] !== undefined && item !== 'divider' && !item.disabled && !(item as MenuCommandItem | MenuOptionItem).divider && this._currentCell !== undefined && this._currentRow !== undefined) {\r\n if (type === 'option' && !this._grid.getEditorLock().commitCurrentEdit()) {\r\n return;\r\n }\r\n const optionOrCommand = (item as any)[type] !== undefined ? (item as any)[type] : '';\r\n const row = this._currentRow;\r\n const cell = this._currentCell;\r\n const columnDef = this._grid.getColumns()[cell];\r\n const dataContext = this._grid.getDataItem(row);\r\n\r\n if (optionOrCommand !== undefined && !(item as any)[`${type}Items`]) {\r\n // user could execute a callback through 2 ways\r\n // via the onCommand/onOptionSelected event and/or an action callback\r\n const callbackArgs = {\r\n cell,\r\n row,\r\n grid: this._grid,\r\n [type]: optionOrCommand,\r\n item,\r\n column: columnDef,\r\n dataContext,\r\n };\r\n const eventType = type === 'command' ? 'onCommand' : 'onOptionSelected';\r\n this[eventType].notify(callbackArgs as any, e, this);\r\n\r\n // execute action callback when defined\r\n if (typeof item.action === 'function') {\r\n (item as any).action.call(this, e, callbackArgs);\r\n }\r\n\r\n // unless prevented, close the menu\r\n if (!e.defaultPrevented) {\r\n this.closeMenu(e, { cell, row, grid: this._grid });\r\n }\r\n } else if ((item as MenuCommandItem).commandItems || (item as MenuOptionItem).optionItems) {\r\n this.repositionSubMenu(item, type, level, e);\r\n } else {\r\n this.destroySubMenus();\r\n }\r\n this._lastMenuTypeClicked = type;\r\n }\r\n }\r\n\r\n /**\r\n * Method that user can pass to override the default behavior.\r\n * In order word, user can choose or an item is (usable/visible/enable) by providing his own logic.\r\n * @param overrideFn: override function callback\r\n * @param args: multiple arguments provided to the override (cell, row, columnDef, dataContext, grid)\r\n */\r\n protected runOverrideFunctionWhenExists(overrideFn: ((args: any) => boolean) | undefined, args: T): boolean {\r\n if (typeof overrideFn === 'function') {\r\n return overrideFn.call(this, args);\r\n }\r\n return true;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n Plugins: {\r\n CellMenu: SlickCellMenu\r\n }\r\n }\r\n });\r\n}\r\n", "import type { CSSStyleDeclarationWritable, CellRangeDecoratorOption, SlickPlugin } from '../models/index.js';\r\nimport { Utils as Utils_, type SlickRange } from '../slick.core.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 Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\n/***\r\n * Displays an overlay on top of a given cell range.\r\n *\r\n * TODO:\r\n * Currently, it blocks mouse events to DOM nodes behind it.\r\n * Use FF and WebKit-specific \"pointer-events\" CSS style, or some kind of event forwarding.\r\n * Could also construct the borders separately using 4 individual DIVs.\r\n *\r\n * @param {Grid} grid\r\n * @param {Object} options\r\n */\r\nexport class SlickCellRangeDecorator implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CellRangeDecorator' as const;\r\n\r\n // --\r\n // protected props\r\n protected _options: CellRangeDecoratorOption;\r\n protected _elem?: HTMLDivElement | null;\r\n protected _defaults = {\r\n selectionCssClass: 'slick-range-decorator',\r\n selectionCss: {\r\n zIndex: '9999',\r\n border: '2px dashed red'\r\n },\r\n offset: { top: -1, left: -1, height: -2, width: -2 }\r\n } as CellRangeDecoratorOption;\r\n\r\n constructor(protected readonly grid: SlickGrid, options?: Partial) {\r\n this._options = Utils.extend(true, {}, this._defaults, options);\r\n }\r\n\r\n destroy() {\r\n this.hide();\r\n }\r\n\r\n init() { }\r\n\r\n hide() {\r\n this._elem?.remove();\r\n this._elem = null;\r\n }\r\n\r\n show(range: SlickRange) {\r\n if (!this._elem) {\r\n this._elem = document.createElement('div');\r\n this._elem.className = this._options.selectionCssClass;\r\n Object.keys(this._options.selectionCss as CSSStyleDeclaration).forEach((cssStyleKey) => {\r\n this._elem!.style[cssStyleKey as CSSStyleDeclarationWritable] = this._options.selectionCss[cssStyleKey as CSSStyleDeclarationWritable];\r\n });\r\n this._elem.style.position = 'absolute';\r\n const canvasNode = this.grid.getActiveCanvasNode();\r\n if (canvasNode) {\r\n canvasNode.appendChild(this._elem);\r\n }\r\n }\r\n\r\n const from = this.grid.getCellNodeBox(range.fromRow, range.fromCell);\r\n const to = this.grid.getCellNodeBox(range.toRow, range.toCell);\r\n\r\n if (from && to && this._options?.offset) {\r\n this._elem.style.top = `${from.top + this._options.offset.top}px`;\r\n this._elem.style.left = `${from.left + this._options.offset.left}px`;\r\n this._elem.style.height = `${to.bottom - from.top + this._options.offset.height}px`;\r\n this._elem.style.width = `${to.right - from.left + this._options.offset.width}px`;\r\n }\r\n\r\n return this._elem;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n CellRangeDecorator: SlickCellRangeDecorator\r\n }\r\n });\r\n}\r\n", "import type { DragItem, DragPosition, DraggableOption, MouseWheelOption, ResizableOption } from './models/index.js';\r\nimport { Utils as Utils_ } from './slick.core.js';\r\n\r\n// for (iife) load Slick methods from global Slick object, or use imports for (esm)\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\n/***\r\n * Interactions, add basic behaviors to any element.\r\n * All the packages are written in pure vanilla JS and supports both mouse & touch events.\r\n * @module Interactions\r\n * @namespace Slick\r\n */\r\n\r\n/**\r\n * Draggable Class, enables dragging functionality for any element for example cell & row selections.\r\n * Note that mouse/touch start is on the specified container element but all other events are on the document body.\r\n * code refs:\r\n * https://betterprogramming.pub/perfecting-drag-and-drop-in-pure-vanilla-javascript-a761184b797a\r\n * available optional options:\r\n * - containerElement: container DOM element, defaults to \"document\"\r\n * - allowDragFrom: when defined, only allow dragging from an element that matches a specific query selector\r\n * - allowDragFromClosest: when defined, only allow dragging from an element or its parent matching a specific .closest() query selector\r\n * - onDragInit: drag initialized callback\r\n * - onDragStart: drag started callback\r\n * - onDrag: drag callback\r\n * - onDragEnd: drag ended callback\r\n * @param {Object} options\r\n * @returns - Draggable instance which includes destroy method\r\n * @class Draggable\r\n */\r\nexport function Draggable(options: DraggableOption) {\r\n let { containerElement } = options;\r\n const { onDragInit, onDragStart, onDrag, onDragEnd, preventDragFromKeys } = options;\r\n let element: HTMLElement | null;\r\n let startX: number;\r\n let startY: number;\r\n let deltaX: number;\r\n let deltaY: number;\r\n let dragStarted: boolean;\r\n\r\n if (!containerElement) {\r\n containerElement = document.body;\r\n }\r\n\r\n let originaldd: Partial = {\r\n dragSource: containerElement,\r\n dragHandle: null,\r\n };\r\n\r\n function init() {\r\n if (containerElement) {\r\n containerElement.addEventListener('mousedown', userPressed as EventListener);\r\n containerElement.addEventListener('touchstart', userPressed as EventListener);\r\n }\r\n }\r\n\r\n function executeDragCallbackWhenDefined(callback?: (e: DragEvent, dd: DragItem) => boolean | void, evt?: MouseEvent | Touch | TouchEvent | KeyboardEvent, dd?: DragItem) {\r\n if (typeof callback === 'function') {\r\n return callback(evt as DragEvent, dd as DragItem);\r\n }\r\n }\r\n\r\n function destroy() {\r\n if (containerElement) {\r\n containerElement.removeEventListener('mousedown', userPressed as EventListener);\r\n containerElement.removeEventListener('touchstart', userPressed as EventListener);\r\n }\r\n }\r\n\r\n /** Do we want to prevent Drag events from happening (for example prevent onDrag when Ctrl key is pressed while dragging) */\r\n function preventDrag(event: MouseEvent | TouchEvent | KeyboardEvent) {\r\n let eventPrevented = false;\r\n if (preventDragFromKeys) {\r\n preventDragFromKeys.forEach(key => {\r\n if ((event as KeyboardEvent)[key]) {\r\n eventPrevented = true;\r\n }\r\n });\r\n }\r\n return eventPrevented;\r\n }\r\n\r\n function userPressed(event: MouseEvent | TouchEvent | KeyboardEvent) {\r\n if (!preventDrag(event)) {\r\n element = event.target as HTMLElement;\r\n const targetEvent: MouseEvent | Touch = (event as TouchEvent)?.touches?.[0] ?? event;\r\n const { target } = targetEvent;\r\n\r\n if (!options.allowDragFrom || (options.allowDragFrom && (element.matches(options.allowDragFrom)) || (options.allowDragFromClosest && element.closest(options.allowDragFromClosest)))) {\r\n originaldd.dragHandle = element as HTMLElement;\r\n const winScrollPos = Utils.windowScrollPosition();\r\n startX = winScrollPos.left + targetEvent.clientX;\r\n startY = winScrollPos.top + targetEvent.clientY;\r\n deltaX = targetEvent.clientX - targetEvent.clientX;\r\n deltaY = targetEvent.clientY - targetEvent.clientY;\r\n originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target });\r\n const result = executeDragCallbackWhenDefined(onDragInit as (e: DragEvent, dd: DragPosition) => boolean | void, event, originaldd as DragItem);\r\n\r\n if (result !== false) {\r\n document.body.addEventListener('mousemove', userMoved);\r\n document.body.addEventListener('touchmove', userMoved);\r\n document.body.addEventListener('mouseup', userReleased);\r\n document.body.addEventListener('touchend', userReleased);\r\n document.body.addEventListener('touchcancel', userReleased);\r\n }\r\n }\r\n }\r\n }\r\n\r\n function userMoved(event: MouseEvent | TouchEvent | KeyboardEvent) {\r\n if (!preventDrag(event)) {\r\n const targetEvent: MouseEvent | Touch = (event as TouchEvent)?.touches?.[0] ?? event;\r\n deltaX = targetEvent.clientX - startX;\r\n deltaY = targetEvent.clientY - startY;\r\n const { target } = targetEvent;\r\n\r\n if (!dragStarted) {\r\n originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target });\r\n executeDragCallbackWhenDefined(onDragStart, event, originaldd as DragItem);\r\n dragStarted = true;\r\n }\r\n\r\n originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target });\r\n executeDragCallbackWhenDefined(onDrag, event, originaldd as DragItem);\r\n }\r\n }\r\n\r\n function userReleased(event: MouseEvent | TouchEvent) {\r\n document.body.removeEventListener('mousemove', userMoved);\r\n document.body.removeEventListener('touchmove', userMoved);\r\n document.body.removeEventListener('mouseup', userReleased);\r\n document.body.removeEventListener('touchend', userReleased);\r\n document.body.removeEventListener('touchcancel', userReleased);\r\n\r\n // trigger a dragEnd event only after dragging started and stopped\r\n if (dragStarted) {\r\n const { target } = event;\r\n originaldd = Object.assign(originaldd, { target });\r\n executeDragCallbackWhenDefined(onDragEnd, event, originaldd as DragItem);\r\n dragStarted = false;\r\n }\r\n }\r\n\r\n // initialize Slick.MouseWheel by attaching mousewheel event\r\n init();\r\n\r\n // public API\r\n return { destroy };\r\n}\r\n\r\n/**\r\n * MouseWheel Class, add mousewheel listeners and calculate delta values and return them in the callback function.\r\n * available optional options:\r\n * - element: optional DOM element to attach mousewheel values, if undefined we'll attach it to the \"window\" object\r\n * - onMouseWheel: mousewheel callback\r\n * @param {Object} options\r\n * @returns - MouseWheel instance which includes destroy method\r\n * @class MouseWheel\r\n */\r\nexport function MouseWheel(options: MouseWheelOption) {\r\n const { element, onMouseWheel } = options;\r\n\r\n function destroy() {\r\n element.removeEventListener('wheel', wheelHandler as EventListener);\r\n element.removeEventListener('mousewheel', wheelHandler as EventListener);\r\n }\r\n\r\n function init() {\r\n element.addEventListener('wheel', wheelHandler as EventListener);\r\n element.addEventListener('mousewheel', wheelHandler as EventListener);\r\n }\r\n\r\n // copy over the same event handler code used in jquery.mousewheel\r\n function wheelHandler(event: WheelEvent & { axis: number; wheelDelta: number; wheelDeltaX: number; wheelDeltaY: number; HORIZONTAL_AXIS: number; }) {\r\n const orgEvent = event || window.event;\r\n let delta = 0;\r\n let deltaX = 0;\r\n let deltaY = 0;\r\n\r\n // Old school scrollwheel delta\r\n if (orgEvent.wheelDelta) {\r\n delta = orgEvent.wheelDelta / 120;\r\n }\r\n if (orgEvent.detail) {\r\n delta = -orgEvent.detail / 3;\r\n }\r\n\r\n // New school multidimensional scroll (touchpads) deltas\r\n deltaY = delta;\r\n\r\n // Gecko\r\n if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {\r\n deltaY = 0;\r\n deltaX = -1 * delta;\r\n }\r\n\r\n // WebKit\r\n if (orgEvent.wheelDeltaY !== undefined) {\r\n deltaY = orgEvent.wheelDeltaY / 120;\r\n }\r\n if (orgEvent.wheelDeltaX !== undefined) {\r\n deltaX = -1 * orgEvent.wheelDeltaX / 120;\r\n }\r\n\r\n if (typeof onMouseWheel === 'function') {\r\n onMouseWheel(event, delta, deltaX, deltaY);\r\n }\r\n }\r\n\r\n // initialize Slick.MouseWheel by attaching mousewheel event\r\n init();\r\n\r\n // public API\r\n return { destroy };\r\n}\r\n\r\n/**\r\n * Resizable Class, enables resize functionality for any element\r\n * Code mostly comes from these 2 resources:\r\n * https://spin.atomicobject.com/2019/11/21/creating-a-resizable-html-element/\r\n * https://htmldom.dev/make-a-resizable-element/\r\n * available optional options:\r\n * - resizeableElement: resizable DOM element\r\n * - resizeableHandleElement: resizable DOM element\r\n * - onResizeStart: resize start callback\r\n * - onResize: resizing callback\r\n * - onResizeEnd: resize ended callback\r\n * @param {Object} options\r\n * @returns - Resizable instance which includes destroy method\r\n * @class Resizable\r\n */\r\nexport function Resizable(options: ResizableOption) {\r\n const { resizeableElement, resizeableHandleElement, onResizeStart, onResize, onResizeEnd } = options;\r\n if (!resizeableHandleElement || typeof resizeableHandleElement.addEventListener !== 'function') {\r\n throw new Error('[Slick.Resizable] You did not provide a valid html element that will be used for the handle to resize.');\r\n }\r\n\r\n function init() {\r\n // add event listeners on the draggable element\r\n resizeableHandleElement.addEventListener('mousedown', resizeStartHandler);\r\n resizeableHandleElement.addEventListener('touchstart', resizeStartHandler);\r\n }\r\n\r\n function destroy() {\r\n if (typeof resizeableHandleElement?.removeEventListener === 'function') {\r\n resizeableHandleElement.removeEventListener('mousedown', resizeStartHandler);\r\n resizeableHandleElement.removeEventListener('touchstart', resizeStartHandler);\r\n }\r\n }\r\n\r\n function executeResizeCallbackWhenDefined(callback?: Function, e?: MouseEvent | TouchEvent | Touch) {\r\n if (typeof callback === 'function') {\r\n return callback(e, { resizeableElement, resizeableHandleElement });\r\n }\r\n }\r\n\r\n function resizeStartHandler(e: MouseEvent | TouchEvent) {\r\n e.preventDefault();\r\n const event = (e as TouchEvent).touches ? (e as TouchEvent).changedTouches[0] : e;\r\n const result = executeResizeCallbackWhenDefined(onResizeStart, event);\r\n if (result !== false) {\r\n document.body.addEventListener('mousemove', resizingHandler);\r\n document.body.addEventListener('mouseup', resizeEndHandler);\r\n document.body.addEventListener('touchmove', resizingHandler);\r\n document.body.addEventListener('touchend', resizeEndHandler);\r\n }\r\n }\r\n\r\n function resizingHandler(e: MouseEvent | TouchEvent) {\r\n if (e.preventDefault && e.type !== 'touchmove') {\r\n e.preventDefault();\r\n }\r\n const event = (e as TouchEvent).touches ? (e as TouchEvent).changedTouches[0] : e;\r\n if (typeof onResize === 'function') {\r\n onResize(event, { resizeableElement, resizeableHandleElement });\r\n }\r\n }\r\n\r\n /** Remove all mouse/touch handlers */\r\n function resizeEndHandler(e: MouseEvent | TouchEvent) {\r\n const event = (e as TouchEvent).touches ? (e as TouchEvent).changedTouches[0] : e;\r\n executeResizeCallbackWhenDefined(onResizeEnd, event);\r\n document.body.removeEventListener('mousemove', resizingHandler);\r\n document.body.removeEventListener('mouseup', resizeEndHandler);\r\n document.body.removeEventListener('touchmove', resizingHandler);\r\n document.body.removeEventListener('touchend', resizeEndHandler);\r\n }\r\n\r\n init();\r\n\r\n // public API\r\n return { destroy };\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(Slick, {\r\n Draggable,\r\n MouseWheel,\r\n Resizable,\r\n });\r\n}\r\n", "import { SlickEvent as SlickEvent_, type SlickEventData, SlickEventHandler as SlickEventHandler_, SlickRange as SlickRange_, Utils as Utils_ } from '../slick.core.js';\r\nimport { Draggable as Draggable_ } from '../slick.interactions.js';\r\nimport { SlickCellRangeDecorator as SlickCellRangeDecorator_ } from './slick.cellrangedecorator.js';\r\nimport type { CellRangeSelectorOption, DragPosition, DragRange, DragRowMove, GridOption, MouseOffsetViewport, OnScrollEventArgs, SlickPlugin } 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 SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;\r\nconst SlickEventHandler = IIFE_ONLY ? Slick.EventHandler : SlickEventHandler_;\r\nconst SlickRange = IIFE_ONLY ? Slick.Range : SlickRange_;\r\nconst Draggable = IIFE_ONLY ? Slick.Draggable : Draggable_;\r\nconst SlickCellRangeDecorator = IIFE_ONLY ? Slick.CellRangeDecorator : SlickCellRangeDecorator_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\nexport class SlickCellRangeSelector implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CellRangeSelector' as const;\r\n onBeforeCellRangeSelected = new SlickEvent<{ row: number; cell: number; }>('onBeforeCellRangeSelected');\r\n onCellRangeSelected = new SlickEvent<{ range: SlickRange_; }>('onCellRangeSelected');\r\n onCellRangeSelecting = new SlickEvent<{ range: SlickRange_; }>('onCellRangeSelecting');\r\n\r\n // --\r\n // protected props\r\n protected _grid!: SlickGrid;\r\n protected _currentlySelectedRange: DragRange | null = null;\r\n protected _canvas: HTMLElement | null = null;\r\n protected _decorator!: SlickCellRangeDecorator_;\r\n protected _gridOptions!: GridOption;\r\n protected _activeCanvas!: HTMLElement;\r\n protected _dragging = false;\r\n protected _handler = new SlickEventHandler();\r\n protected _options: CellRangeSelectorOption;\r\n protected _defaults = {\r\n autoScroll: true,\r\n minIntervalToShowNextCell: 30,\r\n maxIntervalToShowNextCell: 600, // better to a multiple of minIntervalToShowNextCell\r\n accelerateInterval: 5, // increase 5ms when cursor 1px outside the viewport.\r\n selectionCss: {\r\n border: '2px dashed blue'\r\n }\r\n } as CellRangeSelectorOption;\r\n\r\n // Frozen row & column variables\r\n protected _rowOffset = 0;\r\n protected _columnOffset = 0;\r\n protected _isRightCanvas = false;\r\n protected _isBottomCanvas = false;\r\n\r\n // autoScroll related constiables\r\n protected _activeViewport!: HTMLElement;\r\n protected _autoScrollTimerId?: number;\r\n protected _draggingMouseOffset!: MouseOffsetViewport;\r\n protected _moveDistanceForOneCell!: { x: number; y: number; };\r\n protected _xDelayForNextCell = 0;\r\n protected _yDelayForNextCell = 0;\r\n protected _viewportHeight = 0;\r\n protected _viewportWidth = 0;\r\n protected _isRowMoveRegistered = false;\r\n\r\n // Scrollings\r\n protected _scrollLeft = 0;\r\n protected _scrollTop = 0;\r\n\r\n constructor(options?: Partial) {\r\n this._options = Utils.extend(true, {}, this._defaults, options);\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n if (Draggable === undefined) {\r\n throw new Error('Slick.Draggable is undefined, make sure to import \"slick.interactions.js\"');\r\n }\r\n\r\n this._decorator = this._options.cellDecorator || new SlickCellRangeDecorator(grid, this._options);\r\n this._grid = grid;\r\n Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);\r\n this._canvas = this._grid.getCanvasNode();\r\n this._gridOptions = this._grid.getOptions();\r\n this._handler\r\n .subscribe(this._grid.onScroll, this.handleScroll.bind(this))\r\n .subscribe(this._grid.onDragInit, this.handleDragInit.bind(this))\r\n .subscribe(this._grid.onDragStart, this.handleDragStart.bind(this))\r\n .subscribe(this._grid.onDrag, this.handleDrag.bind(this))\r\n .subscribe(this._grid.onDragEnd, this.handleDragEnd.bind(this));\r\n }\r\n\r\n destroy() {\r\n this._handler.unsubscribeAll();\r\n this._activeCanvas = null as any;\r\n this._activeViewport = null as any;\r\n this._canvas = null;\r\n this._decorator?.destroy();\r\n }\r\n\r\n getCellDecorator() {\r\n return this._decorator;\r\n }\r\n\r\n protected handleScroll(_e: SlickEventData, args: OnScrollEventArgs) {\r\n this._scrollTop = args.scrollTop;\r\n this._scrollLeft = args.scrollLeft;\r\n }\r\n\r\n protected handleDragInit(e: SlickEventData) {\r\n // Set the active canvas node because the decorator needs to append its\r\n // box to the correct canvas\r\n this._activeCanvas = this._grid.getActiveCanvasNode(e);\r\n this._activeViewport = this._grid.getActiveViewportNode(e);\r\n\r\n const scrollbarDimensions = this._grid.getDisplayedScrollbarDimensions();\r\n this._viewportWidth = this._activeViewport.offsetWidth - scrollbarDimensions.width;\r\n this._viewportHeight = this._activeViewport.offsetHeight - scrollbarDimensions.height;\r\n\r\n this._moveDistanceForOneCell = {\r\n x: this._grid.getAbsoluteColumnMinWidth() / 2,\r\n y: this._grid.getOptions().rowHeight! / 2\r\n };\r\n this._isRowMoveRegistered = this.hasRowMoveManager();\r\n\r\n this._rowOffset = 0;\r\n this._columnOffset = 0;\r\n this._isBottomCanvas = this._activeCanvas.classList.contains('grid-canvas-bottom');\r\n\r\n if (this._gridOptions.frozenRow! > -1 && this._isBottomCanvas) {\r\n const canvasSelector = `.${this._grid.getUID()} .grid-canvas-${this._gridOptions.frozenBottom ? 'bottom' : 'top'}`;\r\n const canvasElm = document.querySelector(canvasSelector);\r\n if (canvasElm) {\r\n this._rowOffset = canvasElm.clientHeight || 0;\r\n }\r\n }\r\n\r\n this._isRightCanvas = this._activeCanvas.classList.contains('grid-canvas-right');\r\n\r\n if (this._gridOptions.frozenColumn! > -1 && this._isRightCanvas) {\r\n const canvasLeftElm = document.querySelector(`.${this._grid.getUID()} .grid-canvas-left`);\r\n if (canvasLeftElm) {\r\n this._columnOffset = canvasLeftElm.clientWidth || 0;\r\n }\r\n }\r\n\r\n // prevent the grid from cancelling drag'n'drop by default\r\n e.stopImmediatePropagation();\r\n e.preventDefault();\r\n }\r\n\r\n protected handleDragStart(e: SlickEventData, dd: DragRowMove) {\r\n const cell = this._grid.getCellFromEvent(e);\r\n if (cell && this.onBeforeCellRangeSelected.notify(cell).getReturnValue() !== false && this._grid.canCellBeSelected(cell.row, cell.cell)) {\r\n this._dragging = true;\r\n e.stopImmediatePropagation();\r\n }\r\n if (!this._dragging) {\r\n return;\r\n }\r\n\r\n this._grid.focus();\r\n\r\n const canvasOffset = Utils.offset(this._canvas);\r\n\r\n let startX = dd.startX - (canvasOffset?.left ?? 0);\r\n if (this._gridOptions.frozenColumn! >= 0 && this._isRightCanvas) {\r\n startX += this._scrollLeft;\r\n }\r\n\r\n let startY = dd.startY - (canvasOffset?.top ?? 0);\r\n if (this._gridOptions.frozenRow! >= 0 && this._isBottomCanvas) {\r\n startY += this._scrollTop;\r\n }\r\n\r\n const start = this._grid.getCellFromPoint(startX, startY);\r\n\r\n dd.range = { start, end: {} };\r\n this._currentlySelectedRange = dd.range;\r\n return this._decorator.show(new SlickRange(start.row, start.cell));\r\n }\r\n\r\n protected handleDrag(evt: SlickEventData, dd: DragRowMove) {\r\n if (!this._dragging && !this._isRowMoveRegistered) {\r\n return;\r\n }\r\n if (!this._isRowMoveRegistered) {\r\n evt.stopImmediatePropagation();\r\n }\r\n\r\n const e = evt.getNativeEvent();\r\n if (this._options.autoScroll) {\r\n this._draggingMouseOffset = this.getMouseOffsetViewport(e, dd);\r\n if (this._draggingMouseOffset.isOutsideViewport) {\r\n return this.handleDragOutsideViewport();\r\n }\r\n }\r\n this.stopIntervalTimer();\r\n this.handleDragTo(e, dd);\r\n }\r\n\r\n protected getMouseOffsetViewport(e: MouseEvent | TouchEvent, dd: DragRowMove): MouseOffsetViewport {\r\n const targetEvent: MouseEvent | Touch = (e as TouchEvent)?.touches?.[0] ?? e;\r\n const viewportLeft = this._activeViewport.scrollLeft;\r\n const viewportTop = this._activeViewport.scrollTop;\r\n const viewportRight = viewportLeft + this._viewportWidth;\r\n const viewportBottom = viewportTop + this._viewportHeight;\r\n\r\n const viewportOffset = Utils.offset(this._activeViewport);\r\n const viewportOffsetLeft = viewportOffset?.left ?? 0;\r\n const viewportOffsetTop = viewportOffset?.top ?? 0;\r\n const viewportOffsetRight = viewportOffsetLeft + this._viewportWidth;\r\n const viewportOffsetBottom = viewportOffsetTop + this._viewportHeight;\r\n\r\n const result = {\r\n e,\r\n dd,\r\n viewport: {\r\n left: viewportLeft,\r\n top: viewportTop,\r\n right: viewportRight,\r\n bottom: viewportBottom,\r\n offset: {\r\n left: viewportOffsetLeft,\r\n top: viewportOffsetTop,\r\n right: viewportOffsetRight,\r\n bottom: viewportOffsetBottom\r\n }\r\n },\r\n // Consider the viewport as the origin, the `offset` is based on the coordinate system:\r\n // the cursor is on the viewport's left/bottom when it is less than 0, and on the right/top when greater than 0.\r\n offset: {\r\n x: 0,\r\n y: 0\r\n },\r\n isOutsideViewport: false\r\n };\r\n // ... horizontal\r\n if (targetEvent.pageX < viewportOffsetLeft) {\r\n result.offset.x = targetEvent.pageX - viewportOffsetLeft;\r\n } else if (targetEvent.pageX > viewportOffsetRight) {\r\n result.offset.x = targetEvent.pageX - viewportOffsetRight;\r\n }\r\n // ... vertical\r\n if (targetEvent.pageY < viewportOffsetTop) {\r\n result.offset.y = viewportOffsetTop - targetEvent.pageY;\r\n } else if (targetEvent.pageY > viewportOffsetBottom) {\r\n result.offset.y = viewportOffsetBottom - targetEvent.pageY;\r\n }\r\n result.isOutsideViewport = !!result.offset.x || !!result.offset.y;\r\n return result;\r\n }\r\n\r\n protected handleDragOutsideViewport() {\r\n this._xDelayForNextCell = this._options.maxIntervalToShowNextCell - Math.abs(this._draggingMouseOffset.offset.x) * this._options.accelerateInterval;\r\n this._yDelayForNextCell = this._options.maxIntervalToShowNextCell - Math.abs(this._draggingMouseOffset.offset.y) * this._options.accelerateInterval;\r\n // only one timer is created to handle the case that cursor outside the viewport\r\n if (!this._autoScrollTimerId) {\r\n let xTotalDelay = 0;\r\n let yTotalDelay = 0;\r\n this._autoScrollTimerId = window.setInterval(() => {\r\n let xNeedUpdate = false;\r\n let yNeedUpdate = false;\r\n // ... horizontal\r\n if (this._draggingMouseOffset.offset.x) {\r\n xTotalDelay += this._options.minIntervalToShowNextCell;\r\n xNeedUpdate = xTotalDelay >= this._xDelayForNextCell;\r\n } else {\r\n xTotalDelay = 0;\r\n }\r\n // ... vertical\r\n if (this._draggingMouseOffset.offset.y) {\r\n yTotalDelay += this._options.minIntervalToShowNextCell;\r\n yNeedUpdate = yTotalDelay >= this._yDelayForNextCell;\r\n } else {\r\n yTotalDelay = 0;\r\n }\r\n if (xNeedUpdate || yNeedUpdate) {\r\n if (xNeedUpdate) {\r\n xTotalDelay = 0;\r\n }\r\n if (yNeedUpdate) {\r\n yTotalDelay = 0;\r\n }\r\n this.handleDragToNewPosition(xNeedUpdate, yNeedUpdate);\r\n }\r\n }, this._options.minIntervalToShowNextCell);\r\n }\r\n }\r\n\r\n protected handleDragToNewPosition(xNeedUpdate: boolean, yNeedUpdate: boolean) {\r\n let pageX = this._draggingMouseOffset.e.pageX;\r\n let pageY = this._draggingMouseOffset.e.pageY;\r\n const mouseOffsetX = this._draggingMouseOffset.offset.x;\r\n const mouseOffsetY = this._draggingMouseOffset.offset.y;\r\n const viewportOffset = this._draggingMouseOffset.viewport.offset;\r\n // ... horizontal\r\n if (xNeedUpdate && mouseOffsetX) {\r\n if (mouseOffsetX > 0) {\r\n pageX = viewportOffset.right + this._moveDistanceForOneCell.x;\r\n } else {\r\n pageX = viewportOffset.left - this._moveDistanceForOneCell.x;\r\n }\r\n }\r\n // ... vertical\r\n if (yNeedUpdate && mouseOffsetY) {\r\n if (mouseOffsetY > 0) {\r\n pageY = viewportOffset.top - this._moveDistanceForOneCell.y;\r\n } else {\r\n pageY = viewportOffset.bottom + this._moveDistanceForOneCell.y;\r\n }\r\n }\r\n this.handleDragTo({ pageX, pageY }, this._draggingMouseOffset.dd);\r\n }\r\n\r\n protected stopIntervalTimer() {\r\n if (this._autoScrollTimerId) {\r\n window.clearInterval(this._autoScrollTimerId);\r\n this._autoScrollTimerId = undefined;\r\n }\r\n }\r\n\r\n protected handleDragTo(e: { pageX: number; pageY: number; }, dd: DragPosition) {\r\n const targetEvent: MouseEvent | Touch = (e as unknown as TouchEvent)?.touches?.[0] ?? e;\r\n const canvasOffset = Utils.offset(this._activeCanvas);\r\n const end = this._grid.getCellFromPoint(\r\n targetEvent.pageX - (canvasOffset?.left ?? 0) + this._columnOffset,\r\n targetEvent.pageY - (canvasOffset?.top ?? 0) + this._rowOffset\r\n );\r\n\r\n // ... frozen column(s),\r\n if (this._gridOptions.frozenColumn! >= 0 && (!this._isRightCanvas && (end.cell > this._gridOptions.frozenColumn!)) || (this._isRightCanvas && (end.cell <= this._gridOptions.frozenColumn!))) {\r\n return;\r\n }\r\n\r\n // ... or frozen row(s)\r\n if (this._gridOptions.frozenRow! >= 0 && (!this._isBottomCanvas && (end.row >= this._gridOptions.frozenRow!)) || (this._isBottomCanvas && (end.row < this._gridOptions.frozenRow!))) {\r\n return;\r\n }\r\n\r\n // scrolling the viewport to display the target `end` cell if it is not fully displayed\r\n if (this._options.autoScroll && this._draggingMouseOffset) {\r\n const endCellBox = this._grid.getCellNodeBox(end.row, end.cell);\r\n if (!endCellBox) {\r\n return;\r\n }\r\n const viewport = this._draggingMouseOffset.viewport;\r\n if (endCellBox.left < viewport.left || endCellBox.right > viewport.right\r\n || endCellBox.top < viewport.top || endCellBox.bottom > viewport.bottom) {\r\n this._grid.scrollCellIntoView(end.row, end.cell);\r\n }\r\n }\r\n\r\n // ... or regular grid (without any frozen options)\r\n if (!this._grid.canCellBeSelected(end.row, end.cell)) {\r\n return;\r\n }\r\n\r\n if (dd?.range) {\r\n dd.range.end = end;\r\n\r\n const range = new SlickRange(dd.range.start.row ?? 0, dd.range.start.cell ?? 0, end.row, end.cell);\r\n this._decorator.show(range);\r\n this.onCellRangeSelecting.notify({\r\n range\r\n });\r\n }\r\n }\r\n\r\n protected hasRowMoveManager() {\r\n return !!(this._grid.getPluginByName('RowMoveManager') || this._grid.getPluginByName('CrossGridRowMoveManager'));\r\n }\r\n\r\n protected handleDragEnd(e: SlickEventData, dd: DragPosition) {\r\n this._decorator.hide();\r\n if (!this._dragging) {\r\n return;\r\n }\r\n\r\n this._dragging = false;\r\n e.stopImmediatePropagation();\r\n\r\n this.stopIntervalTimer();\r\n this.onCellRangeSelected.notify({\r\n range: new SlickRange(\r\n dd.range.start.row ?? 0,\r\n dd.range.start.cell ?? 0,\r\n dd.range.end.row,\r\n dd.range.end.cell\r\n )\r\n });\r\n }\r\n\r\n getCurrentRange() {\r\n return this._currentlySelectedRange;\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(Slick, {\r\n CellRangeSelector: SlickCellRangeSelector\r\n });\r\n}\r\n", "import { SlickEvent as SlickEvent_, SlickEventData as SlickEventData_, SlickRange as SlickRange_, Utils as Utils_ } from '../slick.core.js';\r\nimport { SlickCellRangeSelector as SlickCellRangeSelector_ } from './slick.cellrangeselector.js';\r\nimport type { CustomDataView, OnActiveCellChangedEventArgs } from '../models/index.js';\r\nimport type { SlickDataView } from '../slick.dataview.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 SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;\r\nconst SlickEventData = IIFE_ONLY ? Slick.EventData : SlickEventData_;\r\nconst SlickRange = IIFE_ONLY ? Slick.Range : SlickRange_;\r\nconst SlickCellRangeSelector = IIFE_ONLY ? Slick.CellRangeSelector : SlickCellRangeSelector_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\nexport interface CellSelectionModelOption {\r\n selectActiveCell: boolean;\r\n cellRangeSelector?: SlickCellRangeSelector_;\r\n}\r\n\r\nexport class SlickCellSelectionModel {\r\n // --\r\n // public API\r\n pluginName = 'CellSelectionModel' as const;\r\n onSelectedRangesChanged = new SlickEvent('onSelectedRangesChanged');\r\n\r\n // --\r\n // protected props\r\n protected _cachedPageRowCount = 0;\r\n protected _dataView?: CustomDataView | SlickDataView;\r\n protected _grid!: SlickGrid;\r\n protected _prevSelectedRow?: number;\r\n protected _prevKeyDown = '';\r\n protected _ranges: SlickRange_[] = [];\r\n protected _selector: SlickCellRangeSelector_;\r\n protected _options?: CellSelectionModelOption;\r\n protected _defaults: CellSelectionModelOption = {\r\n selectActiveCell: true\r\n };\r\n\r\n constructor(options?: { selectActiveCell: boolean; cellRangeSelector: SlickCellRangeSelector_; }) {\r\n if (options === undefined || options.cellRangeSelector === undefined) {\r\n this._selector = new SlickCellRangeSelector({ selectionCss: { border: '2px solid black' } as CSSStyleDeclaration });\r\n } else {\r\n this._selector = options.cellRangeSelector;\r\n }\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n this._options = Utils.extend(true, {}, this._defaults, this._options);\r\n this._grid = grid;\r\n if (grid.hasDataView()) {\r\n this._dataView = grid.getData();\r\n }\r\n this._grid.onActiveCellChanged.subscribe(this.handleActiveCellChange.bind(this));\r\n this._grid.onKeyDown.subscribe(this.handleKeyDown.bind(this));\r\n grid.registerPlugin(this._selector);\r\n this._selector.onCellRangeSelected.subscribe(this.handleCellRangeSelected.bind(this));\r\n this._selector.onBeforeCellRangeSelected.subscribe(this.handleBeforeCellRangeSelected.bind(this));\r\n }\r\n\r\n destroy() {\r\n this._grid.onActiveCellChanged.unsubscribe(this.handleActiveCellChange.bind(this));\r\n this._grid.onKeyDown.unsubscribe(this.handleKeyDown.bind(this));\r\n this._selector.onCellRangeSelected.unsubscribe(this.handleCellRangeSelected.bind(this));\r\n this._selector.onBeforeCellRangeSelected.unsubscribe(this.handleBeforeCellRangeSelected.bind(this));\r\n this._grid.unregisterPlugin(this._selector);\r\n this._selector?.destroy();\r\n }\r\n\r\n protected removeInvalidRanges(ranges: SlickRange_[]) {\r\n const result: SlickRange_[] = [];\r\n\r\n for (let i = 0; i < ranges.length; i++) {\r\n const r = ranges[i];\r\n if (this._grid.canCellBeSelected(r.fromRow, r.fromCell) && this._grid.canCellBeSelected(r.toRow, r.toCell)) {\r\n result.push(r);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n protected rangesAreEqual(range1: SlickRange_[], range2: SlickRange_[]) {\r\n let areDifferent = (range1.length !== range2.length);\r\n if (!areDifferent) {\r\n for (let i = 0; i < range1.length; i++) {\r\n if (\r\n range1[i].fromCell !== range2[i].fromCell\r\n || range1[i].fromRow !== range2[i].fromRow\r\n || range1[i].toCell !== range2[i].toCell\r\n || range1[i].toRow !== range2[i].toRow\r\n ) {\r\n areDifferent = true;\r\n break;\r\n }\r\n }\r\n }\r\n return !areDifferent;\r\n }\r\n\r\n /** Provide a way to force a recalculation of page row count (for example on grid resize) */\r\n resetPageRowCount() {\r\n this._cachedPageRowCount = 0;\r\n }\r\n\r\n setSelectedRanges(ranges: SlickRange_[], caller = 'SlickCellSelectionModel.setSelectedRanges') {\r\n // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged\r\n if ((!this._ranges || this._ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }\r\n\r\n // if range has not changed, don't fire onSelectedRangesChanged\r\n const rangeHasChanged = !this.rangesAreEqual(this._ranges, ranges);\r\n\r\n this._ranges = this.removeInvalidRanges(ranges);\r\n if (rangeHasChanged) {\r\n // provide extra \"caller\" argument through SlickEventData event to avoid breaking the previous pubsub event structure\r\n // that only accepts an array of selected range `SlickRange[]`, the SlickEventData args will be merged and used later by `onSelectedRowsChanged`\r\n const eventData = new SlickEventData(new CustomEvent('click', { detail: { caller } }), this._ranges);\r\n this.onSelectedRangesChanged.notify(this._ranges, eventData);\r\n }\r\n }\r\n\r\n getSelectedRanges() {\r\n return this._ranges;\r\n }\r\n\r\n refreshSelections() {\r\n this.setSelectedRanges(this.getSelectedRanges());\r\n }\r\n\r\n protected handleBeforeCellRangeSelected(e: SlickEventData_): boolean | void {\r\n if (this._grid.getEditorLock().isActive()) {\r\n e.stopPropagation();\r\n return false;\r\n }\r\n }\r\n\r\n protected handleCellRangeSelected(_e: SlickEventData_, args: { range: SlickRange_; }) {\r\n this._grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);\r\n this.setSelectedRanges([args.range]);\r\n }\r\n\r\n protected handleActiveCellChange(_e: SlickEventData_, args: OnActiveCellChangedEventArgs) {\r\n this._prevSelectedRow = undefined;\r\n const isCellDefined = Utils.isDefined(args.cell);\r\n const isRowDefined = Utils.isDefined(args.row);\r\n\r\n if (this._options?.selectActiveCell && isRowDefined && isCellDefined) {\r\n this.setSelectedRanges([new SlickRange(args.row, args.cell)]);\r\n } else if (!this._options?.selectActiveCell || (!isRowDefined && !isCellDefined)) {\r\n // clear the previous selection once the cell changes\r\n this.setSelectedRanges([]);\r\n }\r\n }\r\n\r\n protected isKeyAllowed(key: string, isShiftKeyPressed?: boolean): boolean {\r\n return [\r\n 'ArrowLeft',\r\n 'ArrowRight',\r\n 'ArrowUp',\r\n 'ArrowDown',\r\n 'PageDown',\r\n 'PageUp',\r\n 'Home',\r\n 'End',\r\n ...(!isShiftKeyPressed ? ['a', 'A'] : []),\r\n ].some((k) => k === key);\r\n }\r\n\r\n protected handleKeyDown(e: SlickEventData_) {\r\n let ranges: SlickRange_[], last: SlickRange_;\r\n const colLn = this._grid.getColumns().length;\r\n const active = this._grid.getActiveCell();\r\n let dataLn = 0;\r\n if (this._dataView && 'getPagingInfo' in this._dataView) {\r\n dataLn = this._dataView?.getPagingInfo().pageSize || this._dataView.getLength();\r\n } else {\r\n dataLn = this._grid.getDataLength();\r\n }\r\n\r\n if (active && (e.shiftKey || e.ctrlKey) && !e.altKey && this.isKeyAllowed(e.key as string, e.shiftKey)) {\r\n ranges = this.getSelectedRanges().slice();\r\n if (!ranges.length) {\r\n ranges.push(new SlickRange(active.row, active.cell));\r\n }\r\n // keyboard can work with last range only\r\n last = ranges.pop() as SlickRange_;\r\n\r\n // can't handle selection out of active cell\r\n if (!last.contains(active.row, active.cell)) {\r\n last = new SlickRange(active.row, active.cell);\r\n }\r\n\r\n let dRow = last.toRow - last.fromRow;\r\n let dCell = last.toCell - last.fromCell;\r\n let toCell: undefined | number;\r\n let toRow = 0;\r\n\r\n // when using Ctrl+{a, A} we will change our position to cell 0,0 and select all grid cells\r\n if (e.ctrlKey && e.key?.toLowerCase() === 'a') {\r\n this._grid.setActiveCell(0, 0, false, false, true);\r\n active.row = 0;\r\n active.cell = 0;\r\n toCell = colLn - 1;\r\n toRow = dataLn - 1;\r\n }\r\n\r\n // walking direction\r\n const dirRow = active.row === last.fromRow ? 1 : -1;\r\n const dirCell = active.cell === last.fromCell ? 1 : -1;\r\n const isSingleKeyMove = e.key!.startsWith('Arrow');\r\n\r\n if (isSingleKeyMove && !e.ctrlKey) {\r\n // single cell move: (Arrow{Up/ArrowDown/ArrowLeft/ArrowRight})\r\n if (e.key === 'ArrowLeft') {\r\n dCell -= dirCell;\r\n } else if (e.key === 'ArrowRight') {\r\n dCell += dirCell;\r\n } else if (e.key === 'ArrowUp') {\r\n dRow -= dirRow;\r\n } else if (e.key === 'ArrowDown') {\r\n dRow += dirRow;\r\n }\r\n toRow = active.row + dirRow * dRow;\r\n } else {\r\n // multiple cell moves: (Home, End, Page{Up/Down})\r\n if (this._cachedPageRowCount < 1) {\r\n this._cachedPageRowCount = this._grid.getViewportRowCount();\r\n }\r\n if (this._prevSelectedRow === undefined) {\r\n this._prevSelectedRow = active.row;\r\n }\r\n\r\n if ((!e.ctrlKey && e.shiftKey && e.key === 'Home') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowLeft')) {\r\n toCell = 0;\r\n toRow = active.row;\r\n } else if ((!e.ctrlKey && e.shiftKey && e.key === 'End') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowRight')) {\r\n toCell = colLn - 1;\r\n toRow = active.row;\r\n } else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowUp') {\r\n toRow = 0;\r\n } else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowDown') {\r\n toRow = dataLn - 1;\r\n } else if (e.ctrlKey && e.shiftKey && e.key === 'Home') {\r\n toCell = 0;\r\n toRow = 0;\r\n } else if (e.ctrlKey && e.shiftKey && e.key === 'End') {\r\n toCell = colLn - 1;\r\n toRow = dataLn - 1;\r\n } else if (e.key === 'PageUp') {\r\n if (this._prevSelectedRow >= 0) {\r\n toRow = this._prevSelectedRow - this._cachedPageRowCount;\r\n }\r\n if (toRow < 0) {\r\n toRow = 0;\r\n }\r\n } else if (e.key === 'PageDown') {\r\n if (this._prevSelectedRow <= dataLn - 1) {\r\n toRow = this._prevSelectedRow + this._cachedPageRowCount;\r\n }\r\n if (toRow > dataLn - 1) {\r\n toRow = dataLn - 1;\r\n }\r\n }\r\n this._prevSelectedRow = toRow;\r\n }\r\n\r\n // define new selection range\r\n toCell ??= active.cell + dirCell * dCell;\r\n const new_last = new SlickRange(active.row, active.cell, toRow, toCell);\r\n if (this.removeInvalidRanges([new_last]).length) {\r\n ranges.push(new_last);\r\n const viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;\r\n const viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;\r\n\r\n if (isSingleKeyMove) {\r\n this._grid.scrollRowIntoView(viewRow);\r\n this._grid.scrollCellIntoView(viewRow, viewCell);\r\n } else {\r\n this._grid.scrollRowIntoView(toRow);\r\n this._grid.scrollCellIntoView(toRow, viewCell);\r\n }\r\n } else {\r\n ranges.push(last);\r\n }\r\n\r\n this.setSelectedRanges(ranges);\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this._prevKeyDown = e.key as string;\r\n }\r\n }\r\n}\r\n\r\n// extend Slick namespace on window object when building as iife\r\nif (IIFE_ONLY && window.Slick) {\r\n Utils.extend(true, window, {\r\n Slick: {\r\n CellSelectionModel: SlickCellSelectionModel\r\n }\r\n });\r\n}\r\n", "import type { CheckboxSelectorOption, Column, DOMEvent, SlickPlugin, SelectableOverrideCallback, OnHeaderClickEventArgs } from '../models/index.js';\r\nimport { BindingEventService as BindingEventService_, type SlickEventData, SlickEventHandler as SlickEventHandler_, Utils as Utils_ } from '../slick.core.js';\r\nimport type { SlickDataView } from '../slick.dataview.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 SlickEventHandler = IIFE_ONLY ? Slick.EventHandler : SlickEventHandler_;\r\nconst Utils = IIFE_ONLY ? Slick.Utils : Utils_;\r\n\r\nexport class SlickCheckboxSelectColumn implements SlickPlugin {\r\n // --\r\n // public API\r\n pluginName = 'CheckboxSelectColumn' as const;\r\n\r\n // --\r\n // protected props\r\n protected _dataView!: SlickDataView;\r\n protected _grid!: SlickGrid;\r\n protected _isUsingDataView = false;\r\n protected _selectableOverride: SelectableOverrideCallback | null = null;\r\n protected _headerRowNode?: HTMLElement;\r\n protected _selectAll_UID: number;\r\n protected _handler = new SlickEventHandler();\r\n protected _selectedRowsLookup: any = {};\r\n protected _checkboxColumnCellIndex: number | null = null;\r\n protected _options: CheckboxSelectorOption;\r\n protected _defaults: CheckboxSelectorOption = {\r\n columnId: '_checkbox_selector',\r\n cssClass: undefined,\r\n hideSelectAllCheckbox: false,\r\n name: '',\r\n toolTip: 'Select/Deselect All',\r\n width: 30,\r\n reorderable: false,\r\n applySelectOnAllPages: false, // defaults to false, when that is enabled the \"Select All\" will be applied to all pages (when using Pagination)\r\n hideInColumnTitleRow: false,\r\n hideInFilterHeaderRow: true\r\n };\r\n protected _isSelectAllChecked = false;\r\n protected _bindingEventService: BindingEventService_;\r\n\r\n constructor(options?: Partial) {\r\n this._bindingEventService = new BindingEventService();\r\n this._options = Utils.extend(true, {}, this._defaults, options);\r\n this._selectAll_UID = this.createUID();\r\n\r\n // user could override the checkbox icon logic from within the options or after instantiating the plugin\r\n if (typeof this._options.selectableOverride === 'function') {\r\n this.selectableOverride(this._options.selectableOverride);\r\n }\r\n }\r\n\r\n init(grid: SlickGrid) {\r\n this._grid = grid;\r\n this._isUsingDataView = !Array.isArray(grid.getData());\r\n if (this._isUsingDataView) {\r\n this._dataView = grid.getData();\r\n }\r\n this._handler\r\n .subscribe(this._grid.onSelectedRowsChanged, this.handleSelectedRowsChanged.bind(this))\r\n .subscribe(this._grid.onClick, this.handleClick.bind(this))\r\n .subscribe(this._grid.onKeyDown, this.handleKeyDown.bind(this))\r\n // whenever columns changed, we need to rerender Select All checkbox\r\n .subscribe(this._grid.onAfterSetColumns, () => this.renderSelectAllCheckbox(this._isSelectAllChecked));\r\n\r\n if (this._isUsingDataView && this._dataView && this._options.applySelectOnAllPages) {\r\n this._handler\r\n .subscribe(this._dataView.onSelectedRowIdsChanged, this.handleDataViewSelectedIdsChanged.bind(this))\r\n .subscribe(this._dataView.onPagingInfoChanged, this.handleDataViewSelectedIdsChanged.bind(this));\r\n }\r\n\r\n if (!this._options.hideInFilterHeaderRow) {\r\n this.addCheckboxToFilterHeaderRow(grid);\r\n }\r\n if (!this._options.hideInColumnTitleRow) {\r\n this._handler.subscribe(this._grid.onHeaderClick, this.handleHeaderClick.bind(this));\r\n }\r\n }\r\n\r\n destroy() {\r\n this._handler.unsubscribeAll();\r\n this._bindingEventService.unbindAll();\r\n }\r\n\r\n getOptions() {\r\n return this._options;\r\n }\r\n\r\n setOptions(options: Partial) {\r\n this._options = Utils.extend(true, {}, this._options, options);\r\n\r\n if (this._options.hideSelectAllCheckbox) {\r\n this.hideSelectAllFromColumnHeaderTitleRow();\r\n this.hideSelectAllFromColumnHeaderFilterRow();\r\n } else {\r\n if (!this._options.hideInColumnTitleRow) {\r\n this.renderSelectAllCheckbox(this._isSelectAllChecked);\r\n this._handler.subscribe(this._grid.onHeaderClick, this.handleHeaderClick.bind(this));\r\n } else {\r\n this.hideSelectAllFromColumnHeaderTitleRow();\r\n if (this._options.name) {\r\n this._grid.updateColumnHeader(this._options.columnId || '', this._options.name, '');\r\n }\r\n }\r\n\r\n if (!this._options.hideInFilterHeaderRow) {\r\n const selectAllContainerElm = this._headerRowNode?.querySelector('#filter-checkbox-selectall-container');\r\n if (selectAllContainerElm) {\r\n selectAllContainerElm.style.display = 'flex';\r\n const selectAllInputElm = selectAllContainerElm.querySelector('input[type=\"checkbox\"]');\r\n if (selectAllInputElm) {\r\n selectAllInputElm.checked = this._isSelectAllChecked;\r\n }\r\n }\r\n } else {\r\n this.hideSelectAllFromColumnHeaderFilterRow();\r\n }\r\n }\r\n }\r\n\r\n protected hideSelectAllFromColumnHeaderTitleRow() {\r\n this._grid.updateColumnHeader(this._options.columnId || '', this._options.name || '', '');\r\n }\r\n\r\n protected hideSelectAllFromColumnHeaderFilterRow() {\r\n const selectAllContainerElm = this._headerRowNode?.querySelector('#filter-checkbox-selectall-container');\r\n if (selectAllContainerElm) {\r\n selectAllContainerElm.style.display = 'none';\r\n }\r\n }\r\n\r\n protected handleSelectedRowsChanged() {\r\n const selectedRows = this._grid.getSelectedRows();\r\n const lookup: any = {};\r\n let row = 0, i = 0, k = 0;\r\n let disabledCount = 0;\r\n if (typeof this._selectableOverride === 'function') {\r\n for (k = 0; k < this._grid.getDataLength(); k++) {\r\n // If we are allowed to select the row\r\n const dataItem = this._grid.getDataItem(k);\r\n if (!this.checkSelectableOverride(i, dataItem, this._grid)) {\r\n disabledCount++;\r\n }\r\n }\r\n }\r\n\r\n const removeList: number[] = [];\r\n for (i = 0; i < selectedRows.length; i++) {\r\n row = selectedRows[i];\r\n\r\n // If we are allowed to select the row\r\n const rowItem = this._grid.getDataItem(row);\r\n if (this.checkSelectableOverride(i, rowItem, this._grid)) {\r\n lookup[row] = true;\r\n if (lookup[row] !== this._selectedRowsLookup[row]) {\r\n this._grid.invalidateRow(row);\r\n delete this._selectedRowsLookup[row];\r\n }\r\n }\r\n else {\r\n removeList.push(row);\r\n }\r\n }\r\n if (typeof this._selectedRowsLookup === 'object') {\r\n Object.keys(this._selectedRowsLookup).forEach(selectedRow => {\r\n if (selectedRow !== undefined) {\r\n this._grid.invalidateRow(+selectedRow);\r\n }\r\n });\r\n }\r\n this._selectedRowsLookup = lookup;\r\n this._grid.render();\r\n this._isSelectAllChecked = (selectedRows?.length ?? 0) + disabledCount >= this._grid.getDataLength();\r\n\r\n if (!this._isUsingDataView || !this._options.applySelectOnAllPages) {\r\n if (!this._options.hideInColumnTitleRow && !this._options.hideSelectAllCheckbox) {\r\n this.renderSelectAllCheckbox(this._isSelectAllChecked);\r\n }\r\n if (!this._options.hideInFilterHeaderRow) {\r\n const selectAllElm = this._headerRowNode?.querySelector(`#header-filter-selector${this._selectAll_UID}`);\r\n if (selectAllElm) {\r\n selectAllElm.checked = this._isSelectAllChecked;\r\n }\r\n }\r\n }\r\n\r\n // Remove items that shouln't of been selected in the first place (Got here Ctrl + click)\r\n if (removeList.length > 0) {\r\n for (i = 0; i < removeList.length; i++) {\r\n const remIdx = selectedRows.indexOf(removeList[i]);\r\n selectedRows.splice(remIdx, 1);\r\n }\r\n this._grid.setSelectedRows(selectedRows, 'click.cleanup');\r\n }\r\n }\r\n\r\n protected handleDataViewSelectedIdsChanged() {\r\n const selectedIds = this._dataView.getAllSelectedFilteredIds();\r\n const filteredItems = this._dataView.getFilteredItems();\r\n let disabledCount = 0;\r\n\r\n if (typeof this._selectableOverride === 'function' && selectedIds.length > 0) {\r\n for (let k = 0; k < this._dataView.getItemCount(); k++) {\r\n // If we are allowed to select the row\r\n const dataItem: T = this._dataView.getItemByIdx(k);\r\n const idProperty = this._dataView.getIdPropertyName();\r\n const dataItemId = dataItem[idProperty as keyof T];\r\n const foundItemIdx = filteredItems.findIndex(function (item) {\r\n return item[idProperty as keyof T] === dataItemId;\r\n });\r\n if (foundItemIdx >= 0 && !this.checkSelectableOverride(k, dataItem, this._grid)) {\r\n disabledCount++;\r\n }\r\n }\r\n }\r\n this._isSelectAllChecked = (selectedIds && selectedIds.length) + disabledCount >= filteredItems.length;\r\n\r\n if (!this._options.hideInColumnTitleRow && !this._options.hideSelectAllCheckbox) {\r\n this.renderSelectAllCheckbox(this._isSelectAllChecked);\r\n }\r\n if (!this._options.hideInFilterHeaderRow) {\r\n const selectAllElm = this._headerRowNode?.querySelector(`#header-filter-selector${this._selectAll_UID}`);\r\n if (selectAllElm) {\r\n selectAllElm.checked = this._isSelectAllChecked;\r\n }\r\n }\r\n }\r\n\r\n protected handleKeyDown(e: SlickEventData, args: any) {\r\n if (e.which === 32) {\r\n if (this._grid.getColumns()[args.cell].id === this._options.columnId) {\r\n // if editing, try to commit\r\n if (!this._grid.getEditorLock().isActive() || this._grid.getEditorLock().commitCurrentEdit()) {\r\n this.toggleRowSelection(args.row);\r\n }\r\n e.preventDefault();\r\n e.stopImmediatePropagation();\r\n }\r\n }\r\n }\r\n\r\n protected handleClick(e: SlickEventData, args: { row: number; cell: number; }) {\r\n // clicking on a row select checkbox\r\n if (this._grid.getColumns()[args.cell].id === this._options.columnId && (e.target as HTMLInputElement).type === 'checkbox') {\r\n // if editing, try to commit\r\n if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {\r\n e.preventDefault();\r\n e.stopImmediatePropagation();\r\n return;\r\n }\r\n\r\n this.toggleRowSelection(args.row);\r\n e.stopPropagation();\r\n e.stopImmediatePropagation();\r\n }\r\n }\r\n\r\n protected toggleRowSelection(row: number) {\r\n const dataContext = this._grid.getDataItem(row);\r\n if (!this.checkSelectableOverride(row, dataContext, this._grid)) {\r\n return;\r\n }\r\n\r\n if (this._selectedRowsLookup[row]) {\r\n const newSelectedRows = this._grid.getSelectedRows().filter((n) => n !== row);\r\n this._grid.setSelectedRows(newSelectedRows, 'click.toggle');\r\n } else {\r\n this._grid.setSelectedRows(this._grid.getSelectedRows().concat(row), 'click.toggle');\r\n }\r\n this._grid.setActiveCell(row, this.getCheckboxColumnCellIndex());\r\n }\r\n\r\n selectRows(rowArray: number[]) {\r\n const addRows: number[] = [];\r\n for (let i = 0, l = rowArray.length; i < l; i++) {\r\n if (!this._selectedRowsLookup[rowArray[i]]) {\r\n addRows[addRows.length] = rowArray[i];\r\n }\r\n }\r\n this._grid.setSelectedRows(this._grid.getSelectedRows().concat(addRows), 'SlickCheckboxSelectColumn.selectRows');\r\n }\r\n\r\n deSelectRows(rowArray: number[]) {\r\n const removeRows: number[] = [];\r\n for (let i = 0, l = rowArray.length; i < l; i++) {\r\n if (this._selectedRowsLookup[rowArray[i]]) {\r\n removeRows[removeRows.length] = rowArray[i];\r\n }\r\n }\r\n\r\n this._grid.setSelectedRows(this._grid.getSelectedRows().filter((n) => removeRows.indexOf(n) < 0), 'SlickCheckboxSelectColumn.deSelectRows');\r\n }\r\n\r\n protected handleHeaderClick(e: DOMEvent | SlickEventData, args: OnHeaderClickEventArgs) {\r\n if (args.column.id === this._options.columnId && (e.target as HTMLInputElement).type === 'checkbox') {\r\n // if editing, try to commit\r\n if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {\r\n e.preventDefault();\r\n e.stopImmediatePropagation();\r\n return;\r\n }\r\n\r\n let isAllSelected = (e.target as HTMLInputElement).checked;\r\n const caller = isAllSelected ? 'click.selectAll' : 'click.unselectAll';\r\n const rows: number[] = [];\r\n\r\n if (isAllSelected) {\r\n for (let i = 0; i < this._grid.getDataLength(); i++) {\r\n // Get the row and check it's a selectable row before pushing it onto the stack\r\n const rowItem = this._grid.getDataItem(i);\r\n if (!rowItem.__group && !rowItem.__groupTotals && this.checkSelectableOverride(i, rowItem, this._grid)) {\r\n rows.push(i);\r\n }\r\n }\r\n isAllSelected = true;\r\n }\r\n if (this._isUsingDataView && this._dataView && this._options.applySelectOnAllPages) {\r\n const ids: Array = [];\r\n const filteredItems = this._dataView.getFilteredItems();\r\n for (let j = 0; j < filteredItems.length; j++) {\r\n // Get the row and check it's a selectable ID (it could be in a different page) before pushing it onto the stack\r\n const dataviewRowItem: T = filteredItems[j];\r\n if (this.checkSelectableOverride(j, dataviewRowItem, this._grid)) {\r\n ids.push(dataviewRowItem[this._dataView.getIdPropertyName() as keyof T] as number | string);\r\n }\r\n }\r\n this._dataView.setSelectedIds(ids, { isRowBeingAdded: isAllSelected });\r\n }\r\n this._grid.setSelectedRows(rows, caller);\r\n e.stopPropagation();\r\n e.stopImmediatePropagation();\r\n }\r\n }\r\n\r\n protected getCheckboxColumnCellIndex() {\r\n if (this._checkboxColumnCellIndex === null) {\r\n this._checkboxColumnCellIndex = 0;\r\n const colArr = this._grid.getColumns();\r\n for (let i = 0; i < colArr.length; i++) {\r\n if (colArr[i].id === this._options.columnId) {\r\n this._checkboxColumnCellIndex = i;\r\n }\r\n }\r\n }\r\n return this._checkboxColumnCellIndex;\r\n }\r\n\r\n /**\r\n * use a DocumentFragment to return a fragment including an then a