@pie-players/pie-tool-line-reader 0.3.43 → 0.3.44

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/dist/index.d.ts CHANGED
@@ -5,4 +5,3 @@
5
5
  * Import the built version for CDN usage, or the .svelte source for Svelte projects.
6
6
  */
7
7
  export {};
8
- //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-tool-line-reader",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "type": "module",
5
5
  "description": "Reading guide overlay tool for PIE assessment player",
6
6
  "repository": {
@@ -19,27 +19,24 @@
19
19
  "reading",
20
20
  "accessibility"
21
21
  ],
22
- "svelte": "./tool-line-reader.svelte",
23
22
  "main": "./dist/tool-line-reader.js",
24
23
  "exports": {
25
24
  ".": {
26
25
  "types": "./dist/index.d.ts",
27
- "import": "./dist/tool-line-reader.js",
28
- "svelte": "./tool-line-reader.svelte"
26
+ "import": "./dist/tool-line-reader.js"
29
27
  }
30
28
  },
31
29
  "files": [
32
30
  "dist",
33
- "tool-line-reader.svelte",
34
31
  "package.json"
35
32
  ],
36
33
  "license": "MIT",
37
34
  "unpkg": "./dist/tool-line-reader.js",
38
35
  "jsdelivr": "./dist/tool-line-reader.js",
39
36
  "dependencies": {
40
- "@pie-players/pie-assessment-toolkit": "0.3.43",
41
- "@pie-players/pie-context": "0.3.43",
42
- "@pie-players/pie-players-shared": "0.3.43"
37
+ "@pie-players/pie-assessment-toolkit": "0.3.44",
38
+ "@pie-players/pie-context": "0.3.44",
39
+ "@pie-players/pie-players-shared": "0.3.44"
43
40
  },
44
41
  "types": "./dist/index.d.ts",
