diff --git a/assets/client/client.css b/assets/client/client.css index d978bf2b4..31cf213fe 100644 --- a/assets/client/client.css +++ b/assets/client/client.css @@ -2,6 +2,7 @@ position: fixed; height: 40px; width: 40px; + box-sizing: border-box; padding-left: 5px; padding-right: 5px; border-radius: 10px; @@ -40,11 +41,26 @@ background: white; border: 1px solid #e2e8f0; border-radius: 8px; + box-sizing: border-box; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); min-width: 160px; z-index: 10000; display: none; font-size: 14px; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif; +} + +#live-debugger-debug-tooltip p { + margin: 0; } #live-debugger-debug-tooltip .live-debugger-tooltip-option { @@ -82,6 +98,16 @@ pointer-events: none; padding: 8px 12px; color: #333; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif; } .live-debugger-tooltip-content { diff --git a/assets/client/client.js b/assets/client/client.js index 8492c9790..90f6e8779 100644 --- a/assets/client/client.js +++ b/assets/client/client.js @@ -38,10 +38,36 @@ window.document.addEventListener('DOMContentLoaded', async () => { debugChannel.push('pong', resp); }); - initElementInspection({ baseURL, debugChannel, socketID: mainSocketID }); - initTooltip(); - initDebugMenu(metaTag, sessionURL, debugChannel); - initHighlight(debugChannel); + const shadowHost = document.createElement('div'); + shadowHost.style.position = 'absolute'; + shadowHost.style.width = '0px'; + shadowHost.style.height = '0px'; + shadowHost.style.left = '0px'; + shadowHost.style.top = '0px'; + shadowHost.style.zIndex = '2147483647'; + document.body.appendChild(shadowHost); + + const shadowRoot = shadowHost.attachShadow({ mode: 'closed' }); + + const cssLink = document.createElement('link'); + cssLink.rel = 'stylesheet'; + cssLink.href = `${baseURL}/assets/live_debugger/client.css`; + shadowRoot.appendChild(cssLink); + + const { debugButton } = initDebugMenu( + metaTag, + sessionURL, + debugChannel, + shadowRoot + ); + initElementInspection({ + baseURL, + debugChannel, + socketID: mainSocketID, + debugButton, + }); + initTooltip(shadowRoot); + initHighlight(debugChannel, shadowRoot); } console.info(`LiveDebugger available at: ${baseURL}`); diff --git a/assets/client/components/debug_menu.js b/assets/client/components/debug_menu.js index 474de5288..6d25a421e 100644 --- a/assets/client/components/debug_menu.js +++ b/assets/client/components/debug_menu.js @@ -3,31 +3,57 @@ import initDebugOptions from './debug_options/debug_options'; import { dispatchCustomEvent } from '../utils/dom'; import { isDebugButtonEnabled } from '../utils/meta'; -export default function initDebugMenu(metaTag, liveDebuggerURL, debugChannel) { +export default function initDebugMenu( + metaTag, + liveDebuggerURL, + debugChannel, + shadowRoot +) { const debugButton = initDebugButton(); const debugMenu = initDebugOptions({ liveDebuggerURL, debugChannel }); + let suppressOutsideClick = false; - if (isDebugButtonEnabled(metaTag)) { - document.body.appendChild(debugButton); - document.body.appendChild(debugMenu); - } + const suppressNext = () => { + suppressOutsideClick = true; + setTimeout(() => { + suppressOutsideClick = false; + }, 0); + }; + + debugButton.addEventListener('click', suppressNext, true); + debugMenu.addEventListener('click', suppressNext, true); + + const mount = () => { + shadowRoot.appendChild(debugButton); + shadowRoot.appendChild(debugMenu); + }; + + const unmount = () => { + debugButton.remove(); + debugMenu.remove(); + }; + + if (isDebugButtonEnabled(metaTag)) mount(); debugChannel.on('toggle-debug-button', ({ enabled }) => { if (enabled) { - document.body.appendChild(debugButton); - document.body.appendChild(debugMenu); + mount(); } else { - debugButton.remove(); - debugMenu.remove(); + unmount(); } }); // Hide menu when clicking outside document.addEventListener('click', (event) => { - if ( - !debugButton.contains(event.target) && - !debugMenu.contains(event.target) - ) { + const path = event.composedPath?.() ?? [event.target]; + const clickedInside = + path.includes(debugButton) || + path.includes(debugMenu) || + path.some((node) => node?.getRootNode?.() === shadowRoot); + + if (suppressOutsideClick) return; + + if (!clickedInside) { dispatchCustomEvent('lvdbg:click-outside-debug-menu'); } }); diff --git a/assets/client/components/tooltip/tooltip.js b/assets/client/components/tooltip/tooltip.js index bd4a04052..4ba6c10c9 100644 --- a/assets/client/components/tooltip/tooltip.js +++ b/assets/client/components/tooltip/tooltip.js @@ -5,25 +5,25 @@ import { addTooltipArrow } from './tooltip_arrow'; const tooltipID = 'live-debugger-tooltip'; const highlightElementID = 'live-debugger-highlight-element'; -function getHighlightElement() { - return document.getElementById(highlightElementID); +function getHighlightElement(shadowRoot) { + return shadowRoot.querySelector(`#${highlightElementID}`); } -function removeTooltip() { - const existingTooltip = document.getElementById(tooltipID); +function removeTooltip(shadowRoot) { + const existingTooltip = shadowRoot.querySelector(`#${tooltipID}`); if (existingTooltip) { existingTooltip.remove(); } } -function showTooltip(data) { - const highlightElement = getHighlightElement(); +function showTooltip(data, shadowRoot) { + const highlightElement = getHighlightElement(shadowRoot); if (!highlightElement) { return; } - removeTooltip(); - const tooltip = createTooltip(data); + removeTooltip(shadowRoot); + const tooltip = createTooltip(data, shadowRoot); const positionData = positionTooltip(tooltip, highlightElement); if (positionData) { @@ -36,9 +36,9 @@ function showTooltip(data) { } } -function handleTooltipResize() { - const tooltip = document.getElementById(tooltipID); - const highlightElement = getHighlightElement(); +function handleTooltipResize(shadowRoot) { + const tooltip = shadowRoot.querySelector(`#${tooltipID}`); + const highlightElement = getHighlightElement(shadowRoot); if (tooltip && highlightElement) { const positionData = positionTooltip(tooltip, highlightElement); @@ -54,22 +54,26 @@ function handleTooltipResize() { } } -function handleShowTooltipEvent(event) { - showTooltip(event.detail); +function handleShowTooltipEvent(event, shadowRoot) { + showTooltip(event.detail, shadowRoot); } -function handleRemoveTooltipEvent() { - removeTooltip(); +function handleRemoveTooltipEvent(shadowRoot) { + removeTooltip(shadowRoot); } -function setupEventListeners() { - window.addEventListener('resize', handleTooltipResize); - window.addEventListener('scroll', handleTooltipResize); +function setupEventListeners(shadowRoot) { + window.addEventListener('resize', () => handleTooltipResize(shadowRoot)); + window.addEventListener('scroll', () => handleTooltipResize(shadowRoot)); - document.addEventListener('lvdbg:show-tooltip', handleShowTooltipEvent); - document.addEventListener('lvdbg:remove-tooltip', handleRemoveTooltipEvent); + document.addEventListener('lvdbg:show-tooltip', (event) => + handleShowTooltipEvent(event, shadowRoot) + ); + document.addEventListener('lvdbg:remove-tooltip', () => + handleRemoveTooltipEvent(shadowRoot) + ); } -export default function initTooltip() { - setupEventListeners(); +export default function initTooltip(shadowRoot) { + setupEventListeners(shadowRoot); } diff --git a/assets/client/components/tooltip/tooltip_creator.js b/assets/client/components/tooltip/tooltip_creator.js index fcc3febed..60329f20e 100644 --- a/assets/client/components/tooltip/tooltip_creator.js +++ b/assets/client/components/tooltip/tooltip_creator.js @@ -41,13 +41,13 @@ function setModuleName(tooltip, data) { moduleName.textContent = data.module || 'Element'; } -export function createTooltip(data) { +export function createTooltip(data, shadowRoot) { const tooltip = createElement(tooltipHtml); setModuleName(tooltip, data); setTypeIcon(tooltip, data); populateInfoSection(tooltip, data); - document.body.appendChild(tooltip); + shadowRoot.appendChild(tooltip); return tooltip; } diff --git a/assets/client/services/highlight.js b/assets/client/services/highlight.js index 1fcbe2501..b6c385853 100644 --- a/assets/client/services/highlight.js +++ b/assets/client/services/highlight.js @@ -59,8 +59,8 @@ function createHighlightElement(activeElement, detail, id) { return highlight; } -function removeHighlightElement() { - const highlightElement = document.getElementById(highlightElementID); +function removeHighlightElement(shadowRoot) { + const highlightElement = shadowRoot.querySelector(`#${highlightElementID}`); if (highlightElement) { highlightElement.remove(); @@ -69,8 +69,8 @@ function removeHighlightElement() { dispatchCustomEvent('lvdbg:remove-tooltip'); } -function handleHighlight({ detail }) { - let highlightElement = document.getElementById(highlightElementID); +function handleHighlight({ detail }, shadowRoot) { + let highlightElement = shadowRoot.querySelector(`#${highlightElementID}`); if (highlightElement) { highlightElement.remove(); @@ -95,13 +95,13 @@ function handleHighlight({ detail }) { highlightElementID ); - document.body.appendChild(highlightElement); + shadowRoot.appendChild(highlightElement); showTooltip(detail); } } -function handleHighlightResize() { - const highlight = document.getElementById(highlightElementID); +function handleHighlightResize(shadowRoot) { + const highlight = shadowRoot.querySelector(`#${highlightElementID}`); if (highlight) { const activeElement = document.querySelector( `[${highlight.dataset.attr}="${highlight.dataset.val}"]` @@ -115,7 +115,7 @@ function handleHighlightResize() { } } -function handlePulse({ detail }) { +function handlePulse({ detail }, shadowRoot) { const activeElement = document.querySelector( `[${detail.attr}="${detail.val}"]` ); @@ -127,7 +127,7 @@ function handlePulse({ detail }) { highlightPulseElementID ); - document.body.appendChild(highlightPulse); + shadowRoot.appendChild(highlightPulse); const w = highlightPulse.offsetWidth; const h = highlightPulse.offsetHeight; @@ -186,13 +186,21 @@ function showTooltip(detail) { dispatchCustomEvent('lvdbg:show-tooltip', props); } -export default function initHighlight(debugChannel) { - document.addEventListener('lvdbg:inspect-highlight', handleHighlight); - document.addEventListener('lvdbg:inspect-pulse', handlePulse); - document.addEventListener('lvdbg:inspect-clear', removeHighlightElement); +export default function initHighlight(debugChannel, shadowRoot) { + document.addEventListener('lvdbg:inspect-highlight', (event) => + handleHighlight(event, shadowRoot) + ); + document.addEventListener('lvdbg:inspect-pulse', (event) => + handlePulse(event, shadowRoot) + ); + document.addEventListener('lvdbg:inspect-clear', () => + removeHighlightElement(shadowRoot) + ); - debugChannel.on('highlight', (e) => handleHighlight({ detail: e })); - debugChannel.on('pulse', (e) => handlePulse({ detail: e })); + debugChannel.on('highlight', (e) => + handleHighlight({ detail: e }, shadowRoot) + ); + debugChannel.on('pulse', (e) => handlePulse({ detail: e }, shadowRoot)); - window.addEventListener('resize', handleHighlightResize); + window.addEventListener('resize', () => handleHighlightResize(shadowRoot)); } diff --git a/assets/client/services/inspect.js b/assets/client/services/inspect.js index aade844a1..6fbd017b3 100644 --- a/assets/client/services/inspect.js +++ b/assets/client/services/inspect.js @@ -4,6 +4,7 @@ export default function initElementInspection({ baseURL, debugChannel, socketID, + debugButton, }) { let inspectMode = false; let lastID = null; @@ -141,7 +142,6 @@ export default function initElementInspection({ inspectMode = false; lastID = null; - const debugButton = document.getElementById('live-debugger-debug-button'); if (debugButton) debugButton.classList.remove('live-debugger-inspect-mode'); pushClearEvent(); @@ -162,7 +162,6 @@ export default function initElementInspection({ inspectMode = true; - const debugButton = document.getElementById('live-debugger-debug-button'); if (debugButton) debugButton.classList.add('live-debugger-inspect-mode'); document.body.classList.add('live-debugger-inspect-mode'); diff --git a/lib/live_debugger.ex b/lib/live_debugger.ex index 781737a91..66be8be5f 100644 --- a/lib/live_debugger.ex +++ b/lib/live_debugger.ex @@ -17,7 +17,6 @@ defmodule LiveDebugger do @default_drainer [shutdown: 1000] @js_path "assets/live_debugger/client.js" - @css_path "assets/live_debugger/client.css" @phoenix_path "assets/phoenix/phoenix.js" def start(_type, _args) do @@ -121,13 +120,11 @@ defmodule LiveDebugger do version = Application.spec(@app_name)[:vsn] |> to_string() live_debugger_js_url = "#{live_debugger_url}/#{@js_path}" - live_debugger_css_url = "#{live_debugger_url}/#{@css_path}" live_debugger_phoenix_url = "#{live_debugger_url}/#{@phoenix_path}" assigns = %{ url: live_debugger_url, js_url: live_debugger_js_url, - css_url: live_debugger_css_url, phoenix_url: live_debugger_phoenix_url, browser_features?: browser_features?, version: version, diff --git a/lib/live_debugger/client/config_component.ex b/lib/live_debugger/client/config_component.ex index c58c9f8c1..029572bc7 100644 --- a/lib/live_debugger/client/config_component.ex +++ b/lib/live_debugger/client/config_component.ex @@ -8,7 +8,6 @@ defmodule LiveDebugger.Client.ConfigComponent do attr(:url, :string, required: true) attr(:js_url, :string, required: true) - attr(:css_url, :string, required: true) attr(:phoenix_url, :string, required: true) attr(:browser_features?, :boolean, default: true) attr(:version, :string, default: nil) @@ -20,7 +19,6 @@ defmodule LiveDebugger.Client.ConfigComponent do <%= if @browser_features? do %> - <% end %>