@pie-players/pie-tool-ruler 0.3.42 → 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-ruler",
3
- "version": "0.3.42",
3
+ "version": "0.3.44",
4
4
  "type": "module",
5
5
  "description": "Ruler measurement tool for PIE assessment player",
6
6
  "repository": {
@@ -20,18 +20,15 @@
20
20
  "geometry",
21
21
  "accessibility"
22
22
  ],
23
- "svelte": "./tool-ruler.svelte",
24
23
  "main": "./dist/tool-ruler.js",
25
24
  "exports": {
26
25
  ".": {
27
26
  "types": "./dist/index.d.ts",
28
- "import": "./dist/tool-ruler.js",
29
- "svelte": "./tool-ruler.svelte"
27
+ "import": "./dist/tool-ruler.js"
30
28
  }
31
29
  },
32
30
  "files": [
33
31
  "dist",
34
- "tool-ruler.svelte",
35
32
  "ruler-cm.svg",
36
33
  "ruler-inches.svg",
37
34
  "package.json",
@@ -41,9 +38,9 @@
41
38
  "unpkg": "./dist/tool-ruler.js",
42
39
  "jsdelivr": "./dist/tool-ruler.js",
43
40
  "dependencies": {
44
- "@pie-players/pie-assessment-toolkit": "0.3.42",
45
- "@pie-players/pie-context": "0.3.42",
46
- "@pie-players/pie-players-shared": "0.3.42",
41
+ "@pie-players/pie-assessment-toolkit": "0.3.44",
42
+ "@pie-players/pie-context": "0.3.44",
43
+ "@pie-players/pie-players-shared": "0.3.44",
47
44
  "moveable": "^0.53.0"
48
45
  },
49
46
  "types": "./dist/index.d.ts",
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/tool-ruler.svelte DELETED
@@ -1,442 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- tag: 'pie-tool-ruler',
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 { onDestroy, onMount } from 'svelte';
23
- import rulerCm from './ruler-cm.svg';
24
- import rulerInches from './ruler-inches.svg';
25
-
26
- // Props
27
- let { visible = false, toolId = 'ruler' }: { visible?: boolean; toolId?: string } = $props();
28
-
29
- // Check if running in browser
30
- const isBrowser = typeof window !== 'undefined';
31
-
32
- // State
33
- let containerEl = $state<HTMLDivElement | undefined>();
34
- let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
35
- const coordinator = $derived(
36
- runtimeContext?.toolCoordinator as ToolCoordinatorApi | undefined,
37
- );
38
- let announceText = $state('');
39
- let unit = $state<'inches' | 'cm'>('inches');
40
- let moveable: Moveable | null = null;
41
-
42
- // Track registration state
43
- let registered = $state(false);
44
-
45
- // Keyboard navigation constants
46
- const MOVE_STEP = 10; // pixels
47
- const ROTATE_STEP = 5; // degrees
48
- const FINE_ROTATE_STEP = 1; // degrees
49
-
50
- $effect(() => {
51
- if (!containerEl) return;
52
- return connectToolRuntimeContext(containerEl, (value: AssessmentToolkitRuntimeContext) => {
53
- runtimeContext = value;
54
- });
55
- });
56
-
57
- let currentRuler = $derived(unit === 'inches' ? rulerInches : rulerCm);
58
-
59
- function announce(message: string) {
60
- announceText = message;
61
- setTimeout(() => announceText = '', 1000);
62
- }
63
-
64
- function toggleUnit() {
65
- unit = unit === 'inches' ? 'cm' : 'inches';
66
- announce(`Switched to ${unit === 'inches' ? 'inches' : 'centimeters'}`);
67
- }
68
-
69
- // Initialize Moveable.js (matching production configuration)
70
- function initMoveable() {
71
- if (!containerEl || !isBrowser) {
72
- return;
73
- }
74
-
75
- // Clean up any existing instance first
76
- if (moveable) {
77
- moveable.destroy();
78
- moveable = null;
79
- }
80
-
81
- coordinator?.bringToFront(containerEl);
82
-
83
- moveable = new Moveable(document.body, {
84
- target: containerEl,
85
- draggable: true,
86
- rotatable: true,
87
- snappable: true,
88
- originDraggable: true,
89
- originRelative: true,
90
- keepRatio: false,
91
- bounds: {
92
- left: 0,
93
- top: 0,
94
- right: 0,
95
- bottom: 0,
96
- position: 'css'
97
- }
98
- });
99
-
100
- // Associate the moveable instance with the tool ID
101
- const controlBox = moveable.getControlBoxElement();
102
- controlBox?.setAttribute('data-moveablejs-tool-control-box', toolId);
103
- const surface = containerEl.getAttribute('data-pie-tool-surface');
104
- if (surface) {
105
- controlBox?.setAttribute('data-pie-tool-surface', surface);
106
- }
107
-
108
- moveable.on('drag', ({ target, transform }) => {
109
- if (target) {
110
- target.style.transform = transform;
111
- }
112
- });
113
-
114
- moveable.on('rotate', ({ target, transform }) => {
115
- if (target) {
116
- target.style.transform = transform;
117
- }
118
- });
119
- }
120
-
121
- function destroyMoveable() {
122
- if (moveable) {
123
- moveable.destroy();
124
- moveable = null;
125
- }
126
- }
127
-
128
- function updateBounds() {
129
- if (moveable) {
130
- moveable.bounds = {
131
- left: 0,
132
- top: 0,
133
- right: 0,
134
- bottom: 0,
135
- position: 'css'
136
- };
137
- moveable.updateRect();
138
- }
139
- }
140
-
141
- // Keyboard navigation (preserved for accessibility)
142
- function handleKeyDown(e: KeyboardEvent) {
143
- if (!moveable || !containerEl) return;
144
-
145
- let handled = false;
146
- const isShift = e.shiftKey;
147
-
148
- // Get current transform from element
149
- const transform = containerEl.style.transform || '';
150
- const matrix = new DOMMatrix(transform || 'none');
151
-
152
- // Extract position and rotation
153
- let x = matrix.e || (isBrowser ? window.innerWidth / 2 : 400);
154
- let y = matrix.f || (isBrowser ? window.innerHeight / 2 : 300);
155
- let rotation = Math.round(Math.atan2(matrix.b, matrix.a) * (180 / Math.PI));
156
-
157
- switch (e.key) {
158
- case 'ArrowUp':
159
- if (isShift) {
160
- rotation = (rotation - ROTATE_STEP + 360) % 360;
161
- announce(`Rotated to ${rotation} degrees`);
162
- } else {
163
- y -= MOVE_STEP;
164
- announce(`Moved up to ${Math.round(y)}`);
165
- }
166
- handled = true;
167
- break;
168
- case 'ArrowDown':
169
- if (isShift) {
170
- rotation = (rotation + ROTATE_STEP) % 360;
171
- announce(`Rotated to ${rotation} degrees`);
172
- } else {
173
- y += MOVE_STEP;
174
- announce(`Moved down to ${Math.round(y)}`);
175
- }
176
- handled = true;
177
- break;
178
- case 'ArrowLeft':
179
- if (isShift) {
180
- rotation = (rotation - ROTATE_STEP + 360) % 360;
181
- announce(`Rotated to ${rotation} degrees`);
182
- } else {
183
- x -= MOVE_STEP;
184
- announce(`Moved left to ${Math.round(x)}`);
185
- }
186
- handled = true;
187
- break;
188
- case 'ArrowRight':
189
- if (isShift) {
190
- rotation = (rotation + ROTATE_STEP) % 360;
191
- announce(`Rotated to ${rotation} degrees`);
192
- } else {
193
- x += MOVE_STEP;
194
- announce(`Moved right to ${Math.round(x)}`);
195
- }
196
- handled = true;
197
- break;
198
- case 'PageUp':
199
- rotation = (rotation - FINE_ROTATE_STEP + 360) % 360;
200
- announce(`Rotated to ${rotation} degrees`);
201
- handled = true;
202
- break;
203
- case 'PageDown':
204
- rotation = (rotation + FINE_ROTATE_STEP) % 360;
205
- announce(`Rotated to ${rotation} degrees`);
206
- handled = true;
207
- break;
208
- case 'u':
209
- case 'U':
210
- toggleUnit();
211
- handled = true;
212
- break;
213
- }
214
-
215
- if (handled && moveable) {
216
- e.preventDefault();
217
- // Apply new transform via Moveable
218
- const newTransform = `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(${rotation}deg)`;
219
- containerEl.style.transform = newTransform;
220
- moveable.updateRect();
221
- }
222
- }
223
-
224
- // Initialize Moveable when visible changes
225
- $effect(() => {
226
- if (visible && containerEl && isBrowser) {
227
- // Wait for the next tick to ensure DOM is updated
228
- setTimeout(initMoveable, 0);
229
- } else {
230
- destroyMoveable();
231
- }
232
- });
233
-
234
- // Register with coordinator when it becomes available
235
- $effect(() => {
236
- if (coordinator && toolId && !registered) {
237
- coordinator.registerTool(toolId, 'Ruler', undefined, ZIndexLayer.TOOL);
238
- registered = true;
239
- }
240
- });
241
-
242
- onMount(() => {
243
- window.addEventListener('resize', updateBounds);
244
- return () => {
245
- destroyMoveable();
246
- window.removeEventListener('resize', updateBounds);
247
- if (coordinator && toolId) {
248
- coordinator.unregisterTool(toolId);
249
- }
250
- };
251
- });
252
-
253
- // Update element reference when container becomes available
254
- $effect(() => {
255
- if (coordinator && containerEl && toolId) {
256
- coordinator.updateToolElement(toolId, containerEl);
257
- }
258
- });
259
-
260
- // Auto-focus when tool becomes visible
261
- $effect(() => {
262
- if (visible && containerEl) {
263
- setTimeout(() => containerEl?.focus(), 100);
264
- }
265
- });
266
- </script>
267
-
268
- {#if visible && isBrowser}
269
- <!-- Screen reader announcements -->
270
- <div class="pie-sr-only" role="status" aria-live="polite" aria-atomic="true">
271
- {announceText}
272
- </div>
273
-
274
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
275
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
276
- <div
277
- bind:this={containerEl}
278
- class="pie-tool-ruler"
279
- data-moveablejs-tool-id={toolId}
280
- onpointerdown={() => coordinator?.bringToFront(containerEl)}
281
- onkeydown={handleKeyDown}
282
- role="application"
283
- tabindex="0"
284
- aria-label="Ruler tool. Use arrow keys to move, Shift+arrows to rotate, PageUp/PageDown for fine rotation, U to toggle units. Current unit: {unit}"
285
- aria-roledescription="Draggable and rotatable ruler measurement tool"
286
- >
287
- <div class="pie-tool-ruler__container">
288
- <img
289
- class="pie-tool-ruler__image"
290
- src={currentRuler}
291
- alt="Ruler showing {unit}"
292
- draggable="false"
293
- />
294
-
295
- <!-- Unit toggle button group (matching production implementation style) -->
296
- <div
297
- class="pie-tool-ruler__unit-group"
298
- role="group"
299
- aria-label="Ruler unit selection"
300
- onpointerdown={(e) => e.stopPropagation()}
301
- >
302
- <button
303
- class="pie-tool-ruler__unit-button"
304
- class:pie-tool-ruler__unit-button--active={unit === 'inches'}
305
- onclick={() => {
306
- unit = 'inches';
307
- announce('Switched to inches');
308
- }}
309
- title="Inches"
310
- aria-label="Switch to inches"
311
- aria-pressed={unit === 'inches'}
312
- >
313
- <span class="pie-tool-ruler__unit-label">Inches</span>
314
- </button>
315
- <button
316
- class="pie-tool-ruler__unit-button"
317
- class:pie-tool-ruler__unit-button--active={unit === 'cm'}
318
- onclick={() => {
319
- unit = 'cm';
320
- announce('Switched to centimeters');
321
- }}
322
- title="Centimeters"
323
- aria-label="Switch to centimeters"
324
- aria-pressed={unit === 'cm'}
325
- >
326
- <span class="pie-tool-ruler__unit-label">Centimeters</span>
327
- </button>
328
- </div>
329
- </div>
330
- </div>
331
- {/if}
332
-
333
- <style>
334
- .pie-sr-only {
335
- position: absolute;
336
- width: 1px;
337
- height: 1px;
338
- padding: 0;
339
- margin: -1px;
340
- overflow: hidden;
341
- clip: rect(0, 0, 0, 0);
342
- white-space: nowrap;
343
- border-width: 0;
344
- }
345
-
346
- .pie-tool-ruler {
347
- border-left: none;
348
- border-right: none;
349
- box-shadow: none;
350
- cursor: move;
351
- left: 50%;
352
- overflow: hidden;
353
- position: absolute;
354
- top: 50%;
355
- transform: translate(-50%, -50%);
356
- user-select: none;
357
- touch-action: none;
358
- width: 540px; /* Matching production implementation frame width */
359
- }
360
-
361
- .pie-tool-ruler:focus-visible {
362
- outline: 3px solid var(--pie-button-focus-outline, var(--pie-primary, #4A90E2));
363
- outline-offset: 2px;
364
- }
365
-
366
- .pie-tool-ruler__container {
367
- background-color: color-mix(in srgb, var(--pie-background, #fff) 90%, transparent); /* Matching production implementation semi-transparent white background */
368
- position: relative;
369
- }
370
-
371
- .pie-tool-ruler__container,
372
- .pie-tool-ruler__image {
373
- height: 100px; /* Matching production implementation ruler height */
374
- width: 864px; /* Matching production implementation ruler width */
375
- }
376
-
377
- .pie-tool-ruler__image {
378
- position: relative;
379
- z-index: 2;
380
- display: block;
381
- }
382
-
383
- /* Unit toggle button group (matching production implementation style) */
384
- .pie-tool-ruler__unit-group {
385
- border: 1px solid var(--pie-primary, #3f51b5); /* Matching production implementation primary color */
386
- bottom: 0.5rem; /* Matching production implementation positioning */
387
- left: 0.5rem; /* Matching production implementation positioning */
388
- position: absolute;
389
- display: flex;
390
- z-index: 10;
391
- background: var(--pie-background, #fff);
392
- border-radius: 4px;
393
- overflow: hidden;
394
- }
395
-
396
- .pie-tool-ruler__unit-button {
397
- background: var(--pie-button-bg, #fff);
398
- border: none;
399
- border-right: 1px solid var(--pie-primary, #3f51b5);
400
- color: var(--pie-button-color, var(--pie-primary, #3f51b5));
401
- cursor: pointer;
402
- padding: 4px 8px;
403
- font-size: 12px;
404
- transition: background-color 0.2s, color 0.2s;
405
- }
406
-
407
- .pie-tool-ruler__unit-button:last-child {
408
- border-right: none;
409
- }
410
-
411
- .pie-tool-ruler__unit-button:hover {
412
- background-color: var(--pie-button-hover-bg, color-mix(in srgb, var(--pie-primary, #3f51b5) 10%, transparent));
413
- }
414
-
415
- .pie-tool-ruler__unit-button.pie-tool-ruler__unit-button--active {
416
- background-color: var(--pie-primary, #3f51b5);
417
- color: var(--pie-white, #fff);
418
- }
419
-
420
- .pie-tool-ruler__unit-button:focus-visible {
421
- outline: 2px solid var(--pie-primary, #3f51b5);
422
- outline-offset: 2px;
423
- }
424
-
425
- .pie-tool-ruler__unit-label {
426
- display: inline-block;
427
- font-size: 12px;
428
- line-height: 1.4;
429
- }
430
-
431
- /* Moveable.js control styling (matching production implementation) */
432
- /* Production implementation uses black (--moveable-color: #000) globally, not red for ruler */
433
- :global(body .moveable-control-box[data-pie-tool-surface="frameless"]) {
434
- --moveable-color: transparent;
435
- z-index: 2003; /* ZIndexLayer.CONTROL */
436
- }
437
-
438
- :global([data-moveablejs-tool-id="ruler"]) {
439
- z-index: 2002; /* ZIndexLayer.MODAL */
440
- }
441
- </style>
442
-