45
42
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -1,450 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- tag: 'pie-tool-line-reader',
4
- shadow: 'open',
5
- props: {
6
- visible: { type: 'Boolean', attribute: 'visible' },
7
- toolId: { type: 'String', attribute: 'tool-id' }
8
- }
9
- }}
10
- />
11
-
12
- <script lang="ts">
13
-
14
- import {
15
- connectToolRuntimeContext,
16
- ZIndexLayer,
17
- } from '@pie-players/pie-assessment-toolkit';
18
- import type {
19
- AssessmentToolkitRuntimeContext,
20
- ToolCoordinatorApi,
21
- } from '@pie-players/pie-assessment-toolkit';
22
- import { onMount } from 'svelte';
23
-
24
- // Props
25
- let { visible = false, toolId = 'lineReader' }: { visible?: boolean; toolId?: string } = $props();
26
-
27
- // Check if running in browser
28
- const isBrowser = typeof window !== 'undefined';
29
-
30
- // State
31
- let containerEl = $state<HTMLDivElement | undefined>();
32
- let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
33
- const coordinator = $derived(
34
- runtimeContext?.toolCoordinator as ToolCoordinatorApi | undefined,
35
- );
36
- let isDragging = $state(false);
37
- let isResizing = $state(false);
38
- let position = $state({
39
- x: isBrowser ? window.innerWidth / 2 : 400,
40
- y: isBrowser ? window.innerHeight / 2 : 300
41
- });
42
- let size = $state({ width: 600, height: 60 });
43
- let dragStart = $state({ x: 0, y: 0 });
44
- let resizeStart = $state({ width: 0, height: 0, mouseY: 0 });
45
- let announceText = $state('');
46
- let currentColor = $state('#ffff00'); // Yellow
47
- let currentOpacity = $state(0.3);
48
- let maskingMode = $state<'highlight' | 'obscure'>('highlight');
49
-
50
- // Track registration state
51
- let registered = $state(false);
52
-
53
- // Available colors
54
- const colors = [
55
- { name: 'Yellow', value: '#ffff00' },
56
- { name: 'Blue', value: '#00bfff' },
57
- { name: 'Pink', value: '#ff69b4' },
58
- { name: 'Green', value: '#00ff7f' },
59
- { name: 'Orange', value: '#ffa500' }
60
- ];
61
-
62
- // Keyboard navigation constants
63
- const MOVE_STEP = 10; // pixels
64
- const RESIZE_STEP = 10; // pixels
65
-
66
- $effect(() => {
67
- if (!containerEl) return;
68
- return connectToolRuntimeContext(containerEl, (value: AssessmentToolkitRuntimeContext) => {
69
- runtimeContext = value;
70
- });
71
- });
72
-
73
- function announce(message: string) {
74
- announceText = message;
75
- setTimeout(() => announceText = '', 1000);
76
- }
77
-
78
- function cycleColor() {
79
- const currentIndex = colors.findIndex(c => c.value === currentColor);
80
- const nextIndex = (currentIndex + 1) % colors.length;
81
- currentColor = colors[nextIndex].value;
82
- announce(`Color changed to ${colors[nextIndex].name}`);
83
- }
84
-
85
- function adjustOpacity(delta: number) {
86
- currentOpacity = Math.max(0.1, Math.min(0.9, currentOpacity + delta));
87
- announce(`Opacity ${Math.round(currentOpacity * 100)}%`);
88
- }
89
-
90
- function toggleMaskingMode() {
91
- maskingMode = maskingMode === 'highlight' ? 'obscure' : 'highlight';
92
- announce(`Mode changed to ${maskingMode === 'highlight' ? 'highlight' : 'masking'}`);
93
- }
94
-
95
- // Pointer event handlers (better for web components)
96
- function handlePointerDown(e: PointerEvent) {
97
- const target = e.target as HTMLElement;
98
-
99
- // Check if clicking the resize handle
100
- if (target.closest('.resize-handle')) {
101
- startResizing(e);
102
- } else {
103
- startDragging(e);
104
- }
105
- }
106
-
107
- function startDragging(e: PointerEvent) {
108
- if (!containerEl) return;
109
-
110
- // Capture pointer for isolated event handling
111
- containerEl.setPointerCapture(e.pointerId);
112
- isDragging = true;
113
- dragStart = {
114
- x: e.clientX - position.x,
115
- y: e.clientY - position.y
116
- };
117
-
118
- coordinator?.bringToFront(containerEl);
119
-
120
- // Add pointer move/up handlers to element (not window!)
121
- containerEl.addEventListener('pointermove', handlePointerMove);
122
- containerEl.addEventListener('pointerup', handlePointerUp);
123
-
124
- e.preventDefault();
125
- }
126
-
127
- function startResizing(e: PointerEvent) {
128
- if (!containerEl) return;
129
-
130
- // Capture pointer for isolated event handling
131
- containerEl.setPointerCapture(e.pointerId);
132
-
133
- isResizing = true;
134
- resizeStart = {
135
- width: size.width,
136
- height: size.height,
137
- mouseY: e.clientY
138
- };
139
-
140
- coordinator?.bringToFront(containerEl);
141
-
142
- // Add pointer move/up handlers to element (not window!)
143
- containerEl.addEventListener('pointermove', handlePointerMove);
144
- containerEl.addEventListener('pointerup', handlePointerUp);
145
-
146
- e.preventDefault();
147
- e.stopPropagation();
148
- }
149
-
150
- function handlePointerMove(e: PointerEvent) {
151
- if (isDragging) {
152
- position = {
153
- x: e.clientX - dragStart.x,
154
- y: e.clientY - dragStart.y
155
- };
156
- } else if (isResizing) {
157
- // Vertical resize only
158
- const deltaY = e.clientY - resizeStart.mouseY;
159
- size.height = Math.max(20, Math.min(400, resizeStart.height + deltaY));
160
- }
161
- }
162
-
163
- function handlePointerUp(e: PointerEvent) {
164
- if (!containerEl) return;
165
-
166
- // Release pointer capture
167
- containerEl.releasePointerCapture(e.pointerId);
168
-
169
- // Clean up event listeners
170
- containerEl.removeEventListener('pointermove', handlePointerMove);
171
- containerEl.removeEventListener('pointerup', handlePointerUp);
172
-
173
- isDragging = false;
174
- isResizing = false;
175
- }
176
-
177
- function handleKeyDown(e: KeyboardEvent) {
178
- let handled = false;
179
-
180
- switch (e.key) {
181
- case 'ArrowUp':
182
- position.y -= MOVE_STEP;
183
- announce(`Moved up to ${Math.round(position.y)}`);
184
- handled = true;
185
- break;
186
- case 'ArrowDown':
187
- position.y += MOVE_STEP;
188
- announce(`Moved down to ${Math.round(position.y)}`);
189
- handled = true;
190
- break;
191
- case 'ArrowLeft':
192
- position.x -= MOVE_STEP;
193
- announce(`Moved left to ${Math.round(position.x)}`);
194
- handled = true;
195
- break;
196
- case 'ArrowRight':
197
- position.x += MOVE_STEP;
198
- announce(`Moved right to ${Math.round(position.x)}`);
199
- handled = true;
200
- break;
201
- case '+':
202
- case '=':
203
- size.height = Math.min(400, size.height + RESIZE_STEP);
204
- announce(`Height ${size.height} pixels`);
205
- handled = true;
206
- break;
207
- case '-':
208
- case '_':
209
- size.height = Math.max(20, size.height - RESIZE_STEP);
210
- announce(`Height ${size.height} pixels`);
211
- handled = true;
212
- break;
213
- case 'c':
214
- case 'C':
215
- cycleColor();
216
- handled = true;
217
- break;
218
- case ']':
219
- adjustOpacity(0.1);
220
- handled = true;
221
- break;
222
- case '[':
223
- adjustOpacity(-0.1);
224
- handled = true;
225
- break;
226
- case 'm':
227
- case 'M':
228
- toggleMaskingMode();
229
- handled = true;
230
- break;
231
- }
232
-
233
- if (handled) {
234
- e.preventDefault();
235
- }
236
- }
237
-
238
- // Register with coordinator when it becomes available
239
- $effect(() => {
240
- if (coordinator && toolId && !registered) {
241
- coordinator.registerTool(toolId, 'Line Reader', undefined, ZIndexLayer.TOOL);
242
- registered = true;
243
- }
244
- });
245
-
246
- onMount(() => {
247
- return () => {
248
- if (coordinator && toolId) {
249
- coordinator.unregisterTool(toolId);
250
- }
251
- };
252
- });
253
-
254
- // Update element reference when container becomes available
255
- $effect(() => {
256
- if (coordinator && containerEl && toolId) {
257
- coordinator.updateToolElement(toolId, containerEl);
258
- }
259
- });
260
-
261
- // Auto-focus when tool becomes visible
262
- $effect(() => {
263
- if (visible && containerEl) {
264
- setTimeout(() => containerEl?.focus(), 100);
265
- }
266
- });
267
-
268
- // Computed background color with opacity
269
- let backgroundColor = $derived(currentColor + Math.round(currentOpacity * 255).toString(16).padStart(2, '0'));
270
- </script>
271
-
272
- {#if visible}
273
- <!-- Screen reader announcements -->
274
- <div class="pie-sr-only" role="status" aria-live="polite" aria-atomic="true">
275
- {announceText}
276
- </div>
277
-
278
- <!-- Masking overlays (only in obscure mode) - 4 rectangles around the line reader window -->
279
- {#if maskingMode === 'obscure'}
280
- <!-- Top mask - from top of viewport to top of line reader -->
281
- <div
282
- class="pie-tool-line-reader__mask pie-tool-line-reader__mask--top"
283
- style="height: {Math.max(0, position.y - size.height / 2)}px;"
284
- aria-hidden="true"
285
- ></div>
286
- <!-- Bottom mask - from bottom of line reader to bottom of viewport -->
287
- <div
288
- class="pie-tool-line-reader__mask pie-tool-line-reader__mask--bottom"
289
- style="top: {position.y + size.height / 2}px;"
290
- aria-hidden="true"
291
- ></div>
292
- <!-- Left mask - left side of line reader window -->
293
- <div
294
- class="pie-tool-line-reader__mask pie-tool-line-reader__mask--left"
295
- style="top: {position.y - size.height / 2}px; height: {size.height}px; width: {Math.max(0, position.x - size.width / 2)}px;"
296
- aria-hidden="true"
297
- ></div>
298
- <!-- Right mask - right side of line reader window -->
299
- <div
300
- class="pie-tool-line-reader__mask pie-tool-line-reader__mask--right"
301
- style="top: {position.y - size.height / 2}px; height: {size.height}px; left: {position.x + size.width / 2}px;"
302
- aria-hidden="true"
303
- ></div>
304
- {/if}
305
-
306
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
307
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
308
- <div
309
- bind:this={containerEl}
310
- class="pie-tool-line-reader"
311
- class:pie-tool-line-reader--masking-mode={maskingMode === 'obscure'}
312
- style="left: {position.x}px; top: {position.y}px; width: {size.width}px; height: {size.height}px;"
313
- onpointerdown={handlePointerDown}
314
- onkeydown={handleKeyDown}
315
- role="group"
316
- tabindex="0"
317
- aria-label="Line Reader tool. Mode: {maskingMode === 'highlight' ? 'Highlight' : 'Masking'}. Use arrow keys to move, +/- to resize height, C to change color, [ and ] to adjust opacity, M to toggle mode. Current color: {colors.find(c => c.value === currentColor)?.name}, Opacity: {Math.round(currentOpacity * 100)}%"
318
- aria-roledescription="Draggable and resizable reading guide overlay"
319
- >
320
- <div class="pie-tool-line-reader__container" style="background-color: {backgroundColor};">
321
- </div>
322
-
323
- <!-- Resize handle -->
324
- <div
325
- class="pie-tool-line-reader__resize-handle pie-tool-line-reader__resize-handle--bottom"
326
- title="Drag to resize height"
327
- role="button"
328
- tabindex="-1"
329
- aria-label="Resize handle - drag to adjust height"
330
- >
331
- <svg width="20" height="8" viewBox="0 0 20 8" aria-hidden="true">
332
- <rect x="8" y="3" width="4" height="2" fill="var(--pie-primary, #4CAF50)" rx="1"/>
333
- </svg>
334
- </div>
335
- </div>
336
-
337
- {/if}
338
-
339
- <style>
340
- .pie-sr-only {
341
- position: absolute;
342
- width: 1px;
343
- height: 1px;
344
- padding: 0;
345
- margin: -1px;
346
- overflow: hidden;
347
- clip: rect(0, 0, 0, 0);
348
- white-space: nowrap;
349
- border-width: 0;
350
- }
351
-
352
- .pie-tool-line-reader {
353
- border: none;
354
- cursor: move;
355
- overflow: visible;
356
- position: absolute;
357
- transform: translate(-50%, -50%);
358
- user-select: none;
359
- pointer-events: auto;
360
- touch-action: none;
361
- }
362
-
363
- .pie-tool-line-reader:focus-visible {
364
- outline: 3px solid var(--pie-button-focus-outline, var(--pie-primary, #4A90E2));
365
- outline-offset: 2px;
366
- }
367
-
368
- .pie-tool-line-reader__container {
369
- width: 100%;
370
- height: 100%;
371
- position: relative;
372
- transition: background-color 0.2s ease;
373
- }
374
-
375
-
376
- .pie-tool-line-reader__resize-handle {
377
- position: absolute;
378
- cursor: ns-resize;
379
- z-index: 10;
380
- display: flex;
381
- align-items: center;
382
- justify-content: center;
383
- }
384
-
385
- .pie-tool-line-reader__resize-handle--bottom {
386
- bottom: -12px;
387
- left: 50%;
388
- transform: translateX(-50%);
389
- width: 44px;
390
- height: 24px;
391
- background-color: color-mix(in srgb, var(--pie-background, #fff) 90%, transparent);
392
- border-radius: 12px;
393
- border: 2px solid var(--pie-primary, #4caf50);
394
- }
395
-
396
- .pie-tool-line-reader__resize-handle:hover {
397
- background-color: color-mix(in srgb, var(--pie-primary, #4caf50) 20%, transparent);
398
- }
399
-
400
- .pie-tool-line-reader__resize-handle:active {
401
- cursor: ns-resize;
402
- }
403
-
404
- .pie-tool-line-reader:active {
405
- cursor: grabbing;
406
- }
407
-
408
- /* Masking overlays for obscure mode - 4 rectangles covering all areas except line reader window */
409
- .pie-tool-line-reader__mask {
410
- position: fixed;
411
- background: color-mix(in srgb, var(--pie-text, #000) 85%, transparent);
412
- z-index: 999;
413
- pointer-events: none;
414
- }
415
-
416
- .pie-tool-line-reader__mask--top {
417
- top: 0;
418
- left: 0;
419
- right: 0;
420
- /* Height set via inline style */
421
- }
422
-
423
- .pie-tool-line-reader__mask--bottom {
424
- /* Top set via inline style */
425
- left: 0;
426
- right: 0;
427
- bottom: 0;
428
- }
429
-
430
- .pie-tool-line-reader__mask--left {
431
- /* Top, height, and width set via inline style */
432
- left: 0;
433
- }
434
-
435
- .pie-tool-line-reader__mask--right {
436
- /* Top, height, and left set via inline style */
437
- right: 0;
438
- }
439
-
440
- /* In masking mode, change the window appearance */
441
- .pie-tool-line-reader.pie-tool-line-reader--masking-mode {
442
- box-shadow: none;
443
- }
444
-
445
- /* In masking mode, the window should be transparent to show content underneath */
446
- .pie-tool-line-reader.pie-tool-line-reader--masking-mode .pie-tool-line-reader__container {
447
- background-color: transparent !important;
448
- }
449
-
450
- </style>