@slickgrid-universal/custom-tooltip-plugin 5.10.2 → 5.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/slickCustomTooltip.js +46 -18
- package/dist/cjs/slickCustomTooltip.js.map +1 -1
- package/dist/esm/slickCustomTooltip.js +46 -18
- package/dist/esm/slickCustomTooltip.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/slickCustomTooltip.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/slickCustomTooltip.ts +101 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slickgrid-universal/custom-tooltip-plugin",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.0",
|
|
4
4
|
"description": "A plugin to add Custom Tooltip when hovering a cell, it subscribes to the cell",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"not dead"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@slickgrid-universal/common": "~5.
|
|
42
|
-
"@slickgrid-universal/utils": "~5.
|
|
41
|
+
"@slickgrid-universal/common": "~5.12.0",
|
|
42
|
+
"@slickgrid-universal/utils": "~5.12.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "d7e892ebc1727d7c83cc1e5cc80db8302eef4f63"
|
|
45
45
|
}
|
|
@@ -72,7 +72,7 @@ export class SlickCustomTooltip {
|
|
|
72
72
|
protected _sharedService?: SharedService | null = null;
|
|
73
73
|
protected _tooltipBodyElm?: HTMLDivElement;
|
|
74
74
|
protected _tooltipElm?: HTMLDivElement;
|
|
75
|
-
protected _mousePosition: { x: number; y: number
|
|
75
|
+
protected _mousePosition: { x: number; y: number } = { x: 0, y: 0 };
|
|
76
76
|
protected _mouseTarget?: HTMLElement | null;
|
|
77
77
|
protected _hasMultipleTooltips = false;
|
|
78
78
|
protected _defaultOptions = {
|
|
@@ -123,7 +123,7 @@ export class SlickCustomTooltip {
|
|
|
123
123
|
|
|
124
124
|
/** Getter for the Grid Options pulled through the Grid Object */
|
|
125
125
|
get gridOptions(): GridOption {
|
|
126
|
-
return this._grid?.getOptions() || {} as GridOption;
|
|
126
|
+
return this._grid?.getOptions() || ({} as GridOption);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/** Getter for the grid uid */
|
|
@@ -146,7 +146,7 @@ export class SlickCustomTooltip {
|
|
|
146
146
|
this._grid = grid;
|
|
147
147
|
this._rxjs = containerService.get<RxJsFacade>('RxJsFacade');
|
|
148
148
|
this._sharedService = containerService.get<SharedService>('SharedService');
|
|
149
|
-
this._addonOptions = { ...this._defaultOptions, ...
|
|
149
|
+
this._addonOptions = { ...this._defaultOptions, ...this._sharedService?.gridOptions?.customTooltip } as CustomTooltipOption;
|
|
150
150
|
this._eventHandler
|
|
151
151
|
.subscribe(grid.onMouseEnter, this.handleOnMouseOver.bind(this))
|
|
152
152
|
.subscribe(grid.onHeaderMouseOver, (e, args) => this.handleOnHeaderMouseOverByType(e, args, 'slick-header-column'))
|
|
@@ -193,7 +193,13 @@ export class SlickCustomTooltip {
|
|
|
193
193
|
* Async process callback will hide any prior tooltip & then merge the new result with the item `dataContext` under a `__params` property
|
|
194
194
|
* (unless a new prop name is provided) to provice as dataContext object to the asyncPostFormatter.
|
|
195
195
|
*/
|
|
196
|
-
protected asyncProcessCallback(
|
|
196
|
+
protected asyncProcessCallback(
|
|
197
|
+
asyncResult: any,
|
|
198
|
+
cell: { row: number; cell: number },
|
|
199
|
+
value: any,
|
|
200
|
+
columnDef: Column,
|
|
201
|
+
dataContext: any
|
|
202
|
+
): void {
|
|
197
203
|
this.hideTooltip();
|
|
198
204
|
const itemWithAsyncData = { ...dataContext, [this.addonOptions?.asyncParamsPropName ?? '__params']: asyncResult };
|
|
199
205
|
if (this._cellAddonOptions?.useRegularTooltip) {
|
|
@@ -215,7 +221,7 @@ export class SlickCustomTooltip {
|
|
|
215
221
|
|
|
216
222
|
const cell = {
|
|
217
223
|
row: -1, // negative row to avoid pulling any dataContext while rendering
|
|
218
|
-
cell: this._grid.getColumns().findIndex((col) => (args?.column?.id ?? '') === col.id)
|
|
224
|
+
cell: this._grid.getColumns().findIndex((col) => (args?.column?.id ?? '') === col.id),
|
|
219
225
|
};
|
|
220
226
|
const columnDef = args.column;
|
|
221
227
|
const item = {};
|
|
@@ -229,8 +235,11 @@ export class SlickCustomTooltip {
|
|
|
229
235
|
args.dataContext = item;
|
|
230
236
|
args.grid = this._grid;
|
|
231
237
|
args.type = isHeaderRowType ? 'header-row' : 'header';
|
|
232
|
-
this._cellAddonOptions = { ...this._addonOptions, ...
|
|
233
|
-
if (
|
|
238
|
+
this._cellAddonOptions = { ...this._addonOptions, ...columnDef?.customTooltip } as CustomTooltipOption;
|
|
239
|
+
if (
|
|
240
|
+
columnDef?.disableTooltip ||
|
|
241
|
+
(typeof this._cellAddonOptions?.usabilityOverride === 'function' && !this._cellAddonOptions.usabilityOverride(args))
|
|
242
|
+
) {
|
|
234
243
|
return;
|
|
235
244
|
}
|
|
236
245
|
|
|
@@ -259,7 +268,7 @@ export class SlickCustomTooltip {
|
|
|
259
268
|
if (event && this._grid) {
|
|
260
269
|
// get cell only when it's possible (ie, Composite Editor will not be able to get cell and so it will never show any tooltip)
|
|
261
270
|
const targetClassName = event?.target?.closest('.slick-cell')?.className;
|
|
262
|
-
const cell =
|
|
271
|
+
const cell = targetClassName && /l\d+/.exec(targetClassName || '') ? this._grid.getCellFromEvent(event) : null;
|
|
263
272
|
|
|
264
273
|
if (cell) {
|
|
265
274
|
const item = this.dataView ? this.dataView.getItem(cell.row) : this._grid.getDataItem(cell.row);
|
|
@@ -267,9 +276,20 @@ export class SlickCustomTooltip {
|
|
|
267
276
|
this._cellNodeElm = this._grid.getCellNode(cell.row, cell.cell) as HTMLDivElement;
|
|
268
277
|
|
|
269
278
|
if (item && columnDef) {
|
|
270
|
-
this._cellAddonOptions = { ...this._addonOptions, ...
|
|
271
|
-
|
|
272
|
-
if (
|
|
279
|
+
this._cellAddonOptions = { ...this._addonOptions, ...columnDef?.customTooltip } as CustomTooltipOption;
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
columnDef?.disableTooltip ||
|
|
283
|
+
(typeof this._cellAddonOptions?.usabilityOverride === 'function' &&
|
|
284
|
+
!this._cellAddonOptions.usabilityOverride({
|
|
285
|
+
cell: cell.cell,
|
|
286
|
+
row: cell.row,
|
|
287
|
+
dataContext: item,
|
|
288
|
+
column: columnDef,
|
|
289
|
+
grid: this._grid,
|
|
290
|
+
type: 'cell',
|
|
291
|
+
}))
|
|
292
|
+
) {
|
|
273
293
|
return;
|
|
274
294
|
}
|
|
275
295
|
|
|
@@ -291,7 +311,9 @@ export class SlickCustomTooltip {
|
|
|
291
311
|
if (typeof this._cellAddonOptions?.asyncProcess === 'function') {
|
|
292
312
|
const asyncProcess = this._cellAddonOptions.asyncProcess(cell.row, cell.cell, value, columnDef, item, this._grid);
|
|
293
313
|
if (!this._cellAddonOptions.asyncPostFormatter) {
|
|
294
|
-
console.error(
|
|
314
|
+
console.error(
|
|
315
|
+
`[Slickgrid-Universal] when using "asyncProcess" with Custom Tooltip, you must also provide an "asyncPostFormatter" formatter.`
|
|
316
|
+
);
|
|
295
317
|
}
|
|
296
318
|
|
|
297
319
|
if (asyncProcess instanceof Promise) {
|
|
@@ -311,7 +333,8 @@ export class SlickCustomTooltip {
|
|
|
311
333
|
.pipe(
|
|
312
334
|
// use `switchMap` so that it cancels any previous subscription, it must return an observable so we can use `of` for that, and then finally we can subscribe to the new observable
|
|
313
335
|
rxjs.switchMap((asyncResult) => rxjs.of(asyncResult))
|
|
314
|
-
)
|
|
336
|
+
)
|
|
337
|
+
.subscribe(
|
|
315
338
|
(asyncResult: any) => this.asyncProcessCallback(asyncResult, cell, value, columnDef, item),
|
|
316
339
|
(error: any) => console.error(error)
|
|
317
340
|
);
|
|
@@ -327,11 +350,20 @@ export class SlickCustomTooltip {
|
|
|
327
350
|
* Parse the Custom Formatter (when provided) or return directly the text when it is already a string.
|
|
328
351
|
* We will also sanitize the text in both cases before returning it so that it can be used safely.
|
|
329
352
|
*/
|
|
330
|
-
protected parseFormatterAndSanitize(
|
|
353
|
+
protected parseFormatterAndSanitize(
|
|
354
|
+
formatterOrText: Formatter | string | undefined,
|
|
355
|
+
cell: { row: number; cell: number },
|
|
356
|
+
value: any,
|
|
357
|
+
columnDef: Column,
|
|
358
|
+
item: unknown
|
|
359
|
+
): string {
|
|
331
360
|
if (typeof formatterOrText === 'function') {
|
|
332
361
|
const tooltipResult = formatterOrText(cell.row, cell.cell, value, columnDef, item, this._grid);
|
|
362
|
+
// prettier-ignore
|
|
333
363
|
const formatterText = isPrimitiveOrHTML(tooltipResult) ? tooltipResult : (tooltipResult as FormatterResultWithHtml).html || (tooltipResult as FormatterResultWithText).text;
|
|
334
|
-
return this._grid.sanitizeHtmlString(
|
|
364
|
+
return this._grid.sanitizeHtmlString(
|
|
365
|
+
(formatterText instanceof HTMLElement ? formatterText.textContent : (formatterText as string)) || ''
|
|
366
|
+
);
|
|
335
367
|
} else if (typeof formatterOrText === 'string') {
|
|
336
368
|
return this._grid.sanitizeHtmlString(formatterOrText);
|
|
337
369
|
}
|
|
@@ -343,19 +375,31 @@ export class SlickCustomTooltip {
|
|
|
343
375
|
* then create a temporary html element to easily retrieve the first [title=""] attribute text content
|
|
344
376
|
* also clear the "title" attribute from the grid div text content so that it won't show also as a 2nd browser tooltip
|
|
345
377
|
*/
|
|
346
|
-
protected renderRegularTooltip(
|
|
378
|
+
protected renderRegularTooltip(
|
|
379
|
+
formatterOrText: Formatter | string | undefined,
|
|
380
|
+
cell: { row: number; cell: number },
|
|
381
|
+
value: any,
|
|
382
|
+
columnDef: Column,
|
|
383
|
+
item: any
|
|
384
|
+
): void {
|
|
347
385
|
const tmpDiv = document.createElement('div');
|
|
348
386
|
this._grid.applyHtmlCode(tmpDiv, this.parseFormatterAndSanitize(formatterOrText, cell, value, columnDef, item));
|
|
349
387
|
this._hasMultipleTooltips = (this._cellNodeElm?.querySelectorAll(SELECTOR_CLOSEST_TOOLTIP_ATTR).length || 0) > 1;
|
|
350
388
|
|
|
351
389
|
let tmpTitleElm: HTMLElement | null | undefined;
|
|
352
|
-
const cellElm =
|
|
353
|
-
|
|
354
|
-
|
|
390
|
+
const cellElm =
|
|
391
|
+
this._cellAddonOptions?.useRegularTooltipFromCellTextOnly || !this._mouseTarget
|
|
392
|
+
? (this._cellNodeElm as HTMLElement)
|
|
393
|
+
: this._mouseTarget;
|
|
355
394
|
|
|
356
395
|
let tooltipText = columnDef?.toolTip ?? '';
|
|
357
396
|
if (!tooltipText) {
|
|
358
|
-
if (
|
|
397
|
+
if (
|
|
398
|
+
this._cellType === 'slick-cell' &&
|
|
399
|
+
cellElm &&
|
|
400
|
+
cellElm.clientWidth < cellElm.scrollWidth &&
|
|
401
|
+
!this._cellAddonOptions?.useRegularTooltipFromFormatterOnly
|
|
402
|
+
) {
|
|
359
403
|
tooltipText = cellElm.textContent?.trim() ?? '';
|
|
360
404
|
if (this._cellAddonOptions?.tooltipTextMaxLength && tooltipText.length > this._cellAddonOptions?.tooltipTextMaxLength) {
|
|
361
405
|
tooltipText = tooltipText.substring(0, this._cellAddonOptions.tooltipTextMaxLength - 3) + '...';
|
|
@@ -374,6 +418,7 @@ export class SlickCustomTooltip {
|
|
|
374
418
|
}
|
|
375
419
|
}
|
|
376
420
|
|
|
421
|
+
// prettier-ignore
|
|
377
422
|
if (tmpTitleElm?.style.display === 'none' || (this._hasMultipleTooltips && (!cellElm || cellElm === this._cellNodeElm))) {
|
|
378
423
|
tooltipText = '';
|
|
379
424
|
} else if (!tooltipText || (typeof formatterOrText === 'function' && this._cellAddonOptions?.useRegularTooltipFromFormatterOnly)) {
|
|
@@ -390,7 +435,15 @@ export class SlickCustomTooltip {
|
|
|
390
435
|
this.swapAndClearTitleAttribute(tmpTitleElm, tooltipText);
|
|
391
436
|
}
|
|
392
437
|
|
|
393
|
-
protected renderTooltipFormatter(
|
|
438
|
+
protected renderTooltipFormatter(
|
|
439
|
+
formatter: Formatter | string | undefined,
|
|
440
|
+
cell: { row: number; cell: number },
|
|
441
|
+
value: any,
|
|
442
|
+
columnDef: Column,
|
|
443
|
+
item: unknown,
|
|
444
|
+
tooltipText?: string,
|
|
445
|
+
inputTitleElm?: Element | null
|
|
446
|
+
): void {
|
|
394
447
|
// create the tooltip DOM element with the text returned by the Formatter
|
|
395
448
|
this._tooltipElm = createDomElement('div', { className: this.className });
|
|
396
449
|
this._tooltipBodyElm = createDomElement('div', { className: this.bodyClassName });
|
|
@@ -408,17 +461,21 @@ export class SlickCustomTooltip {
|
|
|
408
461
|
}
|
|
409
462
|
|
|
410
463
|
let outputText = tooltipText || this.parseFormatterAndSanitize(formatter, cell, value, columnDef, item) || '';
|
|
411
|
-
outputText =
|
|
464
|
+
outputText =
|
|
465
|
+
this._cellAddonOptions?.tooltipTextMaxLength && outputText.length > this._cellAddonOptions.tooltipTextMaxLength
|
|
466
|
+
? outputText.substring(0, this._cellAddonOptions.tooltipTextMaxLength - 3) + '...'
|
|
467
|
+
: outputText;
|
|
412
468
|
|
|
413
469
|
let finalOutputText = '';
|
|
414
470
|
if (!tooltipText || this._cellAddonOptions?.renderRegularTooltipAsHtml) {
|
|
415
471
|
finalOutputText = this._grid.sanitizeHtmlString(outputText);
|
|
416
472
|
this._grid.applyHtmlCode(this._tooltipBodyElm, finalOutputText);
|
|
417
|
-
this._tooltipBodyElm.style.whiteSpace = this._cellAddonOptions?.whiteSpace ?? this._defaultOptions.whiteSpace as string;
|
|
473
|
+
this._tooltipBodyElm.style.whiteSpace = this._cellAddonOptions?.whiteSpace ?? (this._defaultOptions.whiteSpace as string);
|
|
418
474
|
} else {
|
|
419
475
|
finalOutputText = outputText || '';
|
|
420
476
|
this._tooltipBodyElm.textContent = finalOutputText;
|
|
421
|
-
this._tooltipBodyElm.style.whiteSpace =
|
|
477
|
+
this._tooltipBodyElm.style.whiteSpace =
|
|
478
|
+
this._cellAddonOptions?.regularTooltipWhiteSpace ?? (this._defaultOptions.regularTooltipWhiteSpace as string); // use `pre` so that sequences of white space are collapsed. Lines are broken at newline characters
|
|
422
479
|
}
|
|
423
480
|
|
|
424
481
|
// optional max height/width of the tooltip container
|
|
@@ -453,9 +510,9 @@ export class SlickCustomTooltip {
|
|
|
453
510
|
* Most of the time positioning of the tooltip will be to the "top-right" of the cell is ok but if our column is completely on the right side then we'll want to change the position to "left" align.
|
|
454
511
|
* Same goes for the top/bottom position, Most of the time positioning the tooltip to the "top" but if we are hovering a cell at the top of the grid and there's no room to display it then we might need to reposition to "bottom" instead.
|
|
455
512
|
*/
|
|
456
|
-
protected reposition(cell: { row: number; cell: number
|
|
513
|
+
protected reposition(cell: { row: number; cell: number }): void {
|
|
457
514
|
if (this._tooltipElm) {
|
|
458
|
-
this._cellNodeElm = this._cellNodeElm || this._grid.getCellNode(cell.row, cell.cell) as HTMLDivElement;
|
|
515
|
+
this._cellNodeElm = this._cellNodeElm || (this._grid.getCellNode(cell.row, cell.cell) as HTMLDivElement);
|
|
459
516
|
const cellPosition = getOffset(this._cellNodeElm);
|
|
460
517
|
const cellContainerWidth = this._cellNodeElm.offsetWidth;
|
|
461
518
|
const calculatedTooltipHeight = this._tooltipElm.getBoundingClientRect().height;
|
|
@@ -472,13 +529,16 @@ export class SlickCustomTooltip {
|
|
|
472
529
|
const position = this._cellAddonOptions?.position ?? 'auto';
|
|
473
530
|
let finalTooltipPosition = '';
|
|
474
531
|
if (position === 'center') {
|
|
475
|
-
newPositionLeft +=
|
|
532
|
+
newPositionLeft += cellContainerWidth / 2 - calculatedTooltipWidth / 2 + (this._cellAddonOptions?.offsetRight ?? 0);
|
|
476
533
|
finalTooltipPosition = 'top-center';
|
|
477
534
|
this._tooltipElm.classList.remove('arrow-left-align', 'arrow-right-align');
|
|
478
535
|
this._tooltipElm.classList.add('arrow-center-align');
|
|
479
|
-
} else if (
|
|
536
|
+
} else if (
|
|
537
|
+
position === 'right-align' ||
|
|
538
|
+
((position === 'auto' || position !== 'left-align') && newPositionLeft + calculatedTooltipWidth > calculatedBodyWidth)
|
|
539
|
+
) {
|
|
480
540
|
finalTooltipPosition = 'right';
|
|
481
|
-
newPositionLeft -=
|
|
541
|
+
newPositionLeft -= calculatedTooltipWidth - cellContainerWidth - (this._cellAddonOptions?.offsetLeft ?? 0);
|
|
482
542
|
this._tooltipElm.classList.remove('arrow-center-align', 'arrow-left-align');
|
|
483
543
|
this._tooltipElm.classList.add('arrow-right-align');
|
|
484
544
|
} else {
|
|
@@ -489,7 +549,10 @@ export class SlickCustomTooltip {
|
|
|
489
549
|
|
|
490
550
|
// do the same calculation/reposition with top/bottom (default is top of the cell or in other word starting from the cell going down)
|
|
491
551
|
// NOTE the class name is for the arrow and is inverse compare to the tooltip itself, so if user ask for "bottom", then the arrow will in fact be "arrow-top"
|
|
492
|
-
if (
|
|
552
|
+
if (
|
|
553
|
+
position === 'bottom' ||
|
|
554
|
+
((position === 'auto' || position !== 'top') && calculatedTooltipHeight > calculateAvailableSpace(this._cellNodeElm).top)
|
|
555
|
+
) {
|
|
493
556
|
newPositionTop = (cellPosition.top || 0) + (this.gridOptions.rowHeight ?? 0) + (this._cellAddonOptions?.offsetTopBottom ?? 0);
|
|
494
557
|
finalTooltipPosition = `bottom-${finalTooltipPosition}`;
|
|
495
558
|
this._tooltipElm.classList.remove('arrow-down');
|
|
@@ -506,7 +569,8 @@ export class SlickCustomTooltip {
|
|
|
506
569
|
if (finalTooltipPosition.includes('left') || finalTooltipPosition === 'top-center') {
|
|
507
570
|
newPositionLeft = mouseElmOffset.left - (this._addonOptions?.offsetArrow ?? 3);
|
|
508
571
|
} else if (finalTooltipPosition.includes('right')) {
|
|
509
|
-
newPositionLeft =
|
|
572
|
+
newPositionLeft =
|
|
573
|
+
mouseElmOffset.left - calculatedTooltipWidth + (this._mouseTarget?.offsetWidth ?? 0) + (this._addonOptions?.offsetArrow ?? 3);
|
|
510
574
|
}
|
|
511
575
|
}
|
|
512
576
|
|
|
@@ -525,8 +589,13 @@ export class SlickCustomTooltip {
|
|
|
525
589
|
// OR in a child element (most commonly as a custom formatter)
|
|
526
590
|
let cellWithTitleElm: Element | null | undefined;
|
|
527
591
|
if (inputTitleElm) {
|
|
528
|
-
cellWithTitleElm =
|
|
592
|
+
cellWithTitleElm =
|
|
593
|
+
this._cellNodeElm &&
|
|
594
|
+
(this._cellNodeElm.hasAttribute('title') && this._cellNodeElm.getAttribute('title')
|
|
595
|
+
? this._cellNodeElm
|
|
596
|
+
: this._cellNodeElm?.querySelector('[title]'));
|
|
529
597
|
}
|
|
598
|
+
// prettier-ignore
|
|
530
599
|
const titleElm = inputTitleElm || (this._cellNodeElm && ((this._cellNodeElm.hasAttribute('title') && this._cellNodeElm.getAttribute('title')) ? this._cellNodeElm : this._cellNodeElm?.querySelector('[title]')));
|
|
531
600
|
|
|
532
601
|
// flip tooltip text from `title` to `data-slick-tooltip`
|