{"version":3,"file":"devtools.cjs","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["import type { ResolveStatus, Verbaly } from './types';\n\nexport interface DevtoolsOptions {\n  root?: ParentNode;\n  attribute?: string;\n  hotkey?: 'alt' | 'ctrl' | 'shift' | 'meta';\n  catalogDir?: string;\n}\n\ninterface Bound {\n  key: string;\n  status: ResolveStatus;\n}\n\nconst HOTKEY: Record<NonNullable<DevtoolsOptions['hotkey']>, keyof MouseEvent> = {\n  alt: 'altKey',\n  ctrl: 'ctrlKey',\n  shift: 'shiftKey',\n  meta: 'metaKey',\n};\n\nfunction statusOf(info: { from: string } | undefined, currentLocale: string): ResolveStatus {\n  return !info ? 'miss' : info.from === currentLocale ? 'hit' : 'fallback';\n}\n\n// dev-only inspector: \"what key is this text?\" in the browser (opt-in, tree-shakeable)\nexport function attachDevtools(instance: Verbaly, options: DevtoolsOptions = {}): () => void {\n  if (typeof document === 'undefined') throw new Error('[verbaly] devtools requires a DOM');\n  const root = options.root ?? document.body;\n  const attr = options.attribute ?? 'data-verbaly';\n  const modifier = HOTKEY[options.hotkey ?? 'alt'];\n  const dir = options.catalogDir ?? 'locales';\n\n  const style = document.createElement('style');\n  style.textContent = CSS;\n  document.head.appendChild(style);\n\n  const panel = document.createElement('div');\n  panel.className = 'verbaly-dt';\n  root.appendChild(panel);\n\n  const tip = document.createElement('div');\n  tip.className = 'verbaly-dt-tip';\n  tip.hidden = true;\n  root.appendChild(tip);\n\n  function scan(): Bound[] {\n    const out: Bound[] = [];\n    for (const el of root.querySelectorAll(`[${attr}]`)) {\n      const key = el.getAttribute(attr);\n      if (!key) continue;\n      const info = instance.inspect(key);\n      out.push({ key, status: statusOf(info, instance.locale) });\n    }\n    return out;\n  }\n\n  function render(): void {\n    const bound = scan();\n    const miss = bound.filter((b) => b.status === 'miss');\n    const fb = bound.filter((b) => b.status === 'fallback');\n    const rows = miss\n      .map(\n        (b) =>\n          `<li><code>${esc(b.key)}</code><span>add to <code>${esc(dir)}/${esc(instance.locale)}.json</code></span></li>`,\n      )\n      .join('');\n    panel.innerHTML =\n      `<div class=\"verbaly-dt-hd\"><b>verbaly</b> · ${esc(instance.locale)}</div>` +\n      `<div class=\"verbaly-dt-stats\">` +\n      `<span class=\"ok\">${bound.length - miss.length - fb.length} ok</span>` +\n      `<span class=\"fb\">${fb.length} fallback</span>` +\n      `<span class=\"miss\">${miss.length} missing</span></div>` +\n      (rows ? `<ul class=\"verbaly-dt-miss\">${rows}</ul>` : '') +\n      `<div class=\"verbaly-dt-hint\">hold <kbd>${options.hotkey ?? 'Alt'}</kbd> + hover to inspect</div>`;\n  }\n\n  function onMove(e: MouseEvent): void {\n    if (!e[modifier]) {\n      tip.hidden = true;\n      return;\n    }\n    const el = (e.target as Element | null)?.closest?.(`[${attr}]`);\n    if (!el) {\n      tip.hidden = true;\n      return;\n    }\n    const key = el.getAttribute(attr)!;\n    const info = instance.inspect(key);\n    const status = statusOf(info, instance.locale);\n    tip.innerHTML =\n      `<div class=\"verbaly-dt-key\">${esc(key)}</div>` +\n      `<div class=\"verbaly-dt-row ${status}\">${status}${info && info.from !== instance.locale ? ` · from ${esc(info.from)}` : ''}</div>` +\n      (info ? `<div class=\"verbaly-dt-src\">${esc(info.source)}</div>` : '');\n    tip.hidden = false;\n    tip.style.left = `${Math.min(e.clientX + 14, window.innerWidth - 320)}px`;\n    tip.style.top = `${e.clientY + 14}px`;\n  }\n\n  const unsub = instance.subscribe(render);\n  // own panel/tip writes must not re-trigger render (infinite microtask loop)\n  const observer = new MutationObserver((records) => {\n    if (records.every((r) => panel.contains(r.target) || tip.contains(r.target))) return;\n    render();\n  });\n  observer.observe(root, {\n    childList: true,\n    subtree: true,\n    attributes: true,\n    attributeFilter: [attr],\n  });\n  document.addEventListener('mousemove', onMove);\n  render();\n\n  return () => {\n    unsub();\n    observer.disconnect();\n    document.removeEventListener('mousemove', onMove);\n    panel.remove();\n    tip.remove();\n    style.remove();\n  };\n}\n\nfunction esc(text: string): string {\n  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nconst CSS = `\n.verbaly-dt{position:fixed;bottom:12px;right:12px;z-index:2147483646;max-width:300px;font:12px/1.4 ui-monospace,monospace;color:#e6f0e0;background:#0b120c;border:1px solid #2a3a2c;border-radius:10px;padding:10px;box-shadow:0 8px 30px rgba(0,0,0,.4)}\n.verbaly-dt-hd{font-size:11px;opacity:.85;margin-bottom:6px}\n.verbaly-dt-stats{display:flex;gap:8px;margin-bottom:6px}\n.verbaly-dt-stats span{padding:2px 6px;border-radius:6px;background:#152016}\n.verbaly-dt-stats .ok{color:#bef12d}\n.verbaly-dt-stats .fb{color:#f1c40f}\n.verbaly-dt-stats .miss{color:#ff6b6b}\n.verbaly-dt-miss{list-style:none;margin:0 0 6px;padding:0;max-height:180px;overflow:auto}\n.verbaly-dt-miss li{display:flex;flex-direction:column;gap:2px;padding:4px 0;border-top:1px solid #1c281d}\n.verbaly-dt-miss span{opacity:.6;font-size:11px}\n.verbaly-dt-hint{opacity:.55;font-size:11px}\n.verbaly-dt-hint kbd{background:#152016;border-radius:4px;padding:0 4px}\n.verbaly-dt-tip{position:fixed;z-index:2147483647;max-width:300px;font:12px/1.4 ui-monospace,monospace;color:#e6f0e0;background:#0b120c;border:1px solid #2a3a2c;border-radius:8px;padding:8px;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.4)}\n.verbaly-dt-key{color:#bef12d;font-weight:600;margin-bottom:4px}\n.verbaly-dt-row{margin-bottom:4px}\n.verbaly-dt-row.miss{color:#ff6b6b}\n.verbaly-dt-row.fallback{color:#f1c40f}\n.verbaly-dt-row.hit{color:#7bd88f}\n.verbaly-dt-src{opacity:.75;white-space:pre-wrap;word-break:break-word}\n`;\n"],"mappings":"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"}