Skip to content

Commit

Permalink
feat: tooltip anchoring
Browse files Browse the repository at this point in the history
  • Loading branch information
skokenes committed Nov 15, 2024
1 parent 30bc5fd commit b6bec66
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 27 deletions.
5 changes: 2 additions & 3 deletions packages/malloy-render/src/component/chart/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import {Explore, ExploreField, QueryData} from '@malloydata/malloy';
import {VegaChart, ViewInterface} from '../vega/vega-chart';
import {ChartTooltipEntry, RenderResultMetadata} from '../types';
import {Tooltip} from '../tooltip/tooltip';
import {createEffect, createSignal, createMemo, lazy, Show} from 'solid-js';
import {createEffect, createSignal, createMemo, Show} from 'solid-js';
import {DefaultChartTooltip} from './default-chart-tooltip';
import {EventListenerHandler, Runtime, View} from 'vega';
import {useResultStore, VegaBrushOutput} from '../result-store/result-store';
import css from './chart.css?raw';
import {useConfig} from '../render';
import {DebugIcon} from './debug_icon';

const ChartDevTool = lazy(() => import('./chart-dev-tool'));
import ChartDevTool from './chart-dev-tool';

let IS_STORYBOOK = false;
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {ComponentOptions} from 'component-register';

export type MalloyModalWCProps = {
stylesheet?: CSSStyleSheet;
};

export function MalloyModalWC(
props: MalloyModalWCProps,
{element}: ComponentOptions
) {
const root = element.renderRoot;
if (root instanceof ShadowRoot && props.stylesheet) {
root.adoptedStyleSheets.push(props.stylesheet);
}

/*
Move child nodes into ShadowDOM. This probably only works if modal root elements stay consistent.
*/
const allChildren = [...element['childNodes']];
return <div>{...allChildren}</div>;
}
32 changes: 32 additions & 0 deletions packages/malloy-render/src/component/malloy-modal/malloy-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Portal} from 'solid-js/web';
import {JSX, JSXElement} from 'solid-js';
import {useConfig} from '../render';

export function MalloyModal(props: {
style?: string | JSX.CSSProperties;
children?: JSXElement;
ref?: HTMLDivElement | ((el: HTMLDivElement) => void);
}) {
const config = useConfig();
return (
<Portal mount={config.modalElement}>
<div ref={props.ref} style={props.style}>
<div>
<malloy-modal stylesheet={config.stylesheet}>
<div>{props.children}</div>
</malloy-modal>
</div>
</div>
</Portal>
);
}

declare module 'solid-js' {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
interface IntrinsicElements {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'malloy-modal': any;
}
}
}
19 changes: 19 additions & 0 deletions packages/malloy-render/src/component/register-webcomponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {compose, register} from 'component-register';
import {withSolid} from 'solid-element';
import {MalloyRender, MalloyRenderProps} from './render';
import {
MalloyModalWC,
MalloyModalWCProps,
} from './malloy-modal/malloy-modal-wc';

export default function registerWebComponent({
customElements = window.customElements,
Expand All @@ -19,6 +23,7 @@ export default function registerWebComponent({
vegaConfigOverride: undefined,
tableConfig: undefined,
dashboardConfig: undefined,
modalElement: undefined,
},
{customElements, BaseElement: HTMLElement}
),
Expand All @@ -30,10 +35,24 @@ export default function registerWebComponent({
"The custom element 'malloy-render' has already been defined. Make sure you are not loading multiple versions of the malloy-render package as they could conflict."
);
}

if (!customElements.get('malloy-modal')) {
compose(
register(
'malloy-modal',
{
stylesheet: undefined,
},
{customElements, BaseElement: HTMLElement}
),
withSolid
)(MalloyModalWC);
}
}

declare global {
interface HTMLElementTagNameMap {
'malloy-render': HTMLElement & MalloyRenderProps;
'malloy-modal': HTMLElement & MalloyModalWCProps;
}
}
60 changes: 39 additions & 21 deletions packages/malloy-render/src/component/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type MalloyRenderProps = {
queryResult?: QueryResult;
modelDef?: ModelDef;
scrollEl?: HTMLElement;
modalElement?: HTMLElement;
onClick?: (payload: MalloyClickEventPayload) => void;
vegaConfigOverride?: VegaConfigHandler;
tableConfig?: Partial<TableConfig>;
Expand All @@ -48,10 +49,12 @@ const ConfigContext = createContext<{
tableConfig: Accessor<TableConfig>;
dashboardConfig: Accessor<DashboardConfig>;
element: ICustomElement;
stylesheet: CSSStyleSheet;
addCSSToShadowRoot: (css: string) => void;
addCSSToDocument: (id: string, css: string) => void;
onClick?: (payload: MalloyClickEventPayload) => void;
vegaConfigOverride?: VegaConfigHandler;
modalElement?: HTMLElement;
}>();

export const useConfig = () => {
Expand All @@ -74,6 +77,12 @@ export function MalloyRender(
else return null;
});

// Create one stylesheet for web component to use for all styles
// This is so we can pass the stylesheet to other components to share, like <malloy-modal>
const stylesheet = new CSSStyleSheet();
if (element.renderRoot instanceof ShadowRoot)
element.renderRoot.adoptedStyleSheets.push(stylesheet);

const addedStylesheets = new Set();
function addCSSToShadowRoot(css: string) {
const root = element.renderRoot;
Expand All @@ -85,9 +94,13 @@ export function MalloyRender(
return;
}
if (!addedStylesheets.has(css)) {
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(css);
root.adoptedStyleSheets.push(stylesheet);
const newStyleSheetTexts: string[] = [];
for (let i = 0; i < stylesheet.cssRules.length; i++) {
const cssText = stylesheet.cssRules.item(i)?.cssText;
if (cssText) newStyleSheetTexts.push(cssText);
}
newStyleSheetTexts.push(css);
stylesheet.replaceSync(newStyleSheetTexts.join('\n'));
addedStylesheets.add(css);
}
}
Expand All @@ -100,6 +113,7 @@ export function MalloyRender(
document.head.appendChild(style);
}
}

addCSSToShadowRoot(css);

const tableConfig: Accessor<TableConfig> = () =>
Expand Down Expand Up @@ -127,10 +141,12 @@ export function MalloyRender(
onClick: props.onClick,
vegaConfigOverride: props.vegaConfigOverride,
element,
stylesheet,
addCSSToShadowRoot,
addCSSToDocument,
tableConfig,
dashboardConfig,
modalElement: props.modalElement,
}}
>
<MalloyRenderInner
Expand Down Expand Up @@ -168,12 +184,12 @@ export function MalloyRenderInner(props: {
};
};

