@pie-players/pie-tool-annotation-toolbar 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-annotation-toolbar",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "type": "module",
5
5
  "description": "Text annotation toolbar with highlighting and underlining for PIE assessment player",
6
6
  "repository": {
@@ -20,19 +20,15 @@
20
20
  "highlighting",
21
21
  "accessibility"
22
22
  ],
23
- "svelte": "./tool-annotation-toolbar.svelte",
24
23
  "main": "./dist/tool-annotation-toolbar.js",
25
24
  "exports": {
26
25
  ".": {
27
26
  "types": "./dist/index.d.ts",
28
- "import": "./dist/tool-annotation-toolbar.js",
29
- "svelte": "./tool-annotation-toolbar.svelte"
30
- },
31
- "./tool-annotation-toolbar.svelte": "./tool-annotation-toolbar.svelte"
27
+ "import": "./dist/tool-annotation-toolbar.js"
28
+ }
32
29
  },
33
30
  "files": [
34
31
  "dist",
35
- "tool-annotation-toolbar.svelte",
36
32
  "package.json",
37
33
  "README.md"
38
34
  ],
@@ -40,8 +36,8 @@
40
36
  "unpkg": "./dist/tool-annotation-toolbar.js",
41
37
  "jsdelivr": "./dist/tool-annotation-toolbar.js",
42
38
  "dependencies": {
43
- "@pie-players/pie-assessment-toolkit": "0.3.43",
44
- "@pie-players/pie-players-shared": "0.3.43"
39
+ "@pie-players/pie-assessment-toolkit": "0.3.44",
40
+ "@pie-players/pie-players-shared": "0.3.44"
45
41
  },
46
42
  "types": "./dist/index.d.ts",
