@pie-players/pie-tool-protractor 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 +0 -1
- package/package.json +5 -8
- package/dist/index.d.ts.map +0 -1
- package/tool-protractor.svelte +0 -358
package/dist/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-tool-protractor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Draggable and rotatable protractor tool for PIE assessment player",
|
|
6
6
|
"repository": {
|
|
@@ -19,18 +19,15 @@
|
|
|
19
19
|
"geometry",
|
|
20
20
|
"measurement"
|
|
21
21
|
],
|
|
22
|
-
"svelte": "./tool-protractor.svelte",
|
|
23
22
|
"main": "./dist/tool-protractor.js",
|
|
24
23
|
"exports": {
|
|
25
24
|
".": {
|
|
26
25
|
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/tool-protractor.js"
|
|
28
|
-
"svelte": "./tool-protractor.svelte"
|
|
26
|
+
"import": "./dist/tool-protractor.js"
|
|
29
27
|
}
|
|
30
28
|
},
|
|
31
29
|
"files": [
|
|
32
30
|
"dist",
|
|
33
|
-
"tool-protractor.svelte",
|
|
34
31
|
"protractor.svg",
|
|
35
32
|
"README.md"
|
|
36
33
|
],
|
|
@@ -38,9 +35,9 @@
|
|
|
38
35
|
"unpkg": "./dist/tool-protractor.js",
|
|
39
36
|
"jsdelivr": "./dist/tool-protractor.js",
|
|
40
37
|
"dependencies": {
|
|
41
|
-
"@pie-players/pie-assessment-toolkit": "0.3.
|
|
42
|
-
"@pie-players/pie-context": "0.3.
|
|
43
|
-
"@pie-players/pie-players-shared": "0.3.
|
|
38
|
+
"@pie-players/pie-assessment-toolkit": "0.3.44",
|
|
39
|
+
"@pie-players/pie-context": "0.3.44",
|
|
40
|
+
"@pie-players/pie-players-shared": "0.3.44",
|
|
44
41
|
"moveable": "^0.53.0"
|
|
45
42
|
},
|
|
46
43
|
"types": "./dist/index.d.ts",
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/tool-protractor.svelte
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
<svelte:options
|
|
2
|
-
customElement={{
|
|
3
|
-
tag: 'pie-tool-protractor',
|
|
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
|
-
import {
|
|
14
|
-
connectToolRuntimeContext,
|
|
15
|
-
ZIndexLayer,
|
|
16
|
-
} from '@pie-players/pie-assessment-toolkit';
|
|
17
|
-
import type {
|
|
18
|
-
AssessmentToolkitRuntimeContext,
|
|
19
|
-
ToolCoordinatorApi,
|
|
20
|
-
} from '@pie-players/pie-assessment-toolkit';
|
|
21
|
-
import Moveable from 'moveable';
|
|
22
|
-
import { onMount } from 'svelte';
|
|
23
|
-
import protractorSvg from './protractor.svg';
|
|
24
|
-
|
|
25
|
-
// Props
|
|
26
|
-
let { visible = false, toolId = 'protractor' }: { visible?: boolean; toolId?: string } = $props();
|
|
27
|
-
|
|
28
|
-
// Check if running in browser
|
|
29
|
-
const isBrowser = typeof window !== 'undefined';
|
|
30
|
-
|
|
31
|
-
// State
|
|
32
|
-
let containerEl = $state<HTMLDivElement | undefined>();
|
|
33
|
-
let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
|
|
34
|
-
const coordinator = $derived(
|
|
35
|
-
runtimeContext?.toolCoordinator as ToolCoordinatorApi | undefined,
|
|
36
|
-
);
|
|
37
|
-
let announceText = $state('');
|
|
38
|
-
let moveable: Moveable | null = null;
|
|
39
|
-
|
|
40
|
-
// Track registration state
|
|
41
|
-
let registered = $state(false);
|
|
42
|
-
|
|
43
|
-
// Keyboard navigation constants
|
|
44
|
-
const MOVE_STEP = 10; // pixels
|
|
45
|
-
const ROTATE_STEP = 5; // degrees
|
|
46
|
-
const FINE_ROTATE_STEP = 1; // degrees
|
|
47
|
-
|
|
48
|
-
$effect(() => {
|
|
49
|
-
if (!containerEl) return;
|
|
50
|
-
return connectToolRuntimeContext(containerEl, (value: AssessmentToolkitRuntimeContext) => {
|
|
51
|
-
runtimeContext = value;
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
function announce(message: string) {
|
|
56
|
-
announceText = message;
|
|
57
|
-
setTimeout(() => announceText = '', 1000);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Initialize Moveable.js (matching production configuration)
|
|
61
|
-
function initMoveable() {
|
|
62
|
-
if (!containerEl || !isBrowser) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Clean up any existing instance first
|
|
67
|
-
if (moveable) {
|
|
68
|
-
moveable.destroy();
|
|
69
|
-
moveable = null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
coordinator?.bringToFront(containerEl);
|
|
73
|
-
|
|
74
|
-
moveable = new Moveable(document.body, {
|
|
75
|
-
target: containerEl,
|
|
76
|
-
draggable: true,
|
|
77
|
-
rotatable: true,
|
|
78
|
-
snappable: true,
|
|
79
|
-
originDraggable: false,
|
|
80
|
-
originRelative: true,
|
|
81
|
-
origin: [0.5, 1], // Bottom center (matching production implementation)
|
|
82
|
-
hideDefaultLines: true,
|
|
83
|
-
keepRatio: false,
|
|
84
|
-
bounds: {
|
|
85
|
-
left: -110,
|
|
86
|
-
top: -110,
|
|
87
|
-
right: -110,
|
|
88
|
-
bottom: -110,
|
|
89
|
-
position: 'css'
|
|
90
|
-
}
|
|
91
|
-
} as any); // Type assertion needed for Moveable.js config
|
|
92
|
-
|
|
93
|
-
// Associate the moveable instance with the tool ID
|
|
94
|
-
const controlBox = moveable.getControlBoxElement();
|
|
95
|
-
controlBox?.setAttribute('data-moveablejs-tool-control-box', toolId);
|
|
96
|
-
const surface = containerEl.getAttribute('data-pie-tool-surface');
|
|
97
|
-
if (surface) {
|
|
98
|
-
controlBox?.setAttribute('data-pie-tool-surface', surface);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
moveable.on('drag', ({ target, transform }) => {
|
|
102
|
-
if (target) {
|
|
103
|
-
target.style.transform = transform;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
moveable.on('rotate', ({ target, transform }) => {
|
|
108
|
-
if (target) {
|
|
109
|
-
target.style.transform = transform;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function destroyMoveable() {
|
|
115
|
-
if (moveable) {
|
|
116
|
-
moveable.destroy();
|
|
117
|
-
moveable = null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function updateBounds() {
|
|
122
|
-
if (moveable) {
|
|
123
|
-
moveable.bounds = {
|
|
124
|
-
left: -110,
|
|
125
|
-
top: -110,
|
|
126
|
-
right: -110,
|
|
127
|
-
bottom: -110,
|
|
128
|
-
position: 'css'
|
|
129
|
-
};
|
|
130
|
-
moveable.updateRect();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Keyboard navigation (preserved for accessibility)
|
|
135
|
-
function handleKeyDown(e: KeyboardEvent) {
|
|
136
|
-
if (!moveable || !containerEl) return;
|
|
137
|
-
|
|
138
|
-
let handled = false;
|
|
139
|
-
const isShift = e.shiftKey;
|
|
140
|
-
|
|
141
|
-
// Get current transform from element
|
|
142
|
-
const transform = containerEl.style.transform || '';
|
|
143
|
-
const matrix = new DOMMatrix(transform || 'none');
|
|
144
|
-
|
|
145
|
-
// Extract position and rotation
|
|
146
|
-
let x = matrix.e || (isBrowser ? window.innerWidth / 2 : 400);
|
|
147
|
-
let y = matrix.f || (isBrowser ? window.innerHeight / 2 : 300);
|
|
148
|
-
let rotation = Math.round(Math.atan2(matrix.b, matrix.a) * (180 / Math.PI));
|
|
149
|
-
|
|
150
|
-
switch (e.key) {
|
|
151
|
-
case 'ArrowUp':
|
|
152
|
-
if (isShift) {
|
|
153
|
-
rotation = (rotation - ROTATE_STEP + 360) % 360;
|
|
154
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
155
|
-
} else {
|
|
156
|
-
y -= MOVE_STEP;
|
|
157
|
-
announce(`Moved up to ${Math.round(y)}`);
|
|
158
|
-
}
|
|
159
|
-
handled = true;
|
|
160
|
-
break;
|
|
161
|
-
case 'ArrowDown':
|
|
162
|
-
if (isShift) {
|
|
163
|
-
rotation = (rotation + ROTATE_STEP) % 360;
|
|
164
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
165
|
-
} else {
|
|
166
|
-
y += MOVE_STEP;
|
|
167
|
-
announce(`Moved down to ${Math.round(y)}`);
|
|
168
|
-
}
|
|
169
|
-
handled = true;
|
|
170
|
-
break;
|
|
171
|
-
case 'ArrowLeft':
|
|
172
|
-
if (isShift) {
|
|
173
|
-
rotation = (rotation - ROTATE_STEP + 360) % 360;
|
|
174
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
175
|
-
} else {
|
|
176
|
-
x -= MOVE_STEP;
|
|
177
|
-
announce(`Moved left to ${Math.round(x)}`);
|
|
178
|
-
}
|
|
179
|
-
handled = true;
|
|
180
|
-
break;
|
|
181
|
-
case 'ArrowRight':
|
|
182
|
-
if (isShift) {
|
|
183
|
-
rotation = (rotation + ROTATE_STEP) % 360;
|
|
184
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
185
|
-
} else {
|
|
186
|
-
x += MOVE_STEP;
|
|
187
|
-
announce(`Moved right to ${Math.round(x)}`);
|
|
188
|
-
}
|
|
189
|
-
handled = true;
|
|
190
|
-
break;
|
|
191
|
-
case 'PageUp':
|
|
192
|
-
rotation = (rotation - FINE_ROTATE_STEP + 360) % 360;
|
|
193
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
194
|
-
handled = true;
|
|
195
|
-
break;
|
|
196
|
-
case 'PageDown':
|
|
197
|
-
rotation = (rotation + FINE_ROTATE_STEP) % 360;
|
|
198
|
-
announce(`Rotated to ${rotation} degrees`);
|
|
199
|
-
handled = true;
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (handled && moveable) {
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
// Apply new transform via Moveable
|
|
206
|
-
const newTransform = `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(${rotation}deg)`;
|
|
207
|
-
containerEl.style.transform = newTransform;
|
|
208
|
-
moveable.updateRect();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Initialize Moveable when visible changes
|
|
213
|
-
$effect(() => {
|
|
214
|
-
if (visible && containerEl && isBrowser) {
|
|
215
|
-
// Wait for the next tick to ensure DOM is updated
|
|
216
|
-
setTimeout(initMoveable, 0);
|
|
217
|
-
} else {
|
|
218
|
-
destroyMoveable();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// Register with coordinator when it becomes available
|
|
223
|
-
$effect(() => {
|
|
224
|
-
if (coordinator && toolId && !registered) {
|
|
225
|
-
coordinator.registerTool(toolId, 'Protractor', undefined, ZIndexLayer.TOOL);
|
|
226
|
-
registered = true;
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
onMount(() => {
|
|
231
|
-
window.addEventListener('resize', updateBounds);
|
|
232
|
-
return () => {
|
|
233
|
-
destroyMoveable();
|
|
234
|
-
window.removeEventListener('resize', updateBounds);
|
|
235
|
-
if (coordinator && toolId) {
|
|
236
|
-
coordinator.unregisterTool(toolId);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Update element reference when container becomes available
|
|
242
|
-
$effect(() => {
|
|
243
|
-
if (coordinator && containerEl && toolId) {
|
|
244
|
-
coordinator.updateToolElement(toolId, containerEl);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Auto-focus when tool becomes visible
|
|
249
|
-
$effect(() => {
|
|
250
|
-
if (visible && containerEl) {
|
|
251
|
-
setTimeout(() => containerEl?.focus(), 100);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
</script>
|
|
255
|
-
|
|
256
|
-
{#if visible && isBrowser}
|
|
257
|
-
<!-- Screen reader announcements -->
|
|
258
|
-
<div class="pie-sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
259
|
-
{announceText}
|
|
260
|
-
</div>
|
|
261
|
-
|
|
262
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
263
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
264
|
-
<div
|
|
265
|
-
bind:this={containerEl}
|
|
266
|
-
class="pie-tool-protractor"
|
|
267
|
-
data-moveablejs-tool-id={toolId}
|
|
268
|
-
onpointerdown={() => coordinator?.bringToFront(containerEl)}
|
|
269
|
-
onkeydown={handleKeyDown}
|
|
270
|
-
role="application"
|
|
271
|
-
tabindex="0"
|
|
272
|
-
aria-label="Protractor tool. Use arrow keys to move, Shift+arrows to rotate, PageUp/PageDown for fine rotation. Current rotation displayed via Moveable.js"
|
|
273
|
-
aria-roledescription="Draggable and rotatable protractor measurement tool"
|
|
274
|
-
>
|
|
275
|
-
<div class="pie-tool-protractor__container">
|
|
276
|
-
<img
|
|
277
|
-
class="pie-tool-protractor__image"
|
|
278
|
-
src={protractorSvg}
|
|
279
|
-
alt="Protractor with 180-degree semicircular scale marked from 0 to 180 degrees in both directions, with degree markings every 10 degrees"
|
|
280
|
-
draggable="false"
|
|
281
|
-
/>
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
{/if}
|
|
285
|
-
|
|
286
|
-
<style>
|
|
287
|
-
.pie-sr-only {
|
|
288
|
-
position: absolute;
|
|
289
|
-
width: 1px;
|
|
290
|
-
height: 1px;
|
|
291
|
-
padding: 0;
|
|
292
|
-
margin: -1px;
|
|
293
|
-
overflow: hidden;
|
|
294
|
-
clip: rect(0, 0, 0, 0);
|
|
295
|
-
white-space: nowrap;
|
|
296
|
-
border-width: 0;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.pie-tool-protractor {
|
|
300
|
-
border: 0;
|
|
301
|
-
cursor: move;
|
|
302
|
-
left: 50%;
|
|
303
|
-
overflow: hidden;
|
|
304
|
-
position: absolute;
|
|
305
|
-
top: 50%;
|
|
306
|
-
transform: translate(-50%, -50%);
|
|
307
|
-
transform-origin: 50% calc(100% - 10px); /* Rotation origin at bottom center (matching production implementation) */
|
|
308
|
-
user-select: none;
|
|
309
|
-
touch-action: none;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.pie-tool-protractor:focus-visible {
|
|
313
|
-
outline: 3px solid var(--pie-button-focus-outline, var(--pie-primary, #4A90E2));
|
|
314
|
-
outline-offset: 2px;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.pie-tool-protractor__container {
|
|
318
|
-
border: 0;
|
|
319
|
-
position: relative;
|
|
320
|
-
width: 400px;
|
|
321
|
-
height: 210px;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/* Semi-transparent white overlay for visibility (matching production implementation) */
|
|
325
|
-
.pie-tool-protractor__container::after {
|
|
326
|
-
background-color: var(--pie-background, #fff);
|
|
327
|
-
border-radius: 283px 283px 0 0;
|
|
328
|
-
box-shadow: none;
|
|
329
|
-
content: '';
|
|
330
|
-
display: block;
|
|
331
|
-
height: 283px;
|
|
332
|
-
opacity: 0.5;
|
|
333
|
-
position: absolute;
|
|
334
|
-
top: 0;
|
|
335
|
-
width: 400px;
|
|
336
|
-
z-index: 1;
|
|
337
|
-
pointer-events: none;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.pie-tool-protractor__image {
|
|
341
|
-
width: 400px;
|
|
342
|
-
height: 210px;
|
|
343
|
-
position: relative;
|
|
344
|
-
z-index: 2;
|
|
345
|
-
display: block;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/* Moveable.js control styling (matching production implementation) */
|
|
349
|
-
:global(body .moveable-control-box[data-pie-tool-surface="frameless"]) {
|
|
350
|
-
--moveable-color: transparent;
|
|
351
|
-
z-index: 2003; /* ZIndexLayer.CONTROL */
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
:global([data-moveablejs-tool-id="protractor"]) {
|
|
355
|
-
z-index: 2002; /* ZIndexLayer.MODAL */
|
|
356
|
-
}
|
|
357
|
-
</style>
|
|
358
|
-
|