diff --git a/src/managers/message-component-manager.ts b/src/managers/message-component-manager.ts index f0c55b9..eaef212 100644 --- a/src/managers/message-component-manager.ts +++ b/src/managers/message-component-manager.ts @@ -1,5 +1,6 @@ import Gist from '../gist'; import { log } from '../utilities/log'; +import { findElement } from '../utilities/dom'; import { resolveMessageProperties } from './gist-properties-manager'; import { embedHTMLTemplate } from '../templates/embed'; import { messageHTMLTemplate } from '../templates/message'; @@ -36,7 +37,7 @@ export function loadEmbedComponent( options: MessageOptions, stepName: string | null = null ): void { - const element = safelyFetchElement(elementId); + const element = findElement(elementId); if (element) { const messageElementId = getMessageElementId(message.instanceId ?? ''); element.classList.add(messageElementId); @@ -59,14 +60,14 @@ export function loadEmbedComponent( } export function showEmbedComponent(elementId: string): void { - const element = safelyFetchElement(elementId); + const element = findElement(elementId); if (element) { element.classList.add('gist-visible'); } } export function hideEmbedComponent(elementId: string): void { - const element = safelyFetchElement(elementId); + const element = findElement(elementId); if (element) { element.classList.remove('gist-visible'); const classesToRemove = Array.from(element.classList).filter((cls) => cls.startsWith('gist-')); @@ -78,7 +79,7 @@ export function hideEmbedComponent(elementId: string): void { } export function elementHasHeight(elementId: string): boolean | undefined { - const element = safelyFetchElement(elementId); + const element = findElement(elementId); if (element) { return !!(element.style && element.style.height && element.style.height !== '0px'); } @@ -91,7 +92,7 @@ export function resizeComponent( const elementId = message.elementId ? message.elementId : getMessageElementId(message.instanceId ?? ''); - const element = safelyFetchElement(elementId); + const element = findElement(elementId); if (element) { const style = element.style; if (size.height > 0) { @@ -256,7 +257,7 @@ export async function showTooltipComponent(message: GistMessage): Promise { it('returns null and logs warning for invalid selector', () => { expect(findTargetElement('[invalid')).toBeNull(); - expect(log).toHaveBeenCalledWith('Invalid selector for tooltip target: [invalid'); + expect(log).toHaveBeenCalledWith('Tooltip target element not found for selector: [invalid'); }); }); diff --git a/src/managers/tooltip-position-manager.ts b/src/managers/tooltip-position-manager.ts index 020e743..6d5d4e2 100644 --- a/src/managers/tooltip-position-manager.ts +++ b/src/managers/tooltip-position-manager.ts @@ -1,4 +1,5 @@ import { log } from '../utilities/log'; +import { findElement } from '../utilities/dom'; import { ARROW_SIZE } from '../templates/tooltip'; export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right'; @@ -23,16 +24,11 @@ const ARROW_CLASS_FOR_POSITION: Record = { }; export function findTargetElement(selector: string): Element | null { - try { - const element = document.querySelector(selector); - if (!element) { - log(`Tooltip target element not found for selector: ${selector}`); - } - return element; - } catch { - log(`Invalid selector for tooltip target: ${selector}`); - return null; + const element = findElement(selector); + if (!element) { + log(`Tooltip target element not found for selector: ${selector}`); } + return element; } function calculatePosition( diff --git a/src/types.ts b/src/types.ts index bdc77c9..c613ea1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,7 @@ export interface DisplaySettings { overlayPosition?: string; elementSelector?: string; tooltipPosition?: string; + tooltipArrowColor?: string; maxWidth?: number; overlayColor?: string; dismissOutsideClick?: boolean; diff --git a/src/utilities/dom.ts b/src/utilities/dom.ts new file mode 100644 index 0000000..84cd070 --- /dev/null +++ b/src/utilities/dom.ts @@ -0,0 +1,8 @@ +export function findElement(selector: string): HTMLElement | null { + try { + const element = document.getElementById(selector) ?? document.querySelector(selector); + return (element as HTMLElement) || null; + } catch { + return null; + } +} diff --git a/src/utilities/message-utils.test.ts b/src/utilities/message-utils.test.ts index 795bd7a..67d7548 100644 --- a/src/utilities/message-utils.test.ts +++ b/src/utilities/message-utils.test.ts @@ -65,6 +65,7 @@ vi.mock('../managers/gist-properties-manager', () => ({ persistent: false, exitClick: !!gist?.exitClick, hasCustomWidth: (gist?.messageWidth ?? 0) > 0, + tooltipArrowColor: gist?.tooltipArrowColor || '#fff', }; }), })); @@ -337,6 +338,22 @@ describe('hasDisplayChanged', () => { ).toBe(true); }); + it('returns true when tooltip arrow color changes', () => { + const msg = makeMessage({ + tooltipPosition: 'top', + elementId: 'my-element', + properties: { gist: { tooltipArrowColor: '#fff' } }, + }); + expect( + hasDisplayChanged(msg, { + displayType: 'tooltip', + tooltipPosition: 'top', + elementSelector: 'my-element', + tooltipArrowColor: '#FF5733', + }) + ).toBe(true); + }); + it('returns false when nothing changed for tooltip', () => { const msg = makeMessage({ tooltipPosition: 'top', elementId: 'my-element' }); expect( @@ -348,6 +365,21 @@ describe('hasDisplayChanged', () => { ).toBe(false); }); + it('returns false when tooltip arrow color is not in display settings', () => { + const msg = makeMessage({ + tooltipPosition: 'top', + elementId: 'my-element', + properties: { gist: { tooltipArrowColor: '#FF5733' } }, + }); + expect( + hasDisplayChanged(msg, { + displayType: 'tooltip', + tooltipPosition: 'top', + elementSelector: 'my-element', + }) + ).toBe(false); + }); + it('returns true when maxWidth changes', () => { const msg = makeMessage({ overlay: true, @@ -406,6 +438,32 @@ describe('applyDisplaySettings', () => { expect(msg.position).toBeNull(); }); + it('sets tooltipArrowColor for tooltip type when provided', () => { + const msg = makeMessage({ overlay: true }); + applyDisplaySettings(msg, { + displayType: 'tooltip', + elementSelector: 'my-element', + tooltipPosition: 'top', + tooltipArrowColor: '#FF5733', + }); + + expect(msg.properties?.gist?.tooltipArrowColor).toBe('#FF5733'); + }); + + it('does not set tooltipArrowColor when not provided in display settings', () => { + const msg = makeMessage({ + overlay: true, + properties: { gist: { tooltipArrowColor: '#original' } }, + }); + applyDisplaySettings(msg, { + displayType: 'tooltip', + elementSelector: 'my-element', + tooltipPosition: 'top', + }); + + expect(msg.properties?.gist?.tooltipArrowColor).toBe('#original'); + }); + it('clears tooltipPosition when switching from tooltip to modal', () => { const msg = makeMessage({ tooltipPosition: 'top', elementId: 'my-element' }); applyDisplaySettings(msg, { displayType: 'modal' }); @@ -433,6 +491,39 @@ describe('applyDisplaySettings', () => { expect(getCurrentDisplayType(msg)).toBe('inline'); }); + it('clears tooltipArrowColor when switching from tooltip to modal', () => { + const msg = makeMessage({ + tooltipPosition: 'top', + elementId: 'my-element', + properties: { gist: { tooltipArrowColor: '#FF5733' } }, + }); + applyDisplaySettings(msg, { displayType: 'modal' }); + + expect(msg.properties?.gist?.tooltipArrowColor).toBeUndefined(); + }); + + it('clears tooltipArrowColor when switching from tooltip to overlay', () => { + const msg = makeMessage({ + tooltipPosition: 'top', + elementId: 'my-element', + properties: { gist: { tooltipArrowColor: '#FF5733' } }, + }); + applyDisplaySettings(msg, { displayType: 'overlay', overlayPosition: 'topCenter' }); + + expect(msg.properties?.gist?.tooltipArrowColor).toBeUndefined(); + }); + + it('clears tooltipArrowColor when switching from tooltip to inline', () => { + const msg = makeMessage({ + tooltipPosition: 'top', + elementId: 'my-element', + properties: { gist: { tooltipArrowColor: '#FF5733' } }, + }); + applyDisplaySettings(msg, { displayType: 'inline', elementSelector: 'my-container' }); + + expect(msg.properties?.gist?.tooltipArrowColor).toBeUndefined(); + }); + it('clears custom width for wide overlay positions', () => { const msg = makeMessage({ overlay: false, diff --git a/src/utilities/message-utils.ts b/src/utilities/message-utils.ts index d2ae9b0..091a03f 100644 --- a/src/utilities/message-utils.ts +++ b/src/utilities/message-utils.ts @@ -138,6 +138,12 @@ export function hasDisplayChanged( if (currentMessage.elementId !== displaySettings.elementSelector) { return true; } + if ( + displaySettings.tooltipArrowColor !== undefined && + resolvedProps.tooltipArrowColor !== displaySettings.tooltipArrowColor + ) { + return true; + } break; } } @@ -172,6 +178,7 @@ export function applyDisplaySettings(message: GistMessage, displaySettings: Disp message.properties.gist.position = displaySettings.modalPosition || 'center'; message.tooltipPosition = undefined; message.properties.gist.tooltipPosition = undefined; + message.properties.gist.tooltipArrowColor = undefined; } else if (displaySettings.displayType === 'overlay') { message.overlay = false; const elementId = mapOverlayPositionToElementId(displaySettings.overlayPosition); @@ -181,6 +188,7 @@ export function applyDisplaySettings(message: GistMessage, displaySettings: Disp message.properties.gist.position = null; message.tooltipPosition = undefined; message.properties.gist.tooltipPosition = undefined; + message.properties.gist.tooltipArrowColor = undefined; } else if (displaySettings.displayType === 'inline') { message.overlay = false; message.elementId = displaySettings.elementSelector; @@ -189,6 +197,7 @@ export function applyDisplaySettings(message: GistMessage, displaySettings: Disp message.properties.gist.position = null; message.tooltipPosition = undefined; message.properties.gist.tooltipPosition = undefined; + message.properties.gist.tooltipArrowColor = undefined; } else if (displaySettings.displayType === 'tooltip') { message.overlay = false; message.elementId = displaySettings.elementSelector; @@ -197,6 +206,9 @@ export function applyDisplaySettings(message: GistMessage, displaySettings: Disp message.properties.gist.tooltipPosition = displaySettings.tooltipPosition; message.position = null; message.properties.gist.position = null; + if (displaySettings.tooltipArrowColor !== undefined) { + message.properties.gist.tooltipArrowColor = displaySettings.tooltipArrowColor; + } } const isWideOverlayPosition =