UNPKG

@web-widget/inspector

Version:

Web Widget Inspector - Development toolbar for web-widget elements

1,427 lines (1,374 loc) 40.7 kB
import { css, LitElement, html } from 'lit'; import { property, state } from 'lit/decorators.js'; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? undefined : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; // src/utils/widget-finder.ts function findWebWidgetContainer(element) { if (!element) { return null; } if (element.tagName === "WEB-WIDGET" && element.hasAttribute("import")) { return element; } const container = element.closest("web-widget[import]"); return container; } // src/utils/storage.ts function getStoredValue(key, defaultValue) { const stored = localStorage.getItem(key); if (stored === null) { return defaultValue; } try { return JSON.parse(stored); } catch { return defaultValue; } } function setStoredValue(key, value) { localStorage.setItem(key, JSON.stringify(value)); } // src/utils/design-system.ts var spacing = { xs: "0.25rem", sm: "0.5rem", md: "0.75rem", lg: "1rem", xl: "1.25rem" }; var typography = { fontFamily: { sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' }, fontSize: { xs: "0.75rem", sm: "0.875rem", base: "1rem" }, fontWeight: { normal: "400", medium: "500", semibold: "600" }, lineHeight: { normal: "1.5" }, letterSpacing: { normal: "0" } }; var boxShadow = { base: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)" }; var zIndex = { TOOLTIP: 2147483647, TOOLBAR: 2147483646, OVERLAY: 2147483645 }; var colors = { green: "#22c55e", // Primary green overlayBorder: "#22c55e" }; var themes = { light: { bg: "rgba(255, 255, 255, 0.92)", fg: "#222", fgHover: "#111", fgSelected: "#000", border: "#e5e7eb", accent: "#888", accentHover: "#666", accentSelected: "#444", primary: colors.green, primaryHover: "#16a34a", primarySelected: "#15803d", overlayBorder: colors.overlayBorder }, dark: { bg: "rgba(17, 17, 17, 0.92)", fg: "#f3f4f6", fgHover: "#ffffff", fgSelected: "#ffffff", border: "#374151", accent: "#9ca3af", accentHover: "#d1d5db", accentSelected: "#f3f4f6", primary: colors.green, primaryHover: "#4ade80", primarySelected: "#16a34a", overlayBorder: colors.overlayBorder } }; function generateCSSVariables() { return { // Typography - only used values "--wwi-font-sans": typography.fontFamily.sans, "--wwi-font-size-xs": typography.fontSize.xs, "--wwi-font-size-sm": typography.fontSize.sm, "--wwi-font-size-base": typography.fontSize.base, "--wwi-font-weight-normal": typography.fontWeight.normal, "--wwi-font-weight-medium": typography.fontWeight.medium, "--wwi-font-weight-semibold": typography.fontWeight.semibold, "--wwi-line-height-normal": typography.lineHeight.normal, "--wwi-letter-spacing-normal": typography.letterSpacing.normal, // Spacing - only used values "--wwi-spacing-xs": spacing.xs, "--wwi-spacing-sm": spacing.sm, "--wwi-spacing-md": spacing.md, "--wwi-spacing-lg": spacing.lg, "--wwi-spacing-xl": spacing.xl, // Box shadow - only used values "--wwi-shadow-base": boxShadow.base, // Z-index - only used values "--wwi-z-tooltip": zIndex.TOOLTIP.toString(), "--wwi-z-toolbar": zIndex.TOOLBAR.toString(), "--wwi-z-overlay": zIndex.OVERLAY.toString(), // Brand colors - only used values "--wwi-green": colors.green, "--wwi-overlay-border": colors.overlayBorder }; } var designSystem = { spacing, typography, boxShadow, zIndex, colors, themes, cssVariables: generateCSSVariables() }; var DESIGN_SYSTEM = { spacing, typography, boxShadow, zIndex, colors }; function getThemeVariables(theme) { return themes[theme]; } // src/utils/theme.ts function applyTheme(vars) { const existingStyle = document.getElementById("wwi-design-system-styles"); if (existingStyle) { existingStyle.remove(); } const style = document.createElement("style"); style.id = "wwi-design-system-styles"; const cssVariables = Object.entries(designSystem.cssVariables).map(([property2, value]) => ` ${property2}: ${value};`).join("\n"); const themeVariables = ` --wwi-bg: ${vars.bg}; --wwi-fg: ${vars.fg}; --wwi-border: ${vars.border}; --wwi-accent: ${vars.accent}; `; style.textContent = ` :root { ${cssVariables} ${themeVariables} } `; document.head.appendChild(style); } function detectAutoTheme() { if (typeof document === "undefined" || !document.body || !document.documentElement) { return undefined; } const bodyStyle = getComputedStyle(document.body); let bg = bodyStyle?.backgroundColor; if (!bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent") { const docStyle = getComputedStyle(document.documentElement); bg = docStyle?.backgroundColor; } let isTransparent = false; if (!bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent") { bg = "rgb(255,255,255)"; isTransparent = true; } let r = 255; let g = 255; let b = 255; const match = bg.match(/rgba?\(([^)]+)\)/); if (match) { const parts = match[1]?.split(",").map((x) => parseFloat(x.trim())) ?? []; r = parts[0] ?? 255; g = parts[1] ?? 255; b = parts[2] ?? 255; } const brightness = (r * 299 + g * 587 + b * 114) / 1e3; if (isTransparent || Number.isNaN(brightness) || brightness >= 128) { return "light"; } else { return "dark"; } } function applyThemeMode(mode) { if (mode !== "auto") { const theme = getThemeVariables(mode); if (theme) { applyTheme(theme); } return; } const autoTheme = detectAutoTheme(); if (autoTheme) { const theme = getThemeVariables(autoTheme); if (theme) { applyTheme(theme); } else { applyTheme(getThemeVariables("light")); } } else { applyTheme(getThemeVariables("light")); } } // src/utils/box.ts function isValidRect(rect) { return rect.width > 0 || rect.height > 0; } function mergeRects(rects) { if (rects.length === 0) return null; let top = Infinity, left = Infinity, right = -Infinity, bottom = -Infinity; for (const rect of rects) { top = Math.min(top, rect.top); left = Math.min(left, rect.left); right = Math.max(right, rect.right); bottom = Math.max(bottom, rect.bottom); } return { top, left, right, bottom, width: right - left, height: bottom - top }; } function getNodeRectsDeep(node) { const rects = []; if (node.nodeType === Node.ELEMENT_NODE) { const el = node; const style = getComputedStyle(el); if (style.display === "none") return []; const rect = el.getBoundingClientRect(); if (isValidRect(rect)) rects.push(rect); const children = el.shadowRoot ? Array.from(el.shadowRoot.children) : Array.from(el.children); for (const child of children) { rects.push(...getNodeRectsDeep(child)); } if (el.tagName === "SLOT") { const assigned = el.assignedNodes({ flatten: true }); for (const assignedNode of assigned) { rects.push(...getNodeRectsDeep(assignedNode)); } } } else if (node.nodeType === Node.TEXT_NODE) { const range = document.createRange(); range.selectNodeContents(node); const rect = range.getBoundingClientRect(); if (isValidRect(rect)) rects.push(rect); } return rects; } function getElementBox(el) { const allRects = getNodeRectsDeep(el); return mergeRects(allRects); } // src/utils/debug-data.ts function collectElementData(element) { const bounds = getElementBox(element) ?? { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }; const isWebWidget = element.tagName === "WEB-WIDGET"; const data = { tag: element.tagName.toLowerCase(), bounds, isWebWidget }; if (element.id) { data.id = element.id; } if (isWebWidget) { data.webWidgetData = collectWebWidgetData(element); } return data; } function collectWebWidgetData(element) { const data = {}; const webWidgetElement = element; const name = element.getAttribute("name"); if (name) { data.name = name; } const importUrl = element.getAttribute("import"); if (importUrl) { data.module = importUrl; } const loading = webWidgetElement.loading; if (loading && loading !== "eager") { data.loading = loading; } const renderTarget = webWidgetElement.renderTarget; if (renderTarget && renderTarget !== "light") { data.renderTarget = renderTarget; } const status = webWidgetElement.status; if (status) { data.status = status; } if (webWidgetElement.inactive) { data.inactive = true; } const contextData = webWidgetElement.contextData; if (contextData && typeof contextData === "object") { data.contextData = contextData; } const performanceData = collectPerformanceData(element); if (performanceData.loadTime !== undefined) { data.loadTime = performanceData.loadTime; } if (performanceData.mountTime !== undefined) { data.mountTime = performanceData.mountTime; } return data; } function collectPerformanceData(element) { const data = {}; const webWidgetElement = element; const performanceData = webWidgetElement.performance; if (performanceData && typeof performanceData === "object") { if (performanceData.loadTime) { const loadTimeStr = performanceData.loadTime; if (typeof loadTimeStr === "string" && loadTimeStr.endsWith("ms")) { data.loadTime = parseInt(loadTimeStr, 10); } } if (performanceData.mountTime) { const mountTimeStr = performanceData.mountTime; if (typeof mountTimeStr === "string" && mountTimeStr.endsWith("ms")) { data.mountTime = parseInt(mountTimeStr, 10); } } } return data; } function formatDebugData(data, element) { const items = []; if (data.isWebWidget && data.webWidgetData) { const widgetData = data.webWidgetData; const componentName = element.getAttribute("name"); if (componentName) { items.push({ key: "Name", value: componentName, priority: 1 }); } if (widgetData.module) { items.push({ key: "Module", value: widgetData.module, priority: 2 }); } if (widgetData.loadTime !== undefined) { items.push({ key: "Load Time", value: `${widgetData.loadTime}ms`, priority: 3 }); } if (widgetData.mountTime !== undefined) { items.push({ key: "Mount Time", value: `${widgetData.mountTime}ms`, priority: 3 }); } if (widgetData.status) { items.push({ key: "Status", value: widgetData.status, priority: 4 }); } if (widgetData.loading) { items.push({ key: "Loading", value: widgetData.loading, priority: 4 }); } if (widgetData.renderTarget) { items.push({ key: "Render", value: widgetData.renderTarget, priority: 4 }); } if (widgetData.contextData) { const dataKeys = Object.keys(widgetData.contextData); if (dataKeys.length > 0) { items.push({ key: "Parameters", value: `${dataKeys.length} keys`, priority: 5 }); } } } else { items.push({ key: "Tag", value: data.tag, priority: 1 }); if (data.id) { items.push({ key: "ID", value: data.id, priority: 2 }); } } items.push({ key: "Size", value: `${Math.round(data.bounds.width)}\xD7${Math.round(data.bounds.height)}px`, priority: 10 }); return items.sort((a, b) => a.priority - b.priority); } function getPriorityClass(priority) { if (priority <= 3) return "priority-high"; if (priority <= 6) return "priority-medium"; return "priority-low"; } // src/element.ts var HTMLWebWidgetInspectorElement = class extends LitElement { constructor() { super(...arguments); this.dir = ""; this.keys = []; this.pageSource = ""; this._theme = "auto"; this.isToolbarVisible = true; this.isInspectorMode = false; this.isMinimized = false; this.hoveredElement = null; this.widgetCount = 0; this.pressedKeys = /* @__PURE__ */ new Set(); this.tooltipElement = null; this.currentElementBounds = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; this.overlayElement = null; } get theme() { return this._theme; } set theme(value) { const oldValue = this._theme; this._theme = value; this.setAttribute("theme", value); if (oldValue !== value) { this.initializeTheme(); } } connectedCallback() { super.connectedCallback(); this.initializeTheme(); this.initializeEventListeners(); this.updateWidgetCount(); this.loadStoredState(); this.loadRouteModuleSourceFromHeaders(); } disconnectedCallback() { super.disconnectedCallback(); this.cleanupInspectorElements(); } initializeTheme() { applyThemeMode(this.theme); } initializeEventListeners() { document.addEventListener("keydown", (e) => this.handleKeyEvent(e, true), { capture: true }); document.addEventListener("keyup", (e) => this.handleKeyEvent(e, false), { capture: true }); window.addEventListener("blur", this.clearKeys.bind(this), { capture: true }); document.addEventListener("mousemove", this.handleHoverEvent.bind(this), { capture: true }); document.addEventListener("mouseover", this.handleHoverEvent.bind(this), { capture: true }); document.addEventListener("click", this.handleClickEvent.bind(this), { capture: true }); document.addEventListener("contextmenu", this.clearKeys.bind(this), { capture: true }); window.addEventListener("resize", this.updateState.bind(this)); document.addEventListener("scroll", this.updateState.bind(this)); } loadStoredState() { this.isToolbarVisible = getStoredValue( "web-widget-inspector-visible", true ); this.isMinimized = getStoredValue("web-widget-inspector-minimized", false); } handleKeyEvent(event, add) { if (event.code === "Escape" && this.isInspectorMode) { this.isInspectorMode = false; this.updateState(); event.preventDefault(); return; } if (add) { this.pressedKeys.add(event.code); } else { this.pressedKeys.delete(event.code); } this.updateState(); } clearKeys() { this.pressedKeys.clear(); } checkKeysMatch(targetKeys) { if (!targetKeys.length) return false; return targetKeys.every((key) => { const normalizedKey = key === "Shift" ? "ShiftLeft" : key; return this.pressedKeys.has(key) || this.pressedKeys.has(normalizedKey); }); } handleHoverEvent(event) { if (!this.isInspectorMode) return; const target = findWebWidgetContainer(event.target); if (this.hoveredElement !== target) { if (this.hoveredElement) { this.hoveredElement.style.removeProperty("cursor"); } this.hoveredElement = target; this.updateState(); } } handleClickEvent(event) { if (this.isInspectorMode) { const toolbar = this.renderRoot?.querySelector(".wwi-toolbar"); const isToolbarClick = toolbar?.contains(event.target); if (isToolbarClick) { return; } const widgetTarget = findWebWidgetContainer(event.target); if (widgetTarget) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); const inspectUrl = widgetTarget?.getAttribute("import"); if (inspectUrl) { document.body.style.cursor = "progress"; this.openInEditor(inspectUrl, this.dir); this.updateState(); } } } } updateState() { const targetKeys = this.keys; if (!this.isToolbarVisible && this.checkKeysMatch(targetKeys)) { this.showToolbar(); } if (this.isInspectorMode && this.hoveredElement) { this.updateInspectorOverlays(this.hoveredElement); } else { this.clearInspectorOverlays(); } this.setButtonSelected(this.isInspectorMode); if (this.isInspectorMode) { if (this.hoveredElement) { this.hoveredElement.style.cursor = "pointer"; } } else { document.body.style.removeProperty("cursor"); const allElements = document.querySelectorAll("*"); allElements.forEach((el) => { if (el instanceof HTMLElement) { el.style.removeProperty("cursor"); } }); } this.requestUpdate(); } createOverlayElement() { if (this.overlayElement) { return this.overlayElement; } if (!this.isInspectorMode) { const dummy = document.createElement("div"); dummy.style.visibility = "hidden"; return dummy; } this.ensureOverlayStyles(); const overlay = document.createElement("div"); overlay.className = "wwi-overlay"; overlay.setAttribute("aria-hidden", "true"); document.body.appendChild(overlay); this.overlayElement = overlay; return overlay; } showOverlay(element) { if (!this.isInspectorMode || !element) return; const overlay = this.createOverlayElement(); const rect = this.currentElementBounds; overlay.style.top = `${rect.top}px`; overlay.style.left = `${rect.left}px`; overlay.style.width = `${rect.width}px`; overlay.style.height = `${rect.height}px`; overlay.style.visibility = "visible"; } cleanupOverlay() { if (this.overlayElement) { this.overlayElement.remove(); this.overlayElement = null; } } setButtonSelected(selected) { const btn = this.renderRoot?.querySelector(".wwi-inspect-btn"); if (btn) { btn.setAttribute("data-selected", selected ? "true" : "false"); btn.setAttribute("aria-pressed", selected ? "true" : "false"); } } updateWidgetCount() { this.widgetCount = document.querySelectorAll("web-widget[import]").length; } createTooltipElement() { if (this.tooltipElement) { return this.tooltipElement; } if (!this.isInspectorMode) { const dummy = document.createElement("div"); dummy.style.visibility = "hidden"; return dummy; } this.ensureTooltipStyles(); const tooltip = document.createElement("div"); tooltip.className = "wwi-tooltip"; tooltip.setAttribute("aria-hidden", "true"); document.body.appendChild(tooltip); this.tooltipElement = tooltip; return tooltip; } ensureOverlayStyles() { if (!this.isInspectorMode) { return; } if (document.getElementById("wwi-overlay-styles")) { return; } const style = document.createElement("style"); style.id = "wwi-overlay-styles"; style.textContent = ` .wwi-overlay { position: fixed; z-index: ${DESIGN_SYSTEM.zIndex.OVERLAY}; pointer-events: none; visibility: hidden; opacity: 1; display: block; background: rgba(34, 197, 94, 0.3); border: 2px solid var(--wwi-overlay-border, #22c55e); border-radius: 0; box-sizing: border-box; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } `; document.head.appendChild(style); } ensureTooltipStyles() { if (!this.isInspectorMode) { return; } if (document.getElementById("wwi-tooltip-styles")) { return; } const style = document.createElement("style"); style.id = "wwi-tooltip-styles"; style.textContent = ` .wwi-tooltip { position: fixed; z-index: ${DESIGN_SYSTEM.zIndex.TOOLTIP}; pointer-events: none; background: var(--wwi-bg, rgba(255, 255, 255, 0.95)); color: var(--wwi-fg, #222); border: 1px solid var(--wwi-border, #e5e7eb); border-radius: 0.375rem; padding: ${DESIGN_SYSTEM.spacing.lg} ${DESIGN_SYSTEM.spacing.xl}; font-size: ${DESIGN_SYSTEM.typography.fontSize.base}; font-family: ${DESIGN_SYSTEM.typography.fontFamily.sans}; line-height: ${DESIGN_SYSTEM.typography.lineHeight.normal}; font-weight: ${DESIGN_SYSTEM.typography.fontWeight.normal}; letter-spacing: ${DESIGN_SYSTEM.typography.letterSpacing.normal}; word-wrap: break-word; white-space: pre-wrap; box-shadow: ${DESIGN_SYSTEM.boxShadow.base}; backdrop-filter: blur(8px); max-width: 640px; min-width: 280px; visibility: hidden; display: block; } .wwi-tooltip-table { width: 100%; border-collapse: collapse; font-size: ${DESIGN_SYSTEM.typography.fontSize.xs}; line-height: ${DESIGN_SYSTEM.typography.lineHeight.normal}; } .wwi-tooltip-table td { padding: ${DESIGN_SYSTEM.spacing.xs} 0; vertical-align: top; } .wwi-tooltip-table .key { color: var(--wwi-accent, #888); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.medium}; padding-right: ${DESIGN_SYSTEM.spacing.lg}; white-space: nowrap; text-align: left; opacity: 0.8; } .wwi-tooltip-table .value { color: var(--wwi-fg, #222); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.normal}; word-break: break-all; text-align: right; } .wwi-tooltip-table tr.priority-high .key { color: var(--wwi-fg, #222); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.semibold}; opacity: 1; } .wwi-tooltip-table tr.priority-high .value { color: var(--wwi-fg, #222); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.medium}; } .wwi-tooltip-table tr.priority-medium .key { color: var(--wwi-accent, #666); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.medium}; opacity: 0.9; } .wwi-tooltip-table tr.priority-medium .value { color: var(--wwi-fg, #444); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.normal}; } .wwi-tooltip-table tr.priority-low .key { color: var(--wwi-accent, #888); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.normal}; opacity: 0.7; } .wwi-tooltip-table tr.priority-low .value { color: var(--wwi-fg, #666); font-weight: ${DESIGN_SYSTEM.typography.fontWeight.normal}; opacity: 0.8; } .wwi-tooltip-help { margin-top: ${DESIGN_SYSTEM.spacing.md}; padding-top: ${DESIGN_SYSTEM.spacing.md}; border-top: 1px solid var(--wwi-border, #e5e7eb); font-size: ${DESIGN_SYSTEM.typography.fontSize.xs}; color: var(--wwi-accent, #888); text-align: center; opacity: 0.8; } @media (max-width: 768px) { .inspector-toolbar { bottom: ${DESIGN_SYSTEM.spacing.md}; left: ${DESIGN_SYSTEM.spacing.md}; right: ${DESIGN_SYSTEM.spacing.md}; flex-wrap: wrap; max-width: calc(100vw - ${DESIGN_SYSTEM.spacing.xl}); padding: ${DESIGN_SYSTEM.spacing.md} ${DESIGN_SYSTEM.spacing.lg}; gap: ${DESIGN_SYSTEM.spacing.md}; } } `; document.head.appendChild(style); } calculateSimplePosition(elementRect, tooltipRect) { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const padding = 8; const minMargin = 8; let x = elementRect.left; let y = elementRect.top - tooltipRect.height - padding; if (y < minMargin) { y = elementRect.top + elementRect.height + padding; } x = Math.max( minMargin, Math.min(viewportWidth - tooltipRect.width - minMargin, x) ); y = Math.max( minMargin, Math.min(viewportHeight - tooltipRect.height - minMargin, y) ); return { x, y }; } generateTooltipContent(element) { const debugData = collectElementData(element); const formattedData = formatDebugData(debugData, element); const tableRows = formattedData.map((item) => { const priorityClass = getPriorityClass(item.priority); return `<tr class="${priorityClass}"><td class="key">${item.key}</td><td class="value">${item.value}</td></tr>`; }).join(""); const helpText = debugData.isWebWidget ? '<div class="wwi-tooltip-help">\u{1F4A1} Click to open source code</div>' : ""; return `<table class="wwi-tooltip-table">${tableRows}</table>${helpText}`; } showTooltip(element) { if (!this.isInspectorMode || !element) return; const tooltip = this.createTooltipElement(); const content = this.generateTooltipContent(element); tooltip.innerHTML = content; tooltip.style.visibility = "hidden"; tooltip.style.display = "block"; tooltip.offsetHeight; const elementRect = this.currentElementBounds; const tooltipRect = tooltip.getBoundingClientRect(); const position = this.calculateSimplePosition(elementRect, tooltipRect); tooltip.style.left = `${position.x}px`; tooltip.style.top = `${position.y}px`; tooltip.style.visibility = "visible"; } hideTooltip() { if (this.tooltipElement) { this.tooltipElement.style.visibility = "hidden"; } } cleanupTooltip() { if (this.tooltipElement) { this.tooltipElement.remove(); this.tooltipElement = null; } } updateTooltipContent() { if (this.hoveredElement && this.tooltipElement) { const content = this.generateTooltipContent(this.hoveredElement); this.tooltipElement.innerHTML = content; } } cleanupInspectorElements() { if (this.overlayElement) { this.overlayElement.remove(); this.overlayElement = null; } const overlayStyles = document.getElementById("wwi-overlay-styles"); if (overlayStyles) { overlayStyles.remove(); } const tooltipStyles = document.getElementById("wwi-tooltip-styles"); if (tooltipStyles) { tooltipStyles.remove(); } if (this.tooltipElement) { this.tooltipElement.remove(); this.tooltipElement = null; } document.body.style.removeProperty("cursor"); } toggleInspector() { this.isInspectorMode = !this.isInspectorMode; if (!this.isInspectorMode) { this.cleanupInspectorElements(); } this.updateState(); } showToolbar() { this.isToolbarVisible = true; this.updateState(); setStoredValue("web-widget-inspector-visible", true); } hideToolbar() { this.isToolbarVisible = false; this.updateState(); setStoredValue("web-widget-inspector-visible", false); } minimizeToolbar() { this.isMinimized = !this.isMinimized; this.updateState(); setStoredValue("web-widget-inspector-minimized", this.isMinimized); } openInEditor(path, srcDir) { if (srcDir) { const resolvedURL = new URL(path, document.baseURI); if (resolvedURL.origin === location.origin) { const filePath = srcDir + resolvedURL.pathname; this.openFileInEditor(filePath); } else { location.href = resolvedURL.href; } } else { this.openFileInEditor(path); } } openFileInEditor(filePath) { document.body.style.cursor = "progress"; fetch(`/__open-in-editor?file=${encodeURIComponent(filePath)}`).finally( () => { document.body.style.removeProperty("cursor"); } ); } getCurrentFilePath() { if (!this.pageSource) return ""; try { if (this.pageSource.startsWith("source://")) { return this.pageSource.replace("source://", ""); } const url = new URL(this.pageSource); return url.pathname; } catch { return this.pageSource; } } loadRouteModuleSourceFromHeaders() { if (!this.pageSource) { fetch(window.location.href, { method: "HEAD" }).then((response) => { const moduleSource = response.headers.get("x-module-source"); if (moduleSource) { this.pageSource = moduleSource; } }).catch((error) => { console.error( "Error loading route module source from headers:", error ); }); } } /** * Batch update all inspector overlays for the current hovered element * This avoids multiple expensive getElementBox calls */ updateInspectorOverlays(element) { const bounds = getElementBox(element); this.currentElementBounds = bounds ?? { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; this.showOverlay(element); this.showTooltip(element); } clearInspectorOverlays() { this.currentElementBounds = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; this.cleanupOverlay(); this.cleanupTooltip(); } render() { if (!this.isToolbarVisible) { return html``; } return html` <div class="wwi-toolbar" data-minimized=${this.isMinimized ? "true" : "false"} data-inspector-active=${this.isInspectorMode ? "true" : "false"}> <div class="wwi-header"> <div class="wwi-logo" tabindex="0" title="Web Widget Inspector" @click=${this.minimizeToolbar}> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2Z" /> </svg> <div class="logo-text"> <span class="logo-title">WEB WIDGET</span> <span class="logo-subtitle">DEV INSPECTOR</span> </div> </div> </div> <div class="wwi-info"> <span class="widget-count">${this.widgetCount} widgets</span> </div> <div class="wwi-actions"> <button class="wwi-btn wwi-inspect-btn" @click=${this.toggleInspector} title="Click to activate widget inspector (ESC to exit)" aria-label="Activate widget inspector mode"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8" /> <line x1="21" y1="21" x2="16.65" y2="16.65" /> </svg> <span>Inspect</span> </button> <button class="wwi-btn wwi-page-btn" @click=${() => { const currentFilePath = this.getCurrentFilePath(); if (currentFilePath) { this.openInEditor(currentFilePath); } }} title="Open current page source code" aria-label="Open current page source code"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="18" height="18" rx="2" /> <path d="M9 9h6v6H9z" /> </svg> <span>Open Source</span> </button> </div> <div class="wwi-controls"> <button class="wwi-btn wwi-hide-btn" @click=${this.hideToolbar} title="Hide Web Widget Inspector" aria-label="Hide inspector toolbar"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18" /> <line x1="6" y1="6" x2="18" y2="18" /> </svg> </button> <button class="wwi-btn wwi-toggle-btn" @click=${this.minimizeToolbar} title="Minimize Web Widget Inspector" aria-label="Minimize inspector toolbar"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="6 9 12 15 18 9" /> </svg> </button> </div> </div> `; } }; HTMLWebWidgetInspectorElement.styles = css` :host { display: contents; } .wwi-toolbar, .wwi-toolbar *, .wwi-tooltip, .wwi-tooltip * { font-family: var( --wwi-font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif ); } .wwi-toolbar { display: flex; align-items: center; gap: 0.75rem; position: fixed; bottom: 1rem; left: 1rem; z-index: ${DESIGN_SYSTEM.zIndex.TOOLBAR}; background: var(--wwi-bg, rgba(255, 255, 255, 0.95)); color: var(--wwi-fg, #222); padding: 0.75rem 1rem; border-radius: 0.375rem; border: 1px solid var(--wwi-border, #e5e7eb); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); font-family: var( --wwi-font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif ); font-size: 0.75rem; min-width: max-content; transition: all 0.2s; backdrop-filter: blur(8px); } .wwi-toolbar[data-minimized='true'] { padding: 0; gap: 0; min-width: 0; width: 32px; height: 32px; background: var(--wwi-bg, rgba(255, 255, 255, 0.95)); border: 1px solid var(--wwi-border, #e5e7eb); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); border-radius: 50%; cursor: pointer; justify-content: center; align-items: center; display: flex; transition: background 0.2s, border 0.2s; } .wwi-toolbar[data-minimized='true']:hover { background: var(--wwi-bg, rgba(255, 255, 255, 0.98)); border: 1px solid var(--wwi-border, #e5e7eb); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .wwi-toolbar[data-minimized='true'] .wwi-header { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 0; margin: 0; height: 100%; } .wwi-toolbar[data-minimized='true'] .wwi-logo { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; padding: 0; } .wwi-toolbar[data-minimized='true'] .wwi-logo svg { margin: 0; width: 16px; height: 16px; display: block; opacity: 0.7; filter: none; transition: opacity 0.2s; } .wwi-toolbar[data-minimized='true']:hover .wwi-logo svg { opacity: 1; filter: none; } .wwi-toolbar[data-minimized='true'] .wwi-header .wwi-logo span, .wwi-toolbar[data-minimized='true'] .wwi-logo .logo-text, .wwi-toolbar[data-minimized='true'] .wwi-info, .wwi-toolbar[data-minimized='true'] .wwi-actions, .wwi-toolbar[data-minimized='true'] .wwi-controls { display: none; } .wwi-overlay { position: fixed; z-index: ${DESIGN_SYSTEM.zIndex.OVERLAY}; pointer-events: none; background: repeating-linear-gradient( 45deg, rgba(102, 126, 234, 0.5), rgba(102, 126, 234, 0.5) 5px, rgba(255, 255, 255, 0.5) 5px, rgba(255, 255, 255, 0.5) 10px ); border: 2px solid rgba(102, 126, 234, 0.9); border-radius: 0; box-sizing: border-box; } .wwi-tooltip { /* Styles dynamically injected by ensureTooltipStyles */ visibility: hidden; } .wwi-header { display: flex; align-items: center; gap: 0.375rem; padding-right: 0.5rem; } .wwi-logo { display: flex; align-items: center; gap: 0.375rem; font-weight: 600; color: var(--wwi-accent, #888); font-size: 0.75rem; user-select: none; } .wwi-logo svg { cursor: pointer; color: var(--wwi-accent, #888); width: 16px; height: 16px; margin-right: 0.125rem; } .logo-text { display: flex; flex-direction: column; line-height: 1; gap: 0.125rem; align-items: flex-start; } .logo-title { font-weight: 700; font-size: 0.7rem; color: var(--wwi-accent, #888); letter-spacing: 0.05em; text-transform: uppercase; } .logo-subtitle { font-weight: 500; font-size: 0.5rem; color: var(--wwi-accent, #888); opacity: 0.75; letter-spacing: 0.14em; text-transform: uppercase; } .wwi-info { display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: var(--wwi-fg, #222); opacity: 0.7; } .widget-count { color: var(--wwi-accent, #888); font-weight: 500; } .wwi-actions { display: flex; align-items: center; gap: 0.375rem; } .wwi-controls { display: flex; align-items: center; gap: 0.125rem; } .wwi-btn { display: flex; align-items: center; gap: 0.25rem; background: transparent; border: 1px solid transparent; color: var(--wwi-fg, #222); padding: 0.25rem 0.5rem; border-radius: 0.25rem; cursor: pointer; transition: all 0.15s ease; font-size: 0.75rem; font-weight: 500; white-space: nowrap; opacity: 0.8; } .wwi-btn:hover { background: rgba(0, 0, 0, 0.12); border-color: rgba(0, 0, 0, 0.2); color: var(--wwi-fg, #111); opacity: 1; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } .wwi-btn[data-selected='true'] { background: rgba(34, 197, 94, 0.15); border-color: #22c55e; box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); opacity: 1; position: relative; } .wwi-btn svg { flex-shrink: 0; width: 14px; height: 14px; stroke: currentColor; opacity: 0.9; } .wwi-controls .wwi-btn { padding: 0.25rem; border-radius: 50%; } .wwi-toolbar[data-inspector-active='true'] { background: var(--wwi-bg, rgba(255, 255, 255, 0.98)); } @media (max-width: 768px) { .wwi-toolbar { bottom: 0.75rem; left: 0.75rem; right: 0.75rem; flex-wrap: wrap; max-width: calc(100vw - 1.25rem); } } `; __decorateClass([ property({ type: String }) ], HTMLWebWidgetInspectorElement.prototype, "dir", 2); __decorateClass([ property({ type: Array }) ], HTMLWebWidgetInspectorElement.prototype, "keys", 2); __decorateClass([ property({ type: String, attribute: "page-source" }) ], HTMLWebWidgetInspectorElement.prototype, "pageSource", 2); __decorateClass([ property({ type: String }) ], HTMLWebWidgetInspectorElement.prototype, "theme", 1); __decorateClass([ state() ], HTMLWebWidgetInspectorElement.prototype, "isToolbarVisible", 2); __decorateClass([ state() ], HTMLWebWidgetInspectorElement.prototype, "isInspectorMode", 2); __decorateClass([ state() ], HTMLWebWidgetInspectorElement.prototype, "isMinimized", 2); __decorateClass([ state() ], HTMLWebWidgetInspectorElement.prototype, "hoveredElement", 2); __decorateClass([ state() ], HTMLWebWidgetInspectorElement.prototype, "widgetCount", 2); export { HTMLWebWidgetInspectorElement }; //# sourceMappingURL=element.js.map //# sourceMappingURL=element.js.map