louper 0.1.0 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ https://github.com/user-attachments/assets/e575222a-4639-456f-953c-d728c78761e6
2
+
3
+ [Louper](https://github.com/willgarman/louper) is a tiny magnifying loupe for the web. Hold <kbd>Option/Alt</kbd> to magnify. Scroll to zoom from 1x to 10x. Zero dependencies.
4
+
5
+ ## Usage
6
+
7
+ Drop in a script tag:
8
+
9
+ ```html
10
+ <script src="https://unpkg.com/louper/dist/auto.global.js"></script>
11
+ ```
12
+
13
+ Or install from npm:
14
+
15
+ ```bash
16
+ npm install louper
17
+ ```
18
+
19
+ ```js
20
+ import { louper } from 'louper'
21
+
22
+ louper()
23
+ ```
24
+
25
+ ## API
26
+
27
+ ```js
28
+ import { louper } from 'louper'
29
+
30
+ const loupe = louper({
31
+ zoomLevel: 2, // starting zoom (default: 2)
32
+ radius: 80, // lens radius in px (default: 80)
33
+ borderWidth: 3, // lens border in px (default: 3)
34
+ borderColor: 'white', // lens border color (default: 'white')
35
+ hotkey: 'Alt', // activation key, or false to disable (default: 'Alt')
36
+ })
37
+
38
+ loupe.activate() // show the loupe programmatically
39
+ loupe.deactivate() // hide the loupe
40
+ loupe.update({ radius: 200 }) // change options on the fly
41
+ loupe.destroy() // remove everything, clean up listeners
42
+ ```
43
+
44
+ ## Development
45
+
46
+ ```bash
47
+ pnpm install
48
+ pnpm dev
49
+ open examples/vanilla/index.html
50
+ ```
51
+
52
+ ## Acknowledgments
53
+
54
+ Louper takes full inspiration from a tool shown by [Adam Wathan](https://x.com/adamwathan/status/2019826659159937318) on the [Tailwind CSS](https://tailwindcss.com) team.
55
+
56
+ ## License
57
+
58
+ MIT
@@ -1,4 +1,4 @@
1
- "use strict";var Louper=(()=>{var _={zoomLevel:2,radius:150,borderWidth:3,borderColor:"white",hotkey:"Alt"},D=1,F=10,K=.002,I=100;function q(f={}){let t={..._,...f},l=document.createElement("louper-lens"),c=l.attachShadow({mode:"closed"}),u=document.createElement("style");u.textContent=g(),c.appendChild(u);let p=document.createElement("div");p.className="louper-clone-wrapper";let n=document.createElement("div");n.className="louper-clone-container",p.appendChild(n),c.appendChild(p),document.body.appendChild(l);let r=!1,y=0,L=0,b=0,E=0,a=t.zoomLevel,h=null,x=!1,d=null;function g(){let e=t.radius*2;return`
1
+ "use strict";var Louper=(()=>{var _={zoomLevel:2,radius:80,borderWidth:3,borderColor:"white",hotkey:"Alt"},D=1,F=10,K=.002,I=100;function q(f={}){let t={..._,...f},s=document.createElement("louper-lens"),c=s.attachShadow({mode:"closed"}),u=document.createElement("style");u.textContent=T(),c.appendChild(u);let p=document.createElement("div");p.className="louper-clone-wrapper";let n=document.createElement("div");n.className="louper-clone-container",p.appendChild(n),c.appendChild(p),document.body.appendChild(s);let r=!1,y=0,b=0,L=0,E=0,a=t.zoomLevel,h=null,x=!1,d=null;function T(){let e=t.radius*2;return`
2
2
  :host {
3
3
  position: fixed; top: 0; left: 0;
4
4
  z-index: 2147483647;
@@ -6,7 +6,7 @@
6
6
  width: ${e}px; height: ${e}px;
7
7
  border-radius: 50%;
8
8
  border: ${t.borderWidth}px solid ${t.borderColor};
9
- box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
9
+ box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);
10
10
  overflow: hidden; opacity: 0;
11
11
  transition: opacity ${I}ms ease-out;
12
12
  will-change: transform, opacity;
@@ -21,5 +21,5 @@
21
21
  position: absolute; top: 0; left: 0;
22
22
  transform-origin: 0 0;
23
23
  }
24
- `}function Y(){x=!1,h=null;let{radius:e,borderWidth:i}=t;l.style.transform=`translate(${y-e-i}px,${L-e-i}px)`;let M=e-(y+b)*a,T=e-(L+E)*a;n.style.transform=`scale(${a}) translate(${M/a}px,${T/a}px)`}function m(){x||(x=!0,h=requestAnimationFrame(Y))}function k(){let e=document.documentElement,i=e.cloneNode(!0);i.querySelectorAll("script").forEach(o=>o.remove()),i.querySelectorAll("louper-lens").forEach(o=>o.remove()),c.querySelectorAll("[data-louper-sheet]").forEach(o=>o.remove());for(let o of Array.from(document.styleSheets))try{if(o.href){let s=document.createElement("link");s.rel="stylesheet",s.href=o.href,s.setAttribute("data-louper-sheet",""),c.appendChild(s)}else if(o.ownerNode instanceof HTMLStyleElement){let s=document.createElement("style");s.textContent=o.ownerNode.textContent,s.setAttribute("data-louper-sheet",""),c.appendChild(s)}}catch{}let M=e.querySelectorAll("canvas"),T=i.querySelectorAll("canvas");M.forEach((o,s)=>{let w=T[s];if(!w)return;w.width=o.width,w.height=o.height;let W=w.getContext("2d");if(W)try{W.drawImage(o,0,0)}catch{}}),i.style.margin="0",i.style.position="absolute",i.style.top="0",i.style.left="0",i.style.width=e.scrollWidth+"px",i.style.height=e.scrollHeight+"px",n.innerHTML="",n.appendChild(i)}let C=new MutationObserver(()=>{r&&(d&&clearTimeout(d),d=setTimeout(()=>{r&&k()},16))});function O(){r||(r=!0,b=window.scrollX,E=window.scrollY,a=t.zoomLevel,k(),l.classList.add("louper-active"),m(),C.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:["class","style","data-state"]}))}function v(){r&&(r=!1,C.disconnect(),d&&(clearTimeout(d),d=null),l.classList.remove("louper-active"),setTimeout(()=>{r||(n.innerHTML="")},I))}function S(e){e.key===t.hotkey&&!r&&O()}function A(e){e.key===t.hotkey&&v()}function z(e){y=e.clientX,L=e.clientY,r&&m()}function N(){r&&(b=window.scrollX,E=window.scrollY,m())}function $(e){r&&(e.preventDefault(),a=Math.min(F,Math.max(D,a*(1-e.deltaY*K))),m())}t.hotkey&&(window.addEventListener("keydown",S),window.addEventListener("keyup",A),window.addEventListener("blur",v)),window.addEventListener("mousemove",z),window.addEventListener("scroll",N,{passive:!0}),window.addEventListener("wheel",$,{passive:!1});function H(){r=!1,C.disconnect(),d&&clearTimeout(d),h!==null&&cancelAnimationFrame(h),window.removeEventListener("keydown",S),window.removeEventListener("keyup",A),window.removeEventListener("mousemove",z),window.removeEventListener("blur",v),window.removeEventListener("scroll",N),window.removeEventListener("wheel",$),l.remove()}function X(e){t={...t,...e},e.zoomLevel!==void 0&&(a=e.zoomLevel),u.textContent=g(),r&&m()}return{destroy:H,update:X,activate:O,deactivate:v}}function P(){let f=document.currentScript;if(!f)return{};let{zoom:t,radius:l,borderWidth:c,borderColor:u,hotkey:p}=f.dataset,n={};return t&&(n.zoomLevel=Number(t)),l&&(n.radius=Number(l)),c&&(n.borderWidth=Number(c)),u&&(n.borderColor=u),p&&(n.hotkey=p),n}var R=q(P());window.__louper=R;})();
24
+ `}function Y(){x=!1,h=null;let{radius:e,borderWidth:i}=t;s.style.transform=`translate(${y-e-i}px,${b-e-i}px)`;let g=e-(y+L)*a,M=e-(b+E)*a;n.style.transform=`scale(${a}) translate(${g/a}px,${M/a}px)`}function m(){x||(x=!0,h=requestAnimationFrame(Y))}function k(){let e=document.documentElement,i=e.cloneNode(!0);i.querySelectorAll("script").forEach(o=>o.remove()),i.querySelectorAll("louper-lens").forEach(o=>o.remove()),c.querySelectorAll("[data-louper-sheet]").forEach(o=>o.remove());for(let o of Array.from(document.styleSheets))try{if(o.href){let l=document.createElement("link");l.rel="stylesheet",l.href=o.href,l.setAttribute("data-louper-sheet",""),c.appendChild(l)}else if(o.ownerNode instanceof HTMLStyleElement){let l=document.createElement("style");l.textContent=o.ownerNode.textContent,l.setAttribute("data-louper-sheet",""),c.appendChild(l)}}catch{}let g=e.querySelectorAll("canvas"),M=i.querySelectorAll("canvas");g.forEach((o,l)=>{let w=M[l];if(!w)return;w.width=o.width,w.height=o.height;let W=w.getContext("2d");if(W)try{W.drawImage(o,0,0)}catch{}}),i.style.margin="0",i.style.position="absolute",i.style.top="0",i.style.left="0",i.style.width=e.scrollWidth+"px",i.style.height=e.scrollHeight+"px",n.innerHTML="",n.appendChild(i)}let C=new MutationObserver(()=>{r&&(d&&clearTimeout(d),d=setTimeout(()=>{r&&k()},16))});function O(){r||(r=!0,L=window.scrollX,E=window.scrollY,a=t.zoomLevel,k(),s.classList.add("louper-active"),m(),C.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:["class","style","data-state"]}))}function v(){r&&(r=!1,C.disconnect(),d&&(clearTimeout(d),d=null),s.classList.remove("louper-active"),setTimeout(()=>{r||(n.innerHTML="")},I))}function S(e){e.key===t.hotkey&&!r&&O()}function A(e){e.key===t.hotkey&&v()}function z(e){y=e.clientX,b=e.clientY,r&&m()}function N(){r&&(L=window.scrollX,E=window.scrollY,m())}function $(e){r&&(e.preventDefault(),a=Math.min(F,Math.max(D,a*(1-e.deltaY*K))),m())}t.hotkey&&(window.addEventListener("keydown",S),window.addEventListener("keyup",A),window.addEventListener("blur",v)),window.addEventListener("mousemove",z),window.addEventListener("scroll",N,{passive:!0}),window.addEventListener("wheel",$,{passive:!1});function H(){r=!1,C.disconnect(),d&&clearTimeout(d),h!==null&&cancelAnimationFrame(h),window.removeEventListener("keydown",S),window.removeEventListener("keyup",A),window.removeEventListener("mousemove",z),window.removeEventListener("blur",v),window.removeEventListener("scroll",N),window.removeEventListener("wheel",$),s.remove()}function X(e){t={...t,...e},e.zoomLevel!==void 0&&(a=e.zoomLevel),u.textContent=T(),r&&m()}return{destroy:H,update:X,activate:O,deactivate:v}}function P(){let f=document.currentScript;if(!f)return{};let{zoom:t,radius:s,borderWidth:c,borderColor:u,hotkey:p}=f.dataset,n={};return t&&(n.zoomLevel=Number(t)),s&&(n.radius=Number(s)),c&&(n.borderWidth=Number(c)),u&&(n.borderColor=u),p&&(n.hotkey=p),n}var R=q(P());window.__louper=R;})();
25
25
  //# sourceMappingURL=auto.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/auto.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n","import { createLouper, type LoupeOptions } from '.'\n\nfunction getScriptOptions(): LoupeOptions {\n const el = document.currentScript as HTMLScriptElement | null\n if (!el) return {}\n const { zoom, radius, borderWidth, borderColor, hotkey } = el.dataset\n const opts: LoupeOptions = {}\n if (zoom) opts.zoomLevel = Number(zoom)\n if (radius) opts.radius = Number(radius)\n if (borderWidth) opts.borderWidth = Number(borderWidth)\n if (borderColor) opts.borderColor = borderColor\n if (hotkey) opts.hotkey = hotkey as LoupeOptions['hotkey']\n return opts\n}\n\nconst instance = createLouper(getScriptOptions())\n;(window as any).__louper = instance\n"],"mappings":"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","names":["DEFAULTS","MIN_ZOOM","MAX_ZOOM","ZOOM_SENSITIVITY","FADE_OUT","createLouper","userOpts","opts","host","root","styleEl","css","wrapper","content","active","mouseX","mouseY","scrollX","scrollY","zoomLevel","rafId","pending","mutationTimer","d","render","radius","borderWidth","cx","cy","scheduleRender","clonePage","docEl","copy","s","el","sheet","link","srcCanvases","dstCanvases","src","i","dst","ctx","observer","activate","deactivate","onKeyDown","onKeyUp","onMouseMove","onScroll","onWheel","destroy","update","newOpts","getScriptOptions","el","zoom","radius","borderWidth","borderColor","hotkey","opts","instance","createLouper"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/auto.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 80,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function louper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n\n","import { louper, type LoupeOptions } from '.'\n\nfunction getScriptOptions(): LoupeOptions {\n const el = document.currentScript as HTMLScriptElement | null\n if (!el) return {}\n const { zoom, radius, borderWidth, borderColor, hotkey } = el.dataset\n const opts: LoupeOptions = {}\n if (zoom) opts.zoomLevel = Number(zoom)\n if (radius) opts.radius = Number(radius)\n if (borderWidth) opts.borderWidth = Number(borderWidth)\n if (borderColor) opts.borderColor = borderColor\n if (hotkey) opts.hotkey = hotkey as LoupeOptions['hotkey']\n return opts\n}\n\nconst instance = louper(getScriptOptions())\n;(window as any).__louper = instance\n"],"mappings":"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","names":["DEFAULTS","MIN_ZOOM","MAX_ZOOM","ZOOM_SENSITIVITY","FADE_OUT","louper","userOpts","opts","host","root","styleEl","css","wrapper","content","active","mouseX","mouseY","scrollX","scrollY","zoomLevel","rafId","pending","mutationTimer","d","render","radius","borderWidth","cx","cy","scheduleRender","clonePage","docEl","copy","s","el","sheet","link","srcCanvases","dstCanvases","src","i","dst","ctx","observer","activate","deactivate","onKeyDown","onKeyUp","onMouseMove","onScroll","onWheel","destroy","update","newOpts","getScriptOptions","el","zoom","radius","borderWidth","borderColor","hotkey","opts","instance","louper"]}
