Skip to content

Commit

Permalink
fix: align tooltip z-index to EUI tooltip z-index (#931)
Browse files Browse the repository at this point in the history
This commit moves the z-index value to display the tooltip below the current navigation bars but above the rest of the UI in Kibana

Co-authored-by: nickofthyme <nick.ryan.partridge@gmail.com>
  • Loading branch information
markov00 and nickofthyme authored Jan 6, 2021
1 parent f9218ad commit ffd626b
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 23 deletions.
6 changes: 6 additions & 0 deletions .storybook/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ html {
background-color: white;
}

#root {
z-index: 200;
position: relative;
}

.story-chart {
box-sizing: border-box;
background: white;
Expand All @@ -23,6 +28,7 @@ html {
width: 100%;
height: 400px;
position: relative;
z-index: 500;
box-sizing: border-box;
background-color: blanchedalmond;

Expand Down
2 changes: 2 additions & 0 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ export interface BubbleSeriesStyle {
export class Chart extends React.Component<ChartProps, ChartState> {
constructor(props: ChartProps);
// (undocumented)
componentDidMount(): void;
// (undocumented)
componentWillUnmount(): void;
// (undocumented)
static defaultProps: ChartProps;
Expand Down
4 changes: 0 additions & 4 deletions src/chart_types/xy_chart/renderer/dom/_crosshair.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@
position: absolute;
pointer-events: none;
}

.echCrosshair__line {
z-index: $euiZLevel8;
}
1 change: 0 additions & 1 deletion src/chart_types/xy_chart/renderer/dom/_highlighter.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.echHighlighter {
position: absolute;
z-index: 1000;
pointer-events: none;
top: 0;
bottom: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ interface AnnotationTooltipProps {
state: AnnotationTooltipState | null;
chartRef: RefObject<HTMLDivElement>;
chartId: string;
zIndex: number;
onScroll?: () => void;
}

/** @internal */
export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: AnnotationTooltipProps) => {
export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll, zIndex }: AnnotationTooltipProps) => {
const renderTooltip = useCallback(() => {
if (!state || !state.isVisible) {
return null;
Expand Down Expand Up @@ -77,6 +78,8 @@ export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: Annota
<TooltipPortal
scope="AnnotationTooltip"
chartId={chartId}
// increasing by 100 the tooltip portal zIndex to avoid conflicts with highlighters and other elements in the DOM
zIndex={zIndex + 100}
anchor={{
position,
ref: chartRef.current,
Expand Down
17 changes: 14 additions & 3 deletions src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface AnnotationsStateProps {
annotationDimensions: Map<AnnotationId, AnnotationDimensions>;
annotationSpecs: AnnotationSpec[];
chartId: string;
zIndex: number;
}

interface AnnotationsOwnProps {
Expand Down Expand Up @@ -93,6 +94,7 @@ const AnnotationsComponent = ({
annotationDimensions,
getChartContainerRef,
chartId,
zIndex,
onPointerMove,
}: AnnotationsProps) => {
const renderAnnotationMarkers = useCallback((): JSX.Element[] => {
Expand Down Expand Up @@ -125,7 +127,13 @@ const AnnotationsComponent = ({
return (
<>
{renderAnnotationMarkers()}
<AnnotationTooltip chartId={chartId} state={tooltipState} chartRef={getChartContainerRef()} onScroll={onScroll} />
<AnnotationTooltip
chartId={chartId}
zIndex={zIndex}
state={tooltipState}
chartRef={getChartContainerRef()}
onScroll={onScroll}
/>
</>
);
};
Expand All @@ -136,14 +144,16 @@ const mapDispatchToProps = (dispatch: Dispatch): AnnotationsDispatchProps =>
bindActionCreators({ onPointerMove: onPointerMoveAction }, dispatch);

const mapStateToProps = (state: GlobalChartState): AnnotationsStateProps => {
const { zIndex, chartId } = state;
if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) {
return {
isChartEmpty: true,
chartDimensions: { top: 0, left: 0, width: 0, height: 0 },
annotationDimensions: new Map(),
annotationSpecs: [],
tooltipState: null,
chartId: '',
chartId,
zIndex,
};
}
return {
Expand All @@ -152,7 +162,8 @@ const mapStateToProps = (state: GlobalChartState): AnnotationsStateProps => {
annotationDimensions: computeAnnotationDimensionsSelector(state),
annotationSpecs: getAnnotationSpecsSelector(state),
tooltipState: getAnnotationTooltipStateSelector(state),
chartId: state.chartId,
chartId,
zIndex,
};
};

Expand Down
6 changes: 6 additions & 0 deletions src/chart_types/xy_chart/renderer/dom/crosshair.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface CrosshairProps {
cursorLinePosition?: Dimensions;
tooltipType: TooltipType;
fromExternalEvent?: boolean;
zIndex: number;
}

function canRenderBand(type: TooltipType, visible: boolean, fromExternalEvent?: boolean) {
Expand Down Expand Up @@ -84,6 +85,7 @@ class CrosshairComponent extends React.Component<CrosshairProps> {
cursorLinePosition,
tooltipType,
chartRotation,
zIndex,
} = this.props;

if (!cursorLinePosition || !canRenderHelpLine(tooltipType, line.visible)) {
Expand All @@ -97,13 +99,15 @@ class CrosshairComponent extends React.Component<CrosshairProps> {
borderTopWidth: line.strokeWidth,
borderTopColor: line.stroke,
borderTopStyle: line.dash ? 'dashed' : 'solid',
zIndex,
};
} else {
style = {
...cursorLinePosition,
borderLeftWidth: line.strokeWidth,
borderLeftColor: line.stroke,
borderLeftStyle: line.dash ? 'dashed' : 'solid',
zIndex,
};
}
return <div className="echCrosshair__line" style={style} />;
Expand All @@ -125,6 +129,7 @@ const mapStateToProps = (state: GlobalChartState): CrosshairProps => {
theme: LIGHT_THEME,
chartRotation: 0,
tooltipType: TooltipType.None,
zIndex: 0,
};
}
const settings = getSettingsSpecSelector(state);
Expand All @@ -138,6 +143,7 @@ const mapStateToProps = (state: GlobalChartState): CrosshairProps => {
cursorLinePosition: getCursorLinePositionSelector(state),
tooltipType,
fromExternalEvent: cursorBandPosition?.fromExternalEvent,
zIndex: state.zIndex,
};
};

Expand Down
13 changes: 9 additions & 4 deletions src/chart_types/xy_chart/renderer/dom/highlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { computeChartTransform } from '../../state/utils/utils';
interface HighlighterProps {
initialized: boolean;
chartId: string;
zIndex: number;
highlightedGeometries: IndexedGeometry[];
chartTransform: Transform;
chartDimensions: Dimensions;
Expand All @@ -51,12 +52,12 @@ class HighlighterComponent extends React.Component<HighlighterProps> {
static displayName = 'Highlighter';

render() {
const { highlightedGeometries, chartDimensions, chartRotation, chartId } = this.props;
const { highlightedGeometries, chartDimensions, chartRotation, chartId, zIndex } = this.props;
const clipWidth = [90, -90].includes(chartRotation) ? chartDimensions.height : chartDimensions.width;
const clipHeight = [90, -90].includes(chartRotation) ? chartDimensions.width : chartDimensions.height;
const clipPathId = `echHighlighterClipPath__${chartId}`;
return (
<svg className="echHighlighter">
<svg className="echHighlighter" style={{ zIndex }}>
<defs>
<clipPath id={clipPathId}>
<rect x="0" y="0" width={clipWidth} height={clipHeight} />
Expand Down Expand Up @@ -104,10 +105,12 @@ class HighlighterComponent extends React.Component<HighlighterProps> {
}

const mapStateToProps = (state: GlobalChartState): HighlighterProps => {
const { chartId, zIndex } = state;
if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) {
return {
initialized: false,
chartId: state.chartId,
chartId,
zIndex,
highlightedGeometries: [],
chartTransform: {
x: 0,
Expand All @@ -118,9 +121,11 @@ const mapStateToProps = (state: GlobalChartState): HighlighterProps => {
chartRotation: 0,
};
}

return {
initialized: true,
chartId: state.chartId,
chartId,
zIndex,
highlightedGeometries: getHighlightedGeomsSelector(state),
chartTransform: computeChartTransformSelector(state),
chartDimensions: computeChartDimensionsSelector(state).chartDimensions,
Expand Down
2 changes: 1 addition & 1 deletion src/components/__snapshots__/chart.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Chart should render the legend name test 1`] = `"<div class=\\"echChart\\" style=\\"width: 100px; height: 100px;\\"><div class=\\"echChartBackground\\" style=\\"background-color: transparent;\\"></div><div class=\\"echChartStatus\\" data-ech-render-complete=\\"true\\" data-ech-render-count=\\"1\\"></div><div class=\\"echChartResizer\\"></div><div class=\\"echLegend echLegend--right echLegend--debug\\"><div style=\\"width: 50px; max-width: 50px; margin-left: 0px; margin-right: 0px;\\" class=\\"echLegendListContainer\\"><ul style=\\"padding-top: 10px; padding-bottom: 10px;\\" class=\\"echLegendList\\"><li class=\\"echLegendItem echLegendItem--right\\" data-ech-series-name=\\"test\\"><div class=\\"echLegendItem__color\\" title=\\"series color\\"><svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"16\\" height=\\"16\\" class=\\"echIcon\\" color=\\"#1EA593\\" focusable=\\"false\\" aria-label=\\"series color: #1EA593\\"><defs><circle id=\\"dot-a\\" cx=\\"8\\" cy=\\"8\\" r=\\"4\\"></circle></defs><g><use xlink:href=\\"#dot-a\\"></use></g></svg></div><button type=\\"button\\" class=\\"echLegendItem__label echLegendItem__label--clickable\\" title=\\"test\\" aria-label=\\"test; Activate to hide series in graph\\">test</button></li></ul></div></div><div class=\\"echContainer\\"><div class=\\"echChartPointerContainer\\" style=\\"cursor: default;\\"><div class=\\"echCrosshair\\"><div class=\\"echCrosshair__band\\" style=\\"top: -1px; left: -1px; width: 0px; height: 0px; background: rgb(245, 245, 245);\\"></div></div><canvas class=\\"echCanvasRenderer\\" width=\\"150\\" height=\\"200\\" style=\\"width: 150px; height: 200px;\\"></canvas><svg class=\\"echHighlighter\\"><defs><clipPath id=\\"echHighlighterClipPath__chart1\\"><rect x=\\"0\\" y=\\"0\\" width=\\"130\\" height=\\"180\\"></rect></clipPath></defs><g></g></svg></div></div></div>"`;
exports[`Chart should render the legend name test 1`] = `"<div class=\\"echChart\\" style=\\"width: 100px; height: 100px;\\"><div class=\\"echChartBackground\\" style=\\"background-color: transparent;\\"></div><div class=\\"echChartStatus\\" data-ech-render-complete=\\"true\\" data-ech-render-count=\\"1\\"></div><div class=\\"echChartResizer\\"></div><div class=\\"echLegend echLegend--right echLegend--debug\\"><div style=\\"width: 50px; max-width: 50px; margin-left: 0px; margin-right: 0px;\\" class=\\"echLegendListContainer\\"><ul style=\\"padding-top: 10px; padding-bottom: 10px;\\" class=\\"echLegendList\\"><li class=\\"echLegendItem echLegendItem--right\\" data-ech-series-name=\\"test\\"><div class=\\"echLegendItem__color\\" title=\\"series color\\"><svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"16\\" height=\\"16\\" class=\\"echIcon\\" color=\\"#1EA593\\" focusable=\\"false\\" aria-label=\\"series color: #1EA593\\"><defs><circle id=\\"dot-a\\" cx=\\"8\\" cy=\\"8\\" r=\\"4\\"></circle></defs><g><use xlink:href=\\"#dot-a\\"></use></g></svg></div><button type=\\"button\\" class=\\"echLegendItem__label echLegendItem__label--clickable\\" title=\\"test\\" aria-label=\\"test; Activate to hide series in graph\\">test</button></li></ul></div></div><div class=\\"echContainer\\"><div class=\\"echChartPointerContainer\\" style=\\"cursor: default;\\"><div class=\\"echCrosshair\\"><div class=\\"echCrosshair__band\\" style=\\"top: -1px; left: -1px; width: 0px; height: 0px; background: rgb(245, 245, 245);\\"></div></div><canvas class=\\"echCanvasRenderer\\" width=\\"150\\" height=\\"200\\" style=\\"width: 150px; height: 200px;\\"></canvas><svg class=\\"echHighlighter\\" style=\\"z-index: 0;\\"><defs><clipPath id=\\"echHighlighterClipPath__chart1\\"><rect x=\\"0\\" y=\\"0\\" width=\\"130\\" height=\\"180\\"></rect></clipPath></defs><g></g></svg></div></div></div>"`;
9 changes: 9 additions & 0 deletions src/components/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { isHorizontalAxis } from '../chart_types/xy_chart/utils/axis_type_utils'
import { PointerEvent } from '../specs';
import { SpecsParser } from '../specs/specs_parser';
import { onExternalPointerEvent } from '../state/actions/events';
import { onComputedZIndex } from '../state/actions/z_index';
import { chartStoreReducer, GlobalChartState } from '../state/chart_state';
import { getInternalIsInitializedSelector, InitStatus } from '../state/selectors/get_internal_is_intialized';
import { getSettingsSpecSelector } from '../state/selectors/get_settings_specs';
Expand All @@ -38,6 +39,7 @@ import { ChartResizer } from './chart_resizer';
import { ChartStatus } from './chart_status';
import { ErrorBoundary } from './error_boundary';
import { Legend } from './legend/legend';
import { getElementZIndex } from './portal/utils';

interface ChartProps {
/**
Expand Down Expand Up @@ -108,6 +110,13 @@ export class Chart extends React.Component<ChartProps, ChartState> {
});
}

componentDidMount() {
if (this.chartContainerRef.current) {
const zIndex = getElementZIndex(this.chartContainerRef.current, document.body);
this.chartStore.dispatch(onComputedZIndex(zIndex));
}
}

componentWillUnmount() {
this.unsubscribeToStore();
}
Expand Down
1 change: 0 additions & 1 deletion src/components/portal/_portal.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[id^='echTooltipPortal'] {
pointer-events: none;
z-index: 10000000;
}

[id^='echAnchor'] {
Expand Down
18 changes: 16 additions & 2 deletions src/components/portal/tooltip_portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { DEFAULT_POPPER_SETTINGS, getOrCreateNode, isHTMLElement } from './utils
* @todo make this type conditional to use PortalAnchorProps or PortalAnchorRefProps
*/
type PortalTooltipProps = {
zIndex: number;
/**
* String used to designate unique portal
*/
Expand All @@ -55,7 +56,15 @@ type PortalTooltipProps = {
chartId: string;
};

const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, chartId }: PortalTooltipProps) => {
const TooltipPortalComponent = ({
anchor,
scope,
settings,
children,
visible,
chartId,
zIndex,
}: PortalTooltipProps) => {
/**
* Anchor element used to position tooltip
*/
Expand All @@ -69,7 +78,12 @@ const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, ch
* This must not be removed from DOM throughout life of this component.
* Otherwise the portal will loose reference to the correct node.
*/
const portalNodeElement = getOrCreateNode(`echTooltipPortal${scope}__${chartId}`, 'echTooltipPortal__invisible');
const portalNodeElement = getOrCreateNode(
`echTooltipPortal${scope}__${chartId}`,
'echTooltipPortal__invisible',
undefined,
zIndex,
);

const portalNode = useRef(portalNodeElement);

Expand Down
72 changes: 71 additions & 1 deletion src/components/portal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export const DEFAULT_POPPER_SETTINGS: Required<TooltipPortalSettings, 'fallbackP
*
* @internal
*/
export function getOrCreateNode(id: string, className?: string, parent: HTMLElement = document.body): HTMLDivElement {
export function getOrCreateNode(
id: string,
className?: string,
parent: HTMLElement = document.body,
zIndex: number = 0,
): HTMLDivElement {
// eslint-disable-next-line unicorn/prefer-query-selector
const node = document.getElementById(id);
if (node) {
Expand All @@ -45,6 +50,7 @@ export function getOrCreateNode(id: string, className?: string, parent: HTMLElem
if (className) {
newNode.classList.add(className);
}
newNode.style.zIndex = `${zIndex}`;
parent.appendChild(newNode);
return newNode;
}
Expand All @@ -56,3 +62,67 @@ export function getOrCreateNode(id: string, className?: string, parent: HTMLElem
export function isHTMLElement(value: any): value is HTMLElement {
return typeof value === 'object' && value !== null && value.hasOwnProperty('nodeName');
}

/**
* Returns the top-most defined z-index in the element's ancestor hierarchy
* relative to the `target` element; if no z-index is defined, returns 0
* @param element {HTMLElement}
* @param cousin {HTMLElement}
* @returns {number}
*/
export function getElementZIndex(element: HTMLElement, cousin: HTMLElement): number {
/**
* finding the z-index of `element` is not the full story
* its the CSS stacking context that is important
* take this DOM for example:
* body
* section[z-index: 1000]
* p[z-index: 500]
* button
* div
*
* what z-index does the `div` need to display next to `button`?
* the `div` and `section` are where the stacking context splits
* so `div` needs to copy `section`'s z-index in order to
* appear next to / over `button`
*
* calculate this by starting at `button` and finding its offsetParents
* then walk the parents from top -> down until the stacking context
* split is found, or if there is no split then a specific z-index is unimportant
*/

// build the array of the element + its offset parents
const nodesToInspect: HTMLElement[] = [];
while (true) {
nodesToInspect.push(element);

// AFAICT this is a valid cast - the libdefs appear wrong
element = element.offsetParent as HTMLElement;

// stop if there is no parent
if (element == null) {
break;
}

// stop if the parent contains the related element
// as this is the z-index ancestor
if (element.contains(cousin)) {
break;
}
}

// reverse the nodes to walk from top -> element
for (let i = nodesToInspect.length - 1; i >= 0; i--) {
const node = nodesToInspect[i];
// get this node's z-index css value
const zIndex = window.document.defaultView!.getComputedStyle(node).getPropertyValue('z-index');

// if the z-index is not a number (e.g. "auto") return null, else the value
const parsedZIndex = parseInt(zIndex, 10);
if (!isNaN(parsedZIndex)) {
return parsedZIndex;
}
}

return 0;
}
Loading

0 comments on commit ffd626b

Please sign in to comment.