From 4c8c4f62423665bc2e1dcf0675b1300607397b6a Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 7 Oct 2021 18:29:29 -0400 Subject: [PATCH] feat(plugin): create new Custom Tooltip plugin --- .../src/examples/example03.ts | 57 ++++++++- .../src/examples/example14.ts | 4 +- .../webpack-demo-vanilla-bundle/src/main.ts | 4 - .../common/src/enums/extensionName.enum.ts | 1 + packages/common/src/extensions/index.ts | 1 + .../extensions/slickCustomTooltipExtension.ts | 108 ++++++++++++++++++ .../common/src/interfaces/column.interface.ts | 8 ++ .../customTooltipOption.interface.ts | 35 ++++++ .../src/interfaces/gridOption.interface.ts | 17 ++- packages/common/src/interfaces/index.ts | 1 + .../common/src/services/extension.service.ts | 8 ++ packages/common/src/styles/_variables.scss | 21 +++- packages/common/src/styles/slick-plugins.scss | 29 +++++ .../common/src/styles/slickgrid-examples.scss | 8 ++ .../components/slick-vanilla-grid-bundle.ts | 4 + .../src/salesforce-global-grid-options.ts | 2 +- 16 files changed, 294 insertions(+), 14 deletions(-) create mode 100644 packages/common/src/extensions/slickCustomTooltipExtension.ts create mode 100644 packages/common/src/interfaces/customTooltipOption.interface.ts diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example03.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example03.ts index 6112feabc..dfb4a5fe6 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example03.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example03.ts @@ -91,7 +91,11 @@ export class Example3 { aggregators: [new Aggregators.Sum('cost')], aggregateCollapsed: false, collapsed: false - } + }, + customTooltip: { + formatter: this.tooltipTaskFormatter.bind(this), + // usabilityOverride: (args) => !!(args.dataContext?.id % 2) // show it only every second row + }, }, { id: 'duration', name: 'Duration', field: 'duration', sortable: true, filterable: true, @@ -291,6 +295,14 @@ export class Example3 { excelExportOptions: { exportWithFormatter: true }, + // Custom Tooltip options can be defined in a Column or Grid Options or a mixed of both (first options found wins) + enableCustomTooltip: true, + customTooltip: { + arrowMarginLeft: '30%', + formatter: this.tooltipFormatter.bind(this), + usabilityOverride: (args) => (args.cell !== 0 && args.cell !== args.grid.getColumns().length - 1), // don't show on first/last columns + // hideArrow: true, // defaults to False + }, registerExternalResources: [this.excelExportService], enableFiltering: true, rowSelectionOptions: { @@ -435,7 +447,7 @@ export class Example3 { } } - groupByFieldName(_fieldName, _index) { + groupByFieldName(/* _fieldName, _index */) { this.clearGrouping(); if (this.draggableGroupingPlugin && this.draggableGroupingPlugin.setDroppedGroups) { this.showPreHeader(); @@ -524,4 +536,45 @@ export class Example3 { this.sgb?.slickGrid.gotoCell(command.row, command.cell, false); } } + + tooltipFormatter(row, cell, value, column, dataContext) { + const tooltipTitle = 'Custom Tooltip'; + return `
${tooltipTitle}
+
Id:
${dataContext.id}
+
Title:
${dataContext.title}
+
Completion:
${this.loadCompletionIcons(dataContext.percentComplete)}
+ `; + } + + tooltipTaskFormatter(row, cell, value, column, dataContext, grid) { + const tooltipTitle = `Task ${dataContext.id} - Tooltip`; + + // use a 2nd Formatter to get the percent completion + const completionBar = Formatters.percentCompleteBarWithText(row, cell, dataContext.percentComplete, column, dataContext, grid); + const out = `
${tooltipTitle}
+
Completion:
${completionBar}
+ `; + return out; + } + + loadCompletionIcons(percentComplete: number) { + let output = ''; + let iconCount = 0; + if (percentComplete > 5 && percentComplete < 25) { + iconCount = 1; + } else if (percentComplete >= 25 && percentComplete < 50) { + iconCount = 2; + } else if (percentComplete >= 50 && percentComplete < 75) { + iconCount = 3; + } else if (percentComplete >= 75 && percentComplete < 100) { + iconCount = 4; + } else if (percentComplete === 100) { + iconCount = 5; + } + for (let i = 0; i < iconCount; i++) { + const icon = iconCount === 5 ? 'color-success' : iconCount >= 3 ? 'color-alt-warning' : 'color-se-secondary-light'; + output += ``; + } + return output; + } } diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts index 2638eeb05..2d6e84df8 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts @@ -185,8 +185,8 @@ export class Example14 { id: 'complexity', name: 'Complexity', field: 'complexity', resizeCalcWidthRatio: 0.82, // default calc ratio is 1 or 0.95 for field type of string sortable: true, filterable: true, columnGroup: 'Analysis', - formatter: (_row, _cell, value) => this.complexityLevelList[value].label, - exportCustomFormatter: (_row, _cell, value) => this.complexityLevelList[value].label, + formatter: (_row, _cell, value) => this.complexityLevelList[value]?.label, + exportCustomFormatter: (_row, _cell, value) => this.complexityLevelList[value]?.label, filter: { model: Filters.multipleSelect, collection: this.complexityLevelList diff --git a/examples/webpack-demo-vanilla-bundle/src/main.ts b/examples/webpack-demo-vanilla-bundle/src/main.ts index 48db5a591..551828628 100644 --- a/examples/webpack-demo-vanilla-bundle/src/main.ts +++ b/examples/webpack-demo-vanilla-bundle/src/main.ts @@ -5,10 +5,6 @@ import './styles.scss'; // import all other 3rd party libs required by Slickgrid-Universal // also only import jQueryUI necessary widget (note autocomplete & slider are imported in their respective editors/filters) -import 'jquery'; -import 'jquery-ui/ui/widgets/draggable'; -import 'jquery-ui/ui/widgets/droppable'; -import 'jquery-ui/ui/widgets/sortable'; import { Renderer } from './renderer'; import * as SlickerModule from '@slickgrid-universal/vanilla-bundle'; import { App } from './app'; diff --git a/packages/common/src/enums/extensionName.enum.ts b/packages/common/src/enums/extensionName.enum.ts index 9962761ea..10a0c0387 100644 --- a/packages/common/src/enums/extensionName.enum.ts +++ b/packages/common/src/enums/extensionName.enum.ts @@ -6,6 +6,7 @@ export enum ExtensionName { checkboxSelector = 'checkboxSelector', columnPicker = 'columnPicker', contextMenu = 'contextMenu', + customTooltip = 'customTooltip', draggableGrouping = 'draggableGrouping', groupItemMetaProvider = 'groupItemMetaProvider', gridMenu = 'gridMenu', diff --git a/packages/common/src/extensions/index.ts b/packages/common/src/extensions/index.ts index 3e84f1aad..7761bde2c 100644 --- a/packages/common/src/extensions/index.ts +++ b/packages/common/src/extensions/index.ts @@ -13,3 +13,4 @@ export * from './headerMenuExtension'; export * from './rowDetailViewExtension'; export * from './rowMoveManagerExtension'; export * from './rowSelectionExtension'; +export * from './slickCustomTooltipExtension'; diff --git a/packages/common/src/extensions/slickCustomTooltipExtension.ts b/packages/common/src/extensions/slickCustomTooltipExtension.ts new file mode 100644 index 000000000..766c31f3f --- /dev/null +++ b/packages/common/src/extensions/slickCustomTooltipExtension.ts @@ -0,0 +1,108 @@ +import { CustomTooltipOption, GridOption, SlickDataView, SlickEventData, SlickEventHandler, SlickGrid, SlickNamespace } from '../interfaces/index'; +import { getHtmlElementOffset, sanitizeTextByAvailableSanitizer } from '../services/utilities'; +import { SharedService } from '../services/shared.service'; + +// using external SlickGrid JS libraries +declare const Slick: SlickNamespace; + +export class SlickCustomTooltip { + protected _addonOptions?: CustomTooltipOption; + protected _defaultOptions = { + className: 'slick-custom-tooltip', + offsetLeft: 0, + offsetTop: 5, + hideArrow: false, + } as CustomTooltipOption; + protected _grid!: SlickGrid; + protected _eventHandler: SlickEventHandler; + + constructor(protected readonly sharedService: SharedService) { + this._eventHandler = new Slick.EventHandler(); + } + + get addonOptions(): CustomTooltipOption | undefined { + return this._addonOptions; + } + + get className(): string { + return this._addonOptions?.className ?? 'slick-custom-tooltip'; + } + get dataView(): SlickDataView { + return this._grid.getData() || {}; + } + + /** Getter for the Grid Options pulled through the Grid Object */ + get gridOptions(): GridOption { + return this._grid.getOptions() || {}; + } + + /** Getter for the grid uid */ + get gridUid(): string { + return this._grid.getUID() || ''; + } + get gridUidSelector(): string { + return this.gridUid ? `.${this.gridUid}` : ''; + } + + init(grid: SlickGrid) { + this._grid = grid; + this._eventHandler + .subscribe(grid.onMouseEnter, this.handleOnMouseEnter.bind(this) as EventListener) + .subscribe(grid.onMouseLeave, this.handleOnMouseLeave.bind(this) as EventListener); + } + + dispose() { + this._eventHandler.unsubscribeAll(); + } + + handleOnMouseEnter(e: SlickEventData) { + if (this._grid && e) { + const cell = this._grid.getCellFromEvent(e); + if (cell) { + const item = this.dataView.getItem(cell.row); + const columnDef = this._grid.getColumns()[cell.cell]; + if (item && columnDef) { + this._addonOptions = { ...this._defaultOptions, ...(this.sharedService?.gridOptions?.customTooltip), ...(columnDef?.customTooltip) }; + + let showTooltip = true; + if (typeof this._addonOptions?.usabilityOverride === 'function') { + showTooltip = this._addonOptions.usabilityOverride({ cell: cell.cell, row: cell.row, dataContext: item, column: columnDef, grid: this._grid }); + } + + if (showTooltip && typeof this._addonOptions?.formatter === 'function') { + const itemValue = item.hasOwnProperty(columnDef.field) ? item[columnDef.field] : null; + const value = sanitizeTextByAvailableSanitizer(this.gridOptions, itemValue); + const tooltipText = this._addonOptions.formatter(cell.row, cell.cell, value, columnDef, item, this._grid); + + // create the tooltip DOM element with the text returned by the Formatter + const tooltipElm = document.createElement('div'); + tooltipElm.className = `${this.className} ${this.gridUid}`; + tooltipElm.innerHTML = typeof tooltipText === 'object' ? tooltipText.text : tooltipText; + document.body.appendChild(tooltipElm); + + // reposition the tooltip on top of the cell that triggered the mouse over event + const cellPosition = getHtmlElementOffset(this._grid.getCellNode(cell.row, cell.cell)); + tooltipElm.style.left = `${cellPosition.left}px`; + tooltipElm.style.top = `${cellPosition.top - tooltipElm.clientHeight - (this._addonOptions?.offsetTop ?? 0)}px`; + + // user could optionally hide the tooltip arrow (we can simply update the CSS variables, that's the only way we have to update CSS pseudo) + const root = document.documentElement; + if (this._addonOptions?.hideArrow) { + root.style.setProperty('--slick-tooltip-arrow-border-left', '0'); + root.style.setProperty('--slick-tooltip-arrow-border-right', '0'); + } + if (this._addonOptions?.arrowMarginLeft) { + const marginLeft = typeof this._addonOptions.arrowMarginLeft === 'string' ? this._addonOptions.arrowMarginLeft : `${this._addonOptions.arrowMarginLeft}px`; + root.style.setProperty('--slick-tooltip-arrow-margin-left', marginLeft); + } + } + } + } + } + } + + handleOnMouseLeave() { + const prevTooltip = document.body.querySelector(`.${this.className}${this.gridUidSelector}`); + prevTooltip?.remove(); + } +} diff --git a/packages/common/src/interfaces/column.interface.ts b/packages/common/src/interfaces/column.interface.ts index a55e74882..415697d32 100644 --- a/packages/common/src/interfaces/column.interface.ts +++ b/packages/common/src/interfaces/column.interface.ts @@ -3,6 +3,7 @@ import { CellMenu, ColumnEditor, ColumnFilter, + CustomTooltipOption, EditorValidator, Formatter, Grouping, @@ -53,6 +54,13 @@ export interface Column { /** CSS class to add to the column cell */ cssClass?: string; + /** + * Custom Tooltip Options, you must first enable `enableCustomTooltip: true`. + * The tooltip could defined in any of the Column Definition or in the Grid Options, + * it will first try to find it in the Column that the user is hovering over or else (when not found) go and try to find it in the Grid Options + */ + customTooltip?: CustomTooltipOption; + /** Data key, for example this could be used as a property key for complex object comparison (e.g. dataKey: 'id') */ dataKey?: string; diff --git a/packages/common/src/interfaces/customTooltipOption.interface.ts b/packages/common/src/interfaces/customTooltipOption.interface.ts new file mode 100644 index 000000000..b83f692f3 --- /dev/null +++ b/packages/common/src/interfaces/customTooltipOption.interface.ts @@ -0,0 +1,35 @@ +import { Column, Formatter, SlickGrid } from './index'; + +export interface CustomTooltipOption { + /** + * defaults to 25(px), left margin to display the arrow. + * when a number is provided it will assume the value is in pixel, + * or else we could also a string for example "50%" would show the arrow in the center. + */ + arrowMarginLeft?: number | string; + + /** defaults to False, should we hide the tooltip pointer arrow? */ + hideArrow?: boolean; + + /** defaults to "slick-custom-tooltip" */ + className?: string; + + /** Formatter to execute for display the data that will show */ + formatter: Formatter; + + /** defaults to 0, optional left offset, it must be a positive/negative number (in pixel) that will be added to the offset position calculation of the tooltip container. */ + offsetLeft?: number; + + /** defaults to 0, optional top offset, it must be a positive/negative number (in pixel) that will be added to the offset position calculation of the tooltip container. */ + offsetTop?: number; + + // -- + // callback functions + // ------------------- + + // -- + // Methods + + /** Callback method that user can override the default behavior of showing the tooltip. If it returns False, then the tooltip won't show */ + usabilityOverride?: (args: { cell: number; row: number; column: Column; dataContext: any; grid: SlickGrid; }) => boolean; +} \ No newline at end of file diff --git a/packages/common/src/interfaces/gridOption.interface.ts b/packages/common/src/interfaces/gridOption.interface.ts index ae235e92b..318d4f5c0 100644 --- a/packages/common/src/interfaces/gridOption.interface.ts +++ b/packages/common/src/interfaces/gridOption.interface.ts @@ -9,6 +9,7 @@ import { CompositeEditorOpenDetailOption, ContextMenu, CustomFooterOption, + CustomTooltipOption, DataViewOption, DraggableGrouping, EditCommand, @@ -124,6 +125,9 @@ export interface GridOption { /** Checkbox Select Plugin options (columnId, cssClass, toolTip, width) */ checkboxSelector?: CheckboxSelectorOption; + /** A callback function that will be used to define row spanning accross multiple columns */ + colspanCallback?: (item: any) => ItemMetadata; + /** Defaults to " - ", separator between the column group label and the column label. */ columnGroupSeparator?: string; @@ -139,15 +143,19 @@ export interface GridOption { /** Defaults to false, which leads to create the footer row of the grid */ createFooterRow?: boolean; - /** A callback function that will be used to define row spanning accross multiple columns */ - colspanCallback?: (item: any) => ItemMetadata; - /** Default to false, which leads to create an extra pre-header panel (on top of column header) for column grouping purposes */ createPreHeaderPanel?: boolean; /** Custom Footer Options */ customFooterOptions?: CustomFooterOption; + /** + * Custom Tooltip Options, you must first enable `enableCustomTooltip: true`. + * The tooltip could defined in any of the Column Definition or in the Grid Options, + * it will first try to find it in the Column that the user is hovering over or else (when not found) go and try to find it in the Grid Options + */ + customTooltip?: CustomTooltipOption; + /** Data item column value extractor (getter) that can be used by the Excel like copy buffer plugin */ dataItemColumnValueExtractor?: (item: any, columnDef: Column) => any; @@ -275,6 +283,9 @@ export interface GridOption { /** Do we want to enable Context Menu? (mouse right+click) */ enableContextMenu?: boolean; + /** Do we want to enable Custom Tooltip feature? */ + enableCustomTooltip?: boolean; + /** * Defaults to false, do we want to make a deep copy of the dataset before loading it into the grid? * Useful with Salesforce to avoid proxy object error when trying to update a property of an item object by reference (which SlickGrid does a lot) diff --git a/packages/common/src/interfaces/index.ts b/packages/common/src/interfaces/index.ts index f1f938cc5..7d93a3d64 100644 --- a/packages/common/src/interfaces/index.ts +++ b/packages/common/src/interfaces/index.ts @@ -35,6 +35,7 @@ export * from './currentPinning.interface'; export * from './currentRowSelection.interface'; export * from './currentSorter.interface'; export * from './customFooterOption.interface'; +export * from './customTooltipOption.interface'; export * from './dataViewOption.interface'; export * from './domEvent.interface'; export * from './draggableGrouping.interface'; diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index fe2c4029a..ef6478d30 100644 --- a/packages/common/src/services/extension.service.ts +++ b/packages/common/src/services/extension.service.ts @@ -20,6 +20,7 @@ import { RowDetailViewExtension, RowMoveManagerExtension, RowSelectionExtension, + SlickCustomTooltip, } from '../extensions/index'; import { SharedService } from './shared.service'; import { TranslaterService } from './translater.service'; @@ -126,6 +127,13 @@ export class ExtensionService { this.translateItems(this.sharedService.allColumns, 'nameKey', 'name'); } + // Custom Tooltip Plugin + if (this.sharedService.gridOptions.enableCustomTooltip) { + const tooltipPlugin = new SlickCustomTooltip(this.sharedService); + tooltipPlugin.init(this.sharedService.slickGrid); + this._extensionList[ExtensionName.customTooltip] = { name: ExtensionName.customTooltip, class: tooltipPlugin, instance: tooltipPlugin }; + } + // Auto Tooltip Plugin if (this.sharedService.gridOptions.enableAutoTooltip && this.autoTooltipExtension && this.autoTooltipExtension.register) { const instance = this.autoTooltipExtension.register(); diff --git a/packages/common/src/styles/_variables.scss b/packages/common/src/styles/_variables.scss index ef8421be8..ec6699391 100644 --- a/packages/common/src/styles/_variables.scss +++ b/packages/common/src/styles/_variables.scss @@ -18,7 +18,7 @@ $focus-color: rgb(115, 179, 229) !defaul /* Slickgrid container, including headers but ex cluding pagination */ $container-border-top: 0 none !default; $container-border-right: 0 none !default; -$container-border-bottom: 1px solid $border-color !default; +$container-border-bottom: 1px solid #{$border-color} !default; $container-border-left: 0 none !default; /* grid */ @@ -35,7 +35,7 @@ $cell-box-shadow: inherit !default; $cell-text-color: #000000 !default; $cell-font-family: $font-family !default; $cell-font-weight: normal !default; -$cell-border-top: 1px solid $border-color !default; +$cell-border-top: 1px solid #{$border-color} !default; $cell-border-right: 1px transparent !default; $cell-border-bottom: 1px transparent !default; $cell-border-left: 1px transparent !default; @@ -902,6 +902,23 @@ $footer-right-text-align: right !default; $footer-right-text-color: $footer-text-color !default; $footer-right-width: 50% !default; +/** Custom Tooltip */ +$slick-tooltip-background-color: #ffffff !default; +$slick-tooltip-border-color: #BFBDBD !default; +$slick-tooltip-border: 1px solid #{$slick-tooltip-border-color} !default; +$slick-tooltip-border-radius: 4px !default; +$slick-tooltip-color: inherit !default; +$slick-tooltip-height: auto !default; +$slick-tooltip-padding: 7px !default; +$slick-tooltip-width: auto !default; +$slick-tooltip-z-index: 10 !default; +$slick-tooltip-arrow-border-left: 10px solid transparent !default; +$slick-tooltip-arrow-border-right: 10px solid transparent !default; +$slick-tooltip-arrow-border-top: 7px solid #{$slick-tooltip-border-color} !default; +$slick-tooltip-arrow-bottom: -7px !default; +$slick-tooltip-arrow-left: 25px !default; +$slick-tooltip-arrow-margin-left: -10px !default; + /** Empty Data Warning element */ $empty-data-warning-color: $cell-text-color !default; $empty-data-warning-font-family: $font-family !default; diff --git a/packages/common/src/styles/slick-plugins.scss b/packages/common/src/styles/slick-plugins.scss index d32f5b510..0ec92566b 100644 --- a/packages/common/src/styles/slick-plugins.scss +++ b/packages/common/src/styles/slick-plugins.scss @@ -267,6 +267,35 @@ } } +// ---------------------------------------------- +// Slick Tooltip Component +// ---------------------------------------------- + +.slick-custom-tooltip { + position: absolute; + background-color: var(--slick-tooltip-background-color, $slick-tooltip-background-color); + border: var(--slick-tooltip-border, $slick-tooltip-border); + border-radius: var(--slick-tooltip-border-radius, $slick-tooltip-border-radius); + color: var(--slick-tooltip-color, $slick-tooltip-color); + padding: var(--slick-tooltip-padding, $slick-tooltip-padding); + height: var(--slick-tooltip-height, $slick-tooltip-height); + width: var(--slick-tooltip-width, $slick-tooltip-width); + z-index: var(--slick-tooltip-z-index, $slick-tooltip-z-index); + + &::after { + content: ""; + height: 0; + position: absolute; + width: 0; + border-left: var(--slick-tooltip-arrow-border-left, $slick-tooltip-arrow-border-left); + border-right: var(--slick-tooltip-arrow-border-right, $slick-tooltip-arrow-border-right); + border-top: var(--slick-tooltip-arrow-border-top, $slick-tooltip-arrow-border-top); + margin-left: var(--slick-tooltip-arrow-margin-left, $slick-tooltip-arrow-margin-left); + bottom: var(--slick-tooltip-arrow-bottom, $slick-tooltip-arrow-bottom); + left: var(--slick-tooltip-arrow-left, $slick-tooltip-arrow-left); + } +} + // --------------------------------------------------------- // Header Button Plugin - add button in header with command // --------------------------------------------------------- diff --git a/packages/common/src/styles/slickgrid-examples.scss b/packages/common/src/styles/slickgrid-examples.scss index 8a98480a8..f12b731fd 100644 --- a/packages/common/src/styles/slickgrid-examples.scss +++ b/packages/common/src/styles/slickgrid-examples.scss @@ -19,3 +19,11 @@ top: 2px; color: #ffffff; } + +.tooltip-2cols-row { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + column-gap: 5px; + line-height: 20px; +} \ No newline at end of file diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts index f1714efec..5de0ed1ae 100644 --- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts @@ -1,4 +1,8 @@ import { dequal } from 'dequal/lite'; +import 'jquery'; +import 'jquery-ui/ui/widgets/draggable'; +import 'jquery-ui/ui/widgets/droppable'; +import 'jquery-ui/ui/widgets/sortable'; import 'flatpickr/dist/l10n/fr'; import 'slickgrid/lib/jquery.event.drag-2.3.0'; import 'slickgrid/lib/jquery.mousewheel'; diff --git a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts index be572db02..b6b301fe0 100644 --- a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts +++ b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts @@ -53,7 +53,7 @@ export const SalesforceGlobalGridOptions = { iconSortAscCommand: 'fa fa-sort-amount-asc mdi mdi-arrow-up', iconSortDescCommand: 'fa fa-sort-amount-desc mdi mdi-arrow-down', }, - sanitizer: (dirtyHtml: string) => (dirtyHtml.replace(/(\b)(on\S+)(\s*)=|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script([<>]*)/gi, '')), + sanitizer: (dirtyHtml: string) => typeof dirtyHtml === 'string' ? (dirtyHtml.replace(/(\b)(on\S+)(\s*)=|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script([<>]*)/gi, '')) : '', showCustomFooter: true, customFooterOptions: { hideMetrics: false,