@pie-players/pie-tool-magnifier 0.1.6 → 1.0.0-beta.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/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@pie-players/pie-tool-magnifier",
3
- "version": "0.1.6",
3
+ "version": "1.0.0-beta.2",
4
4
  "type": "module",
5
- "description": "PIE magnification tool for accessibility - magnifies content on screen",
5
+ "description": "Magnifying lens tool for PIE assessment player",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/pie-framework/pie-players.git"
8
+ "url": "git+https://github.com/pie-framework/pie-players.git",
9
+ "directory": "packages/tool-magnifier"
9
10
  },
10
11
  "publishConfig": {
11
12
  "access": "public"
@@ -16,51 +17,54 @@
16
17
  "tool",
17
18
  "magnifier",
18
19
  "magnification",
19
- "accessibility",
20
- "zoom"
20
+ "low-vision",
21
+ "accessibility"
21
22
  ],
22
- "svelte": "./tool-magnifier.svelte",
23
23
  "main": "./dist/tool-magnifier.js",
24
- "types": "./dist/index.d.ts",
25
24
  "exports": {
26
25
  ".": {
27
26
  "types": "./dist/index.d.ts",
28
- "import": "./dist/tool-magnifier.js",
29
- "svelte": "./tool-magnifier.svelte"
27
+ "import": "./dist/tool-magnifier.js"
30
28
  }
31
29
  },
30
+ "customElements": "custom-elements.json",
32
31
  "files": [
33
32
  "dist",
34
- "tool-magnifier.svelte",
35
- "index.ts",
36
- "Magnifier.svelte",
37
- "package.json",
38
- "README.md"
33
+ "custom-elements.json",
34
+ "package.json"
39
35
  ],
40
- "peerDependencies": {
41
- "svelte": "^5.51.0"
42
- },
43
36
  "license": "MIT",
44
37
  "unpkg": "./dist/tool-magnifier.js",
45
38
  "jsdelivr": "./dist/tool-magnifier.js",
46
39
  "dependencies": {
47
- "@pie-players/pie-assessment-toolkit": "0.2.5",
48
- "@pie-players/pie-players-shared": "0.2.2",
49
- "@sveltejs/kit": "^2.52.0",
50
- "daisyui": "^5.5.18"
40
+ "@pie-players/pie-assessment-toolkit": "1.0.0-beta.2",
41
+ "@pie-players/pie-players-shared": "1.0.0-beta.2"
51
42
  },
43
+ "types": "./dist/index.d.ts",
52
44
  "scripts": {
53
45
  "build": "vite build",
54
46
  "dev": "vite build --watch",
55
- "typecheck": "tsc --noEmit",
56
- "lint": "biome check ."
47
+ "test": "bun test tests",
48
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
49
+ "check": "svelte-check --tsconfig ./tsconfig.json --compiler-warnings options_missing_custom_element:ignore",
50
+ "lint": "biome lint ."
57
51
  },
58
52
  "devDependencies": {
59
- "@biomejs/biome": "^2.3.10",
60
- "@sveltejs/vite-plugin-svelte": "^6.1.4",
61
- "svelte": "^5.51.3",
62
- "typescript": "^5.7.0",
63
- "vite": "^7.0.8",
64
- "vite-plugin-dts": "^4.5.3"
65
- }
53
+ "@biomejs/biome": "^2.5.15",
54
+ "@happy-dom/global-registrator": "^20.14.5",
55
+ "@sveltejs/vite-plugin-svelte": "^7.3.1",
56
+ "svelte": "^5.57.2",
57
+ "svelte-check": "^4.7.6",
58
+ "typescript": "^5.9.3",
59
+ "vite": "^8.3.3",
60
+ "vite-plugin-dts": "^5.1.2"
61
+ },
62
+ "homepage": "https://github.com/pie-framework/pie-players/tree/master/packages/tool-magnifier#readme",
63
+ "bugs": {
64
+ "url": "https://github.com/pie-framework/pie-players/issues"
65
+ },
66
+ "engines": {
67
+ "node": ">=20.0.0"
68
+ },
69
+ "sideEffects": true
66
70
  }
