From b6bec664c8be0fcf37ead1621b217bde1cee3f7c Mon Sep 17 00:00:00 2001 From: Speros Kokenes Date: Thu, 14 Nov 2024 16:13:58 -0500 Subject: [PATCH] feat: tooltip anchoring --- .../src/component/chart/chart.tsx | 5 +- .../malloy-modal/malloy-modal-wc.tsx | 21 +++++++ .../component/malloy-modal/malloy-modal.tsx | 32 ++++++++++ .../src/component/register-webcomponent.ts | 19 ++++++ .../malloy-render/src/component/render.tsx | 60 ++++++++++++------- .../src/component/tooltip/tooltip.tsx | 6 +- 6 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 packages/malloy-render/src/component/malloy-modal/malloy-modal-wc.tsx create mode 100644 packages/malloy-render/src/component/malloy-modal/malloy-modal.tsx diff --git a/packages/malloy-render/src/component/chart/chart.tsx b/packages/malloy-render/src/component/chart/chart.tsx index ef00e0663..6868c7765 100644 --- a/packages/malloy-render/src/component/chart/chart.tsx +++ b/packages/malloy-render/src/component/chart/chart.tsx @@ -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 { diff --git a/packages/malloy-render/src/component/malloy-modal/malloy-modal-wc.tsx b/packages/malloy-render/src/component/malloy-modal/malloy-modal-wc.tsx new file mode 100644 index 000000000..0b25beef1 --- /dev/null +++ b/packages/malloy-render/src/component/malloy-modal/malloy-modal-wc.tsx @@ -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
{...allChildren}
; +} diff --git a/packages/malloy-render/src/component/malloy-modal/malloy-modal.tsx b/packages/malloy-render/src/component/malloy-modal/malloy-modal.tsx new file mode 100644 index 000000000..2fa51b28d --- /dev/null +++ b/packages/malloy-render/src/component/malloy-modal/malloy-modal.tsx @@ -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 ( + +
+
+ +
{props.children}
+
+
+
+
+ ); +} + +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; + } + } +} diff --git a/packages/malloy-render/src/component/register-webcomponent.ts b/packages/malloy-render/src/component/register-webcomponent.ts index 9cbb3dbf3..77a17ff2b 100644 --- a/packages/malloy-render/src/component/register-webcomponent.ts +++ b/packages/malloy-render/src/component/register-webcomponent.ts @@ -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, @@ -19,6 +23,7 @@ export default function registerWebComponent({ vegaConfigOverride: undefined, tableConfig: undefined, dashboardConfig: undefined, + modalElement: undefined, }, {customElements, BaseElement: HTMLElement} ), @@ -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; } } diff --git a/packages/malloy-render/src/component/render.tsx b/packages/malloy-render/src/component/render.tsx index 1a8fa603a..cc174588a 100644 --- a/packages/malloy-render/src/component/render.tsx +++ b/packages/malloy-render/src/component/render.tsx @@ -38,6 +38,7 @@ export type MalloyRenderProps = { queryResult?: QueryResult; modelDef?: ModelDef; scrollEl?: HTMLElement; + modalElement?: HTMLElement; onClick?: (payload: MalloyClickEventPayload) => void; vegaConfigOverride?: VegaConfigHandler; tableConfig?: Partial; @@ -48,10 +49,12 @@ const ConfigContext = createContext<{ tableConfig: Accessor; dashboardConfig: Accessor; 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 = () => { @@ -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 + 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; @@ -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); } } @@ -100,6 +113,7 @@ export function MalloyRender( document.head.appendChild(style); } } + addCSSToShadowRoot(css); const tableConfig: Accessor = () => @@ -127,10 +141,12 @@ export function MalloyRender( onClick: props.onClick, vegaConfigOverride: props.vegaConfigOverride, element, + stylesheet, addCSSToShadowRoot, addCSSToDocument, tableConfig, dashboardConfig, + modalElement: props.modalElement, }} > { 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); } }); @@ -219,8 +235,6 @@ function getThemeValue(prop: string, ...themes: Array) { } function generateThemeStyle(modelTheme?: Tag, localTheme?: Tag) { - const style: Record = {}; - const tableRowHeight = getThemeValue( 'tableRowHeight', localTheme, @@ -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; } diff --git a/packages/malloy-render/src/component/tooltip/tooltip.tsx b/packages/malloy-render/src/component/tooltip/tooltip.tsx index f9f453db2..ffdde91b0 100644 --- a/packages/malloy-render/src/component/tooltip/tooltip.tsx +++ b/packages/malloy-render/src/component/tooltip/tooltip.tsx @@ -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]); @@ -57,17 +58,16 @@ export function Tooltip(props: {show: boolean; children: JSXElement}) { }); } }); - return ( -
{props.children}
-
+
); }