47
43
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -1,654 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- tag: 'pie-tool-annotation-toolbar',
4
- shadow: 'open',
5
- props: {
6
- enabled: { type: 'Boolean', attribute: 'enabled' },
7
- highlightCoordinator: { type: 'Object' },
8
- ttsService: { type: 'Object' }
9
- }
10
- }}
11
- />
12
-
13
- <script lang="ts">
14
- import "./highlights.css";
15
- import type {
16
- AssessmentToolkitRegionScopeContext,
17
- AssessmentToolkitShellContext,
18
- HighlightCoordinator,
19
- TtsServiceApi
20
- } from '@pie-players/pie-assessment-toolkit';
21
- import {
22
- connectAssessmentToolkitRegionScopeContext,
23
- connectAssessmentToolkitShellContext,
24
- HighlightColor
25
- } from '@pie-players/pie-assessment-toolkit';
26
-
27
- interface Props {
28
- enabled?: boolean;
29
- highlightCoordinator?: HighlightCoordinator | null;
30
- ttsService?: TtsServiceApi | null;
31
- }
32
-
33
- let {
34
- enabled = true,
35
- highlightCoordinator = null,
36
- ttsService = null
37
- }: Props = $props();
38
-
39
- const isBrowser = typeof window !== 'undefined';
40
-
41
- // Storage key for sessionStorage
42
- const STORAGE_KEY = 'pie-annotations';
43
-
44
- // Available highlight colors (modern, accessible palette)
45
- const HIGHLIGHT_COLORS = [
46
- { name: HighlightColor.YELLOW, hex: '#fde995', label: 'Yellow highlight' },
47
- { name: HighlightColor.PINK, hex: '#ff9fae', label: 'Pink highlight' },
48
- { name: HighlightColor.BLUE, hex: '#a7e0f6', label: 'Blue highlight' },
49
- { name: HighlightColor.GREEN, hex: '#a6e1c5', label: 'Green highlight' }
50
- ] as const;
51
-
52
- // Disallowed elements - don't show toolbar when selecting these
53
- const DISALLOWED_SELECTORS = [
54
- 'button',
55
- 'input',
56
- 'select',
57
- 'textarea',
58
- '[contenteditable="true"]',
59
- '.pie-tool-annotation-toolbar',
60
- '.pie-tool-toolbar',
61
- '[role="button"]',
62
- '[role="textbox"]'
63
- ];
64
-
65
- // State - using Svelte 5 $state rune for reactive state
66
- let contextHostElement = $state<HTMLElement | null>(null);
67
- let toolbarElement = $state<HTMLElement | null>(null);
68
- let shellContext = $state<AssessmentToolkitShellContext | null>(null);
69
- let regionScopeContext = $state<AssessmentToolkitRegionScopeContext | null>(null);
70
- let toolbarState = $state({
71
- isVisible: false,
72
- selectedText: '',
73
- selectedRange: null as Range | null,
74
- toolbarPosition: { x: 0, y: 0 }
75
- });
76
-
77
- // TTS state
78
- let ttsSpeaking = $state(false);
79
-
80
- // UX state
81
- let justShown = $state(false); // Flag to prevent immediate hiding after showing
82
- let positionAnnouncement = $state(''); // For screen readers when toolbar is repositioned
83
-
84
- // Track annotation count for reactivity (increments on add/remove to trigger UI updates)
85
- let annotationCount = $state(0);
86
-
87
- // Track if current selection overlaps with an existing annotation
88
- let overlappingAnnotationId = $state<string | null>(null);
89
-
90
- // Derived state
91
- let hasAnnotations = $derived(annotationCount > 0);
92
- let hasOverlappingAnnotation = $derived(overlappingAnnotationId !== null);
93
- let effectiveScopeElement = $derived(
94
- regionScopeContext?.scopeElement || shellContext?.scopeElement || null
95
- );
96
-
97
- function getEffectiveRoot(): HTMLElement {
98
- const ownerDoc = contextHostElement?.ownerDocument;
99
- return effectiveScopeElement || ownerDoc?.documentElement || document.documentElement;
100
- }
101
-
102
- function getStorageKey(): string {
103
- const scopeKey = shellContext?.canonicalItemId || shellContext?.itemId || 'global';
104
- return `${STORAGE_KEY}:${scopeKey}`;
105
- }
106
-
107
- /**
108
- * Find annotation that overlaps with the given range
109
- */
110
- function findOverlappingAnnotation(range: Range): string | null {
111
- if (!highlightCoordinator) return null;
112
-
113
- const annotations = highlightCoordinator.getAnnotations();
114
- for (const annotation of annotations) {
115
- // Check if ranges overlap
116
- // Two ranges overlap if: startA < endB && startB < endA
117
- const cmp1 = range.compareBoundaryPoints(Range.START_TO_START, annotation.range);
118
- const cmp2 = range.compareBoundaryPoints(Range.END_TO_END, annotation.range);
119
- const cmp3 = range.compareBoundaryPoints(Range.START_TO_END, annotation.range);
120
- const cmp4 = range.compareBoundaryPoints(Range.END_TO_START, annotation.range);
121
-
122
- // Check various overlap conditions:
123
- // 1. Selection is inside annotation
124
- // 2. Annotation is inside selection
125
- // 3. Selection partially overlaps annotation
126
- if (
127
- (cmp1 >= 0 && cmp2 <= 0) || // selection inside annotation
128
- (cmp1 <= 0 && cmp2 >= 0) || // annotation inside selection
129
- (cmp3 > 0 && cmp4 < 0) // partial overlap
130
- ) {
131
- return annotation.id;
132
- }
133
- }
134
- return null;
135
- }
136
-
137
- /**
138
- * Check if selection is in an allowed area
139
- */
140
- function isInAllowedArea(node: Node): boolean {
141
- if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE) {
142
- return false;
143
- }
144
-
145
- // For text nodes, check parent element
146
- const element = node.nodeType === Node.TEXT_NODE ? node.parentElement : (node as Element);
147
- if (!element) return false;
148
-
149
- // Check if element or any ancestor matches disallowed selectors
150
- return !DISALLOWED_SELECTORS.some((sel) => {
151
- try {
152
- return element.closest(sel) !== null;
153
- } catch {
154
- return false;
155
- }
156
- });
157
- }
158
-
159
- function isWithinScope(range: Range): boolean {
160
- if (!effectiveScopeElement) return true;
161
- const ancestor = range.commonAncestorContainer;
162
- const element =
163
- ancestor.nodeType === Node.TEXT_NODE
164
- ? ancestor.parentElement
165
- : (ancestor as Element);
166
- return !!element && effectiveScopeElement.contains(element);
167
- }
168
-
169
- /**
170
- * Save annotations to sessionStorage.
171
- * Uses HighlightCoordinator's exportAnnotations for proper serialization.
172
- */
173
- function saveAnnotations() {
174
- if (!isBrowser || !highlightCoordinator) return;
175
-
176
- try {
177
- const root = getEffectiveRoot();
178
- const serialized = highlightCoordinator.exportAnnotations(root);
179
- sessionStorage.setItem(getStorageKey(), JSON.stringify(serialized));
180
- } catch (error) {
181
- console.error('[AnnotationToolbar] Failed to save annotations:', error);
182
- }
183
- }
184
-
185
- /**
186
- * Load annotations from sessionStorage.
187
- * Uses HighlightCoordinator's importAnnotations for proper deserialization.
188
- */
189
- function loadAnnotations() {
190
- if (!isBrowser || !highlightCoordinator) return;
191
-
192
- try {
193
- const json = sessionStorage.getItem(getStorageKey());
194
- if (!json) return;
195
-
196
- const data = JSON.parse(json);
197
- const root = getEffectiveRoot();
198
- const restored = highlightCoordinator.importAnnotations(data, root);
199
-
200
- console.log(`[AnnotationToolbar] Restored ${restored} annotations`);
201
- annotationCount = highlightCoordinator.getAnnotations().length;
202
- } catch (error) {
203
- console.error('[AnnotationToolbar] Failed to load annotations:', error);
204
- }
205
- }
206
-
207
- /**
208
- * Handle selection change - show toolbar if valid selection
209
- */
210
- function handleSelectionChange() {
211
- if (!enabled || !isBrowser) return;
212
-
213
- const sel = window.getSelection();
214
- if (!sel || sel.rangeCount === 0) return hideToolbar();
215
-
216
- const range = sel.getRangeAt(0);
217
- const text = sel.toString().trim();
218
-
219
- // Hide if empty or in disallowed area
220
- if (!text || !isWithinScope(range) || !isInAllowedArea(range.commonAncestorContainer)) {
221
- return hideToolbar();
222
- }
223
-
224
- // Calculate position
225
- const rect = range.getBoundingClientRect();
226
- const x = rect.left + rect.width / 2;
227
- const y = rect.top - 8;
228
-
229
- // Check if selection overlaps with an existing annotation
230
- overlappingAnnotationId = findOverlappingAnnotation(range);
231
-
232
- toolbarState.isVisible = true;
233
- toolbarState.selectedText = text;
234
- toolbarState.selectedRange = range.cloneRange();
235
- toolbarState.toolbarPosition = { x, y };
236
-
237
- // Announce to screen readers
238
- const textPreview = text.length > 30 ? text.substring(0, 30) + '...' : text;
239
- positionAnnouncement = `Annotation toolbar opened for "${textPreview}"`;
240
- setTimeout(() => { positionAnnouncement = ''; }, 2000);
241
-
242
- // Set justShown flag to prevent immediate hiding
243
- justShown = true;
244
- setTimeout(() => {
245
- justShown = false;
246
- }, 100);
247
- }
248
-
249
- /**
250
- * Hide toolbar and clean up TTS
251
- */
252
- function hideToolbar() {
253
- if (ttsSpeaking && ttsService) {
254
- ttsService.stop();
255
- ttsSpeaking = false;
256
- }
257
- toolbarState.isVisible = false;
258
- toolbarState.selectedText = '';
259
- toolbarState.selectedRange = null;
260
- }
261
-
262
- /**
263
- * Add highlight annotation
264
- */
265
- function handleHighlight(color: HighlightColor) {
266
- if (!toolbarState.selectedRange || !highlightCoordinator) return;
267
- const text = toolbarState.selectedText;
268
- highlightCoordinator.addAnnotation(toolbarState.selectedRange, color);
269
- annotationCount = highlightCoordinator.getAnnotations().length;
270
- saveAnnotations();
271
-
272
- // Announce to screen readers
273
- const colorName = color === HighlightColor.UNDERLINE ? 'underlined' : `highlighted in ${color}`;
274
- const textPreview = text.length > 30 ? text.substring(0, 30) + '...' : text;
275
- positionAnnouncement = `"${textPreview}" ${colorName}`;
276
- setTimeout(() => { positionAnnouncement = ''; }, 3000);
277
-
278
- hideToolbar();
279
- }
280
-
281
- /**
282
- * Remove the annotation that overlaps with current selection
283
- */
284
- function handleRemoveAnnotation() {
285
- if (!overlappingAnnotationId || !highlightCoordinator) {
286
- console.warn('[AnnotationToolbar] No overlapping annotation to remove');
287
- return;
288
- }
289
-
290
- console.log('[AnnotationToolbar] Removing annotation:', overlappingAnnotationId);
291
-
292
- const annotation = highlightCoordinator.getAnnotation(overlappingAnnotationId);
293
- if (!annotation) {
294
- console.warn('[AnnotationToolbar] Annotation not found:', overlappingAnnotationId);
295
- return;
296
- }
297
-
298
- const text = annotation.range.toString();
299
- highlightCoordinator.removeAnnotation(overlappingAnnotationId);
300
- const newCount = highlightCoordinator.getAnnotations().length;
301
- annotationCount = newCount;
302
- console.log('[AnnotationToolbar] Annotations remaining:', newCount);
303
- saveAnnotations();
304
-
305
- // Announce to screen readers
306
- const textPreview = text.length > 30 ? text.substring(0, 30) + '...' : text;
307
- positionAnnouncement = `Removed annotation from "${textPreview}"`;
308
- setTimeout(() => { positionAnnouncement = ''; }, 3000);
309
-
310
- hideToolbar();
311
- }
312
-
313
- /**
314
- * Clear all annotations
315
- */
316
- function handleClearAnnotations() {
317
- const count = annotationCount;
318
- highlightCoordinator?.clearAnnotations();
319
- annotationCount = 0;
320
- sessionStorage.removeItem(getStorageKey());
321
-
322
- // Announce to screen readers
323
- positionAnnouncement = `${count} annotation${count === 1 ? '' : 's'} cleared`;
324
- setTimeout(() => { positionAnnouncement = ''; }, 3000);
325
-
326
- hideToolbar();
327
- }
328
-
329
- /**
330
- * Read aloud with TTS
331
- */
332
- async function handleTTSClick() {
333
- if (!toolbarState.selectedRange || !ttsService) return;
334
-
335
- ttsSpeaking = true;
336
- try {
337
- console.log('[AnnotationToolbar] Speaking range:', toolbarState.selectedRange.toString().substring(0, 50));
338
-
339
- // Use speakRange for accurate word highlighting
340
- // Note: TTS service should already be initialized by ToolkitCoordinator
341
- await ttsService.speakRange(toolbarState.selectedRange, {
342
- contentRoot: getEffectiveRoot()
343
- });
344
-
345
- console.log('[AnnotationToolbar] TTS completed successfully');
346
- } catch (error) {
347
- console.error('[AnnotationToolbar] TTS error:', error);
348
- alert(`TTS failed: ${error instanceof Error ? error.message : String(error)}`);
349
- } finally {
350
- ttsSpeaking = false;
351
- }
352
- }
353
-
354
- /**
355
- * Handle keyboard shortcuts
356
- */
357
- function handleKeyDown(e: KeyboardEvent) {
358
- if (e.key === 'Escape' && toolbarState.isVisible) {
359
- e.preventDefault();
360
- hideToolbar();
361
- }
362
- }
363
-
364
- /**
365
- * Handle click outside toolbar
366
- */
367
- function handleDocumentClick(e: Event) {
368
- if (!toolbarState.isVisible || justShown) return;
369
-
370
- if (toolbarElement && !toolbarElement.contains(e.target as Node)) {
371
- hideToolbar();
372
- }
373
- }
374
-
375
- // Effect for event listeners and initialization
376
- $effect(() => {
377
- if (!isBrowser) return;
378
-
379
- // Load persisted annotations after a delay to ensure content is rendered
380
- // PIE section player needs time to render items before we can restore ranges
381
- // Increased from 500ms to 2000ms to ensure all content is fully loaded
382
- const loadTimer = setTimeout(() => {
383
- loadAnnotations();
384
- }, 2000);
385
-
386
- const pointerEventTarget: HTMLElement | Document = effectiveScopeElement || document;
387
- pointerEventTarget.addEventListener('mouseup', handleSelectionChange);
388
- pointerEventTarget.addEventListener('click', handleDocumentClick);
389
- pointerEventTarget.addEventListener('touchend', handleSelectionChange);
390
- pointerEventTarget.addEventListener('touchstart', handleDocumentClick);
391
-
392
- // Keyboard and scroll events
393
- document.addEventListener('keydown', handleKeyDown);
394
- window.addEventListener('scroll', hideToolbar, true);
395
-
396
- return () => {
397
- clearTimeout(loadTimer);
398
-
399
- pointerEventTarget.removeEventListener('mouseup', handleSelectionChange);
400
- pointerEventTarget.removeEventListener('click', handleDocumentClick);
401
- pointerEventTarget.removeEventListener('touchend', handleSelectionChange);
402
- pointerEventTarget.removeEventListener('touchstart', handleDocumentClick);
403
-
404
- // Remove keyboard and scroll events
405
- document.removeEventListener('keydown', handleKeyDown);
406
- window.removeEventListener('scroll', hideToolbar, true);
407
- };
408
- });
409
-
410
- $effect(() => {
411
- if (!contextHostElement) return;
412
- const cleanupShell = connectAssessmentToolkitShellContext(
413
- contextHostElement,
414
- (value: AssessmentToolkitShellContext) => {
415
- shellContext = value;
416
- }
417
- );
418
- const cleanupRegion = connectAssessmentToolkitRegionScopeContext(
419
- contextHostElement,
420
- (value: AssessmentToolkitRegionScopeContext) => {
421
- regionScopeContext = value;
422
- }
423
- );
424
- return () => {
425
- cleanupRegion();
426
- cleanupShell();
427
- };
428
- });
429
- </script>
430
-
431
- <div bind:this={contextHostElement} style="display: none;" aria-hidden="true"></div>
432
-
433
- {#if toolbarState.isVisible}
434
- <div
435
- bind:this={toolbarElement}
436
- class="pie-tool-annotation-toolbar notranslate"
437
- style={`left:${toolbarState.toolbarPosition.x}px; top:${toolbarState.toolbarPosition.y}px; transform: translate(-50%, -100%);`}
438
- role="toolbar"
439
- aria-label="Text annotation toolbar"
440
- translate="no"
441
- >
442
- <!-- Highlight Color Swatches -->
443
- {#each HIGHLIGHT_COLORS as color}
444
- <button
445
- class="pie-tool-annotation-toolbar__highlight-swatch"
446
- style="background-color: {color.hex};"
447
- onclick={() => handleHighlight(color.name)}
448
- aria-label={color.label}
449
- title={color.label}
450
- >
451
- <span class="pie-sr-only">{color.label}</span>
452
- </button>
453
- {/each}
454
-
455
- <!-- Underline Button -->
456
- <button
457
- class="pie-tool-annotation-toolbar__button pie-tool-annotation-toolbar__button--icon"
458
- onclick={() => handleHighlight(HighlightColor.UNDERLINE)}
459
- aria-label="Underline selected text"
460
- title="Underline"
461
- >
462
- <svg
463
- xmlns="http://www.w3.org/2000/svg"
464
- viewBox="0 0 24 24"
465
- width="18"
466
- height="18"
467
- fill="currentColor"
468
- aria-hidden="true"
469
- >
470
- <path
471
- d="M5,21H19V19H5V21M12,17A6,6 0 0,0 18,11V3H15.5V11A3.5,3.5 0 0,1 12,14.5A3.5,3.5 0 0,1 8.5,11V3H6V11A6,6 0 0,0 12,17Z"
472
- />
473
- </svg>
474
- </button>
475
-
476
- <!-- Text-to-Speech (only if ttsService available) -->
477
- {#if ttsService}
478
- <div class="divider divider-horizontal mx-0 w-px"></div>
479
- <button
480
- class="pie-tool-annotation-toolbar__button pie-tool-annotation-toolbar__button--icon"
481
- onclick={handleTTSClick}
482
- disabled={ttsSpeaking}
483
- aria-label="Read selected text aloud"
484
- title="Read Aloud"
485
- >
486
- <svg
487
- xmlns="http://www.w3.org/2000/svg"
488
- viewBox="0 0 24 24"
489
- width="18"
490
- height="18"
491
- fill="currentColor"
492
- aria-hidden="true"
493
- >
494
- <path
495
- d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"
496
- />
497
- </svg>
498
- </button>
499
- {/if}
500
-
501
- <!-- Divider before Remove/Clear -->
502
- {#if hasOverlappingAnnotation || hasAnnotations}
503
- <div class="divider divider-horizontal mx-0 w-px"></div>
504
-
505
- <!-- Remove This Annotation -->
506
- {#if hasOverlappingAnnotation}
507
- <button
508
- class="pie-tool-annotation-toolbar__button pie-tool-annotation-toolbar__button--warning"
509
- onclick={handleRemoveAnnotation}
510
- aria-label="Remove this annotation"
511
- title="Remove"
512
- >
513
- <svg
514
- xmlns="http://www.w3.org/2000/svg"
515
- viewBox="0 0 24 24"
516
- width="18"
517
- height="18"
518
- fill="currentColor"
519
- aria-hidden="true"
520
- >
521
- <path
522
- d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
523
- />
524
- </svg>
525
- </button>
526
- {/if}
527
-
528
- <!-- Clear All Annotations -->
529
- {#if hasAnnotations}
530
- <button
531
- class="pie-tool-annotation-toolbar__button pie-tool-annotation-toolbar__button--danger"
532
- onclick={handleClearAnnotations}
533
- aria-label="Clear all annotations from document"
534
- title="Clear All"
535
- >
536
- Clear All
537
- </button>
538
- {/if}
539
- {/if}
540
- </div>
541
- {/if}
542
-
543
- <!-- Screen reader announcements -->
544
- <div role="status" aria-live="polite" aria-atomic="true" class="pie-sr-only">
545
- {positionAnnouncement}
546
- </div>
547
-
548
- <style>
549
- .pie-tool-annotation-toolbar {
550
- position: fixed;
551
- z-index: 4200;
552
- display: flex;
553
- gap: 0.25rem;
554
- padding: 0.5rem;
555
- border-radius: 0.5rem;
556
- background: var(--pie-background, #fff);
557
- color: var(--pie-text, #111827);
558
- border: 1px solid var(--pie-border, #d1d5db);
559
- box-shadow: 0 10px 25px -8px rgb(0 0 0 / 0.3);
560
- user-select: none;
561
- }
562
-
563
- .pie-tool-annotation-toolbar__highlight-swatch {
564
- width: 2.5rem;
565
- height: 2rem;
566
- border: 2px solid color-mix(in srgb, var(--pie-border-dark, #111827) 20%, transparent);
567
- border-radius: 0.375rem;
568
- cursor: pointer;
569
- transition: all 0.15s ease;
570
- display: flex;
571
- align-items: center;
572
- justify-content: center;
573
- padding: 0;
574
- }
575
-
576
- .pie-tool-annotation-toolbar__highlight-swatch:hover {
577
- transform: scale(1.1);
578
- border-color: color-mix(in srgb, var(--pie-border-dark, #111827) 45%, transparent);
579
- box-shadow: 0 2px 8px rgb(0 0 0 / 0.15);
580
- }
581
-
582
- .pie-tool-annotation-toolbar__highlight-swatch:focus-visible {
583
- outline: 2px solid var(--pie-button-focus-outline, var(--pie-primary, #3f51b5));
584
- outline-offset: 2px;
585
- }
586
-
587
- .pie-tool-annotation-toolbar .divider-horizontal {
588
- height: auto;
589
- width: 1px;
590
- background-color: color-mix(in srgb, var(--pie-border, #d1d5db) 70%, transparent);
591
- }
592
-
593
- /* Screen reader only content */
594
- .pie-sr-only {
595
- position: absolute;
596
- width: 1px;
597
- height: 1px;
598
- padding: 0;
599
- margin: -1px;
600
- overflow: hidden;
601
- clip: rect(0, 0, 0, 0);
602
- white-space: nowrap;
603
- border-width: 0;
604
- }
605
-
606
- /* Button styling */
607
- .pie-tool-annotation-toolbar__button {
608
- display: inline-flex;
609
- align-items: center;
610
- justify-content: center;
611
- gap: 0.35rem;
612
- padding: 0.4rem 0.55rem;
613
- border: 1px solid var(--pie-button-border, #d1d5db);
614
- border-radius: 0.4rem;
615
- background: var(--pie-button-bg, #fff);
616
- color: var(--pie-button-color, var(--pie-text, #111827));
617
- cursor: pointer;
618
- }
619
-
620
- .pie-tool-annotation-toolbar__button--icon {
621
- min-width: 2rem;
622
- min-height: 2rem;
623
- padding: 0.45rem;
624
- }
625
-
626
- .pie-tool-annotation-toolbar__button:hover {
627
- background: var(--pie-button-hover-bg, #f9fafb);
628
- color: var(--pie-button-hover-color, var(--pie-text, #111827));
629
- border-color: var(--pie-button-hover-border, #9ca3af);
630
- }
631
-
632
- .pie-tool-annotation-toolbar__button:focus-visible {
633
- outline: 2px solid var(--pie-button-focus-outline, var(--pie-primary, #3f51b5));
634
- outline-offset: 2px;
635
- }
636
-
637
- .pie-tool-annotation-toolbar__button:disabled {
638
- opacity: 0.6;
639
- cursor: not-allowed;
640
- }
641
-
642
- .pie-tool-annotation-toolbar__button--warning {
643
- color: var(--pie-missing-icon, #92400e);
644
- }
645
-
646
- .pie-tool-annotation-toolbar__button--danger {
647
- color: var(--pie-incorrect-icon, #b91c1c);
648
- }
649
-
650
- .pie-tool-annotation-toolbar__button svg {
651
- width: 18px;
652
- height: 18px;
653
- }
654
- </style>