package/Magnifier.svelte DELETED
@@ -1,340 +0,0 @@
1
- <!-- Magnifier.svelte - Modern Svelte 5 implementation of HTML magnifier -->
2
- <script lang="ts">
3
- import { onMount, untrack } from 'svelte';
4
-
5
- interface MagnifierProps {
6
- zoom?: number;
7
- width?: number;
8
- height?: number;
9
- shape?: 'square' | 'circle';
10
- visible?: boolean;
11
- onPrepareContent?: (clonedContent: HTMLElement) => void;
12
- onContentUpdated?: (bodyElement: HTMLElement) => void;
13
- }
14
-
15
- let {
16
- zoom = 2,
17
- width = 420,
18
- height = 280,
19
- shape = 'square',
20
- visible = $bindable(false),
21
- onPrepareContent,
22
- onContentUpdated
23
- }: MagnifierProps = $props();
24
-
25
- // Reactive state
26
- let magnifierEl = $state<HTMLDivElement | null>(null);
27
- let contentEl = $state<HTMLDivElement | null>(null);
28
- let clonedBody = $state<HTMLElement | null>(null);
29
- let position = $state({ x: 200, y: 200 });
30
- let isDragging = $state(false);
31
- let dragOffset = $state({ x: 0, y: 0 });
32
-
33
- // MutationObserver to sync content changes
34
- let observer: MutationObserver | null = null;
35
- let syncTimeoutHandle: number | null = null;
36
-
37
- // Derived styles
38
- let magnifierStyle = $derived(`
39
- left: ${position.x}px;
40
- top: ${position.y}px;
41
- width: ${width}px;
42
- height: ${height}px;
43
- ${shape === 'circle' ? 'border-radius: 50%;' : ''}
44
- display: ${visible ? 'block' : 'none'};
45
- `);
46
-
47
- let contentStyle = $derived(`
48
- transform: scale(${zoom});
49
- left: ${-position.x * zoom - window.scrollX * zoom}px;
50
- top: ${-position.y * zoom - window.scrollY * zoom}px;
51
- width: ${document.body.scrollWidth}px;
52
- height: ${document.body.scrollHeight}px;
53
- `);
54
-
55
- function cloneDocumentBody() {
56
- const bodyOriginal = document.body;
57
- const bodyCopy = bodyOriginal.cloneNode(true) as HTMLElement;
58
-
59
- // Copy background color
60
- const bgColor = getComputedStyle(bodyOriginal).backgroundColor;
61
- if (bgColor && magnifierEl) {
62
- magnifierEl.style.backgroundColor = bgColor;
63
- }
64
-
65
- // Reset styles
66
- bodyCopy.style.cursor = 'auto';
67
- bodyCopy.style.paddingTop = '0px';
68
- bodyCopy.setAttribute('unselectable', 'on');
69
-
70
- // Copy canvas contents
71
- const canvasOriginal = bodyOriginal.querySelectorAll('canvas');
72
- const canvasCopy = bodyCopy.querySelectorAll('canvas');
73
- if (canvasOriginal.length > 0 && canvasOriginal.length === canvasCopy.length) {
74
- for (let i = 0; i < canvasOriginal.length; i++) {
75
- const ctx = canvasCopy[i].getContext('2d');
76
- if (ctx) {
77
- try {
78
- ctx.drawImage(canvasOriginal[i], 0, 0);
79
- } catch (error) {
80
- // Silent fail - likely CORS issue
81
- }
82
- }
83
- }
84
- }
85
-
86
- // Remove elements that shouldn't be magnified
87
- const elementsToRemove = [
88
- ...bodyCopy.querySelectorAll('script'),
89
- ...bodyCopy.querySelectorAll('audio'),
90
- ...bodyCopy.querySelectorAll('video'),
91
- ...bodyCopy.querySelectorAll('.magnifier'),
92
- ...bodyCopy.querySelectorAll('.magnifier-clone-ignore'),
93
- ...bodyCopy.querySelectorAll('[data-magnifier-ignore]')
94
- ];
95
- elementsToRemove.forEach(el => el.remove());
96
-
97
- // Custom content preparation
98
- if (onPrepareContent) {
99
- onPrepareContent(bodyCopy);
100
- }
101
-
102
- return bodyCopy;
103
- }
104
-
105
- function updateContent() {
106
- if (!visible || !contentEl) return;
107
-
108
- // Clear existing content
109
- contentEl.innerHTML = '';
110
-
111
- // Clone and prepare content
112
- clonedBody = cloneDocumentBody();
113
- contentEl.appendChild(clonedBody);
114
-
115
- // Sync scroll positions
116
- syncScrollPositions();
117
-
118
- // Notify content update
119
- if (onContentUpdated && clonedBody) {
120
- const bodyInClone = clonedBody.querySelector('body');
121
- if (bodyInClone) {
122
- onContentUpdated(bodyInClone as HTMLElement);
123
- }
124
- }
125
- }
126
-
127
- function syncScrollPositions() {
128
- if (!clonedBody) return;
129
-
130
- // Find all scrolled divs in the original document
131
- const scrolledDivs = Array.from(document.querySelectorAll('div')).filter(
132
- div => div.scrollTop > 0 && !magnifierEl?.contains(div)
133
- );
134
-
135
- // Sync scroll positions to cloned elements
136
- scrolledDivs.forEach(div => {
137
- const selectors: string[] = [];
138
-
139
- if (div.id) {
140
- selectors.push('#' + div.id);
141
- }
142
- if (div.className) {
143
- selectors.push('.' + div.className.split(' ').join('.'));
144
- }
145
-
146
- selectors.forEach(selector => {
147
- const targets = clonedBody!.querySelectorAll(selector);
148
- if (targets.length === 1) {
149
- const target = targets[0] as HTMLElement;
150
- target.scrollTop = div.scrollTop;
151
- target.scrollLeft = div.scrollLeft;
152
- }
153
- });
154
- });
155
- }
156
-
157
- function queueContentUpdate() {
158
- if (syncTimeoutHandle !== null) {
159
- clearTimeout(syncTimeoutHandle);
160
- }
161
- syncTimeoutHandle = window.setTimeout(() => {
162
- updateContent();
163
- }, 100);
164
- }
165
-
166
- function startObserving() {
167
- if (observer) return;
168
-
169
- observer = new MutationObserver((mutations) => {
170
- for (const mutation of mutations) {
171
- // Skip mutations within the magnifier itself
172
- if (magnifierEl && magnifierEl.contains(mutation.target as Node)) {
173
- continue;
174
- }
175
- queueContentUpdate();
176
- break;
177
- }
178
- });
179
-
180
- observer.observe(document.body, {
181
- childList: true,
182
- subtree: true,
183
- attributes: true,
184
- attributeFilter: ['class', 'width', 'height', 'style'],
185
- attributeOldValue: true
186
- });
187
- }
188
-
189
- function stopObserving() {
190
- if (observer) {
191
- observer.disconnect();
192
- observer = null;
193
- }
194
- if (syncTimeoutHandle !== null) {
195
- clearTimeout(syncTimeoutHandle);
196
- syncTimeoutHandle = null;
197
- }
198
- }
199
-
200
- // Drag handlers
201
- function handlePointerDown(e: PointerEvent) {
202
- // Don't start drag on interactive elements or their children
203
- const target = e.target as HTMLElement;
204
-
205
- // Check if the target or any parent is an interactive element
206
- const interactiveTags = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'];
207
- let element: HTMLElement | null = target;
208
- while (element && element !== magnifierEl) {
209
- if (interactiveTags.includes(element.tagName.toUpperCase())) {
210
- return;
211
- }
212
- element = element.parentElement;
213
- }
214
-
215
- isDragging = true;
216
- dragOffset = {
217
- x: e.clientX - position.x,
218
- y: e.clientY - position.y
219
- };
220
-
221
- if (magnifierEl) {
222
- magnifierEl.setPointerCapture(e.pointerId);
223
- }
224
- }
225
-
226
- function handlePointerMove(e: PointerEvent) {
227
- if (!isDragging) return;
228
-
229
- position = {
230
- x: e.clientX - dragOffset.x,
231
- y: e.clientY - dragOffset.y
232
- };
233
- }
234
-
235
- function handlePointerUp(e: PointerEvent) {
236
- if (isDragging && magnifierEl) {
237
- magnifierEl.releasePointerCapture(e.pointerId);
238
- }
239
- isDragging = false;
240
- }
241
-
242
- // Scroll sync handler
243
- function handleScroll(e: Event) {
244
- if (!visible) return;
245
-
246
- const target = e.target as HTMLElement;
247
- if (magnifierEl?.contains(target)) return;
248
-
249
- // Queue a content update to sync scroll positions
250
- queueContentUpdate();
251
- }
252
-
253
- // Update content when becoming visible
254
- $effect(() => {
255
- if (visible) {
256
- untrack(() => {
257
- updateContent();
258
- startObserving();
259
- });
260
- } else {
261
- untrack(() => {
262
- stopObserving();
263
- });
264
- }
265
- });
266
-
267
- // Update content when zoom changes
268
- $effect(() => {
269
- // Track zoom dependency
270
- zoom;
271
- if (visible) {
272
- untrack(() => {
273
- updateContent();
274
- });
275
- }
276
- });
277
-
278
- // Lifecycle
279
- onMount(() => {
280
- // Listen for scroll events
281
- window.addEventListener('scroll', handleScroll, true);
282
- window.addEventListener('resize', queueContentUpdate);
283
-
284
- return () => {
285
- stopObserving();
286
- window.removeEventListener('scroll', handleScroll, true);
287
- window.removeEventListener('resize', queueContentUpdate);
288
- };
289
- });
290
- </script>
291
-
292
- <div
293
- bind:this={magnifierEl}
294
- class="magnifier"
295
- style={magnifierStyle}
296
- onpointerdown={handlePointerDown}
297
- onpointermove={handlePointerMove}
298
- onpointerup={handlePointerUp}
299
- role="region"
300
- aria-label="Content magnifier"
301
- >
302
- <div bind:this={contentEl} class="magnifier-content" style={contentStyle}></div>
303
- <div class="magnifier-glass"></div>
304
- <slot />
305
- </div>
306
-
307
- <style>
308
- .magnifier {
309
- position: fixed;
310
- overflow: hidden;
311
- background-color: white;
312
- border: 1px solid #555;
313
- border-radius: 4px;
314
- z-index: 10000;
315
- cursor: move;
316
- touch-action: none;
317
- }
318
-
319
- .magnifier-content {
320
- position: absolute;
321
- top: 0;
322
- left: 0;
323
- overflow: visible;
324
- display: block;
325
- transform-origin: left top;
326
- user-select: none;
327
- pointer-events: none;
328
- }
329
-
330
- .magnifier-glass {
331
- position: absolute;
332
- top: 0;
333
- left: 0;
334
- width: 100%;
335
- height: 100%;
336
- opacity: 0;
337
- background-color: transparent;
338
- pointer-events: all;
339
- }
340
- </style>
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}