@staticcanvas/typeflow 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +637 -0
- package/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
- package/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
- package/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
- package/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
- package/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
- package/branding/dist/png/typeflow-logo-128x128.png +0 -0
- package/branding/dist/png/typeflow-logo-16x16.png +0 -0
- package/branding/dist/png/typeflow-logo-256x256.png +0 -0
- package/branding/dist/png/typeflow-logo-512x512.png +0 -0
- package/branding/dist/png/typeflow-logo-64x64.png +0 -0
- package/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
- package/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
- package/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
- package/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
- package/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
- package/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
- package/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
- package/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
- package/branding/gen-logo.ps1 +14 -0
- package/branding/typeflow-logo.svg +162 -0
- package/dist/typeflow/LICENSE +21 -0
- package/dist/typeflow/README.md +231 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-128x128.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-16x16.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-256x256.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-512x512.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-64x64.png +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
- package/dist/typeflow/branding/gen-logo.ps1 +14 -0
- package/dist/typeflow/branding/typeflow-logo.svg +162 -0
- package/dist/typeflow/dist/typeflow-caret.cjs +47 -0
- package/dist/typeflow/dist/typeflow-caret.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.esm.js +47 -0
- package/dist/typeflow/dist/typeflow-caret.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-caret.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.js +47 -0
- package/dist/typeflow/dist/typeflow-caret.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.min.js +2 -0
- package/dist/typeflow/dist/typeflow-caret.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.mjs +47 -0
- package/dist/typeflow/dist/typeflow-caret.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.cjs +109 -0
- package/dist/typeflow/dist/typeflow-debug.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.esm.js +109 -0
- package/dist/typeflow/dist/typeflow-debug.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-debug.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.js +109 -0
- package/dist/typeflow/dist/typeflow-debug.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.min.js +2 -0
- package/dist/typeflow/dist/typeflow-debug.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.mjs +109 -0
- package/dist/typeflow/dist/typeflow-debug.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.cjs +66 -0
- package/dist/typeflow/dist/typeflow-extchars.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.js +66 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.js +66 -0
- package/dist/typeflow/dist/typeflow-extchars.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.min.js +2 -0
- package/dist/typeflow/dist/typeflow-extchars.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.mjs +66 -0
- package/dist/typeflow/dist/typeflow-extchars.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.cjs +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.js +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.js +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.min.js +2 -0
- package/dist/typeflow/dist/typeflow-keystroke.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.mjs +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.cjs +76 -0
- package/dist/typeflow/dist/typeflow-metrics.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.js +76 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.js +76 -0
- package/dist/typeflow/dist/typeflow-metrics.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.min.js +2 -0
- package/dist/typeflow/dist/typeflow-metrics.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.mjs +76 -0
- package/dist/typeflow/dist/typeflow-metrics.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.cjs +3 -0
- package/dist/typeflow/dist/typeflow-parallel.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.js +3 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.js +3 -0
- package/dist/typeflow/dist/typeflow-parallel.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.min.js +2 -0
- package/dist/typeflow/dist/typeflow-parallel.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.mjs +3 -0
- package/dist/typeflow/dist/typeflow-parallel.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.cjs +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.js +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.js +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.min.js +2 -0
- package/dist/typeflow/dist/typeflow-webaudio.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.mjs +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow.cjs +277 -0
- package/dist/typeflow/dist/typeflow.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow.esm.js +277 -0
- package/dist/typeflow/dist/typeflow.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow.js +277 -0
- package/dist/typeflow/dist/typeflow.js.map +1 -0
- package/dist/typeflow/dist/typeflow.min.js +2 -0
- package/dist/typeflow/dist/typeflow.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow.mjs +277 -0
- package/dist/typeflow/dist/typeflow.mjs.map +1 -0
- package/dist/typeflow/jsr.jsonc +27 -0
- package/dist/typeflow/package.json +79 -0
- package/dist/typeflow/src/typeflow-audio-presets.js +137 -0
- package/dist/typeflow/src/typeflow-caret.d.ts +65 -0
- package/dist/typeflow/src/typeflow-caret.js +303 -0
- package/dist/typeflow/src/typeflow-debug.d.ts +34 -0
- package/dist/typeflow/src/typeflow-debug.js +223 -0
- package/dist/typeflow/src/typeflow-extchars.d.ts +28 -0
- package/dist/typeflow/src/typeflow-extchars.js +132 -0
- package/dist/typeflow/src/typeflow-keystroke.d.ts +28 -0
- package/dist/typeflow/src/typeflow-keystroke.js +220 -0
- package/dist/typeflow/src/typeflow-metrics.d.ts +38 -0
- package/dist/typeflow/src/typeflow-metrics.js +556 -0
- package/dist/typeflow/src/typeflow-parallel.d.ts +36 -0
- package/dist/typeflow/src/typeflow-parallel.js +157 -0
- package/dist/typeflow/src/typeflow-webaudio.d.ts +83 -0
- package/dist/typeflow/src/typeflow-webaudio.js +517 -0
- package/dist/typeflow/src/typeflow.d.ts +328 -0
- package/dist/typeflow/src/typeflow.js +2119 -0
- package/jsr.jsonc +27 -0
- package/package.json +133 -0
- package/src/typeflow-audio-presets.js +137 -0
- package/src/typeflow-caret.d.ts +65 -0
- package/src/typeflow-caret.js +303 -0
- package/src/typeflow-debug.d.ts +34 -0
- package/src/typeflow-debug.js +223 -0
- package/src/typeflow-extchars.d.ts +28 -0
- package/src/typeflow-extchars.js +132 -0
- package/src/typeflow-keystroke.d.ts +28 -0
- package/src/typeflow-keystroke.js +220 -0
- package/src/typeflow-metrics.d.ts +38 -0
- package/src/typeflow-metrics.js +556 -0
- package/src/typeflow-parallel.d.ts +36 -0
- package/src/typeflow-parallel.js +157 -0
- package/src/typeflow-webaudio.d.ts +83 -0
- package/src/typeflow-webaudio.js +517 -0
- package/src/typeflow.d.ts +328 -0
- package/src/typeflow.js +2119 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-metrics.cjs","sources":["../../../src/typeflow-metrics.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Metrics - Real-time Performance HUD & Diagnostic Tooltip.\n * Mounts an accessible, zero-CLS floating metrics badge/window in the viewport\n * tracking active animations, FPS, typed/erased units, render latency, and heap memory.\n *\n * @module @staticcanvas/typeflow/metrics\n */\n\nexport class TypeFlowMetrics {\n static instance = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Constructs and initializes a TypeFlowMetrics tracker.\n * @param {Object} [options={}] - Configuration options.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.\n * @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.\n * @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.\n * @param {number} [options.zIndex=99999] - CSS z-index for floating badge.\n *\n * -------------\n *\n * @example\n * const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });\n */\n constructor(options = {}) {\n this.options = {\n position: 'bottom-right',\n detailed: false,\n autoTrack: true,\n updateInterval: 250,\n zIndex: 99999,\n ...options,\n };\n\n this.stats = {\n fps: 60,\n activeInstances: 0,\n totalTyped: 0,\n totalErased: 0,\n renderLatencyMs: 0,\n domMutations: 0,\n interruptions: 0,\n startTime: performance.now(),\n lastFrameTime: performance.now(),\n frameCount: 0,\n history: [],\n };\n\n this.container = null;\n this.isExpanded = Boolean(this.options.detailed);\n this.rafId = null;\n this.intervalId = null;\n this.observer = null;\n\n if (typeof window !== 'undefined') {\n this.init();\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Mounts the singleton floating HUD metrics badge into the active browser document.\n * @param {Object} [options={}] - Custom configuration options.\n * @returns {TypeFlowMetrics} The active singleton instance.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to open expanded view initially.\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.mount({ position: 'bottom-right' });\n */\n static mount(options = {}) {\n if (!TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance = new TypeFlowMetrics(options);\n }\n return TypeFlowMetrics.instance;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Unmounts the singleton floating HUD metrics badge and stops all listeners.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.unmount();\n */\n static unmount() {\n if (TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance.destroy();\n TypeFlowMetrics.instance = null;\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of current performance metrics.\n * @returns {Object | null} Metric snapshot data or null if not mounted.\n *\n * -------------\n *\n * @example\n * const stats = TypeFlowMetrics.getStats();\n * console.log('Active animations:', stats?.activeInstances);\n */\n static getStats() {\n return TypeFlowMetrics.instance ? TypeFlowMetrics.instance.getSnapshot() : null;\n }\n\n init() {\n this.createDom();\n this.startFpsTracker();\n this.startDomMutationObserver();\n\n this.intervalId = setInterval(() => {\n this.updateMetrics();\n }, this.options.updateInterval);\n }\n\n createDom() {\n if (this.container || typeof document === 'undefined') return;\n\n this.container = document.createElement('aside');\n this.container.id = 'typeflow-metrics-hud';\n this.container.setAttribute('aria-label', 'TypeFlow Performance Metrics HUD');\n this.container.setAttribute('role', 'region');\n\n const posStyles = {\n 'bottom-right': 'bottom: 16px; right: 16px;',\n 'bottom-left': 'bottom: 16px; left: 16px;',\n 'top-right': 'top: 16px; right: 16px;',\n 'top-left': 'top: 16px; left: 16px;',\n };\n\n const style = document.createElement('style');\n style.textContent = `\n #typeflow-metrics-hud {\n position: fixed;\n ${posStyles[this.options.position] || posStyles['bottom-right']}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;\n\n const pill = document.createElement('div');\n pill.className = 'tf-hud-pill';\n pill.setAttribute('role', 'button');\n pill.setAttribute('tabindex', '0');\n pill.innerHTML = `\n <span class=\"tf-hud-dot\" id=\"tf-hud-dot\"></span>\n <span class=\"tf-hud-mono\" id=\"tf-hud-fps\">60 FPS</span>\n <span class=\"tf-hud-label\">•</span>\n <span class=\"tf-hud-mono tf-hud-accent\" id=\"tf-hud-active\">0 Active</span>\n `;\n\n const modal = document.createElement('div');\n modal.className = `tf-hud-modal ${this.isExpanded ? 'open' : ''}`;\n modal.id = 'tf-hud-modal';\n modal.innerHTML = `\n <div class=\"tf-hud-header\">\n <span class=\"tf-hud-title\">TypeFlow Metrics</span>\n <button class=\"tf-hud-close\" id=\"tf-hud-close\" aria-label=\"Close Metrics Window\">✕</button>\n </div>\n <div class=\"tf-hud-grid\">\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Frame Rate</div>\n <div class=\"tf-hud-card-val tf-hud-green\" id=\"tf-hud-m-fps\">60.0 FPS</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Active Anim</div>\n <div class=\"tf-hud-card-val tf-hud-accent\" id=\"tf-hud-m-active\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Typed Units</div>\n <div class=\"tf-hud-card-val tf-hud-cyan\" id=\"tf-hud-m-typed\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Erased Units</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-erased\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">DOM Mut.</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-mut\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Heap Memory</div>\n <div class=\"tf-hud-card-val tf-hud-mono\" id=\"tf-hud-m-mem\">-- MB</div>\n </div>\n </div>\n <div class=\"tf-hud-footer\">\n <span id=\"tf-hud-uptime\">Uptime: 0s</span>\n <div style=\"display:flex;gap:4px;\">\n <button class=\"tf-hud-btn\" id=\"tf-hud-copy\">Copy JSON</button>\n <button class=\"tf-hud-btn\" id=\"tf-hud-reset\">Reset</button>\n </div>\n </div>\n `;\n\n this.container.appendChild(style);\n this.container.appendChild(pill);\n this.container.appendChild(modal);\n document.body.appendChild(this.container);\n\n pill.addEventListener('click', () => this.toggleModal());\n pill.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.toggleModal();\n }\n });\n\n modal.querySelector('#tf-hud-close')?.addEventListener('click', () => this.toggleModal(false));\n modal.querySelector('#tf-hud-reset')?.addEventListener('click', () => this.resetStats());\n modal.querySelector('#tf-hud-copy')?.addEventListener('click', () => this.copyStats());\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or sets the visibility of the expanded metrics window modal.\n * @param {boolean} [forceState] - Optional explicit state boolean.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * hud.toggleModal(true); // Open\n * hud.toggleModal(false); // Close\n */\n toggleModal(forceState) {\n this.isExpanded = forceState !== undefined ? forceState : !this.isExpanded;\n const modal = this.container?.querySelector('#tf-hud-modal');\n if (modal) {\n modal.classList.toggle('open', this.isExpanded);\n }\n }\n\n startFpsTracker() {\n let lastTime = performance.now();\n let frames = 0;\n\n const tick = (now) => {\n frames++;\n if (now >= lastTime + 1000) {\n this.stats.fps = Math.round((frames * 1000) / (now - lastTime));\n frames = 0;\n lastTime = now;\n }\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n }\n\n startDomMutationObserver() {\n if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;\n this.observer = new MutationObserver((mutations) => {\n let relevant = 0;\n for (const m of mutations) {\n if (m.target === this.container || this.container?.contains(m.target)) continue;\n if (m.target.nodeType === 1 && m.target.classList?.contains('tf-cursor')) continue;\n relevant++;\n }\n this.stats.domMutations += relevant;\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n recordTyped(count = 1) {\n this.stats.totalTyped += count;\n }\n\n recordErased(count = 1) {\n this.stats.totalErased += count;\n }\n\n recordInterruption() {\n this.stats.interruptions += 1;\n }\n\n updateMetrics() {\n if (!this.container) return;\n\n if (typeof window !== 'undefined' && window.TypeFlow?.active) {\n this.stats.activeInstances = window.TypeFlow.active.size;\n }\n\n const dot = this.container.querySelector('#tf-hud-dot');\n const fpsEl = this.container.querySelector('#tf-hud-fps');\n const activeEl = this.container.querySelector('#tf-hud-active');\n\n if (dot) dot.classList.toggle('active', this.stats.activeInstances > 0);\n if (fpsEl) fpsEl.textContent = `${this.stats.fps} FPS`;\n if (activeEl) activeEl.textContent = `${this.stats.activeInstances} Active`;\n\n if (this.isExpanded) {\n const mFps = this.container.querySelector('#tf-hud-m-fps');\n const mActive = this.container.querySelector('#tf-hud-m-active');\n const mTyped = this.container.querySelector('#tf-hud-m-typed');\n const mErased = this.container.querySelector('#tf-hud-m-erased');\n const mMut = this.container.querySelector('#tf-hud-m-mut');\n const mMem = this.container.querySelector('#tf-hud-m-mem');\n const mUptime = this.container.querySelector('#tf-hud-uptime');\n\n if (mFps) mFps.textContent = `${this.stats.fps}.0 FPS`;\n if (mActive) mActive.textContent = `${this.stats.activeInstances}`;\n if (mTyped) mTyped.textContent = this.stats.totalTyped.toLocaleString();\n if (mErased) mErased.textContent = this.stats.totalErased.toLocaleString();\n if (mMut) mMut.textContent = this.stats.domMutations.toLocaleString();\n\n if (mMem) {\n if (performance.memory?.usedJSHeapSize) {\n const mb = (performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);\n mMem.textContent = `${mb} MB`;\n } else {\n mMem.textContent = 'Native';\n }\n }\n\n if (mUptime) {\n const sec = Math.floor((performance.now() - this.stats.startTime) / 1000);\n mUptime.textContent = `Uptime: ${sec}s`;\n }\n }\n }\n\n resetStats() {\n this.stats.totalTyped = 0;\n this.stats.totalErased = 0;\n this.stats.domMutations = 0;\n this.stats.interruptions = 0;\n this.stats.startTime = performance.now();\n this.updateMetrics();\n }\n\n copyStats() {\n const json = JSON.stringify(this.getSnapshot(), null, 2);\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json);\n const btn = this.container?.querySelector('#tf-hud-copy');\n if (btn) {\n const prev = btn.textContent;\n btn.textContent = 'Copied!';\n setTimeout(() => {\n btn.textContent = prev;\n }, 1500);\n }\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of lifetime metrics.\n * @returns {Object} Metric data snapshot.\n */\n getSnapshot() {\n return {\n timestamp: new Date().toISOString(),\n fps: this.stats.fps,\n activeInstances: this.stats.activeInstances,\n totalTyped: this.stats.totalTyped,\n totalErased: this.stats.totalErased,\n domMutations: this.stats.domMutations,\n interruptions: this.stats.interruptions,\n uptimeSeconds: Math.floor((performance.now() - this.stats.startTime) / 1000),\n };\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Cleans up observers, timers, and removes the HUD from the DOM.\n * @returns {void}\n */\n destroy() {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n if (this.intervalId) clearInterval(this.intervalId);\n if (this.observer) this.observer.disconnect();\n if (this.container?.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.container = null;\n }\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowMetrics = TypeFlowMetrics;\n}\n"],"names":["_TypeFlowMetrics","constructor","options","this","__spreadValues","position","detailed","autoTrack","updateInterval","zIndex","stats","fps","activeInstances","totalTyped","totalErased","renderLatencyMs","domMutations","interruptions","startTime","performance","now","lastFrameTime","frameCount","history","container","isExpanded","Boolean","rafId","intervalId","observer","window","init","mount","instance","unmount","destroy","getStats","getSnapshot","createDom","startFpsTracker","startDomMutationObserver","setInterval","updateMetrics","document","createElement","id","setAttribute","posStyles","style","textContent","pill","className","innerHTML","modal","appendChild","body","addEventListener","toggleModal","e","key","preventDefault","_a","querySelector","_b","resetStats","_c","copyStats","forceState","classList","toggle","lastTime","frames","tick","Math","round","requestAnimationFrame","MutationObserver","mutations","relevant","m","target","contains","nodeType","observe","childList","subtree","characterData","recordTyped","count","recordErased","recordInterruption","TypeFlow","active","size","dot","fpsEl","activeEl","mFps","mActive","mTyped","mErased","mMut","mMem","mUptime","toLocaleString","memory","usedJSHeapSize","mb","toFixed","sec","floor","json","JSON","stringify","navigator","clipboard","writeText","btn","prev","setTimeout","timestamp","Date","toISOString","uptimeSeconds","cancelAnimationFrame","clearInterval","disconnect","parentNode","removeChild","TypeFlowMetrics"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*! typeflow-metrics | MIT */
|
|
2
|
+
var t=Object.defineProperty,n=Object.getOwnPropertySymbols,e=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,i=(n,e,s)=>e in n?t(n,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):n[e]=s;const a=class t{
|
|
3
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
4
|
+
* @description Constructs and initializes a TypeFlowMetrics tracker.
|
|
5
|
+
* @param {Object} [options={}] - Configuration options.
|
|
6
|
+
*
|
|
7
|
+
* -------------
|
|
8
|
+
*
|
|
9
|
+
* @Properties
|
|
10
|
+
* @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
|
|
11
|
+
* @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.
|
|
12
|
+
* @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.
|
|
13
|
+
* @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.
|
|
14
|
+
* @param {number} [options.zIndex=99999] - CSS z-index for floating badge.
|
|
15
|
+
*
|
|
16
|
+
* -------------
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });
|
|
20
|
+
*/
|
|
21
|
+
constructor(t={}){this.options=((t,a)=>{for(var o in a||(a={}))e.call(a,o)&&i(t,o,a[o]);if(n)for(var o of n(a))s.call(a,o)&&i(t,o,a[o]);return t})({position:"bottom-right",detailed:!1,autoTrack:!0,updateInterval:250,zIndex:99999},t),this.stats={fps:60,activeInstances:0,totalTyped:0,totalErased:0,renderLatencyMs:0,domMutations:0,interruptions:0,startTime:performance.now(),lastFrameTime:performance.now(),frameCount:0,history:[]},this.container=null,this.isExpanded=Boolean(this.options.detailed),this.rafId=null,this.intervalId=null,this.observer=null,"undefined"!=typeof window&&this.init()}
|
|
22
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
23
|
+
* @description Mounts the singleton floating HUD metrics badge into the active browser document.
|
|
24
|
+
* @param {Object} [options={}] - Custom configuration options.
|
|
25
|
+
* @returns {TypeFlowMetrics} The active singleton instance.
|
|
26
|
+
*
|
|
27
|
+
* -------------
|
|
28
|
+
*
|
|
29
|
+
* @Properties
|
|
30
|
+
* @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
|
|
31
|
+
* @param {boolean} [options.detailed=false] - Whether to open expanded view initially.
|
|
32
|
+
*
|
|
33
|
+
* -------------
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* TypeFlowMetrics.mount({ position: 'bottom-right' });
|
|
37
|
+
*/static mount(n={}){return t.instance||(t.instance=new t(n)),t.instance}
|
|
38
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
39
|
+
* @description Unmounts the singleton floating HUD metrics badge and stops all listeners.
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*
|
|
42
|
+
* -------------
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* TypeFlowMetrics.unmount();
|
|
46
|
+
*/static unmount(){t.instance&&(t.instance.destroy(),t.instance=null)}
|
|
47
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
48
|
+
* @description Returns a snapshot object of current performance metrics.
|
|
49
|
+
* @returns {Object | null} Metric snapshot data or null if not mounted.
|
|
50
|
+
*
|
|
51
|
+
* -------------
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const stats = TypeFlowMetrics.getStats();
|
|
55
|
+
* console.log('Active animations:', stats?.activeInstances);
|
|
56
|
+
*/static getStats(){return t.instance?t.instance.getSnapshot():null}init(){this.createDom(),this.startFpsTracker(),this.startDomMutationObserver(),this.intervalId=setInterval(()=>{this.updateMetrics()},this.options.updateInterval)}createDom(){var t,n,e;if(this.container||"undefined"==typeof document)return;this.container=document.createElement("aside"),this.container.id="typeflow-metrics-hud",this.container.setAttribute("aria-label","TypeFlow Performance Metrics HUD"),this.container.setAttribute("role","region");const s={"bottom-right":"bottom: 16px; right: 16px;","bottom-left":"bottom: 16px; left: 16px;","top-right":"top: 16px; right: 16px;","top-left":"top: 16px; left: 16px;"},i=document.createElement("style");i.textContent=`\n #typeflow-metrics-hud {\n position: fixed;\n ${s[this.options.position]||s["bottom-right"]}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;const a=document.createElement("div");a.className="tf-hud-pill",a.setAttribute("role","button"),a.setAttribute("tabindex","0"),a.innerHTML='\n <span class="tf-hud-dot" id="tf-hud-dot"></span>\n <span class="tf-hud-mono" id="tf-hud-fps">60 FPS</span>\n <span class="tf-hud-label">•</span>\n <span class="tf-hud-mono tf-hud-accent" id="tf-hud-active">0 Active</span>\n ';const o=document.createElement("div");o.className="tf-hud-modal "+(this.isExpanded?"open":""),o.id="tf-hud-modal",o.innerHTML='\n <div class="tf-hud-header">\n <span class="tf-hud-title">TypeFlow Metrics</span>\n <button class="tf-hud-close" id="tf-hud-close" aria-label="Close Metrics Window">✕</button>\n </div>\n <div class="tf-hud-grid">\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Frame Rate</div>\n <div class="tf-hud-card-val tf-hud-green" id="tf-hud-m-fps">60.0 FPS</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Active Anim</div>\n <div class="tf-hud-card-val tf-hud-accent" id="tf-hud-m-active">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Typed Units</div>\n <div class="tf-hud-card-val tf-hud-cyan" id="tf-hud-m-typed">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Erased Units</div>\n <div class="tf-hud-card-val" id="tf-hud-m-erased">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">DOM Mut.</div>\n <div class="tf-hud-card-val" id="tf-hud-m-mut">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Heap Memory</div>\n <div class="tf-hud-card-val tf-hud-mono" id="tf-hud-m-mem">-- MB</div>\n </div>\n </div>\n <div class="tf-hud-footer">\n <span id="tf-hud-uptime">Uptime: 0s</span>\n <div style="display:flex;gap:4px;">\n <button class="tf-hud-btn" id="tf-hud-copy">Copy JSON</button>\n <button class="tf-hud-btn" id="tf-hud-reset">Reset</button>\n </div>\n </div>\n ',this.container.appendChild(i),this.container.appendChild(a),this.container.appendChild(o),document.body.appendChild(this.container),a.addEventListener("click",()=>this.toggleModal()),a.addEventListener("keydown",t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggleModal())}),null==(t=o.querySelector("#tf-hud-close"))||t.addEventListener("click",()=>this.toggleModal(!1)),null==(n=o.querySelector("#tf-hud-reset"))||n.addEventListener("click",()=>this.resetStats()),null==(e=o.querySelector("#tf-hud-copy"))||e.addEventListener("click",()=>this.copyStats())}
|
|
57
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
58
|
+
* @description Toggles or sets the visibility of the expanded metrics window modal.
|
|
59
|
+
* @param {boolean} [forceState] - Optional explicit state boolean.
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*
|
|
62
|
+
* -------------
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* hud.toggleModal(true); // Open
|
|
66
|
+
* hud.toggleModal(false); // Close
|
|
67
|
+
*/toggleModal(t){var n;this.isExpanded=void 0!==t?t:!this.isExpanded;const e=null==(n=this.container)?void 0:n.querySelector("#tf-hud-modal");e&&e.classList.toggle("open",this.isExpanded)}startFpsTracker(){let t=performance.now(),n=0;const e=s=>{n++,s>=t+1e3&&(this.stats.fps=Math.round(1e3*n/(s-t)),n=0,t=s),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}startDomMutationObserver(){"undefined"!=typeof MutationObserver&&"undefined"!=typeof document&&(this.observer=new MutationObserver(t=>{var n,e;let s=0;for(const i of t)i.target===this.container||(null==(n=this.container)?void 0:n.contains(i.target))||1===i.target.nodeType&&(null==(e=i.target.classList)?void 0:e.contains("tf-cursor"))||s++;this.stats.domMutations+=s}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}recordTyped(t=1){this.stats.totalTyped+=t}recordErased(t=1){this.stats.totalErased+=t}recordInterruption(){this.stats.interruptions+=1}updateMetrics(){var t,n;if(!this.container)return;"undefined"!=typeof window&&(null==(t=window.TypeFlow)?void 0:t.active)&&(this.stats.activeInstances=window.TypeFlow.active.size);const e=this.container.querySelector("#tf-hud-dot"),s=this.container.querySelector("#tf-hud-fps"),i=this.container.querySelector("#tf-hud-active");if(e&&e.classList.toggle("active",this.stats.activeInstances>0),s&&(s.textContent=`${this.stats.fps} FPS`),i&&(i.textContent=`${this.stats.activeInstances} Active`),this.isExpanded){const t=this.container.querySelector("#tf-hud-m-fps"),e=this.container.querySelector("#tf-hud-m-active"),s=this.container.querySelector("#tf-hud-m-typed"),i=this.container.querySelector("#tf-hud-m-erased"),a=this.container.querySelector("#tf-hud-m-mut"),o=this.container.querySelector("#tf-hud-m-mem"),r=this.container.querySelector("#tf-hud-uptime");if(t&&(t.textContent=`${this.stats.fps}.0 FPS`),e&&(e.textContent=`${this.stats.activeInstances}`),s&&(s.textContent=this.stats.totalTyped.toLocaleString()),i&&(i.textContent=this.stats.totalErased.toLocaleString()),a&&(a.textContent=this.stats.domMutations.toLocaleString()),o)if(null==(n=performance.memory)?void 0:n.usedJSHeapSize){const t=(performance.memory.usedJSHeapSize/1048576).toFixed(1);o.textContent=`${t} MB`}else o.textContent="Native";if(r){const t=Math.floor((performance.now()-this.stats.startTime)/1e3);r.textContent=`Uptime: ${t}s`}}}resetStats(){this.stats.totalTyped=0,this.stats.totalErased=0,this.stats.domMutations=0,this.stats.interruptions=0,this.stats.startTime=performance.now(),this.updateMetrics()}copyStats(){var t,n;const e=JSON.stringify(this.getSnapshot(),null,2);if(null==(t=navigator.clipboard)?void 0:t.writeText){navigator.clipboard.writeText(e);const t=null==(n=this.container)?void 0:n.querySelector("#tf-hud-copy");if(t){const n=t.textContent;t.textContent="Copied!",setTimeout(()=>{t.textContent=n},1500)}}}
|
|
68
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
69
|
+
* @description Returns a snapshot object of lifetime metrics.
|
|
70
|
+
* @returns {Object} Metric data snapshot.
|
|
71
|
+
*/getSnapshot(){return{timestamp:(new Date).toISOString(),fps:this.stats.fps,activeInstances:this.stats.activeInstances,totalTyped:this.stats.totalTyped,totalErased:this.stats.totalErased,domMutations:this.stats.domMutations,interruptions:this.stats.interruptions,uptimeSeconds:Math.floor((performance.now()-this.stats.startTime)/1e3)}}
|
|
72
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
73
|
+
* @description Cleans up observers, timers, and removes the HUD from the DOM.
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/destroy(){var t;this.rafId&&cancelAnimationFrame(this.rafId),this.intervalId&&clearInterval(this.intervalId),this.observer&&this.observer.disconnect(),(null==(t=this.container)?void 0:t.parentNode)&&this.container.parentNode.removeChild(this.container),this.container=null}};var o;i(a,"symbol"!=typeof(o="instance")?o+"":o,null);let r=a;"undefined"!=typeof window&&(window.TypeFlowMetrics=r);export{r as TypeFlowMetrics};
|
|
76
|
+
//# sourceMappingURL=typeflow-metrics.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-metrics.esm.js","sources":["../../../src/typeflow-metrics.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Metrics - Real-time Performance HUD & Diagnostic Tooltip.\n * Mounts an accessible, zero-CLS floating metrics badge/window in the viewport\n * tracking active animations, FPS, typed/erased units, render latency, and heap memory.\n *\n * @module @staticcanvas/typeflow/metrics\n */\n\nexport class TypeFlowMetrics {\n static instance = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Constructs and initializes a TypeFlowMetrics tracker.\n * @param {Object} [options={}] - Configuration options.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.\n * @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.\n * @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.\n * @param {number} [options.zIndex=99999] - CSS z-index for floating badge.\n *\n * -------------\n *\n * @example\n * const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });\n */\n constructor(options = {}) {\n this.options = {\n position: 'bottom-right',\n detailed: false,\n autoTrack: true,\n updateInterval: 250,\n zIndex: 99999,\n ...options,\n };\n\n this.stats = {\n fps: 60,\n activeInstances: 0,\n totalTyped: 0,\n totalErased: 0,\n renderLatencyMs: 0,\n domMutations: 0,\n interruptions: 0,\n startTime: performance.now(),\n lastFrameTime: performance.now(),\n frameCount: 0,\n history: [],\n };\n\n this.container = null;\n this.isExpanded = Boolean(this.options.detailed);\n this.rafId = null;\n this.intervalId = null;\n this.observer = null;\n\n if (typeof window !== 'undefined') {\n this.init();\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Mounts the singleton floating HUD metrics badge into the active browser document.\n * @param {Object} [options={}] - Custom configuration options.\n * @returns {TypeFlowMetrics} The active singleton instance.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to open expanded view initially.\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.mount({ position: 'bottom-right' });\n */\n static mount(options = {}) {\n if (!TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance = new TypeFlowMetrics(options);\n }\n return TypeFlowMetrics.instance;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Unmounts the singleton floating HUD metrics badge and stops all listeners.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.unmount();\n */\n static unmount() {\n if (TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance.destroy();\n TypeFlowMetrics.instance = null;\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of current performance metrics.\n * @returns {Object | null} Metric snapshot data or null if not mounted.\n *\n * -------------\n *\n * @example\n * const stats = TypeFlowMetrics.getStats();\n * console.log('Active animations:', stats?.activeInstances);\n */\n static getStats() {\n return TypeFlowMetrics.instance ? TypeFlowMetrics.instance.getSnapshot() : null;\n }\n\n init() {\n this.createDom();\n this.startFpsTracker();\n this.startDomMutationObserver();\n\n this.intervalId = setInterval(() => {\n this.updateMetrics();\n }, this.options.updateInterval);\n }\n\n createDom() {\n if (this.container || typeof document === 'undefined') return;\n\n this.container = document.createElement('aside');\n this.container.id = 'typeflow-metrics-hud';\n this.container.setAttribute('aria-label', 'TypeFlow Performance Metrics HUD');\n this.container.setAttribute('role', 'region');\n\n const posStyles = {\n 'bottom-right': 'bottom: 16px; right: 16px;',\n 'bottom-left': 'bottom: 16px; left: 16px;',\n 'top-right': 'top: 16px; right: 16px;',\n 'top-left': 'top: 16px; left: 16px;',\n };\n\n const style = document.createElement('style');\n style.textContent = `\n #typeflow-metrics-hud {\n position: fixed;\n ${posStyles[this.options.position] || posStyles['bottom-right']}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;\n\n const pill = document.createElement('div');\n pill.className = 'tf-hud-pill';\n pill.setAttribute('role', 'button');\n pill.setAttribute('tabindex', '0');\n pill.innerHTML = `\n <span class=\"tf-hud-dot\" id=\"tf-hud-dot\"></span>\n <span class=\"tf-hud-mono\" id=\"tf-hud-fps\">60 FPS</span>\n <span class=\"tf-hud-label\">•</span>\n <span class=\"tf-hud-mono tf-hud-accent\" id=\"tf-hud-active\">0 Active</span>\n `;\n\n const modal = document.createElement('div');\n modal.className = `tf-hud-modal ${this.isExpanded ? 'open' : ''}`;\n modal.id = 'tf-hud-modal';\n modal.innerHTML = `\n <div class=\"tf-hud-header\">\n <span class=\"tf-hud-title\">TypeFlow Metrics</span>\n <button class=\"tf-hud-close\" id=\"tf-hud-close\" aria-label=\"Close Metrics Window\">✕</button>\n </div>\n <div class=\"tf-hud-grid\">\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Frame Rate</div>\n <div class=\"tf-hud-card-val tf-hud-green\" id=\"tf-hud-m-fps\">60.0 FPS</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Active Anim</div>\n <div class=\"tf-hud-card-val tf-hud-accent\" id=\"tf-hud-m-active\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Typed Units</div>\n <div class=\"tf-hud-card-val tf-hud-cyan\" id=\"tf-hud-m-typed\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Erased Units</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-erased\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">DOM Mut.</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-mut\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Heap Memory</div>\n <div class=\"tf-hud-card-val tf-hud-mono\" id=\"tf-hud-m-mem\">-- MB</div>\n </div>\n </div>\n <div class=\"tf-hud-footer\">\n <span id=\"tf-hud-uptime\">Uptime: 0s</span>\n <div style=\"display:flex;gap:4px;\">\n <button class=\"tf-hud-btn\" id=\"tf-hud-copy\">Copy JSON</button>\n <button class=\"tf-hud-btn\" id=\"tf-hud-reset\">Reset</button>\n </div>\n </div>\n `;\n\n this.container.appendChild(style);\n this.container.appendChild(pill);\n this.container.appendChild(modal);\n document.body.appendChild(this.container);\n\n pill.addEventListener('click', () => this.toggleModal());\n pill.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.toggleModal();\n }\n });\n\n modal.querySelector('#tf-hud-close')?.addEventListener('click', () => this.toggleModal(false));\n modal.querySelector('#tf-hud-reset')?.addEventListener('click', () => this.resetStats());\n modal.querySelector('#tf-hud-copy')?.addEventListener('click', () => this.copyStats());\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or sets the visibility of the expanded metrics window modal.\n * @param {boolean} [forceState] - Optional explicit state boolean.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * hud.toggleModal(true); // Open\n * hud.toggleModal(false); // Close\n */\n toggleModal(forceState) {\n this.isExpanded = forceState !== undefined ? forceState : !this.isExpanded;\n const modal = this.container?.querySelector('#tf-hud-modal');\n if (modal) {\n modal.classList.toggle('open', this.isExpanded);\n }\n }\n\n startFpsTracker() {\n let lastTime = performance.now();\n let frames = 0;\n\n const tick = (now) => {\n frames++;\n if (now >= lastTime + 1000) {\n this.stats.fps = Math.round((frames * 1000) / (now - lastTime));\n frames = 0;\n lastTime = now;\n }\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n }\n\n startDomMutationObserver() {\n if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;\n this.observer = new MutationObserver((mutations) => {\n let relevant = 0;\n for (const m of mutations) {\n if (m.target === this.container || this.container?.contains(m.target)) continue;\n if (m.target.nodeType === 1 && m.target.classList?.contains('tf-cursor')) continue;\n relevant++;\n }\n this.stats.domMutations += relevant;\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n recordTyped(count = 1) {\n this.stats.totalTyped += count;\n }\n\n recordErased(count = 1) {\n this.stats.totalErased += count;\n }\n\n recordInterruption() {\n this.stats.interruptions += 1;\n }\n\n updateMetrics() {\n if (!this.container) return;\n\n if (typeof window !== 'undefined' && window.TypeFlow?.active) {\n this.stats.activeInstances = window.TypeFlow.active.size;\n }\n\n const dot = this.container.querySelector('#tf-hud-dot');\n const fpsEl = this.container.querySelector('#tf-hud-fps');\n const activeEl = this.container.querySelector('#tf-hud-active');\n\n if (dot) dot.classList.toggle('active', this.stats.activeInstances > 0);\n if (fpsEl) fpsEl.textContent = `${this.stats.fps} FPS`;\n if (activeEl) activeEl.textContent = `${this.stats.activeInstances} Active`;\n\n if (this.isExpanded) {\n const mFps = this.container.querySelector('#tf-hud-m-fps');\n const mActive = this.container.querySelector('#tf-hud-m-active');\n const mTyped = this.container.querySelector('#tf-hud-m-typed');\n const mErased = this.container.querySelector('#tf-hud-m-erased');\n const mMut = this.container.querySelector('#tf-hud-m-mut');\n const mMem = this.container.querySelector('#tf-hud-m-mem');\n const mUptime = this.container.querySelector('#tf-hud-uptime');\n\n if (mFps) mFps.textContent = `${this.stats.fps}.0 FPS`;\n if (mActive) mActive.textContent = `${this.stats.activeInstances}`;\n if (mTyped) mTyped.textContent = this.stats.totalTyped.toLocaleString();\n if (mErased) mErased.textContent = this.stats.totalErased.toLocaleString();\n if (mMut) mMut.textContent = this.stats.domMutations.toLocaleString();\n\n if (mMem) {\n if (performance.memory?.usedJSHeapSize) {\n const mb = (performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);\n mMem.textContent = `${mb} MB`;\n } else {\n mMem.textContent = 'Native';\n }\n }\n\n if (mUptime) {\n const sec = Math.floor((performance.now() - this.stats.startTime) / 1000);\n mUptime.textContent = `Uptime: ${sec}s`;\n }\n }\n }\n\n resetStats() {\n this.stats.totalTyped = 0;\n this.stats.totalErased = 0;\n this.stats.domMutations = 0;\n this.stats.interruptions = 0;\n this.stats.startTime = performance.now();\n this.updateMetrics();\n }\n\n copyStats() {\n const json = JSON.stringify(this.getSnapshot(), null, 2);\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json);\n const btn = this.container?.querySelector('#tf-hud-copy');\n if (btn) {\n const prev = btn.textContent;\n btn.textContent = 'Copied!';\n setTimeout(() => {\n btn.textContent = prev;\n }, 1500);\n }\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of lifetime metrics.\n * @returns {Object} Metric data snapshot.\n */\n getSnapshot() {\n return {\n timestamp: new Date().toISOString(),\n fps: this.stats.fps,\n activeInstances: this.stats.activeInstances,\n totalTyped: this.stats.totalTyped,\n totalErased: this.stats.totalErased,\n domMutations: this.stats.domMutations,\n interruptions: this.stats.interruptions,\n uptimeSeconds: Math.floor((performance.now() - this.stats.startTime) / 1000),\n };\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Cleans up observers, timers, and removes the HUD from the DOM.\n * @returns {void}\n */\n destroy() {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n if (this.intervalId) clearInterval(this.intervalId);\n if (this.observer) this.observer.disconnect();\n if (this.container?.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.container = null;\n }\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowMetrics = TypeFlowMetrics;\n}\n"],"names":["_TypeFlowMetrics","constructor","options","this","__spreadValues","position","detailed","autoTrack","updateInterval","zIndex","stats","fps","activeInstances","totalTyped","totalErased","renderLatencyMs","domMutations","interruptions","startTime","performance","now","lastFrameTime","frameCount","history","container","isExpanded","Boolean","rafId","intervalId","observer","window","init","mount","instance","unmount","destroy","getStats","getSnapshot","createDom","startFpsTracker","startDomMutationObserver","setInterval","updateMetrics","_a","_b","_c","document","createElement","id","setAttribute","posStyles","style","textContent","pill","className","innerHTML","modal","appendChild","body","addEventListener","toggleModal","e","key","preventDefault","querySelector","resetStats","copyStats","forceState","classList","toggle","lastTime","frames","tick","Math","round","requestAnimationFrame","MutationObserver","mutations","relevant","m","target","contains","nodeType","observe","childList","subtree","characterData","recordTyped","count","recordErased","recordInterruption","TypeFlow","active","size","dot","fpsEl","activeEl","mFps","mActive","mTyped","mErased","mMut","mMem","mUptime","toLocaleString","memory","usedJSHeapSize","mb","toFixed","sec","floor","json","JSON","stringify","navigator","clipboard","writeText","btn","prev","setTimeout","timestamp","Date","toISOString","uptimeSeconds","cancelAnimationFrame","clearInterval","disconnect","parentNode","removeChild","TypeFlowMetrics"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var t=Object.defineProperty,n=Object.getOwnPropertySymbols,e=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,i=(n,e,s)=>e in n?t(n,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):n[e]=s;const a=class t{constructor(t={}){this.options=((t,a)=>{for(var o in a||(a={}))e.call(a,o)&&i(t,o,a[o]);if(n)for(var o of n(a))s.call(a,o)&&i(t,o,a[o]);return t})({position:"bottom-right",detailed:!1,autoTrack:!0,updateInterval:250,zIndex:99999},t),this.stats={fps:60,activeInstances:0,totalTyped:0,totalErased:0,renderLatencyMs:0,domMutations:0,interruptions:0,startTime:performance.now(),lastFrameTime:performance.now(),frameCount:0,history:[]},this.container=null,this.isExpanded=Boolean(this.options.detailed),this.rafId=null,this.intervalId=null,this.observer=null,"undefined"!=typeof window&&this.init()}static mount(n={}){return t.instance||(t.instance=new t(n)),t.instance}static unmount(){t.instance&&(t.instance.destroy(),t.instance=null)}static getStats(){return t.instance?t.instance.getSnapshot():null}init(){this.createDom(),this.startFpsTracker(),this.startDomMutationObserver(),this.intervalId=setInterval(()=>{this.updateMetrics()},this.options.updateInterval)}createDom(){var t,n,e;if(this.container||"undefined"==typeof document)return;this.container=document.createElement("aside"),this.container.id="typeflow-metrics-hud",this.container.setAttribute("aria-label","TypeFlow Performance Metrics HUD"),this.container.setAttribute("role","region");const s={"bottom-right":"bottom: 16px; right: 16px;","bottom-left":"bottom: 16px; left: 16px;","top-right":"top: 16px; right: 16px;","top-left":"top: 16px; left: 16px;"},i=document.createElement("style");i.textContent=`\n #typeflow-metrics-hud {\n position: fixed;\n ${s[this.options.position]||s["bottom-right"]}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;const a=document.createElement("div");a.className="tf-hud-pill",a.setAttribute("role","button"),a.setAttribute("tabindex","0"),a.innerHTML='\n <span class="tf-hud-dot" id="tf-hud-dot"></span>\n <span class="tf-hud-mono" id="tf-hud-fps">60 FPS</span>\n <span class="tf-hud-label">•</span>\n <span class="tf-hud-mono tf-hud-accent" id="tf-hud-active">0 Active</span>\n ';const o=document.createElement("div");o.className="tf-hud-modal "+(this.isExpanded?"open":""),o.id="tf-hud-modal",o.innerHTML='\n <div class="tf-hud-header">\n <span class="tf-hud-title">TypeFlow Metrics</span>\n <button class="tf-hud-close" id="tf-hud-close" aria-label="Close Metrics Window">✕</button>\n </div>\n <div class="tf-hud-grid">\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Frame Rate</div>\n <div class="tf-hud-card-val tf-hud-green" id="tf-hud-m-fps">60.0 FPS</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Active Anim</div>\n <div class="tf-hud-card-val tf-hud-accent" id="tf-hud-m-active">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Typed Units</div>\n <div class="tf-hud-card-val tf-hud-cyan" id="tf-hud-m-typed">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Erased Units</div>\n <div class="tf-hud-card-val" id="tf-hud-m-erased">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">DOM Mut.</div>\n <div class="tf-hud-card-val" id="tf-hud-m-mut">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Heap Memory</div>\n <div class="tf-hud-card-val tf-hud-mono" id="tf-hud-m-mem">-- MB</div>\n </div>\n </div>\n <div class="tf-hud-footer">\n <span id="tf-hud-uptime">Uptime: 0s</span>\n <div style="display:flex;gap:4px;">\n <button class="tf-hud-btn" id="tf-hud-copy">Copy JSON</button>\n <button class="tf-hud-btn" id="tf-hud-reset">Reset</button>\n </div>\n </div>\n ',this.container.appendChild(i),this.container.appendChild(a),this.container.appendChild(o),document.body.appendChild(this.container),a.addEventListener("click",()=>this.toggleModal()),a.addEventListener("keydown",t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggleModal())}),null==(t=o.querySelector("#tf-hud-close"))||t.addEventListener("click",()=>this.toggleModal(!1)),null==(n=o.querySelector("#tf-hud-reset"))||n.addEventListener("click",()=>this.resetStats()),null==(e=o.querySelector("#tf-hud-copy"))||e.addEventListener("click",()=>this.copyStats())}toggleModal(t){var n;this.isExpanded=void 0!==t?t:!this.isExpanded;const e=null==(n=this.container)?void 0:n.querySelector("#tf-hud-modal");e&&e.classList.toggle("open",this.isExpanded)}startFpsTracker(){let t=performance.now(),n=0;const e=s=>{n++,s>=t+1e3&&(this.stats.fps=Math.round(1e3*n/(s-t)),n=0,t=s),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}startDomMutationObserver(){"undefined"!=typeof MutationObserver&&"undefined"!=typeof document&&(this.observer=new MutationObserver(t=>{var n,e;let s=0;for(const i of t)i.target===this.container||(null==(n=this.container)?void 0:n.contains(i.target))||1===i.target.nodeType&&(null==(e=i.target.classList)?void 0:e.contains("tf-cursor"))||s++;this.stats.domMutations+=s}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}recordTyped(t=1){this.stats.totalTyped+=t}recordErased(t=1){this.stats.totalErased+=t}recordInterruption(){this.stats.interruptions+=1}updateMetrics(){var t,n;if(!this.container)return;"undefined"!=typeof window&&(null==(t=window.TypeFlow)?void 0:t.active)&&(this.stats.activeInstances=window.TypeFlow.active.size);const e=this.container.querySelector("#tf-hud-dot"),s=this.container.querySelector("#tf-hud-fps"),i=this.container.querySelector("#tf-hud-active");if(e&&e.classList.toggle("active",this.stats.activeInstances>0),s&&(s.textContent=`${this.stats.fps} FPS`),i&&(i.textContent=`${this.stats.activeInstances} Active`),this.isExpanded){const t=this.container.querySelector("#tf-hud-m-fps"),e=this.container.querySelector("#tf-hud-m-active"),s=this.container.querySelector("#tf-hud-m-typed"),i=this.container.querySelector("#tf-hud-m-erased"),a=this.container.querySelector("#tf-hud-m-mut"),o=this.container.querySelector("#tf-hud-m-mem"),r=this.container.querySelector("#tf-hud-uptime");if(t&&(t.textContent=`${this.stats.fps}.0 FPS`),e&&(e.textContent=`${this.stats.activeInstances}`),s&&(s.textContent=this.stats.totalTyped.toLocaleString()),i&&(i.textContent=this.stats.totalErased.toLocaleString()),a&&(a.textContent=this.stats.domMutations.toLocaleString()),o)if(null==(n=performance.memory)?void 0:n.usedJSHeapSize){const t=(performance.memory.usedJSHeapSize/1048576).toFixed(1);o.textContent=`${t} MB`}else o.textContent="Native";if(r){const t=Math.floor((performance.now()-this.stats.startTime)/1e3);r.textContent=`Uptime: ${t}s`}}}resetStats(){this.stats.totalTyped=0,this.stats.totalErased=0,this.stats.domMutations=0,this.stats.interruptions=0,this.stats.startTime=performance.now(),this.updateMetrics()}copyStats(){var t,n;const e=JSON.stringify(this.getSnapshot(),null,2);if(null==(t=navigator.clipboard)?void 0:t.writeText){navigator.clipboard.writeText(e);const t=null==(n=this.container)?void 0:n.querySelector("#tf-hud-copy");if(t){const n=t.textContent;t.textContent="Copied!",setTimeout(()=>{t.textContent=n},1500)}}}getSnapshot(){return{timestamp:(new Date).toISOString(),fps:this.stats.fps,activeInstances:this.stats.activeInstances,totalTyped:this.stats.totalTyped,totalErased:this.stats.totalErased,domMutations:this.stats.domMutations,interruptions:this.stats.interruptions,uptimeSeconds:Math.floor((performance.now()-this.stats.startTime)/1e3)}}destroy(){var t;this.rafId&&cancelAnimationFrame(this.rafId),this.intervalId&&clearInterval(this.intervalId),this.observer&&this.observer.disconnect(),(null==(t=this.container)?void 0:t.parentNode)&&this.container.parentNode.removeChild(this.container),this.container=null}};var o;i(a,"symbol"!=typeof(o="instance")?o+"":o,null);let r=a;"undefined"!=typeof window&&(window.TypeFlowMetrics=r);export{r as TypeFlowMetrics};
|
|
2
|
+
//# sourceMappingURL=typeflow-metrics.esm.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-metrics.esm.min.js","sources":["../../../src/typeflow-metrics.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Metrics - Real-time Performance HUD & Diagnostic Tooltip.\n * Mounts an accessible, zero-CLS floating metrics badge/window in the viewport\n * tracking active animations, FPS, typed/erased units, render latency, and heap memory.\n *\n * @module @staticcanvas/typeflow/metrics\n */\n\nexport class TypeFlowMetrics {\n static instance = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Constructs and initializes a TypeFlowMetrics tracker.\n * @param {Object} [options={}] - Configuration options.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.\n * @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.\n * @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.\n * @param {number} [options.zIndex=99999] - CSS z-index for floating badge.\n *\n * -------------\n *\n * @example\n * const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });\n */\n constructor(options = {}) {\n this.options = {\n position: 'bottom-right',\n detailed: false,\n autoTrack: true,\n updateInterval: 250,\n zIndex: 99999,\n ...options,\n };\n\n this.stats = {\n fps: 60,\n activeInstances: 0,\n totalTyped: 0,\n totalErased: 0,\n renderLatencyMs: 0,\n domMutations: 0,\n interruptions: 0,\n startTime: performance.now(),\n lastFrameTime: performance.now(),\n frameCount: 0,\n history: [],\n };\n\n this.container = null;\n this.isExpanded = Boolean(this.options.detailed);\n this.rafId = null;\n this.intervalId = null;\n this.observer = null;\n\n if (typeof window !== 'undefined') {\n this.init();\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Mounts the singleton floating HUD metrics badge into the active browser document.\n * @param {Object} [options={}] - Custom configuration options.\n * @returns {TypeFlowMetrics} The active singleton instance.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to open expanded view initially.\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.mount({ position: 'bottom-right' });\n */\n static mount(options = {}) {\n if (!TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance = new TypeFlowMetrics(options);\n }\n return TypeFlowMetrics.instance;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Unmounts the singleton floating HUD metrics badge and stops all listeners.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.unmount();\n */\n static unmount() {\n if (TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance.destroy();\n TypeFlowMetrics.instance = null;\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of current performance metrics.\n * @returns {Object | null} Metric snapshot data or null if not mounted.\n *\n * -------------\n *\n * @example\n * const stats = TypeFlowMetrics.getStats();\n * console.log('Active animations:', stats?.activeInstances);\n */\n static getStats() {\n return TypeFlowMetrics.instance ? TypeFlowMetrics.instance.getSnapshot() : null;\n }\n\n init() {\n this.createDom();\n this.startFpsTracker();\n this.startDomMutationObserver();\n\n this.intervalId = setInterval(() => {\n this.updateMetrics();\n }, this.options.updateInterval);\n }\n\n createDom() {\n if (this.container || typeof document === 'undefined') return;\n\n this.container = document.createElement('aside');\n this.container.id = 'typeflow-metrics-hud';\n this.container.setAttribute('aria-label', 'TypeFlow Performance Metrics HUD');\n this.container.setAttribute('role', 'region');\n\n const posStyles = {\n 'bottom-right': 'bottom: 16px; right: 16px;',\n 'bottom-left': 'bottom: 16px; left: 16px;',\n 'top-right': 'top: 16px; right: 16px;',\n 'top-left': 'top: 16px; left: 16px;',\n };\n\n const style = document.createElement('style');\n style.textContent = `\n #typeflow-metrics-hud {\n position: fixed;\n ${posStyles[this.options.position] || posStyles['bottom-right']}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;\n\n const pill = document.createElement('div');\n pill.className = 'tf-hud-pill';\n pill.setAttribute('role', 'button');\n pill.setAttribute('tabindex', '0');\n pill.innerHTML = `\n <span class=\"tf-hud-dot\" id=\"tf-hud-dot\"></span>\n <span class=\"tf-hud-mono\" id=\"tf-hud-fps\">60 FPS</span>\n <span class=\"tf-hud-label\">•</span>\n <span class=\"tf-hud-mono tf-hud-accent\" id=\"tf-hud-active\">0 Active</span>\n `;\n\n const modal = document.createElement('div');\n modal.className = `tf-hud-modal ${this.isExpanded ? 'open' : ''}`;\n modal.id = 'tf-hud-modal';\n modal.innerHTML = `\n <div class=\"tf-hud-header\">\n <span class=\"tf-hud-title\">TypeFlow Metrics</span>\n <button class=\"tf-hud-close\" id=\"tf-hud-close\" aria-label=\"Close Metrics Window\">✕</button>\n </div>\n <div class=\"tf-hud-grid\">\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Frame Rate</div>\n <div class=\"tf-hud-card-val tf-hud-green\" id=\"tf-hud-m-fps\">60.0 FPS</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Active Anim</div>\n <div class=\"tf-hud-card-val tf-hud-accent\" id=\"tf-hud-m-active\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Typed Units</div>\n <div class=\"tf-hud-card-val tf-hud-cyan\" id=\"tf-hud-m-typed\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Erased Units</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-erased\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">DOM Mut.</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-mut\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Heap Memory</div>\n <div class=\"tf-hud-card-val tf-hud-mono\" id=\"tf-hud-m-mem\">-- MB</div>\n </div>\n </div>\n <div class=\"tf-hud-footer\">\n <span id=\"tf-hud-uptime\">Uptime: 0s</span>\n <div style=\"display:flex;gap:4px;\">\n <button class=\"tf-hud-btn\" id=\"tf-hud-copy\">Copy JSON</button>\n <button class=\"tf-hud-btn\" id=\"tf-hud-reset\">Reset</button>\n </div>\n </div>\n `;\n\n this.container.appendChild(style);\n this.container.appendChild(pill);\n this.container.appendChild(modal);\n document.body.appendChild(this.container);\n\n pill.addEventListener('click', () => this.toggleModal());\n pill.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.toggleModal();\n }\n });\n\n modal.querySelector('#tf-hud-close')?.addEventListener('click', () => this.toggleModal(false));\n modal.querySelector('#tf-hud-reset')?.addEventListener('click', () => this.resetStats());\n modal.querySelector('#tf-hud-copy')?.addEventListener('click', () => this.copyStats());\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or sets the visibility of the expanded metrics window modal.\n * @param {boolean} [forceState] - Optional explicit state boolean.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * hud.toggleModal(true); // Open\n * hud.toggleModal(false); // Close\n */\n toggleModal(forceState) {\n this.isExpanded = forceState !== undefined ? forceState : !this.isExpanded;\n const modal = this.container?.querySelector('#tf-hud-modal');\n if (modal) {\n modal.classList.toggle('open', this.isExpanded);\n }\n }\n\n startFpsTracker() {\n let lastTime = performance.now();\n let frames = 0;\n\n const tick = (now) => {\n frames++;\n if (now >= lastTime + 1000) {\n this.stats.fps = Math.round((frames * 1000) / (now - lastTime));\n frames = 0;\n lastTime = now;\n }\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n }\n\n startDomMutationObserver() {\n if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;\n this.observer = new MutationObserver((mutations) => {\n let relevant = 0;\n for (const m of mutations) {\n if (m.target === this.container || this.container?.contains(m.target)) continue;\n if (m.target.nodeType === 1 && m.target.classList?.contains('tf-cursor')) continue;\n relevant++;\n }\n this.stats.domMutations += relevant;\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n recordTyped(count = 1) {\n this.stats.totalTyped += count;\n }\n\n recordErased(count = 1) {\n this.stats.totalErased += count;\n }\n\n recordInterruption() {\n this.stats.interruptions += 1;\n }\n\n updateMetrics() {\n if (!this.container) return;\n\n if (typeof window !== 'undefined' && window.TypeFlow?.active) {\n this.stats.activeInstances = window.TypeFlow.active.size;\n }\n\n const dot = this.container.querySelector('#tf-hud-dot');\n const fpsEl = this.container.querySelector('#tf-hud-fps');\n const activeEl = this.container.querySelector('#tf-hud-active');\n\n if (dot) dot.classList.toggle('active', this.stats.activeInstances > 0);\n if (fpsEl) fpsEl.textContent = `${this.stats.fps} FPS`;\n if (activeEl) activeEl.textContent = `${this.stats.activeInstances} Active`;\n\n if (this.isExpanded) {\n const mFps = this.container.querySelector('#tf-hud-m-fps');\n const mActive = this.container.querySelector('#tf-hud-m-active');\n const mTyped = this.container.querySelector('#tf-hud-m-typed');\n const mErased = this.container.querySelector('#tf-hud-m-erased');\n const mMut = this.container.querySelector('#tf-hud-m-mut');\n const mMem = this.container.querySelector('#tf-hud-m-mem');\n const mUptime = this.container.querySelector('#tf-hud-uptime');\n\n if (mFps) mFps.textContent = `${this.stats.fps}.0 FPS`;\n if (mActive) mActive.textContent = `${this.stats.activeInstances}`;\n if (mTyped) mTyped.textContent = this.stats.totalTyped.toLocaleString();\n if (mErased) mErased.textContent = this.stats.totalErased.toLocaleString();\n if (mMut) mMut.textContent = this.stats.domMutations.toLocaleString();\n\n if (mMem) {\n if (performance.memory?.usedJSHeapSize) {\n const mb = (performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);\n mMem.textContent = `${mb} MB`;\n } else {\n mMem.textContent = 'Native';\n }\n }\n\n if (mUptime) {\n const sec = Math.floor((performance.now() - this.stats.startTime) / 1000);\n mUptime.textContent = `Uptime: ${sec}s`;\n }\n }\n }\n\n resetStats() {\n this.stats.totalTyped = 0;\n this.stats.totalErased = 0;\n this.stats.domMutations = 0;\n this.stats.interruptions = 0;\n this.stats.startTime = performance.now();\n this.updateMetrics();\n }\n\n copyStats() {\n const json = JSON.stringify(this.getSnapshot(), null, 2);\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json);\n const btn = this.container?.querySelector('#tf-hud-copy');\n if (btn) {\n const prev = btn.textContent;\n btn.textContent = 'Copied!';\n setTimeout(() => {\n btn.textContent = prev;\n }, 1500);\n }\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of lifetime metrics.\n * @returns {Object} Metric data snapshot.\n */\n getSnapshot() {\n return {\n timestamp: new Date().toISOString(),\n fps: this.stats.fps,\n activeInstances: this.stats.activeInstances,\n totalTyped: this.stats.totalTyped,\n totalErased: this.stats.totalErased,\n domMutations: this.stats.domMutations,\n interruptions: this.stats.interruptions,\n uptimeSeconds: Math.floor((performance.now() - this.stats.startTime) / 1000),\n };\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Cleans up observers, timers, and removes the HUD from the DOM.\n * @returns {void}\n */\n destroy() {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n if (this.intervalId) clearInterval(this.intervalId);\n if (this.observer) this.observer.disconnect();\n if (this.container?.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.container = null;\n }\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowMetrics = TypeFlowMetrics;\n}\n"],"names":["_TypeFlowMetrics","constructor","options","this","__spreadValues","position","detailed","autoTrack","updateInterval","zIndex","stats","fps","activeInstances","totalTyped","totalErased","renderLatencyMs","domMutations","interruptions","startTime","performance","now","lastFrameTime","frameCount","history","container","isExpanded","Boolean","rafId","intervalId","observer","window","init","mount","instance","unmount","destroy","getStats","getSnapshot","createDom","startFpsTracker","startDomMutationObserver","setInterval","updateMetrics","_a","_b","_c","document","createElement","id","setAttribute","posStyles","style","textContent","pill","className","innerHTML","modal","appendChild","body","addEventListener","toggleModal","e","key","preventDefault","querySelector","resetStats","copyStats","forceState","classList","toggle","lastTime","frames","tick","Math","round","requestAnimationFrame","MutationObserver","mutations","relevant","m","target","contains","nodeType","observe","childList","subtree","characterData","recordTyped","count","recordErased","recordInterruption","TypeFlow","active","size","dot","fpsEl","activeEl","mFps","mActive","mTyped","mErased","mMut","mMem","mUptime","toLocaleString","memory","usedJSHeapSize","mb","toFixed","sec","floor","json","JSON","stringify","navigator","clipboard","writeText","btn","prev","setTimeout","timestamp","Date","toISOString","uptimeSeconds","cancelAnimationFrame","clearInterval","disconnect","parentNode","removeChild","TypeFlowMetrics"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*! typeflow-metrics | MIT */
|
|
2
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).TypeflowMetrics={})}(this,function(t){"use strict";var n=Object.defineProperty,e=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=(t,e,i)=>e in t?n(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;const a=class t{
|
|
3
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
4
|
+
* @description Constructs and initializes a TypeFlowMetrics tracker.
|
|
5
|
+
* @param {Object} [options={}] - Configuration options.
|
|
6
|
+
*
|
|
7
|
+
* -------------
|
|
8
|
+
*
|
|
9
|
+
* @Properties
|
|
10
|
+
* @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
|
|
11
|
+
* @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.
|
|
12
|
+
* @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.
|
|
13
|
+
* @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.
|
|
14
|
+
* @param {number} [options.zIndex=99999] - CSS z-index for floating badge.
|
|
15
|
+
*
|
|
16
|
+
* -------------
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });
|
|
20
|
+
*/
|
|
21
|
+
constructor(t={}){this.options=((t,n)=>{for(var a in n||(n={}))i.call(n,a)&&o(t,a,n[a]);if(e)for(var a of e(n))s.call(n,a)&&o(t,a,n[a]);return t})({position:"bottom-right",detailed:!1,autoTrack:!0,updateInterval:250,zIndex:99999},t),this.stats={fps:60,activeInstances:0,totalTyped:0,totalErased:0,renderLatencyMs:0,domMutations:0,interruptions:0,startTime:performance.now(),lastFrameTime:performance.now(),frameCount:0,history:[]},this.container=null,this.isExpanded=Boolean(this.options.detailed),this.rafId=null,this.intervalId=null,this.observer=null,"undefined"!=typeof window&&this.init()}
|
|
22
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
23
|
+
* @description Mounts the singleton floating HUD metrics badge into the active browser document.
|
|
24
|
+
* @param {Object} [options={}] - Custom configuration options.
|
|
25
|
+
* @returns {TypeFlowMetrics} The active singleton instance.
|
|
26
|
+
*
|
|
27
|
+
* -------------
|
|
28
|
+
*
|
|
29
|
+
* @Properties
|
|
30
|
+
* @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
|
|
31
|
+
* @param {boolean} [options.detailed=false] - Whether to open expanded view initially.
|
|
32
|
+
*
|
|
33
|
+
* -------------
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* TypeFlowMetrics.mount({ position: 'bottom-right' });
|
|
37
|
+
*/static mount(n={}){return t.instance||(t.instance=new t(n)),t.instance}
|
|
38
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
39
|
+
* @description Unmounts the singleton floating HUD metrics badge and stops all listeners.
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*
|
|
42
|
+
* -------------
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* TypeFlowMetrics.unmount();
|
|
46
|
+
*/static unmount(){t.instance&&(t.instance.destroy(),t.instance=null)}
|
|
47
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
48
|
+
* @description Returns a snapshot object of current performance metrics.
|
|
49
|
+
* @returns {Object | null} Metric snapshot data or null if not mounted.
|
|
50
|
+
*
|
|
51
|
+
* -------------
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const stats = TypeFlowMetrics.getStats();
|
|
55
|
+
* console.log('Active animations:', stats?.activeInstances);
|
|
56
|
+
*/static getStats(){return t.instance?t.instance.getSnapshot():null}init(){this.createDom(),this.startFpsTracker(),this.startDomMutationObserver(),this.intervalId=setInterval(()=>{this.updateMetrics()},this.options.updateInterval)}createDom(){var t,n,e;if(this.container||"undefined"==typeof document)return;this.container=document.createElement("aside"),this.container.id="typeflow-metrics-hud",this.container.setAttribute("aria-label","TypeFlow Performance Metrics HUD"),this.container.setAttribute("role","region");const i={"bottom-right":"bottom: 16px; right: 16px;","bottom-left":"bottom: 16px; left: 16px;","top-right":"top: 16px; right: 16px;","top-left":"top: 16px; left: 16px;"},s=document.createElement("style");s.textContent=`\n #typeflow-metrics-hud {\n position: fixed;\n ${i[this.options.position]||i["bottom-right"]}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;const o=document.createElement("div");o.className="tf-hud-pill",o.setAttribute("role","button"),o.setAttribute("tabindex","0"),o.innerHTML='\n <span class="tf-hud-dot" id="tf-hud-dot"></span>\n <span class="tf-hud-mono" id="tf-hud-fps">60 FPS</span>\n <span class="tf-hud-label">•</span>\n <span class="tf-hud-mono tf-hud-accent" id="tf-hud-active">0 Active</span>\n ';const a=document.createElement("div");a.className="tf-hud-modal "+(this.isExpanded?"open":""),a.id="tf-hud-modal",a.innerHTML='\n <div class="tf-hud-header">\n <span class="tf-hud-title">TypeFlow Metrics</span>\n <button class="tf-hud-close" id="tf-hud-close" aria-label="Close Metrics Window">✕</button>\n </div>\n <div class="tf-hud-grid">\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Frame Rate</div>\n <div class="tf-hud-card-val tf-hud-green" id="tf-hud-m-fps">60.0 FPS</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Active Anim</div>\n <div class="tf-hud-card-val tf-hud-accent" id="tf-hud-m-active">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Typed Units</div>\n <div class="tf-hud-card-val tf-hud-cyan" id="tf-hud-m-typed">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Erased Units</div>\n <div class="tf-hud-card-val" id="tf-hud-m-erased">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">DOM Mut.</div>\n <div class="tf-hud-card-val" id="tf-hud-m-mut">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Heap Memory</div>\n <div class="tf-hud-card-val tf-hud-mono" id="tf-hud-m-mem">-- MB</div>\n </div>\n </div>\n <div class="tf-hud-footer">\n <span id="tf-hud-uptime">Uptime: 0s</span>\n <div style="display:flex;gap:4px;">\n <button class="tf-hud-btn" id="tf-hud-copy">Copy JSON</button>\n <button class="tf-hud-btn" id="tf-hud-reset">Reset</button>\n </div>\n </div>\n ',this.container.appendChild(s),this.container.appendChild(o),this.container.appendChild(a),document.body.appendChild(this.container),o.addEventListener("click",()=>this.toggleModal()),o.addEventListener("keydown",t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggleModal())}),null==(t=a.querySelector("#tf-hud-close"))||t.addEventListener("click",()=>this.toggleModal(!1)),null==(n=a.querySelector("#tf-hud-reset"))||n.addEventListener("click",()=>this.resetStats()),null==(e=a.querySelector("#tf-hud-copy"))||e.addEventListener("click",()=>this.copyStats())}
|
|
57
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
58
|
+
* @description Toggles or sets the visibility of the expanded metrics window modal.
|
|
59
|
+
* @param {boolean} [forceState] - Optional explicit state boolean.
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*
|
|
62
|
+
* -------------
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* hud.toggleModal(true); // Open
|
|
66
|
+
* hud.toggleModal(false); // Close
|
|
67
|
+
*/toggleModal(t){var n;this.isExpanded=void 0!==t?t:!this.isExpanded;const e=null==(n=this.container)?void 0:n.querySelector("#tf-hud-modal");e&&e.classList.toggle("open",this.isExpanded)}startFpsTracker(){let t=performance.now(),n=0;const e=i=>{n++,i>=t+1e3&&(this.stats.fps=Math.round(1e3*n/(i-t)),n=0,t=i),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}startDomMutationObserver(){"undefined"!=typeof MutationObserver&&"undefined"!=typeof document&&(this.observer=new MutationObserver(t=>{var n,e;let i=0;for(const s of t)s.target===this.container||(null==(n=this.container)?void 0:n.contains(s.target))||1===s.target.nodeType&&(null==(e=s.target.classList)?void 0:e.contains("tf-cursor"))||i++;this.stats.domMutations+=i}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}recordTyped(t=1){this.stats.totalTyped+=t}recordErased(t=1){this.stats.totalErased+=t}recordInterruption(){this.stats.interruptions+=1}updateMetrics(){var t,n;if(!this.container)return;"undefined"!=typeof window&&(null==(t=window.TypeFlow)?void 0:t.active)&&(this.stats.activeInstances=window.TypeFlow.active.size);const e=this.container.querySelector("#tf-hud-dot"),i=this.container.querySelector("#tf-hud-fps"),s=this.container.querySelector("#tf-hud-active");if(e&&e.classList.toggle("active",this.stats.activeInstances>0),i&&(i.textContent=`${this.stats.fps} FPS`),s&&(s.textContent=`${this.stats.activeInstances} Active`),this.isExpanded){const t=this.container.querySelector("#tf-hud-m-fps"),e=this.container.querySelector("#tf-hud-m-active"),i=this.container.querySelector("#tf-hud-m-typed"),s=this.container.querySelector("#tf-hud-m-erased"),o=this.container.querySelector("#tf-hud-m-mut"),a=this.container.querySelector("#tf-hud-m-mem"),r=this.container.querySelector("#tf-hud-uptime");if(t&&(t.textContent=`${this.stats.fps}.0 FPS`),e&&(e.textContent=`${this.stats.activeInstances}`),i&&(i.textContent=this.stats.totalTyped.toLocaleString()),s&&(s.textContent=this.stats.totalErased.toLocaleString()),o&&(o.textContent=this.stats.domMutations.toLocaleString()),a)if(null==(n=performance.memory)?void 0:n.usedJSHeapSize){const t=(performance.memory.usedJSHeapSize/1048576).toFixed(1);a.textContent=`${t} MB`}else a.textContent="Native";if(r){const t=Math.floor((performance.now()-this.stats.startTime)/1e3);r.textContent=`Uptime: ${t}s`}}}resetStats(){this.stats.totalTyped=0,this.stats.totalErased=0,this.stats.domMutations=0,this.stats.interruptions=0,this.stats.startTime=performance.now(),this.updateMetrics()}copyStats(){var t,n;const e=JSON.stringify(this.getSnapshot(),null,2);if(null==(t=navigator.clipboard)?void 0:t.writeText){navigator.clipboard.writeText(e);const t=null==(n=this.container)?void 0:n.querySelector("#tf-hud-copy");if(t){const n=t.textContent;t.textContent="Copied!",setTimeout(()=>{t.textContent=n},1500)}}}
|
|
68
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
69
|
+
* @description Returns a snapshot object of lifetime metrics.
|
|
70
|
+
* @returns {Object} Metric data snapshot.
|
|
71
|
+
*/getSnapshot(){return{timestamp:(new Date).toISOString(),fps:this.stats.fps,activeInstances:this.stats.activeInstances,totalTyped:this.stats.totalTyped,totalErased:this.stats.totalErased,domMutations:this.stats.domMutations,interruptions:this.stats.interruptions,uptimeSeconds:Math.floor((performance.now()-this.stats.startTime)/1e3)}}
|
|
72
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
73
|
+
* @description Cleans up observers, timers, and removes the HUD from the DOM.
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/destroy(){var t;this.rafId&&cancelAnimationFrame(this.rafId),this.intervalId&&clearInterval(this.intervalId),this.observer&&this.observer.disconnect(),(null==(t=this.container)?void 0:t.parentNode)&&this.container.parentNode.removeChild(this.container),this.container=null}};var r;o(a,"symbol"!=typeof(r="instance")?r+"":r,null);let d=a;"undefined"!=typeof window&&(window.TypeFlowMetrics=d),t.TypeFlowMetrics=d,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
76
|
+
//# sourceMappingURL=typeflow-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-metrics.js","sources":["../../../src/typeflow-metrics.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Metrics - Real-time Performance HUD & Diagnostic Tooltip.\n * Mounts an accessible, zero-CLS floating metrics badge/window in the viewport\n * tracking active animations, FPS, typed/erased units, render latency, and heap memory.\n *\n * @module @staticcanvas/typeflow/metrics\n */\n\nexport class TypeFlowMetrics {\n static instance = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Constructs and initializes a TypeFlowMetrics tracker.\n * @param {Object} [options={}] - Configuration options.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.\n * @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.\n * @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.\n * @param {number} [options.zIndex=99999] - CSS z-index for floating badge.\n *\n * -------------\n *\n * @example\n * const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });\n */\n constructor(options = {}) {\n this.options = {\n position: 'bottom-right',\n detailed: false,\n autoTrack: true,\n updateInterval: 250,\n zIndex: 99999,\n ...options,\n };\n\n this.stats = {\n fps: 60,\n activeInstances: 0,\n totalTyped: 0,\n totalErased: 0,\n renderLatencyMs: 0,\n domMutations: 0,\n interruptions: 0,\n startTime: performance.now(),\n lastFrameTime: performance.now(),\n frameCount: 0,\n history: [],\n };\n\n this.container = null;\n this.isExpanded = Boolean(this.options.detailed);\n this.rafId = null;\n this.intervalId = null;\n this.observer = null;\n\n if (typeof window !== 'undefined') {\n this.init();\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Mounts the singleton floating HUD metrics badge into the active browser document.\n * @param {Object} [options={}] - Custom configuration options.\n * @returns {TypeFlowMetrics} The active singleton instance.\n *\n * -------------\n *\n * @Properties\n * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.\n * @param {boolean} [options.detailed=false] - Whether to open expanded view initially.\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.mount({ position: 'bottom-right' });\n */\n static mount(options = {}) {\n if (!TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance = new TypeFlowMetrics(options);\n }\n return TypeFlowMetrics.instance;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Unmounts the singleton floating HUD metrics badge and stops all listeners.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * TypeFlowMetrics.unmount();\n */\n static unmount() {\n if (TypeFlowMetrics.instance) {\n TypeFlowMetrics.instance.destroy();\n TypeFlowMetrics.instance = null;\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of current performance metrics.\n * @returns {Object | null} Metric snapshot data or null if not mounted.\n *\n * -------------\n *\n * @example\n * const stats = TypeFlowMetrics.getStats();\n * console.log('Active animations:', stats?.activeInstances);\n */\n static getStats() {\n return TypeFlowMetrics.instance ? TypeFlowMetrics.instance.getSnapshot() : null;\n }\n\n init() {\n this.createDom();\n this.startFpsTracker();\n this.startDomMutationObserver();\n\n this.intervalId = setInterval(() => {\n this.updateMetrics();\n }, this.options.updateInterval);\n }\n\n createDom() {\n if (this.container || typeof document === 'undefined') return;\n\n this.container = document.createElement('aside');\n this.container.id = 'typeflow-metrics-hud';\n this.container.setAttribute('aria-label', 'TypeFlow Performance Metrics HUD');\n this.container.setAttribute('role', 'region');\n\n const posStyles = {\n 'bottom-right': 'bottom: 16px; right: 16px;',\n 'bottom-left': 'bottom: 16px; left: 16px;',\n 'top-right': 'top: 16px; right: 16px;',\n 'top-left': 'top: 16px; left: 16px;',\n };\n\n const style = document.createElement('style');\n style.textContent = `\n #typeflow-metrics-hud {\n position: fixed;\n ${posStyles[this.options.position] || posStyles['bottom-right']}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;\n\n const pill = document.createElement('div');\n pill.className = 'tf-hud-pill';\n pill.setAttribute('role', 'button');\n pill.setAttribute('tabindex', '0');\n pill.innerHTML = `\n <span class=\"tf-hud-dot\" id=\"tf-hud-dot\"></span>\n <span class=\"tf-hud-mono\" id=\"tf-hud-fps\">60 FPS</span>\n <span class=\"tf-hud-label\">•</span>\n <span class=\"tf-hud-mono tf-hud-accent\" id=\"tf-hud-active\">0 Active</span>\n `;\n\n const modal = document.createElement('div');\n modal.className = `tf-hud-modal ${this.isExpanded ? 'open' : ''}`;\n modal.id = 'tf-hud-modal';\n modal.innerHTML = `\n <div class=\"tf-hud-header\">\n <span class=\"tf-hud-title\">TypeFlow Metrics</span>\n <button class=\"tf-hud-close\" id=\"tf-hud-close\" aria-label=\"Close Metrics Window\">✕</button>\n </div>\n <div class=\"tf-hud-grid\">\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Frame Rate</div>\n <div class=\"tf-hud-card-val tf-hud-green\" id=\"tf-hud-m-fps\">60.0 FPS</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Active Anim</div>\n <div class=\"tf-hud-card-val tf-hud-accent\" id=\"tf-hud-m-active\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Typed Units</div>\n <div class=\"tf-hud-card-val tf-hud-cyan\" id=\"tf-hud-m-typed\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Erased Units</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-erased\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">DOM Mut.</div>\n <div class=\"tf-hud-card-val\" id=\"tf-hud-m-mut\">0</div>\n </div>\n <div class=\"tf-hud-card\">\n <div class=\"tf-hud-card-label\">Heap Memory</div>\n <div class=\"tf-hud-card-val tf-hud-mono\" id=\"tf-hud-m-mem\">-- MB</div>\n </div>\n </div>\n <div class=\"tf-hud-footer\">\n <span id=\"tf-hud-uptime\">Uptime: 0s</span>\n <div style=\"display:flex;gap:4px;\">\n <button class=\"tf-hud-btn\" id=\"tf-hud-copy\">Copy JSON</button>\n <button class=\"tf-hud-btn\" id=\"tf-hud-reset\">Reset</button>\n </div>\n </div>\n `;\n\n this.container.appendChild(style);\n this.container.appendChild(pill);\n this.container.appendChild(modal);\n document.body.appendChild(this.container);\n\n pill.addEventListener('click', () => this.toggleModal());\n pill.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.toggleModal();\n }\n });\n\n modal.querySelector('#tf-hud-close')?.addEventListener('click', () => this.toggleModal(false));\n modal.querySelector('#tf-hud-reset')?.addEventListener('click', () => this.resetStats());\n modal.querySelector('#tf-hud-copy')?.addEventListener('click', () => this.copyStats());\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or sets the visibility of the expanded metrics window modal.\n * @param {boolean} [forceState] - Optional explicit state boolean.\n * @returns {void}\n *\n * -------------\n *\n * @example\n * hud.toggleModal(true); // Open\n * hud.toggleModal(false); // Close\n */\n toggleModal(forceState) {\n this.isExpanded = forceState !== undefined ? forceState : !this.isExpanded;\n const modal = this.container?.querySelector('#tf-hud-modal');\n if (modal) {\n modal.classList.toggle('open', this.isExpanded);\n }\n }\n\n startFpsTracker() {\n let lastTime = performance.now();\n let frames = 0;\n\n const tick = (now) => {\n frames++;\n if (now >= lastTime + 1000) {\n this.stats.fps = Math.round((frames * 1000) / (now - lastTime));\n frames = 0;\n lastTime = now;\n }\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n }\n\n startDomMutationObserver() {\n if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;\n this.observer = new MutationObserver((mutations) => {\n let relevant = 0;\n for (const m of mutations) {\n if (m.target === this.container || this.container?.contains(m.target)) continue;\n if (m.target.nodeType === 1 && m.target.classList?.contains('tf-cursor')) continue;\n relevant++;\n }\n this.stats.domMutations += relevant;\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n recordTyped(count = 1) {\n this.stats.totalTyped += count;\n }\n\n recordErased(count = 1) {\n this.stats.totalErased += count;\n }\n\n recordInterruption() {\n this.stats.interruptions += 1;\n }\n\n updateMetrics() {\n if (!this.container) return;\n\n if (typeof window !== 'undefined' && window.TypeFlow?.active) {\n this.stats.activeInstances = window.TypeFlow.active.size;\n }\n\n const dot = this.container.querySelector('#tf-hud-dot');\n const fpsEl = this.container.querySelector('#tf-hud-fps');\n const activeEl = this.container.querySelector('#tf-hud-active');\n\n if (dot) dot.classList.toggle('active', this.stats.activeInstances > 0);\n if (fpsEl) fpsEl.textContent = `${this.stats.fps} FPS`;\n if (activeEl) activeEl.textContent = `${this.stats.activeInstances} Active`;\n\n if (this.isExpanded) {\n const mFps = this.container.querySelector('#tf-hud-m-fps');\n const mActive = this.container.querySelector('#tf-hud-m-active');\n const mTyped = this.container.querySelector('#tf-hud-m-typed');\n const mErased = this.container.querySelector('#tf-hud-m-erased');\n const mMut = this.container.querySelector('#tf-hud-m-mut');\n const mMem = this.container.querySelector('#tf-hud-m-mem');\n const mUptime = this.container.querySelector('#tf-hud-uptime');\n\n if (mFps) mFps.textContent = `${this.stats.fps}.0 FPS`;\n if (mActive) mActive.textContent = `${this.stats.activeInstances}`;\n if (mTyped) mTyped.textContent = this.stats.totalTyped.toLocaleString();\n if (mErased) mErased.textContent = this.stats.totalErased.toLocaleString();\n if (mMut) mMut.textContent = this.stats.domMutations.toLocaleString();\n\n if (mMem) {\n if (performance.memory?.usedJSHeapSize) {\n const mb = (performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);\n mMem.textContent = `${mb} MB`;\n } else {\n mMem.textContent = 'Native';\n }\n }\n\n if (mUptime) {\n const sec = Math.floor((performance.now() - this.stats.startTime) / 1000);\n mUptime.textContent = `Uptime: ${sec}s`;\n }\n }\n }\n\n resetStats() {\n this.stats.totalTyped = 0;\n this.stats.totalErased = 0;\n this.stats.domMutations = 0;\n this.stats.interruptions = 0;\n this.stats.startTime = performance.now();\n this.updateMetrics();\n }\n\n copyStats() {\n const json = JSON.stringify(this.getSnapshot(), null, 2);\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(json);\n const btn = this.container?.querySelector('#tf-hud-copy');\n if (btn) {\n const prev = btn.textContent;\n btn.textContent = 'Copied!';\n setTimeout(() => {\n btn.textContent = prev;\n }, 1500);\n }\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Returns a snapshot object of lifetime metrics.\n * @returns {Object} Metric data snapshot.\n */\n getSnapshot() {\n return {\n timestamp: new Date().toISOString(),\n fps: this.stats.fps,\n activeInstances: this.stats.activeInstances,\n totalTyped: this.stats.totalTyped,\n totalErased: this.stats.totalErased,\n domMutations: this.stats.domMutations,\n interruptions: this.stats.interruptions,\n uptimeSeconds: Math.floor((performance.now() - this.stats.startTime) / 1000),\n };\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Cleans up observers, timers, and removes the HUD from the DOM.\n * @returns {void}\n */\n destroy() {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n if (this.intervalId) clearInterval(this.intervalId);\n if (this.observer) this.observer.disconnect();\n if (this.container?.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.container = null;\n }\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowMetrics = TypeFlowMetrics;\n}\n"],"names":["_TypeFlowMetrics","constructor","options","this","__spreadValues","position","detailed","autoTrack","updateInterval","zIndex","stats","fps","activeInstances","totalTyped","totalErased","renderLatencyMs","domMutations","interruptions","startTime","performance","now","lastFrameTime","frameCount","history","container","isExpanded","Boolean","rafId","intervalId","observer","window","init","mount","instance","unmount","destroy","getStats","getSnapshot","createDom","startFpsTracker","startDomMutationObserver","setInterval","updateMetrics","document","createElement","id","setAttribute","posStyles","style","textContent","pill","className","innerHTML","modal","appendChild","body","addEventListener","toggleModal","e","key","preventDefault","_a","querySelector","_b","resetStats","_c","copyStats","forceState","classList","toggle","lastTime","frames","tick","Math","round","requestAnimationFrame","MutationObserver","mutations","relevant","m","target","contains","nodeType","observe","childList","subtree","characterData","recordTyped","count","recordErased","recordInterruption","TypeFlow","active","size","dot","fpsEl","activeEl","mFps","mActive","mTyped","mErased","mMut","mMem","mUptime","toLocaleString","memory","usedJSHeapSize","mb","toFixed","sec","floor","json","JSON","stringify","navigator","clipboard","writeText","btn","prev","setTimeout","timestamp","Date","toISOString","uptimeSeconds","cancelAnimationFrame","clearInterval","disconnect","parentNode","removeChild","TypeFlowMetrics"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).TypeflowMetrics={})}(this,function(t){"use strict";var n=Object.defineProperty,e=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=(t,e,i)=>e in t?n(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;const a=class t{constructor(t={}){this.options=((t,n)=>{for(var a in n||(n={}))i.call(n,a)&&o(t,a,n[a]);if(e)for(var a of e(n))s.call(n,a)&&o(t,a,n[a]);return t})({position:"bottom-right",detailed:!1,autoTrack:!0,updateInterval:250,zIndex:99999},t),this.stats={fps:60,activeInstances:0,totalTyped:0,totalErased:0,renderLatencyMs:0,domMutations:0,interruptions:0,startTime:performance.now(),lastFrameTime:performance.now(),frameCount:0,history:[]},this.container=null,this.isExpanded=Boolean(this.options.detailed),this.rafId=null,this.intervalId=null,this.observer=null,"undefined"!=typeof window&&this.init()}static mount(n={}){return t.instance||(t.instance=new t(n)),t.instance}static unmount(){t.instance&&(t.instance.destroy(),t.instance=null)}static getStats(){return t.instance?t.instance.getSnapshot():null}init(){this.createDom(),this.startFpsTracker(),this.startDomMutationObserver(),this.intervalId=setInterval(()=>{this.updateMetrics()},this.options.updateInterval)}createDom(){var t,n,e;if(this.container||"undefined"==typeof document)return;this.container=document.createElement("aside"),this.container.id="typeflow-metrics-hud",this.container.setAttribute("aria-label","TypeFlow Performance Metrics HUD"),this.container.setAttribute("role","region");const i={"bottom-right":"bottom: 16px; right: 16px;","bottom-left":"bottom: 16px; left: 16px;","top-right":"top: 16px; right: 16px;","top-left":"top: 16px; left: 16px;"},s=document.createElement("style");s.textContent=`\n #typeflow-metrics-hud {\n position: fixed;\n ${i[this.options.position]||i["bottom-right"]}\n z-index: ${this.options.zIndex};\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 12px;\n color: #f1f5f9;\n user-select: none;\n box-sizing: border-box;\n transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n #typeflow-metrics-hud * {\n box-sizing: border-box;\n }\n .tf-hud-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: rgba(15, 23, 42, 0.85);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 9999px;\n box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);\n cursor: pointer;\n transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;\n }\n .tf-hud-pill:hover {\n transform: translateY(-2px);\n border-color: rgba(255, 107, 43, 0.5);\n box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);\n }\n .tf-hud-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #10b981;\n box-shadow: 0 0 8px #10b981;\n transition: background 0.3s ease;\n }\n .tf-hud-dot.active {\n background: #ff6b2b;\n box-shadow: 0 0 10px #ff6b2b;\n animation: tf-hud-pulse 1.5s infinite;\n }\n @keyframes tf-hud-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.25); opacity: 0.7; }\n }\n .tf-hud-mono {\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n letter-spacing: -0.02em;\n }\n .tf-hud-label {\n color: #94a3b8;\n font-size: 11px;\n }\n .tf-hud-modal {\n display: none;\n width: 290px;\n background: rgba(15, 23, 42, 0.94);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 12px;\n box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);\n padding: 14px;\n margin-top: 8px;\n animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n }\n @keyframes tf-hud-fade-in {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .tf-hud-modal.open {\n display: block;\n }\n .tf-hud-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n margin-bottom: 10px;\n }\n .tf-hud-title {\n font-family: 'Lexend', sans-serif;\n font-weight: 600;\n font-size: 13px;\n background: linear-gradient(135deg, #ff6b2b, #38bdf8);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n }\n .tf-hud-close {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 14px;\n line-height: 1;\n }\n .tf-hud-close:hover {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .tf-hud-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 10px;\n }\n .tf-hud-card {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 8px;\n padding: 8px 10px;\n }\n .tf-hud-card-label {\n font-size: 10px;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 2px;\n }\n .tf-hud-card-val {\n font-family: 'JetBrains Mono', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n }\n .tf-hud-accent {\n color: #ff6b2b;\n }\n .tf-hud-cyan {\n color: #38bdf8;\n }\n .tf-hud-green {\n color: #10b981;\n }\n .tf-hud-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 8px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n font-size: 10px;\n color: #64748b;\n }\n .tf-hud-btn {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.1);\n color: #cbd5e1;\n padding: 3px 8px;\n border-radius: 4px;\n font-size: 10px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .tf-hud-btn:hover {\n background: rgba(255, 107, 43, 0.2);\n border-color: rgba(255, 107, 43, 0.4);\n color: #ff6b2b;\n }\n `;const o=document.createElement("div");o.className="tf-hud-pill",o.setAttribute("role","button"),o.setAttribute("tabindex","0"),o.innerHTML='\n <span class="tf-hud-dot" id="tf-hud-dot"></span>\n <span class="tf-hud-mono" id="tf-hud-fps">60 FPS</span>\n <span class="tf-hud-label">•</span>\n <span class="tf-hud-mono tf-hud-accent" id="tf-hud-active">0 Active</span>\n ';const a=document.createElement("div");a.className="tf-hud-modal "+(this.isExpanded?"open":""),a.id="tf-hud-modal",a.innerHTML='\n <div class="tf-hud-header">\n <span class="tf-hud-title">TypeFlow Metrics</span>\n <button class="tf-hud-close" id="tf-hud-close" aria-label="Close Metrics Window">✕</button>\n </div>\n <div class="tf-hud-grid">\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Frame Rate</div>\n <div class="tf-hud-card-val tf-hud-green" id="tf-hud-m-fps">60.0 FPS</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Active Anim</div>\n <div class="tf-hud-card-val tf-hud-accent" id="tf-hud-m-active">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Typed Units</div>\n <div class="tf-hud-card-val tf-hud-cyan" id="tf-hud-m-typed">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Erased Units</div>\n <div class="tf-hud-card-val" id="tf-hud-m-erased">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">DOM Mut.</div>\n <div class="tf-hud-card-val" id="tf-hud-m-mut">0</div>\n </div>\n <div class="tf-hud-card">\n <div class="tf-hud-card-label">Heap Memory</div>\n <div class="tf-hud-card-val tf-hud-mono" id="tf-hud-m-mem">-- MB</div>\n </div>\n </div>\n <div class="tf-hud-footer">\n <span id="tf-hud-uptime">Uptime: 0s</span>\n <div style="display:flex;gap:4px;">\n <button class="tf-hud-btn" id="tf-hud-copy">Copy JSON</button>\n <button class="tf-hud-btn" id="tf-hud-reset">Reset</button>\n </div>\n </div>\n ',this.container.appendChild(s),this.container.appendChild(o),this.container.appendChild(a),document.body.appendChild(this.container),o.addEventListener("click",()=>this.toggleModal()),o.addEventListener("keydown",t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggleModal())}),null==(t=a.querySelector("#tf-hud-close"))||t.addEventListener("click",()=>this.toggleModal(!1)),null==(n=a.querySelector("#tf-hud-reset"))||n.addEventListener("click",()=>this.resetStats()),null==(e=a.querySelector("#tf-hud-copy"))||e.addEventListener("click",()=>this.copyStats())}toggleModal(t){var n;this.isExpanded=void 0!==t?t:!this.isExpanded;const e=null==(n=this.container)?void 0:n.querySelector("#tf-hud-modal");e&&e.classList.toggle("open",this.isExpanded)}startFpsTracker(){let t=performance.now(),n=0;const e=i=>{n++,i>=t+1e3&&(this.stats.fps=Math.round(1e3*n/(i-t)),n=0,t=i),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}startDomMutationObserver(){"undefined"!=typeof MutationObserver&&"undefined"!=typeof document&&(this.observer=new MutationObserver(t=>{var n,e;let i=0;for(const s of t)s.target===this.container||(null==(n=this.container)?void 0:n.contains(s.target))||1===s.target.nodeType&&(null==(e=s.target.classList)?void 0:e.contains("tf-cursor"))||i++;this.stats.domMutations+=i}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}recordTyped(t=1){this.stats.totalTyped+=t}recordErased(t=1){this.stats.totalErased+=t}recordInterruption(){this.stats.interruptions+=1}updateMetrics(){var t,n;if(!this.container)return;"undefined"!=typeof window&&(null==(t=window.TypeFlow)?void 0:t.active)&&(this.stats.activeInstances=window.TypeFlow.active.size);const e=this.container.querySelector("#tf-hud-dot"),i=this.container.querySelector("#tf-hud-fps"),s=this.container.querySelector("#tf-hud-active");if(e&&e.classList.toggle("active",this.stats.activeInstances>0),i&&(i.textContent=`${this.stats.fps} FPS`),s&&(s.textContent=`${this.stats.activeInstances} Active`),this.isExpanded){const t=this.container.querySelector("#tf-hud-m-fps"),e=this.container.querySelector("#tf-hud-m-active"),i=this.container.querySelector("#tf-hud-m-typed"),s=this.container.querySelector("#tf-hud-m-erased"),o=this.container.querySelector("#tf-hud-m-mut"),a=this.container.querySelector("#tf-hud-m-mem"),r=this.container.querySelector("#tf-hud-uptime");if(t&&(t.textContent=`${this.stats.fps}.0 FPS`),e&&(e.textContent=`${this.stats.activeInstances}`),i&&(i.textContent=this.stats.totalTyped.toLocaleString()),s&&(s.textContent=this.stats.totalErased.toLocaleString()),o&&(o.textContent=this.stats.domMutations.toLocaleString()),a)if(null==(n=performance.memory)?void 0:n.usedJSHeapSize){const t=(performance.memory.usedJSHeapSize/1048576).toFixed(1);a.textContent=`${t} MB`}else a.textContent="Native";if(r){const t=Math.floor((performance.now()-this.stats.startTime)/1e3);r.textContent=`Uptime: ${t}s`}}}resetStats(){this.stats.totalTyped=0,this.stats.totalErased=0,this.stats.domMutations=0,this.stats.interruptions=0,this.stats.startTime=performance.now(),this.updateMetrics()}copyStats(){var t,n;const e=JSON.stringify(this.getSnapshot(),null,2);if(null==(t=navigator.clipboard)?void 0:t.writeText){navigator.clipboard.writeText(e);const t=null==(n=this.container)?void 0:n.querySelector("#tf-hud-copy");if(t){const n=t.textContent;t.textContent="Copied!",setTimeout(()=>{t.textContent=n},1500)}}}getSnapshot(){return{timestamp:(new Date).toISOString(),fps:this.stats.fps,activeInstances:this.stats.activeInstances,totalTyped:this.stats.totalTyped,totalErased:this.stats.totalErased,domMutations:this.stats.domMutations,interruptions:this.stats.interruptions,uptimeSeconds:Math.floor((performance.now()-this.stats.startTime)/1e3)}}destroy(){var t;this.rafId&&cancelAnimationFrame(this.rafId),this.intervalId&&clearInterval(this.intervalId),this.observer&&this.observer.disconnect(),(null==(t=this.container)?void 0:t.parentNode)&&this.container.parentNode.removeChild(this.container),this.container=null}};var r;o(a,"symbol"!=typeof(r="instance")?r+"":r,null);let d=a;"undefined"!=typeof window&&(window.TypeFlowMetrics=d),t.TypeFlowMetrics=d,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
2
|
+
//# sourceMappingURL=typeflow-metrics.min.js.map
|