const config = useConfig();

createEffect(() => {
if (props.element) {
const style = generateThemeStyle(tags().modelTheme, tags().localTheme);
for (const [key, value] of Object.entries(style)) {
props.element['style'].setProperty(key, value);
}
config.addCSSToShadowRoot(style);
}
});

Expand Down Expand Up @@ -219,8 +235,6 @@ function getThemeValue(prop: string, ...themes: Array<Tag | undefined>) {
}

function generateThemeStyle(modelTheme?: Tag, localTheme?: Tag) {
const style: Record<string, string> = {};

const tableRowHeight = getThemeValue(
'tableRowHeight',
localTheme,
Expand Down Expand Up @@ -270,17 +284,21 @@ function generateThemeStyle(modelTheme?: Tag, localTheme?: Tag) {
);
const fontFamily = getThemeValue('fontFamily', localTheme, modelTheme);

style['--malloy-render--table-row-height'] = tableRowHeight;
style['--malloy-render--table-body-color'] = tableBodyColor;
style['--malloy-render--table-font-size'] = tableFontSize;
style['--malloy-render--font-family'] = fontFamily;
style['--malloy-render--table-header-color'] = tableHeaderColor;
style['--malloy-render--table-header-weight'] = tableHeaderWeight;
style['--malloy-render--table-body-weight'] = tableBodyWeight;
style['--malloy-render--table-border'] = tableBorder;
style['--malloy-render--table-background'] = tableBackground;
style['--malloy-render--table-gutter-size'] = tableGutterSize;
style['--malloy-render--table-pinned-background'] = tablePinnedBackground;
style['--malloy-render--table-pinned-border'] = tablePinnedBorder;
return style;
const css = `
:host {
--malloy-render--table-row-height: ${tableRowHeight};
--malloy-render--table-body-color: ${tableBodyColor};
--malloy-render--table-font-size: ${tableFontSize};
--malloy-render--font-family: ${fontFamily};
--malloy-render--table-header-color: ${tableHeaderColor};
--malloy-render--table-header-weight: ${tableHeaderWeight};
--malloy-render--table-body-weight: ${tableBodyWeight};
--malloy-render--table-border: ${tableBorder};
--malloy-render--table-background: ${tableBackground};
--malloy-render--table-gutter-size: ${tableGutterSize};
--malloy-render--table-pinned-background: ${tablePinnedBackground};
--malloy-render--table-pinned-border: ${tablePinnedBorder};
}
`;
return css;
}
6 changes: 3 additions & 3 deletions packages/malloy-render/src/component/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from 'solid-js';
import tooltipCss from './tooltip.css?raw';
import {useConfig} from '../render';
import {MalloyModal} from '../malloy-modal/malloy-modal';

export function Tooltip(props: {show: boolean; children: JSXElement}) {
const [pos, setPos] = createSignal<[number, number]>([0, 0]);
Expand Down Expand Up @@ -57,17 +58,16 @@ export function Tooltip(props: {show: boolean; children: JSXElement}) {
});
}
});

return (
<Show when={props.show}>
<div
<MalloyModal
ref={tip}
style={`position: fixed; top: ${pos()[1] + yOffset()}px; left: ${
pos()[0] + xOffset()
}px; pointer-events: none; z-index: 1000`}
>
<div class="malloy-tooltip">{props.children}</div>
</div>
</MalloyModal>
</Show>
);
}

0 comments on commit b6bec66

Please sign in to comment.