package/dist/index.cjs CHANGED
@@ -21,12 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  DEFAULTS: () => DEFAULTS,
24
- createLouper: () => createLouper
24
+ louper: () => louper
25
25
  });
26
26
  module.exports = __toCommonJS(src_exports);
27
27
  var DEFAULTS = {
28
28
  zoomLevel: 2,
29
- radius: 150,
29
+ radius: 80,
30
30
  borderWidth: 3,
31
31
  borderColor: "white",
32
32
  hotkey: "Alt"
@@ -35,7 +35,7 @@ var MIN_ZOOM = 1;
35
35
  var MAX_ZOOM = 10;
36
36
  var ZOOM_SENSITIVITY = 2e-3;
37
37
  var FADE_OUT = 100;
38
- function createLouper(userOpts = {}) {
38
+ function louper(userOpts = {}) {
39
39
  let opts = { ...DEFAULTS, ...userOpts };
40
40
  const host = document.createElement("louper-lens");
41
41
  const root = host.attachShadow({ mode: "closed" });
@@ -65,7 +65,7 @@ function createLouper(userOpts = {}) {
65
65
  width: ${d}px; height: ${d}px;
66
66
  border-radius: 50%;
67
67
  border: ${opts.borderWidth}px solid ${opts.borderColor};
68
- box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
68
+ box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);
69
69
  overflow: hidden; opacity: 0;
70
70
  transition: opacity ${FADE_OUT}ms ease-out;
71
71
  will-change: transform, opacity;
@@ -234,6 +234,6 @@ function createLouper(userOpts = {}) {
234
234
  // Annotate the CommonJS export names for ESM import in node:
235
235
  0 && (module.exports = {
236
236
  DEFAULTS,
237
- createLouper
237
+ louper
238
238
  });
239
239
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 80,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function louper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -17,6 +17,6 @@ interface LoupeInstance {
17
17
  deactivate: () => void;
18
18
  }
19
19
  declare const DEFAULTS: Required<LoupeOptions>;
20
- declare function createLouper(userOpts?: LoupeOptions): LoupeInstance;
20
+ declare function louper(userOpts?: LoupeOptions): LoupeInstance;
21
21
 
22
- export { DEFAULTS, type LoupeInstance, type LoupeOptions, createLouper };
22
+ export { DEFAULTS, type LoupeInstance, type LoupeOptions, louper };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,6 @@ interface LoupeInstance {
17
17
  deactivate: () => void;
18
18
  }
19
19
  declare const DEFAULTS: Required<LoupeOptions>;
20
- declare function createLouper(userOpts?: LoupeOptions): LoupeInstance;
20
+ declare function louper(userOpts?: LoupeOptions): LoupeInstance;
21
21
 
22
- export { DEFAULTS, type LoupeInstance, type LoupeOptions, createLouper };
22
+ export { DEFAULTS, type LoupeInstance, type LoupeOptions, louper };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  var DEFAULTS = {
3
3
  zoomLevel: 2,
4
- radius: 150,
4
+ radius: 80,
5
5
  borderWidth: 3,
6
6
  borderColor: "white",
7
7
  hotkey: "Alt"
@@ -10,7 +10,7 @@ var MIN_ZOOM = 1;
10
10
  var MAX_ZOOM = 10;
11
11
  var ZOOM_SENSITIVITY = 2e-3;
12
12
  var FADE_OUT = 100;
13
- function createLouper(userOpts = {}) {
13
+ function louper(userOpts = {}) {
14
14
  let opts = { ...DEFAULTS, ...userOpts };
15
15
  const host = document.createElement("louper-lens");
16
16
  const root = host.attachShadow({ mode: "closed" });
@@ -40,7 +40,7 @@ function createLouper(userOpts = {}) {
40
40
  width: ${d}px; height: ${d}px;
41
41
  border-radius: 50%;
42
42
  border: ${opts.borderWidth}px solid ${opts.borderColor};
43
- box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
43
+ box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);
44
44
  overflow: hidden; opacity: 0;
45
45
  transition: opacity ${FADE_OUT}ms ease-out;
46
46
  will-change: transform, opacity;
@@ -208,6 +208,6 @@ function createLouper(userOpts = {}) {
208
208
  }
209
209
  export {
210
210
  DEFAULTS,
211
- createLouper
211
+ louper
212
212
  };
213
213
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 150,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function createLouper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface LoupeOptions {\n /** Default: 2 */\n zoomLevel?: number\n /** Lens radius in px. Default: 150 */\n radius?: number\n /** Default: 3 */\n borderWidth?: number\n /** Default: \"white\" */\n borderColor?: string\n /** Default: \"Alt\". Set to false to disable. */\n hotkey?: 'Alt' | 'Control' | 'Shift' | 'Meta' | false\n}\n\nexport interface LoupeInstance {\n destroy: () => void\n update: (opts: Partial<LoupeOptions>) => void\n activate: () => void\n deactivate: () => void\n}\n\nexport const DEFAULTS: Required<LoupeOptions> = {\n zoomLevel: 2,\n radius: 80,\n borderWidth: 3,\n borderColor: 'white',\n hotkey: 'Alt',\n}\n\nconst MIN_ZOOM = 1\nconst MAX_ZOOM = 10\nconst ZOOM_SENSITIVITY = 0.002\nconst FADE_OUT = 100\n\nexport function louper(userOpts: LoupeOptions = {}): LoupeInstance {\n let opts = { ...DEFAULTS, ...userOpts }\n\n const host = document.createElement('louper-lens')\n const root = host.attachShadow({ mode: 'closed' })\n const styleEl = document.createElement('style')\n styleEl.textContent = css()\n root.appendChild(styleEl)\n const wrapper = document.createElement('div')\n wrapper.className = 'louper-clone-wrapper'\n const content = document.createElement('div')\n content.className = 'louper-clone-container'\n wrapper.appendChild(content)\n root.appendChild(wrapper)\n document.body.appendChild(host)\n\n let active = false\n let mouseX = 0, mouseY = 0, scrollX = 0, scrollY = 0\n let zoomLevel = opts.zoomLevel\n let rafId: number | null = null\n let pending = false\n let mutationTimer: ReturnType<typeof setTimeout> | null = null\n\n function css() {\n const d = opts.radius * 2\n return `\n :host {\n position: fixed; top: 0; left: 0;\n z-index: 2147483647;\n pointer-events: none;\n width: ${d}px; height: ${d}px;\n border-radius: 50%;\n border: ${opts.borderWidth}px solid ${opts.borderColor};\n box-shadow: 0 2px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.1);\n overflow: hidden; opacity: 0;\n transition: opacity ${FADE_OUT}ms ease-out;\n will-change: transform, opacity;\n }\n :host(.louper-active) { opacity: 1; transition: none; }\n .louper-clone-wrapper {\n position: absolute; top: 0; left: 0;\n width: 100%; height: 100%;\n overflow: hidden; border-radius: 50%;\n }\n .louper-clone-container {\n position: absolute; top: 0; left: 0;\n transform-origin: 0 0;\n }\n `\n }\n\n function render() {\n pending = false\n rafId = null\n const { radius, borderWidth } = opts\n host.style.transform = `translate(${mouseX - radius - borderWidth}px,${mouseY - radius - borderWidth}px)`\n const cx = radius - (mouseX + scrollX) * zoomLevel\n const cy = radius - (mouseY + scrollY) * zoomLevel\n content.style.transform = `scale(${zoomLevel}) translate(${cx / zoomLevel}px,${cy / zoomLevel}px)`\n }\n\n function scheduleRender() {\n if (!pending) {\n pending = true\n rafId = requestAnimationFrame(render)\n }\n }\n\n function clonePage() {\n const docEl = document.documentElement\n const copy = docEl.cloneNode(true) as HTMLElement\n copy.querySelectorAll('script').forEach(s => s.remove())\n copy.querySelectorAll('louper-lens').forEach(el => el.remove())\n\n root.querySelectorAll('[data-louper-sheet]').forEach(el => el.remove())\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n if (sheet.href) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = sheet.href\n link.setAttribute('data-louper-sheet', '')\n root.appendChild(link)\n } else if (sheet.ownerNode instanceof HTMLStyleElement) {\n const s = document.createElement('style')\n s.textContent = sheet.ownerNode.textContent\n s.setAttribute('data-louper-sheet', '')\n root.appendChild(s)\n }\n } catch {} // cross-origin\n }\n\n const srcCanvases = docEl.querySelectorAll('canvas')\n const dstCanvases = copy.querySelectorAll('canvas')\n srcCanvases.forEach((src, i) => {\n const dst = dstCanvases[i]\n if (!dst) return\n dst.width = src.width\n dst.height = src.height\n const ctx = dst.getContext('2d')\n if (ctx) try { ctx.drawImage(src, 0, 0) } catch {} // tainted\n })\n\n copy.style.margin = '0'\n copy.style.position = 'absolute'\n copy.style.top = '0'\n copy.style.left = '0'\n copy.style.width = docEl.scrollWidth + 'px'\n copy.style.height = docEl.scrollHeight + 'px'\n content.innerHTML = ''\n content.appendChild(copy)\n }\n\n const observer = new MutationObserver(() => {\n if (!active) return\n if (mutationTimer) clearTimeout(mutationTimer)\n mutationTimer = setTimeout(() => { if (active) clonePage() }, 16)\n })\n\n function activate() {\n if (active) return\n active = true\n scrollX = window.scrollX\n scrollY = window.scrollY\n zoomLevel = opts.zoomLevel\n clonePage()\n host.classList.add('louper-active')\n scheduleRender()\n observer.observe(document.body, {\n childList: true, subtree: true, characterData: true,\n attributes: true, attributeFilter: ['class', 'style', 'data-state'],\n })\n }\n\n function deactivate() {\n if (!active) return\n active = false\n observer.disconnect()\n if (mutationTimer) { clearTimeout(mutationTimer); mutationTimer = null }\n host.classList.remove('louper-active')\n setTimeout(() => { if (!active) content.innerHTML = '' }, FADE_OUT)\n }\n\n function onKeyDown(e: KeyboardEvent) { if (e.key === opts.hotkey && !active) activate() }\n function onKeyUp(e: KeyboardEvent) { if (e.key === opts.hotkey) deactivate() }\n function onMouseMove(e: MouseEvent) {\n mouseX = e.clientX\n mouseY = e.clientY\n if (active) scheduleRender()\n }\n function onScroll() {\n if (!active) return\n scrollX = window.scrollX\n scrollY = window.scrollY\n scheduleRender()\n }\n function onWheel(e: WheelEvent) {\n if (!active) return\n e.preventDefault()\n zoomLevel = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomLevel * (1 - e.deltaY * ZOOM_SENSITIVITY)))\n scheduleRender()\n }\n\n if (opts.hotkey) {\n window.addEventListener('keydown', onKeyDown)\n window.addEventListener('keyup', onKeyUp)\n window.addEventListener('blur', deactivate)\n }\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('scroll', onScroll, { passive: true })\n window.addEventListener('wheel', onWheel, { passive: false })\n\n function destroy() {\n active = false\n observer.disconnect()\n if (mutationTimer) clearTimeout(mutationTimer)\n if (rafId !== null) cancelAnimationFrame(rafId)\n window.removeEventListener('keydown', onKeyDown)\n window.removeEventListener('keyup', onKeyUp)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('blur', deactivate)\n window.removeEventListener('scroll', onScroll)\n window.removeEventListener('wheel', onWheel)\n host.remove()\n }\n\n function update(newOpts: Partial<LoupeOptions>) {\n opts = { ...opts, ...newOpts }\n if (newOpts.zoomLevel !== undefined) zoomLevel = newOpts.zoomLevel\n styleEl.textContent = css()\n if (active) scheduleRender()\n }\n\n return { destroy, update, activate, deactivate }\n}\n\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "louper",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A lightweight zoom circle dev tool. Hold Alt/Option to magnify any part of your page.",
5
5
  "license": "MIT",
6
6
  "author": "Will Garman",