From e6c230514a4e008ee45080e20f320e1a5b490b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 18 Sep 2020 17:54:40 -0400 Subject: [PATCH 01/10] Extend usage of `tooltip` prop to display tooltip on header cells --- .../components/ControlledTable/index.tsx | 16 ++- src/dash-table/components/HeaderFactory.tsx | 4 +- src/dash-table/components/Table/props.ts | 1 + .../derived/cell/eventHandlerProps.ts | 18 +++ src/dash-table/derived/header/wrappers.tsx | 110 ++++++++++++------ src/dash-table/handlers/cellEvents.ts | 53 ++++++++- 6 files changed, 154 insertions(+), 48 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index cd3f8a2dc..8284ba41d 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -1155,15 +1155,21 @@ export default class ControlledTable extends PureComponent< return; } - const id = currentTooltip.id; - const row = currentTooltip.row; + const {id, row, header} = currentTooltip; const {table, tooltip: t} = this.refs as {[key: string]: any}; if (t) { - const cell = table.querySelector( - `td[data-dash-column="${id}"][data-dash-row="${row}"]:not(.phantom-cell)` - ); + const cell = header + ? table.querySelector( + `tr:nth-of-type(${ + row + 1 + }) th[data-dash-column="${id}"]:not(.phantom-cell)` + ) + : table.querySelector( + `td[data-dash-column="${id}"][data-dash-row="${row}"]:not(.phantom-cell)` + ); + (this.refs.tooltip as TableTooltip).updateBounds(cell); } } diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index fb014d850..4c0c4361b 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -22,7 +22,7 @@ export default class HeaderFactory { private readonly headerOperations = derivedHeaderOperations(); private readonly headerStyles = derivedHeaderStyles(); private readonly headerOpStyles = derivedHeaderOpStyles(); - private readonly headerWrappers = derivedHeaderWrappers(); + private readonly headerWrappers = derivedHeaderWrappers(() => this.props); private readonly relevantStyles = derivedRelevantHeaderStyles(); private readonly labelsAndIndices = derivedLabelsAndIndices(); @@ -95,7 +95,7 @@ export default class HeaderFactory { relevantStyles ); - const wrappers = this.headerWrappers( + const wrappers = this.headerWrappers.get( visibleColumns, labelsAndIndices, merge_duplicate_headers diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index 77527a682..20fb6ad74 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -273,6 +273,7 @@ export interface IUserInterfaceViewport { export interface IUSerInterfaceTooltip { delay?: number; duration?: number; + header: boolean; id: ColumnId; row: number; } diff --git a/src/dash-table/derived/cell/eventHandlerProps.ts b/src/dash-table/derived/cell/eventHandlerProps.ts index d6397883e..58f5141d7 100644 --- a/src/dash-table/derived/cell/eventHandlerProps.ts +++ b/src/dash-table/derived/cell/eventHandlerProps.ts @@ -6,8 +6,10 @@ import { handleClick, handleDoubleClick, handleEnter, + handleEnterHeader, handleLeave, handleMove, + handleMoveHeader, handleOnMouseUp, handlePaste } from 'dash-table/handlers/cellEvents'; @@ -17,8 +19,10 @@ export enum Handler { Click = 'click', DoubleClick = 'doubleclick', Enter = 'enter', + EnterHeader = 'enterheader', Leave = 'leave', Move = 'move', + MoveHeader = 'moveheader', MouseUp = 'mouseup', Paste = 'paste' } @@ -60,6 +64,13 @@ class EventHandler { rowIndex, columnIndex ); + case Handler.EnterHeader: + return handleEnterHeader.bind( + undefined, + this.propsFn, + rowIndex, + columnIndex + ); case Handler.Leave: return handleLeave.bind( undefined, @@ -74,6 +85,13 @@ class EventHandler { rowIndex, columnIndex ); + case Handler.MoveHeader: + return handleMoveHeader.bind( + undefined, + this.propsFn, + rowIndex, + columnIndex + ); case Handler.MouseUp: return handleOnMouseUp.bind( undefined, diff --git a/src/dash-table/derived/header/wrappers.tsx b/src/dash-table/derived/header/wrappers.tsx index f35dde315..0a09f0ea9 100644 --- a/src/dash-table/derived/header/wrappers.tsx +++ b/src/dash-table/derived/header/wrappers.tsx @@ -1,47 +1,85 @@ import * as R from 'ramda'; import React from 'react'; -import {memoizeOneFactory} from 'core/memoizer'; +import memoizerCache from 'core/cache/memoizer'; +import derivedCellEventHandlerProps, { + Handler +} from 'dash-table/derived/cell/eventHandlerProps'; +import { + ColumnId, + Columns, + ICellFactoryProps +} from 'dash-table/components/Table/props'; -import {Columns} from 'dash-table/components/Table/props'; +class Wrappers { + constructor( + propsFn: () => ICellFactoryProps, + private readonly handlers = derivedCellEventHandlerProps(propsFn) + ) {} -function getter( - columns: Columns, - labelsAndIndices: R.KeyValuePair[], - mergeHeaders: boolean -): JSX.Element[][] { - return R.map(([labels, indices]) => { - return R.addIndex(R.map)((columnIndex, index) => { - const column = columns[columnIndex]; + get = ( + columns: Columns, + labelsAndIndices: R.KeyValuePair[], + mergeHeaders: boolean + ): JSX.Element[][] => + labelsAndIndices.map(([labels, indices], rowIndex) => + indices.map((columnIndex, i) => { + const column = columns[columnIndex]; - let colSpan: number; - if (!mergeHeaders) { - colSpan = 1; - } else { - if (columnIndex === R.last(indices)) { - colSpan = labels.length - columnIndex; + let colSpan: number; + if (!mergeHeaders) { + colSpan = 1; } else { - colSpan = indices[index + 1] - columnIndex; + if (columnIndex === R.last(indices)) { + colSpan = labels.length - columnIndex; + } else { + colSpan = indices[i + 1] - columnIndex; + } } - } - return ( - - ); - }, indices); - }, labelsAndIndices); + return this.wrapper.get(rowIndex, columnIndex)( + columnIndex, + column.id, + colSpan, + columnIndex === columns.length - 1 || + columnIndex === R.last(indices), + this.handlers(Handler.EnterHeader, rowIndex, columnIndex), + this.handlers(Handler.Leave, rowIndex, columnIndex), + this.handlers(Handler.MoveHeader, rowIndex, columnIndex) + ); + }) + ); + + /** + * Returns the wrapper for a header cell. + */ + private wrapper = memoizerCache< + [number, number] + >()( + ( + columnIndex: number, + columnId: ColumnId, + colSpan: number, + lastIndex: boolean, + onEnter: (e: MouseEvent) => void, + onLeave: (e: MouseEvent) => void, + onMove: (e: MouseEvent) => void + ) => ( + + ) + ); } -export default memoizeOneFactory(getter); +export default (propsFn: () => ICellFactoryProps) => new Wrappers(propsFn); diff --git a/src/dash-table/handlers/cellEvents.ts b/src/dash-table/handlers/cellEvents.ts index e4504341a..e0b21932e 100644 --- a/src/dash-table/handlers/cellEvents.ts +++ b/src/dash-table/handlers/cellEvents.ts @@ -167,13 +167,27 @@ export const handleEnter = ( ) => { const {setState, virtualized, visibleColumns} = propsFn(); - const c = visibleColumns[i]; - const realIdx = virtualized.indices[idx - virtualized.offset.rows]; + setState({ + currentTooltip: { + header: false, + id: visibleColumns[i].id, + row: virtualized.indices[idx - virtualized.offset.rows] + } + }); +}; + +export const handleEnterHeader = ( + propsFn: () => ICellFactoryProps, + idx: number, + i: number +) => { + const {setState, visibleColumns} = propsFn(); setState({ currentTooltip: { - id: c.id, - row: realIdx + header: true, + id: visibleColumns[i].id, + row: idx } }); }; @@ -201,19 +215,48 @@ export const handleMove = ( if ( currentTooltip && currentTooltip.id === c.id && - currentTooltip.row === realIdx + currentTooltip.row === realIdx && + !currentTooltip.header ) { return; } setState({ currentTooltip: { + header: false, id: c.id, row: realIdx } }); }; +export const handleMoveHeader = ( + propsFn: () => ICellFactoryProps, + idx: number, + i: number +) => { + const {currentTooltip, setState, visibleColumns} = propsFn(); + + const c = visibleColumns[i]; + + if ( + currentTooltip && + currentTooltip.id === c.id && + currentTooltip.row === idx && + currentTooltip.header + ) { + return; + } + + setState({ + currentTooltip: { + header: true, + id: c.id, + row: idx + } + }); +}; + export const handleOnMouseUp = ( propsFn: () => ICellFactoryProps, idx: number, From d7cb7413146efe988165dcd20fbae0d8c9b25aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 24 Sep 2020 10:18:44 -0400 Subject: [PATCH 02/10] tooltip + tooltip_header + new test for header/data exclusion --- .../components/ControlledTable/index.tsx | 4 +- src/dash-table/components/Table/props.ts | 7 +- src/dash-table/dash/DataTable.js | 40 ++++++++++ src/dash-table/derived/table/tooltip.ts | 54 ++++++++----- src/dash-table/tooltips/props.ts | 6 ++ tests/selenium/conftest.py | 19 ++++- tests/selenium/test_tooltip.py | 75 +++++++++++++++++++ 7 files changed, 179 insertions(+), 26 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 8284ba41d..0e0c87124 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -887,9 +887,10 @@ export default class ControlledTable extends PureComponent< scrollbarWidth, style_as_list_view, style_table, + tooltip_data, tooltip_delay, tooltip_duration, - tooltip_data, + tooltip_header, uiCell, uiHeaders, uiViewport, @@ -952,6 +953,7 @@ export default class ControlledTable extends PureComponent< const tableTooltip = derivedTooltips( currentTooltip, tooltip_data, + tooltip_header, tooltip_conditional, tooltip, virtualized, diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index 20fb6ad74..f7a42800c 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -304,11 +304,12 @@ export interface IProps { id: string; + tooltip: ITableStaticTooltips; + tooltip_conditional: ConditionalTooltip[]; tooltip_data?: DataTooltips; tooltip_delay: number | null; tooltip_duration: number | null; - tooltip: ITableStaticTooltips; - tooltip_conditional: ConditionalTooltip[]; + tooltip_header?: DataTooltips; active_cell?: ICellCoordinates; cell_selectable?: boolean; @@ -394,6 +395,7 @@ interface IDefaultProps { start_cell: ICellCoordinates; style_as_list_view: boolean; tooltip_data: DataTooltips; + tooltip_header: DataTooltips; page_action: TableAction; page_current: number; @@ -529,6 +531,7 @@ export interface ICellFactoryProps { style_header_conditional: Headers; style_table: Table; tooltip_data: DataTooltips; + tooltip_header: DataTooltips; uiCell?: IUserInterfaceCell; uiViewport?: IUserInterfaceViewport; viewport: IDerivedData; diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 8bad3290f..5ae818638 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -843,6 +843,8 @@ export const propTypes = { * or `text`. Defaults to `text`. * The `value` refers to the syntax-based content of * the tooltip. This value is required. + * The `use_with` refers to whether the tooltip will be shown + * only on data or headers. If not set, it applies to both. * The `delay` represents the delay in milliseconds before * the tooltip is shown when hovering a cell. This overrides * the table's `tooltip_delay` property. If set to `null`, @@ -858,6 +860,7 @@ export const propTypes = { tooltip: PropTypes.objectOf( PropTypes.oneOfType([ PropTypes.exact({ + use_with: PropTypes.oneOf(['data', 'header']), delay: PropTypes.number, duration: PropTypes.number, type: PropTypes.oneOf(['text', 'markdown']), @@ -955,6 +958,43 @@ export const propTypes = { ) ), + /** + * `tooltip_header` represents the tooltip shown + * for different header columns and cells. + * The `property` name refers to the column ID. Each property + * contains a list of tooltips mapped to the source `data` + * row index. + * The `type` refers to the type of tooltip syntax used + * for the tooltip generation. Can either be `markdown` + * or `text`. Defaults to `text`. + * The `value` refers to the syntax-based content of + * the tooltip. This value is required. + * The `delay` represents the delay in milliseconds before + * the tooltip is shown when hovering a cell. This overrides + * the table's `tooltip_delay` property. If set to `null`, + * the tooltip will be shown immediately. + * The `duration` represents the duration in milliseconds + * during which the tooltip is shown when hovering a cell. + * This overrides the table's `tooltip_duration` property. + * If set to `null`, the tooltip will not disappear. + * Alternatively, the value of the property can also be + * a plain string. The `text` syntax will be used in + * that case. + */ + tooltip_header: PropTypes.arrayOf( + PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) + ) + ), + /** * `tooltip_delay` represents the table-wide delay in milliseconds before * the tooltip is shown when hovering a cell. If set to `null`, the tooltip diff --git a/src/dash-table/derived/table/tooltip.ts b/src/dash-table/derived/table/tooltip.ts index b0168d3ff..f5ca7bd49 100644 --- a/src/dash-table/derived/table/tooltip.ts +++ b/src/dash-table/derived/table/tooltip.ts @@ -7,7 +7,11 @@ import { DataTooltips } from 'dash-table/components/Table/props'; import {ifColumnId, ifRowIndex, ifFilter} from 'dash-table/conditional'; -import {ConditionalTooltip, TooltipSyntax} from 'dash-table/tooltips/props'; +import { + ConditionalTooltip, + TooltipUsage, + TooltipSyntax +} from 'dash-table/tooltips/props'; import {memoizeOne} from 'core/memoizer'; // 2^32-1 the largest value setTimout can take safely @@ -16,6 +20,7 @@ export const MAX_32BITS = 2147483647; function getSelectedTooltip( currentTooltip: IUSerInterfaceTooltip, tooltip_data: DataTooltips, + tooltip_header: DataTooltips, tooltip_conditional: ConditionalTooltip[], tooltip_static: ITableStaticTooltips, virtualized: IVirtualizedDerivedData @@ -24,30 +29,39 @@ function getSelectedTooltip( return undefined; } - const {id, row} = currentTooltip; + const {header, id, row} = currentTooltip; + + const tooltip_source = header ? tooltip_header : tooltip_data; if (id === undefined || row === undefined) { return undefined; } + const staticTooltip = tooltip_static?.[id]; + const staticUseWith = + staticTooltip && typeof staticTooltip !== 'string' + ? staticTooltip.use_with + : undefined; + const staticApplicable = + !staticUseWith || (staticUseWith === TooltipUsage.Header) === header; + const resolvedStaticTooltip = staticApplicable ? staticTooltip : undefined; + const appliedStaticTooltip = - (tooltip_data && - tooltip_data.length > row && - tooltip_data[row] && - tooltip_data[row][id]) || - tooltip_static[id]; - - const conditionalTooltips = R.filter(tt => { - return ( - !tt.if || - (ifColumnId(tt.if, id) && - ifRowIndex(tt.if, row) && - ifFilter( - tt.if, - virtualized.data[row - virtualized.offset.rows] - )) - ); - }, tooltip_conditional); + tooltip_source?.[row]?.[id] || resolvedStaticTooltip; + + const conditionalTooltips = header + ? [] + : R.filter(tt => { + return ( + !tt.if || + (ifColumnId(tt.if, id) && + ifRowIndex(tt.if, row) && + ifFilter( + tt.if, + virtualized.data[row - virtualized.offset.rows] + )) + ); + }, tooltip_conditional); return conditionalTooltips.length ? conditionalTooltips.slice(-1)[0] @@ -78,6 +92,7 @@ export default memoizeOne( ( currentTooltip: IUSerInterfaceTooltip, tooltip_data: DataTooltips, + tooltip_header: DataTooltips, tooltip_conditional: ConditionalTooltip[], tooltip_static: ITableStaticTooltips, virtualized: IVirtualizedDerivedData, @@ -87,6 +102,7 @@ export default memoizeOne( const selectedTooltip = getSelectedTooltip( currentTooltip, tooltip_data, + tooltip_header, tooltip_conditional, tooltip_static, virtualized diff --git a/src/dash-table/tooltips/props.ts b/src/dash-table/tooltips/props.ts index 302c7898a..87bae82c1 100644 --- a/src/dash-table/tooltips/props.ts +++ b/src/dash-table/tooltips/props.ts @@ -5,7 +5,13 @@ export enum TooltipSyntax { Markdown = 'markdown' } +export enum TooltipUsage { + Data = 'data', + Header = 'header' +} + export interface ITooltip { + use_with?: TooltipUsage; delay?: number | null; duration?: number | null; type?: TooltipSyntax; diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 8887ef3f9..050b7e087 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -129,16 +129,22 @@ def __init__(self, id, mixin, col_id, state=_ANY): def get(self, row=0): self.mixin._wait_for_table(self.id, self.state) - return self.mixin.find_elements( - '#{} {} tbody tr th.dash-header[data-dash-column="{}"]:not(.phantom-cell)'.format( - self.id, self.state, self.col_id + return self.mixin.find_element( + '#{} {} tbody tr:nth-of-type({}) th.dash-header[data-dash-column="{}"]:not(.phantom-cell)'.format( + self.id, self.state, row + 1, self.col_id ) - )[row] + ) @preconditions(_validate_row) def hide(self, row=0): self.get(row).find_element_by_css_selector(".column-header--hide").click() + @preconditions(_validate_row) + def move_to(self, row=0): + ac = ActionChains(self.mixin.driver) + ac.move_to_element(self.get(row)) + return ac.perform() + @preconditions(_validate_row) def sort(self, row=0): self.get(row).find_element_by_css_selector(".column-header--sort").click() @@ -265,6 +271,11 @@ def exists(self): return tooltip is not None and tooltip.is_displayed() + def missing(self): + self.mixin._wait_for_table(self.id) + + return len(self.mixin.find_elements(".dash-tooltip")) == 0 + def get_text(self): return ( self._get_tooltip() diff --git a/tests/selenium/test_tooltip.py b/tests/selenium/test_tooltip.py index f39949d26..965b13702 100644 --- a/tests/selenium/test_tooltip.py +++ b/tests/selenium/test_tooltip.py @@ -1,6 +1,7 @@ import dash import math import pytest +import time import dash.testing.wait as wait from dash_table import DataTable @@ -138,3 +139,77 @@ def test_ttip003_tooltip_disappears(test): wait.until(lambda: tooltip.exists(), 2.5) wait.until(lambda: not tooltip.exists(), 2.5) + + +def test_ttip004_tooltip_applied(test): + props = { + **base_props, + "columns": [ + dict( + id=str(i), + name=["Column {}".format(math.ceil(i / 3)), "Column {}".format(i)], + ) + for i in range(1, 30) + ], + "merge_duplicate_headers": True, + "tooltip_delay": 0, + "tooltip_duration": 5000, + "tooltip": { + "1": "text1", + "2": {"use_with": "data", "value": "text2"}, + "3": {"use_with": "header", "value": "text3"}, + }, + "tooltip_data": [{"1": "alt-text1"}], + "tooltip_header": [{}, {"1": "alt-header1-row1"}], + } + + app = dash.Dash(__name__) + app.layout = DataTable(**props) + + test.start_server(app) + + target = test.table("table") + target.is_ready() + + cell55 = target.cell(5, "5") + tooltip = target.tooltip + + target.cell(0, "1").move_to() + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "alt-text1" + cell55.move_to() + + target.cell(1, "1").move_to() + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "text1" + cell55.move_to() + + target.cell(0, "2").move_to() + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "text2" + cell55.move_to() + + target.cell(0, "3").move_to() + time.sleep(1) + wait.until(lambda: target.tooltip.missing(), 3) + cell55.move_to() + + target.column("1").move_to(0) + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "text1" + cell55.move_to() + + target.column("1").move_to(1) + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "alt-header1-row1" + cell55.move_to() + + target.column("2").move_to(1) + time.sleep(1) + wait.until(lambda: target.tooltip.missing(), 3) + cell55.move_to() + + target.column("3").move_to(1) + wait.until(lambda: target.tooltip.exists(), 3) + assert tooltip.get_text().strip() == "text3" + cell55.move_to() From ffa6241cb3335f8ed83be4318ef5ef9789fabdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 24 Sep 2020 11:00:55 -0400 Subject: [PATCH 03/10] prop description update --- src/dash-table/dash/DataTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 5ae818638..66747646f 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -962,7 +962,7 @@ export const propTypes = { * `tooltip_header` represents the tooltip shown * for different header columns and cells. * The `property` name refers to the column ID. Each property - * contains a list of tooltips mapped to the source `data` + * contains a list of tooltips mapped to the table's `header` * row index. * The `type` refers to the type of tooltip syntax used * for the tooltip generation. Can either be `markdown` From 71e7ce9b7bbc2752fc121afba4e74a2641061724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 29 Sep 2020 08:36:40 -0400 Subject: [PATCH 04/10] `both` tooltip usage --- src/dash-table/dash/DataTable.js | 5 +++-- src/dash-table/derived/table/tooltip.ts | 5 +++-- src/dash-table/tooltips/props.ts | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 66747646f..30ff0feb7 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -844,7 +844,8 @@ export const propTypes = { * The `value` refers to the syntax-based content of * the tooltip. This value is required. * The `use_with` refers to whether the tooltip will be shown - * only on data or headers. If not set, it applies to both. + * only on data or headers. Can be `both`, `data`, `header`. + * Defaults to `both`. * The `delay` represents the delay in milliseconds before * the tooltip is shown when hovering a cell. This overrides * the table's `tooltip_delay` property. If set to `null`, @@ -860,7 +861,7 @@ export const propTypes = { tooltip: PropTypes.objectOf( PropTypes.oneOfType([ PropTypes.exact({ - use_with: PropTypes.oneOf(['data', 'header']), + use_with: PropTypes.oneOf(['both', 'data', 'header']), delay: PropTypes.number, duration: PropTypes.number, type: PropTypes.oneOf(['text', 'markdown']), diff --git a/src/dash-table/derived/table/tooltip.ts b/src/dash-table/derived/table/tooltip.ts index f5ca7bd49..3751246dc 100644 --- a/src/dash-table/derived/table/tooltip.ts +++ b/src/dash-table/derived/table/tooltip.ts @@ -41,9 +41,10 @@ function getSelectedTooltip( const staticUseWith = staticTooltip && typeof staticTooltip !== 'string' ? staticTooltip.use_with - : undefined; + : TooltipUsage.Both; const staticApplicable = - !staticUseWith || (staticUseWith === TooltipUsage.Header) === header; + staticUseWith === TooltipUsage.Both || + (staticUseWith === TooltipUsage.Header) === header; const resolvedStaticTooltip = staticApplicable ? staticTooltip : undefined; const appliedStaticTooltip = diff --git a/src/dash-table/tooltips/props.ts b/src/dash-table/tooltips/props.ts index 87bae82c1..3b8d40298 100644 --- a/src/dash-table/tooltips/props.ts +++ b/src/dash-table/tooltips/props.ts @@ -6,6 +6,7 @@ export enum TooltipSyntax { } export enum TooltipUsage { + Both = 'both', Data = 'data', Header = 'header' } From a2011e0acdeb1ca1da51d5b255d6e1ef95d5eb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 29 Sep 2020 09:57:06 -0400 Subject: [PATCH 05/10] update tooltip_(data|header) proptypes + test --- src/dash-table/components/Table/props.ts | 8 +-- src/dash-table/dash/DataTable.js | 84 +++++++++++++++++------- src/dash-table/derived/table/tooltip.ts | 6 +- tests/selenium/test_tooltip.py | 13 +++- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index f7a42800c..5879858c1 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -209,7 +209,9 @@ export interface ILoadingState { export type ConditionalDropdowns = IConditionalDropdown[]; export type DataDropdowns = Partial[]; -export type DataTooltips = Partial[]; +export type DataTooltips = + | {[key: string]: Partial}[] + | {[key: string]: Partial[]}; export type StaticDropdowns = Partial; export type Fixed = {headers: false; data?: 0} | {headers: true; data?: number}; @@ -246,10 +248,6 @@ export interface IStaticDropdowns { [key: string]: IDropdown; } -export interface ITableTooltips { - [key: string]: Tooltip; -} - export interface ITableStaticTooltips { [key: string]: Tooltip; } diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 30ff0feb7..06d4e9a84 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -860,14 +860,14 @@ export const propTypes = { */ tooltip: PropTypes.objectOf( PropTypes.oneOfType([ + PropTypes.nully, + PropTypes.string, PropTypes.exact({ - use_with: PropTypes.oneOf(['both', 'data', 'header']), delay: PropTypes.number, duration: PropTypes.number, type: PropTypes.oneOf(['text', 'markdown']), value: PropTypes.string.isRequired - }), - PropTypes.string + }) ]) ), @@ -945,19 +945,36 @@ export const propTypes = { * a plain string. The `text` syntax will be used in * that case. */ - tooltip_data: PropTypes.arrayOf( + tooltip_data: PropTypes.oneOf([ + PropTypes.arrayOf( + PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.nully, + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) + ) + ), PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.string, - PropTypes.exact({ - delay: PropTypes.number, - duration: PropTypes.number, - type: PropTypes.oneOf(['text', 'markdown']), - value: PropTypes.string.isRequired - }) - ]) + PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.nully, + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) + ) ) - ), + ]), /** * `tooltip_header` represents the tooltip shown @@ -982,19 +999,36 @@ export const propTypes = { * a plain string. The `text` syntax will be used in * that case. */ - tooltip_header: PropTypes.arrayOf( + tooltip_header: PropTypes.oneOf([ + PropTypes.arrayOf( + PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.nully, + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) + ) + ), PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.string, - PropTypes.exact({ - delay: PropTypes.number, - duration: PropTypes.number, - type: PropTypes.oneOf(['text', 'markdown']), - value: PropTypes.string.isRequired - }) - ]) + PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.nully, + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) + ) ) - ), + ]), /** * `tooltip_delay` represents the table-wide delay in milliseconds before diff --git a/src/dash-table/derived/table/tooltip.ts b/src/dash-table/derived/table/tooltip.ts index 3751246dc..90ab871e8 100644 --- a/src/dash-table/derived/table/tooltip.ts +++ b/src/dash-table/derived/table/tooltip.ts @@ -31,7 +31,7 @@ function getSelectedTooltip( const {header, id, row} = currentTooltip; - const tooltip_source = header ? tooltip_header : tooltip_data; + const tooltipSource = header ? tooltip_header : tooltip_data; if (id === undefined || row === undefined) { return undefined; @@ -48,7 +48,9 @@ function getSelectedTooltip( const resolvedStaticTooltip = staticApplicable ? staticTooltip : undefined; const appliedStaticTooltip = - tooltip_source?.[row]?.[id] || resolvedStaticTooltip; + (Array.isArray(tooltipSource) + ? tooltipSource?.[row]?.[id] + : tooltipSource?.[id]?.[row]) || resolvedStaticTooltip; const conditionalTooltips = header ? [] diff --git a/tests/selenium/test_tooltip.py b/tests/selenium/test_tooltip.py index 965b13702..479d0df5e 100644 --- a/tests/selenium/test_tooltip.py +++ b/tests/selenium/test_tooltip.py @@ -141,7 +141,14 @@ def test_ttip003_tooltip_disappears(test): wait.until(lambda: not tooltip.exists(), 2.5) -def test_ttip004_tooltip_applied(test): +@pytest.mark.parametrize( + "tooltip_data,tooltip_header", + [ + ([{"1": "alt-text1"}], [{}, {"1": "alt-header1-row1"}]), + ({"1": ["alt-text1"]}, {"1": [None, "alt-header1-row1"]}), + ], +) +def test_ttip004_tooltip_applied(test, tooltip_data, tooltip_header): props = { **base_props, "columns": [ @@ -159,8 +166,8 @@ def test_ttip004_tooltip_applied(test): "2": {"use_with": "data", "value": "text2"}, "3": {"use_with": "header", "value": "text3"}, }, - "tooltip_data": [{"1": "alt-text1"}], - "tooltip_header": [{}, {"1": "alt-header1-row1"}], + "tooltip_data": tooltip_data, + "tooltip_header": tooltip_header, } app = dash.Dash(__name__) From e4433595a01388746905fee36f48d0e239ab050a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 29 Sep 2020 11:05:26 -0400 Subject: [PATCH 06/10] - default tooltip_header value - updated prop description --- src/dash-table/dash/DataTable.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 06d4e9a84..2374ea682 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -77,6 +77,7 @@ export const defaultProps = { tooltip: {}, tooltip_conditional: [], tooltip_data: [], + tooltip_header: [], tooltip_delay: 350, tooltip_duration: 2000, @@ -866,6 +867,7 @@ export const propTypes = { delay: PropTypes.number, duration: PropTypes.number, type: PropTypes.oneOf(['text', 'markdown']), + use_with: PropTypes.oneOf(['both', 'data', 'header']), value: PropTypes.string.isRequired }) ]) @@ -925,9 +927,11 @@ export const propTypes = { /** * `tooltip_data` represents the tooltip shown * for different columns and cells. - * The `property` name refers to the column ID. Each property - * contains a list of tooltips mapped to the source `data` - * row index. + * Can be either an array of objects for which each key is + * a column id and the value a tooltip configuration, or it + * can be an object for which each key us a column id and the + * value an array of tooltip configurations. + * For each tooltip configuration, * The `type` refers to the type of tooltip syntax used * for the tooltip generation. Can either be `markdown` * or `text`. Defaults to `text`. @@ -942,7 +946,7 @@ export const propTypes = { * This overrides the table's `tooltip_duration` property. * If set to `null`, the tooltip will not disappear. * Alternatively, the value of the property can also be - * a plain string. The `text` syntax will be used in + * a plain string or a nully value to . The `text` syntax will be used in * that case. */ tooltip_data: PropTypes.oneOf([ @@ -978,10 +982,12 @@ export const propTypes = { /** * `tooltip_header` represents the tooltip shown - * for different header columns and cells. - * The `property` name refers to the column ID. Each property - * contains a list of tooltips mapped to the table's `header` - * row index. + * for different columns and cells. + * Can be either an array of objects for which each key is + * a column id and the value a tooltip configuration, or it + * can be an object for which each key us a column id and the + * value an array of tooltip configurations. + * For each tooltip configuration, * The `type` refers to the type of tooltip syntax used * for the tooltip generation. Can either be `markdown` * or `text`. Defaults to `text`. @@ -996,7 +1002,7 @@ export const propTypes = { * This overrides the table's `tooltip_duration` property. * If set to `null`, the tooltip will not disappear. * Alternatively, the value of the property can also be - * a plain string. The `text` syntax will be used in + * a plain string or a nully value to . The `text` syntax will be used in * that case. */ tooltip_header: PropTypes.oneOf([ From fdaabb5fb8da247d8c2d81316cc23c46d3793e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 29 Sep 2020 13:48:34 -0400 Subject: [PATCH 07/10] no nully --- src/dash-table/dash/DataTable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 2374ea682..aa54a34f4 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -861,7 +861,6 @@ export const propTypes = { */ tooltip: PropTypes.objectOf( PropTypes.oneOfType([ - PropTypes.nully, PropTypes.string, PropTypes.exact({ delay: PropTypes.number, From 23afc5a83583e809a601a8ead2325d2378bac4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Mon, 5 Oct 2020 14:00:21 -0400 Subject: [PATCH 08/10] update props & test --- src/dash-table/components/Table/props.ts | 19 ++++-- src/dash-table/dash/DataTable.js | 86 +++++++++--------------- src/dash-table/derived/table/tooltip.ts | 59 +++++++++------- tests/selenium/test_tooltip.py | 41 ++++++++--- 4 files changed, 111 insertions(+), 94 deletions(-) diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index 5879858c1..ebe56ec7b 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -209,9 +209,8 @@ export interface ILoadingState { export type ConditionalDropdowns = IConditionalDropdown[]; export type DataDropdowns = Partial[]; -export type DataTooltips = - | {[key: string]: Partial}[] - | {[key: string]: Partial[]}; +export type DataTooltips = Partial[]; + export type StaticDropdowns = Partial; export type Fixed = {headers: false; data?: 0} | {headers: true; data?: number}; @@ -248,6 +247,14 @@ export interface IStaticDropdowns { [key: string]: IDropdown; } +export interface ITableDataTooltips { + [key: string]: Tooltip; +} + +export interface ITableHeaderTooltips { + [key: string]: (Tooltip | null)[] | Tooltip; +} + export interface ITableStaticTooltips { [key: string]: Tooltip; } @@ -307,7 +314,7 @@ export interface IProps { tooltip_data?: DataTooltips; tooltip_delay: number | null; tooltip_duration: number | null; - tooltip_header?: DataTooltips; + tooltip_header?: ITableHeaderTooltips; active_cell?: ICellCoordinates; cell_selectable?: boolean; @@ -393,7 +400,7 @@ interface IDefaultProps { start_cell: ICellCoordinates; style_as_list_view: boolean; tooltip_data: DataTooltips; - tooltip_header: DataTooltips; + tooltip_header: ITableHeaderTooltips; page_action: TableAction; page_current: number; @@ -529,7 +536,7 @@ export interface ICellFactoryProps { style_header_conditional: Headers; style_table: Table; tooltip_data: DataTooltips; - tooltip_header: DataTooltips; + tooltip_header: ITableHeaderTooltips; uiCell?: IUserInterfaceCell; uiViewport?: IUserInterfaceViewport; viewport: IDerivedData; diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index aa54a34f4..bf52897b2 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -77,7 +77,7 @@ export const defaultProps = { tooltip: {}, tooltip_conditional: [], tooltip_data: [], - tooltip_header: [], + tooltip_header: {}, tooltip_delay: 350, tooltip_duration: 2000, @@ -926,10 +926,8 @@ export const propTypes = { /** * `tooltip_data` represents the tooltip shown * for different columns and cells. - * Can be either an array of objects for which each key is - * a column id and the value a tooltip configuration, or it - * can be an object for which each key us a column id and the - * value an array of tooltip configurations. + * A list of objects for which each key is + * a column id and the value a tooltip configuration. * For each tooltip configuration, * The `type` refers to the type of tooltip syntax used * for the tooltip generation. Can either be `markdown` @@ -945,47 +943,30 @@ export const propTypes = { * This overrides the table's `tooltip_duration` property. * If set to `null`, the tooltip will not disappear. * Alternatively, the value of the property can also be - * a plain string or a nully value to . The `text` syntax will be used in + * a plain string. The `text` syntax will be used in * that case. */ - tooltip_data: PropTypes.oneOf([ - PropTypes.arrayOf( - PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.nully, - PropTypes.string, - PropTypes.exact({ - delay: PropTypes.number, - duration: PropTypes.number, - type: PropTypes.oneOf(['text', 'markdown']), - value: PropTypes.string.isRequired - }) - ]) - ) - ), + tooltip_data: PropTypes.arrayOf( PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.nully, - PropTypes.string, - PropTypes.exact({ - delay: PropTypes.number, - duration: PropTypes.number, - type: PropTypes.oneOf(['text', 'markdown']), - value: PropTypes.string.isRequired - }) - ]) - ) + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }) + ]) ) - ]), + ), /** * `tooltip_header` represents the tooltip shown * for different columns and cells. - * Can be either an array of objects for which each key is - * a column id and the value a tooltip configuration, or it - * can be an object for which each key us a column id and the - * value an array of tooltip configurations. + * An object for which each key is a column id and the value + * a list of tooltip configurations or a toolttip configuration. + * If the value is tooltip configuration, the same tooltip will + * be used for all header rows of the corresponding column. * For each tooltip configuration, * The `type` refers to the type of tooltip syntax used * for the tooltip generation. Can either be `markdown` @@ -1004,22 +985,15 @@ export const propTypes = { * a plain string or a nully value to . The `text` syntax will be used in * that case. */ - tooltip_header: PropTypes.oneOf([ - PropTypes.arrayOf( - PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.nully, - PropTypes.string, - PropTypes.exact({ - delay: PropTypes.number, - duration: PropTypes.number, - type: PropTypes.oneOf(['text', 'markdown']), - value: PropTypes.string.isRequired - }) - ]) - ) - ), - PropTypes.objectOf( + tooltip_header: PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.exact({ + delay: PropTypes.number, + duration: PropTypes.number, + type: PropTypes.oneOf(['text', 'markdown']), + value: PropTypes.string.isRequired + }), PropTypes.arrayOf( PropTypes.oneOfType([ PropTypes.nully, @@ -1032,8 +1006,8 @@ export const propTypes = { }) ]) ) - ) - ]), + ]) + ), /** * `tooltip_delay` represents the table-wide delay in milliseconds before diff --git a/src/dash-table/derived/table/tooltip.ts b/src/dash-table/derived/table/tooltip.ts index 90ab871e8..d515e05c0 100644 --- a/src/dash-table/derived/table/tooltip.ts +++ b/src/dash-table/derived/table/tooltip.ts @@ -2,6 +2,7 @@ import * as R from 'ramda'; import { IUSerInterfaceTooltip, + ITableHeaderTooltips, ITableStaticTooltips, IVirtualizedDerivedData, DataTooltips @@ -10,7 +11,8 @@ import {ifColumnId, ifRowIndex, ifFilter} from 'dash-table/conditional'; import { ConditionalTooltip, TooltipUsage, - TooltipSyntax + TooltipSyntax, + Tooltip } from 'dash-table/tooltips/props'; import {memoizeOne} from 'core/memoizer'; @@ -20,7 +22,7 @@ export const MAX_32BITS = 2147483647; function getSelectedTooltip( currentTooltip: IUSerInterfaceTooltip, tooltip_data: DataTooltips, - tooltip_header: DataTooltips, + tooltip_header: ITableHeaderTooltips, tooltip_conditional: ConditionalTooltip[], tooltip_static: ITableStaticTooltips, virtualized: IVirtualizedDerivedData @@ -31,27 +33,9 @@ function getSelectedTooltip( const {header, id, row} = currentTooltip; - const tooltipSource = header ? tooltip_header : tooltip_data; - if (id === undefined || row === undefined) { return undefined; } - - const staticTooltip = tooltip_static?.[id]; - const staticUseWith = - staticTooltip && typeof staticTooltip !== 'string' - ? staticTooltip.use_with - : TooltipUsage.Both; - const staticApplicable = - staticUseWith === TooltipUsage.Both || - (staticUseWith === TooltipUsage.Header) === header; - const resolvedStaticTooltip = staticApplicable ? staticTooltip : undefined; - - const appliedStaticTooltip = - (Array.isArray(tooltipSource) - ? tooltipSource?.[row]?.[id] - : tooltipSource?.[id]?.[row]) || resolvedStaticTooltip; - const conditionalTooltips = header ? [] : R.filter(tt => { @@ -66,9 +50,36 @@ function getSelectedTooltip( ); }, tooltip_conditional); - return conditionalTooltips.length - ? conditionalTooltips.slice(-1)[0] - : appliedStaticTooltip; + if (conditionalTooltips.length) { + return conditionalTooltips.slice(-1)[0]; + } + + let tooltip: Tooltip | null | undefined; + + if (header) { + const headerTooltip = tooltip_header?.[id]; + tooltip = Array.isArray(headerTooltip) + ? headerTooltip?.[row] + : headerTooltip; + } else { + tooltip = tooltip_data?.[row]?.[id]; + } + + if (tooltip) { + return tooltip; + } + + const staticTooltip = tooltip_static?.[id]; + const staticUseWith = + staticTooltip && typeof staticTooltip !== 'string' + ? staticTooltip.use_with + : TooltipUsage.Both; + const staticApplicable = + staticUseWith === TooltipUsage.Both || + (staticUseWith === TooltipUsage.Header) === header; + const resolvedStaticTooltip = staticApplicable ? staticTooltip : undefined; + + return resolvedStaticTooltip; } function convertDelay(delay: number | null) { @@ -95,7 +106,7 @@ export default memoizeOne( ( currentTooltip: IUSerInterfaceTooltip, tooltip_data: DataTooltips, - tooltip_header: DataTooltips, + tooltip_header: ITableHeaderTooltips, tooltip_conditional: ConditionalTooltip[], tooltip_static: ITableStaticTooltips, virtualized: IVirtualizedDerivedData, diff --git a/tests/selenium/test_tooltip.py b/tests/selenium/test_tooltip.py index 479d0df5e..3017d8a3b 100644 --- a/tests/selenium/test_tooltip.py +++ b/tests/selenium/test_tooltip.py @@ -141,14 +141,39 @@ def test_ttip003_tooltip_disappears(test): wait.until(lambda: not tooltip.exists(), 2.5) +ttip004_tooltip = { + "1": "text1", + "2": {"use_with": "data", "value": "text2"}, + "3": {"use_with": "header", "value": "text3"}, +} + + @pytest.mark.parametrize( - "tooltip_data,tooltip_header", + "tooltip_data,tooltip_header,data_expected,header_expected", [ - ([{"1": "alt-text1"}], [{}, {"1": "alt-header1-row1"}]), - ({"1": ["alt-text1"]}, {"1": [None, "alt-header1-row1"]}), + ( + [{"1": "alt-text1"}], + {"1": ["alt-header1-row1", "alt-header1-row2"]}, + ["alt-text1", "text1"], + ["alt-header1-row1", "alt-header1-row2"], + ), + ( + [{"1": "alt-text1"}], + {"1": [None, "alt-header1-row1"]}, + ["alt-text1", "text1"], + ["text1", "alt-header1-row1"], + ), + ( + [{"1": "alt-text1"}, {"1": "alt-text2"}], + {"1": "alt-header1-row1"}, + ["alt-text1", "alt-text2"], + ["alt-header1-row1", "alt-header1-row1"], + ), ], ) -def test_ttip004_tooltip_applied(test, tooltip_data, tooltip_header): +def test_ttip004_tooltip_applied( + test, tooltip_data, tooltip_header, data_expected, header_expected +): props = { **base_props, "columns": [ @@ -183,12 +208,12 @@ def test_ttip004_tooltip_applied(test, tooltip_data, tooltip_header): target.cell(0, "1").move_to() wait.until(lambda: target.tooltip.exists(), 3) - assert tooltip.get_text().strip() == "alt-text1" + assert tooltip.get_text().strip() == data_expected[0] cell55.move_to() target.cell(1, "1").move_to() wait.until(lambda: target.tooltip.exists(), 3) - assert tooltip.get_text().strip() == "text1" + assert tooltip.get_text().strip() == data_expected[1] cell55.move_to() target.cell(0, "2").move_to() @@ -203,12 +228,12 @@ def test_ttip004_tooltip_applied(test, tooltip_data, tooltip_header): target.column("1").move_to(0) wait.until(lambda: target.tooltip.exists(), 3) - assert tooltip.get_text().strip() == "text1" + assert tooltip.get_text().strip() == header_expected[0] cell55.move_to() target.column("1").move_to(1) wait.until(lambda: target.tooltip.exists(), 3) - assert tooltip.get_text().strip() == "alt-header1-row1" + assert tooltip.get_text().strip() == header_expected[1] cell55.move_to() target.column("2").move_to(1) From a80e391b5bfe12f756d626b40d54e529f14c03b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 6 Oct 2020 16:46:26 -0400 Subject: [PATCH 09/10] trigger build From 455db0a845ff3906ddbbede7514d9ca6bb40c7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 8 Oct 2020 14:19:39 -0400 Subject: [PATCH 10/10] R.last R.findLast --- src/dash-table/derived/table/tooltip.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dash-table/derived/table/tooltip.ts b/src/dash-table/derived/table/tooltip.ts index d515e05c0..54620dea9 100644 --- a/src/dash-table/derived/table/tooltip.ts +++ b/src/dash-table/derived/table/tooltip.ts @@ -37,8 +37,8 @@ function getSelectedTooltip( return undefined; } const conditionalTooltips = header - ? [] - : R.filter(tt => { + ? undefined + : R.findLast(tt => { return ( !tt.if || (ifColumnId(tt.if, id) && @@ -50,8 +50,8 @@ function getSelectedTooltip( ); }, tooltip_conditional); - if (conditionalTooltips.length) { - return conditionalTooltips.slice(-1)[0]; + if (conditionalTooltips) { + return conditionalTooltips; } let tooltip: Tooltip | null | undefined;