import { Color, ComponentId, AnyFill } from '../dataModels'; import { colorToCssString, fillToCss } from '../cssUtils'; import { ComponentBase, ComponentState } from './componentBase'; import { RippleEffect } from '../rippleEffect'; export type RectangleState = ComponentState & { _type_: 'Rectangle-builtin'; content?: ComponentId | null; transition_time?: number; cursor?: string; ripple?: boolean; fill?: AnyFill; stroke_color?: Color; stroke_width?: number; corner_radius?: [number, number, number, number]; shadow_color?: Color; shadow_radius?: number; shadow_offset_x?: number; shadow_offset_y?: number; hover_fill?: AnyFill | null; hover_stroke_color?: Color | null; hover_stroke_width?: number | null; hover_corner_radius?: [number, number, number, number] | null; hover_shadow_color?: Color | null; hover_shadow_radius?: number | null; hover_shadow_offset_x?: number | null; hover_shadow_offset_y?: number | null; }; function numberToRem(num: number): string { return `${num}rem`; } // Functions that convert an attribute of `RectangleState` to CSS. The return // value can either be a string or an object of `{cssProperty: cssValue}`. const JS_TO_CSS_VALUE: { [attr: string]: (value: any) => string | { [attr: string]: string }; } = { fill: fillToCss, stroke_color: colorToCssString, stroke_width: numberToRem, corner_radius: (radii: [number, number, number, number]) => radii.map((num) => `${num}rem`).join(' '), shadow_color: colorToCssString, shadow_radius: numberToRem, shadow_offset_x: numberToRem, shadow_offset_y: numberToRem, }; export class RectangleComponent extends ComponentBase { state: Required; // If this rectangle has a ripple effect, this is the ripple instance. // `null` otherwise. private rippleInstance: RippleEffect | null = null; createElement(): HTMLElement { let element = document.createElement('div'); element.classList.add('rio-rectangle'); return element; } updateElement( deltaState: RectangleState, latentComponents: Set ): void { super.updateElement(deltaState, latentComponents); this.replaceOnlyChild(latentComponents, deltaState.content); if (deltaState.transition_time !== undefined) { this.element.style.transitionDuration = `${deltaState.transition_time}s`; } if (deltaState.cursor !== undefined) { if (deltaState.cursor === 'default') { this.element.style.removeProperty('cursor'); } else { this.element.style.cursor = deltaState.cursor; } } if (deltaState.ripple === true) { if (this.rippleInstance === null) { this.rippleInstance = new RippleEffect(this.element); } } else if (deltaState.ripple === false) { if (this.rippleInstance !== null) { this.rippleInstance.destroy(); this.rippleInstance = null; } } // Apply all the styling properties for (let [attrName, js_to_css] of Object.entries(JS_TO_CSS_VALUE)) { let value = deltaState[attrName]; if (value !== undefined && value !== null) { let cssValues = js_to_css(value); if (typeof cssValues === 'string') { cssValues = { [attrName]: cssValues }; } for (let [prop, val] of Object.entries(cssValues)) { this.element.style.setProperty( `--rio-rectangle-${prop}`, val ); } } let hoverValue = deltaState['hover_' + attrName]; if (hoverValue !== undefined) { if (hoverValue === null) { // No hover value? Use the corresponding non-hover value if (value !== undefined && value !== null) { let cssValues = js_to_css(value); if (typeof cssValues === 'string') { cssValues = { [attrName]: cssValues }; } for (let [prop, val] of Object.entries(cssValues)) { this.element.style.setProperty( `--rio-rectangle-hover-${prop}`, `var(--rio-rectangle-${prop})` ); } } } else { let cssValues = js_to_css(hoverValue); if (typeof cssValues === 'string') { cssValues = { [attrName]: cssValues }; } for (let [prop, val] of Object.entries(cssValues)) { this.element.style.setProperty( `--rio-rectangle-hover-${prop}`, val ); } } } } } }