@pie-players/pie-section-tools-toolbar 0.2.3 → 0.2.4

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-section-tools-toolbar",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Section-level tools toolbar for PIE assessment player",
6
6
  "repository": {
@@ -34,20 +34,20 @@
34
34
  "package.json"
35
35
  ],
36
36
  "peerDependencies": {
37
- "svelte": "^5.0.0"
37
+ "svelte": "^5.51.0"
38
38
  },
39
39
  "license": "MIT",
40
40
  "unpkg": "./dist/section-tools-toolbar.js",
41
41
  "jsdelivr": "./dist/section-tools-toolbar.js",
42
42
  "dependencies": {
43
- "@pie-players/pie-assessment-toolkit": "workspace:*",
44
- "@pie-players/pie-players-shared": "workspace:*",
45
- "@pie-players/pie-tool-graph": "workspace:*",
46
- "@pie-players/pie-tool-periodic-table": "workspace:*",
47
- "@pie-players/pie-tool-protractor": "workspace:*",
48
- "@pie-players/pie-tool-line-reader": "workspace:*",
49
- "@pie-players/pie-tool-magnifier": "workspace:*",
50
- "@pie-players/pie-calculator-mathjs": "workspace:*"
43
+ "@pie-players/pie-assessment-toolkit": "0.2.4",
44
+ "@pie-players/pie-players-shared": "0.2.2",
45
+ "@pie-players/pie-tool-graph": "0.1.5",
46
+ "@pie-players/pie-tool-periodic-table": "0.1.5",
47
+ "@pie-players/pie-tool-protractor": "0.1.5",
48
+ "@pie-players/pie-tool-line-reader": "0.1.5",
49
+ "@pie-players/pie-tool-magnifier": "0.1.5",
50
+ "@pie-players/pie-calculator-mathjs": "0.1.1"
51
51
  },
52
52
  "types": "./dist/index.d.ts",
53
53
  "scripts": {
@@ -59,7 +59,7 @@
59
59
  "devDependencies": {
60
60
  "@biomejs/biome": "^2.3.10",
61
61
  "@sveltejs/vite-plugin-svelte": "^6.1.4",
62
- "svelte": "^5.16.1",
62
+ "svelte": "^5.51.0",
63
63
  "typescript": "^5.7.0",
64
64
  "vite": "^7.0.8",
65
65
  "vite-plugin-dts": "^4.5.3"
@@ -4,6 +4,7 @@
4
4
  shadow: 'none',
5
5
  props: {
6
6
  enabledTools: { type: 'String', attribute: 'enabled-tools' },
7
+ position: { type: 'String', attribute: 'position' },
7
8
  // Services passed as JS properties (not attributes)
8
9
  toolCoordinator: { type: 'Object', reflect: false },
9
10
  toolProviderRegistry: { type: 'Object', reflect: false }
@@ -15,8 +16,14 @@
15
16
  SectionToolsToolbar - Section-level floating tools toolbar
16
17
 
17
18
  Displays tool buttons (calculator, graph, periodic table, etc.) in a toolbar
18
- positioned at the bottom of the section layout. Tools appear as floating
19
- overlays managed by the ToolCoordinator.
19
+ that can be positioned at the top, right, bottom, or left of the section layout.
20
+ Tools appear as floating overlays managed by the ToolCoordinator.
21
+
22
+ Position Best Practices:
23
+ - bottom (default): Recommended for section-level tools, doesn't obstruct content
24
+ - right: Good for persistent tool palettes, familiar application pattern
25
+ - left: Better for RTL languages
26
+ - top: More discoverable but can obstruct reading content
20
27
 
21
28
  Similar to SchoolCity pattern - section-wide tools independent of item navigation.
22
29
  -->
@@ -40,11 +47,13 @@
40
47
 
41
48
  // Props
42
49
  let {
43
- enabledTools = 'calculator,graph,periodicTable,protractor,lineReader,magnifier,ruler',
50
+ enabledTools = 'graph,periodicTable,protractor,lineReader,magnifier,ruler',
51
+ position = 'bottom',
44
52
  toolCoordinator,
45
53
  toolProviderRegistry
46
54
  }: {
47
55
  enabledTools?: string;
56
+ position?: 'top' | 'right' | 'bottom' | 'left';
48
57
  toolCoordinator?: IToolCoordinator;
49
58
  toolProviderRegistry?: ToolProviderRegistry;
50
59
  } = $props();
@@ -58,18 +67,17 @@
58
67
  );
59
68
 
60
69
  // Tool visibility state (reactive to coordinator changes)
61
- let showCalculator = $state(false);
62
70
  let showGraph = $state(false);
63
71
  let showPeriodicTable = $state(false);
64
72
  let showProtractor = $state(false);
65
73
  let showLineReader = $state(false);
66
74
  let showMagnifier = $state(false);
67
75
  let showRuler = $state(false);
76
+ let statusMessage = $state('');
68
77
 
69
78
  // Update visibility state from coordinator
70
79
  function updateToolVisibility() {
71
80
  if (!toolCoordinator) return;
72
- showCalculator = toolCoordinator.isToolVisible('calculator');
73
81
  showGraph = toolCoordinator.isToolVisible('graph');
74
82
  showPeriodicTable = toolCoordinator.isToolVisible('periodicTable');
75
83
  showProtractor = toolCoordinator.isToolVisible('protractor');
@@ -83,6 +91,13 @@
83
91
  if (!toolCoordinator) return;
84
92
  toolCoordinator.toggleTool(toolId);
85
93
  updateToolVisibility();
94
+
95
+ // Get tool name for status message
96
+ const tool = toolButtons.find(t => t.id === toolId);
97
+ if (tool) {
98
+ const isVisible = toolCoordinator.isToolVisible(toolId);
99
+ statusMessage = `${tool.label} ${isVisible ? 'opened' : 'closed'}`;
100
+ }
86
101
  }
87
102
 
88
103
  // Subscribe to tool coordinator changes
@@ -103,66 +118,51 @@
103
118
 
104
119
  // Tool button definitions
105
120
  const toolButtons = $derived([
106
- {
107
- id: 'calculator',
108
- label: 'Calculator',
109
- icon: '🔢',
110
- ariaLabel: 'Scientific calculator',
111
- visible: showCalculator,
112
- enabled: enabledToolsList.includes('calculator')
113
- },
114
121
  {
115
122
  id: 'graph',
116
- label: 'Graph',
117
- icon: '📈',
118
123
  ariaLabel: 'Graphing tool',
119
124
  visible: showGraph,
120
- enabled: enabledToolsList.includes('graph')
125
+ enabled: enabledToolsList.includes('graph'),
126
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4.75 5a.76.76 0 0 1 .75.75v11c0 .438.313.75.75.75h13a.76.76 0 0 1 .696 1.039.74.74 0 0 1-.696.461h-13C5 19 4 18 4 16.75v-11A.74.74 0 0 1 4.75 5ZM8 8.25a.74.74 0 0 1 .75-.75h6.5a.76.76 0 0 1 .696 1.039.74.74 0 0 1-.696.461h-6.5A.722.722 0 0 1 8 8.25Zm.75 2.25h4.5a.76.76 0 0 1 .696 1.039.74.74 0 0 1-.696.461h-4.5a.723.723 0 0 1-.75-.75.74.74 0 0 1 .75-.75Zm0 3h8.5a.76.76 0 0 1 .696 1.039.74.74 0 0 1-.696.461h-8.5a.723.723 0 0 1-.75-.75.74.74 0 0 1 .75-.75Z" fill="currentColor"/></svg>'
121
127
  },
122
128
  {
123
129
  id: 'periodicTable',
124
- label: 'Periodic Table',
125
- icon: '⚛️',
126
130
  ariaLabel: 'Periodic table of elements',
127
131
  visible: showPeriodicTable,
128
- enabled: enabledToolsList.includes('periodicTable')
132
+ enabled: enabledToolsList.includes('periodicTable'),
133
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 21c-.85 0-1.454-.38-1.813-1.137-.358-.759-.27-1.463.263-2.113L9 11V5H8a.968.968 0 0 1-.713-.287A.968.968 0 0 1 7 4c0-.283.096-.52.287-.712A.968.968 0 0 1 8 3h8c.283 0 .52.096.712.288.192.191.288.429.288.712s-.096.52-.288.713A.968.968 0 0 1 16 5h-1v6l5.55 6.75c.533.65.62 1.354.262 2.113C20.454 20.62 19.85 21 19 21H5Zm2-3h10l-3.4-4h-3.2L7 18Zm-2 1h14l-6-7.3V5h-2v6.7L5 19Z" fill="currentColor"/></svg>'
129
134
  },
130
135
  {
131
136
  id: 'protractor',
132
- label: 'Protractor',
133
- icon: '📐',
134
137
  ariaLabel: 'Angle measurement tool',
135
138
  visible: showProtractor,
136
- enabled: enabledToolsList.includes('protractor')
139
+ enabled: enabledToolsList.includes('protractor'),
140
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m6.75 21-.25-2.2 2.85-7.85a3.95 3.95 0 0 0 1.75.95l-2.75 7.55L6.75 21Zm10.5 0-1.6-1.55-2.75-7.55a3.948 3.948 0 0 0 1.75-.95l2.85 7.85-.25 2.2ZM12 11a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 9 8c0-.65.188-1.23.563-1.737A2.935 2.935 0 0 1 11 5.2V3h2v2.2c.583.2 1.063.554 1.438 1.063C14.812 6.77 15 7.35 15 8c0 .833-.292 1.542-.875 2.125A2.893 2.893 0 0 1 12 11Zm0-2c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Z" fill="currentColor"/></svg>'
137
141
  },
138
142
  {
139
143
  id: 'lineReader',
140
- label: 'Line Reader',
141
- icon: '📏',
142
144
  ariaLabel: 'Line reading guide',
143
145
  visible: showLineReader,
144
- enabled: enabledToolsList.includes('lineReader')
146
+ enabled: enabledToolsList.includes('lineReader'),
147
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.85 15c.517 0 .98-.15 1.388-.45.408-.3.695-.692.862-1.175l.375-1.15c.267-.8.2-1.537-.2-2.213C8.875 9.337 8.3 9 7.55 9H4.025l.475 3.925c.083.583.346 1.075.787 1.475.442.4.963.6 1.563.6Zm10.3 0c.6 0 1.12-.2 1.563-.6.441-.4.704-.892.787-1.475L19.975 9h-3.5c-.75 0-1.325.342-1.725 1.025-.4.683-.467 1.425-.2 2.225l.35 1.125c.167.483.454.875.862 1.175.409.3.871.45 1.388.45Zm-10.3 2c-1.1 0-2.063-.363-2.887-1.088a4.198 4.198 0 0 1-1.438-2.737L2 9H1V7h6.55c.733 0 1.404.18 2.013.537A3.906 3.906 0 0 1 11 9h2.025c.35-.617.83-1.104 1.438-1.463A3.892 3.892 0 0 1 16.474 7H23v2h-1l-.525 4.175a4.198 4.198 0 0 1-1.438 2.737A4.238 4.238 0 0 1 17.15 17c-.95 0-1.804-.27-2.562-.813A4.234 4.234 0 0 1 13 14.026l-.375-1.125a21.35 21.35 0 0 1-.1-.363 4.926 4.926 0 0 1-.1-.537h-.85c-.033.2-.067.363-.1.488a21.35 21.35 0 0 1-.1.362L11 14a4.3 4.3 0 0 1-1.588 2.175A4.258 4.258 0 0 1 6.85 17Z" fill="currentColor"/></svg>'
145
148
  },
146
149
  {
147
150
  id: 'magnifier',
148
- label: 'Magnifier',
149
- icon: '🔍',
150
151
  ariaLabel: 'Text magnification tool',
151
152
  visible: showMagnifier,
152
- enabled: enabledToolsList.includes('magnifier')
153
+ enabled: enabledToolsList.includes('magnifier'),
154
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.5 5.5c-1.813 0-3.438.969-4.344 2.5a4.937 4.937 0 0 0 0 5 4.974 4.974 0 0 0 4.344 2.5 4.96 4.96 0 0 0 4.313-2.5 4.937 4.937 0 0 0 0-5c-.908-1.531-2.533-2.5-4.313-2.5Zm0 11.5A6.495 6.495 0 0 1 4 10.5C4 6.937 6.906 4 10.5 4c3.563 0 6.5 2.938 6.5 6.5a6.597 6.597 0 0 1-1.406 4.063l4.156 4.187a.685.685 0 0 1 0 1.031.685.685 0 0 1-1.031 0l-4.188-4.156A6.548 6.548 0 0 1 10.5 17Zm-.75-3.75v-2h-2A.723.723 0 0 1 7 10.5a.74.74 0 0 1 .75-.75h2v-2A.74.74 0 0 1 10.5 7a.76.76 0 0 1 .75.75v2h2a.76.76 0 0 1 .696 1.039.741.741 0 0 1-.696.461h-2v2a.74.74 0 0 1-.75.75.723.723 0 0 1-.75-.75Z" fill="currentColor"/></svg>'
153
155
  },
154
156
  {
155
157
  id: 'ruler',
156
- label: 'Ruler',
157
- icon: '📏',
158
158
  ariaLabel: 'Measurement ruler',
159
159
  visible: showRuler,
160
- enabled: enabledToolsList.includes('ruler')
160
+ enabled: enabledToolsList.includes('ruler'),
161
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m8.8 10.95 2.15-2.175-1.4-1.425-1.1 1.1-1.4-1.4 1.075-1.1L7 4.825 4.825 7 8.8 10.95Zm8.2 8.225L19.175 17l-1.125-1.125-1.1 1.075-1.4-1.4 1.075-1.1-1.425-1.4-2.15 2.15L17 19.175ZM7.25 21H3v-4.25l4.375-4.375L2 7l5-5 5.4 5.4 3.775-3.8c.2-.2.425-.35.675-.45a2.068 2.068 0 0 1 1.55 0c.25.1.475.25.675.45L20.4 4.95c.2.2.35.425.45.675.1.25.15.508.15.775a1.975 1.975 0 0 1-.6 1.425l-3.775 3.8L22 17l-5 5-5.375-5.375L7.25 21ZM5 19h1.4l9.8-9.775L14.775 7.8 5 17.6V19Z" fill="currentColor"/></svg>'
161
162
  }
162
163
  ]);
163
164
 
164
165
  // Tool element references for service binding
165
- let calculatorElement = $state<HTMLElement | null>(null);
166
166
  let graphElement = $state<HTMLElement | null>(null);
167
167
  let periodicTableElement = $state<HTMLElement | null>(null);
168
168
  let protractorElement = $state<HTMLElement | null>(null);
@@ -173,9 +173,6 @@
173
173
  // Bind coordinator to tool elements
174
174
  $effect(() => {
175
175
  if (toolCoordinator) {
176
- if (calculatorElement) {
177
- (calculatorElement as any).coordinator = toolCoordinator;
178
- }
179
176
  if (graphElement) {
180
177
  (graphElement as any).coordinator = toolCoordinator;
181
178
  }
@@ -196,27 +193,19 @@
196
193
  }
197
194
  }
198
195
  });
199
-
200
- // Initialize calculator provider if needed
201
- $effect(() => {
202
- if (
203
- isBrowser &&
204
- toolProviderRegistry &&
205
- enabledToolsList.includes('calculator')
206
- ) {
207
- // Pre-initialize calculator provider on toolbar mount
208
- toolProviderRegistry.getProvider('calculator-desmos').catch((err) => {
209
- console.warn(
210
- '[SectionToolsToolbar] Calculator provider not available:',
211
- err
212
- );
213
- });
214
- }
215
- });
216
196
  </script>
217
197
 
218
198
  {#if isBrowser}
219
- <div class="section-tools-toolbar" role="toolbar" aria-label="Assessment tools">
199
+ <div
200
+ class="section-tools-toolbar section-tools-toolbar--{position}"
201
+ class:section-tools-toolbar--top={position === 'top'}
202
+ class:section-tools-toolbar--right={position === 'right'}
203
+ class:section-tools-toolbar--bottom={position === 'bottom'}
204
+ class:section-tools-toolbar--left={position === 'left'}
205
+ data-position={position}
206
+ role="toolbar"
207
+ aria-label="Assessment tools"
208
+ >
220
209
  <div class="tools-buttons">
221
210
  {#each toolButtons as tool (tool.id)}
222
211
  {#if tool.enabled}
@@ -229,8 +218,7 @@
229
218
  aria-label={tool.ariaLabel}
230
219
  aria-pressed={tool.visible}
231
220
  >
232
- <span class="tool-icon" aria-hidden="true">{tool.icon}</span>
233
- <span class="tool-label">{tool.label}</span>
221
+ {@html tool.svg}
234
222
  </button>
235
223
  {/if}
236
224
  {/each}
@@ -240,16 +228,6 @@
240
228
  <!-- Tool Instances - Rendered outside toolbar for floating overlays -->
241
229
  <!-- These are managed by ToolCoordinator with z-index layering -->
242
230
 
243
- {#if enabledToolsList.includes('calculator')}
244
- <pie-tool-calculator
245
- bind:this={calculatorElement}
246
- visible={showCalculator}
247
- coordinator={toolProviderRegistry ? { getToolProvider: (id) => toolProviderRegistry.getProvider(id) } : undefined}
248
-
249
- tool-id="calculator"
250
- ></pie-tool-calculator>
251
- {/if}
252
-
253
231
  {#if enabledToolsList.includes('graph')}
254
232
  <pie-tool-graph
255
233
  bind:this={graphElement}
@@ -297,50 +275,100 @@
297
275
  tool-id="ruler"
298
276
  ></pie-tool-ruler>
299
277
  {/if}
278
+
279
+ <!-- Live region for status announcements -->
280
+ <div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
281
+ {statusMessage}
282
+ </div>
300
283
  {/if}
301
284
 
302
285
  <style>
286
+ /* Base toolbar styles */
303
287
  .section-tools-toolbar {
304
288
  display: flex;
305
289
  align-items: center;
306
290
  padding: 0.75rem 1rem;
307
291
  background-color: var(--pie-background, #ffffff);
292
+ gap: 1rem;
293
+ flex-shrink: 0;
294
+ }
295
+
296
+ /* Position-specific styles */
297
+ .section-tools-toolbar--bottom {
308
298
  border-top: 1px solid var(--pie-border, #e0e0e0);
309
299
  min-height: 60px;
310
- gap: 1rem;
300
+ flex-direction: row;
301
+ }
302
+
303
+ .section-tools-toolbar--top {
304
+ border-bottom: 1px solid var(--pie-border, #e0e0e0);
305
+ min-height: 60px;
306
+ flex-direction: row;
307
+ }
308
+
309
+ .section-tools-toolbar--left,
310
+ .section-tools-toolbar--right {
311
+ flex-direction: column;
312
+ min-width: 80px;
313
+ padding: 1rem 0.5rem;
314
+ }
315
+
316
+ .section-tools-toolbar--left {
317
+ border-right: 1px solid var(--pie-border, #e0e0e0);
318
+ }
319
+
320
+ .section-tools-toolbar--right {
321
+ border-left: 1px solid var(--pie-border, #e0e0e0);
322
+ position: sticky;
323
+ top: 0;
324
+ align-self: flex-start;
325
+ max-height: 100vh;
326
+ overflow-y: auto;
311
327
  }
312
328
 
313
329
  .tools-buttons {
314
330
  display: flex;
315
331
  gap: 0.5rem;
316
- flex-wrap: wrap;
317
332
  align-items: center;
318
333
  }
319
334
 
335
+ /* Horizontal layout for top/bottom */
336
+ .section-tools-toolbar--top .tools-buttons,
337
+ .section-tools-toolbar--bottom .tools-buttons {
338
+ flex-direction: row;
339
+ flex-wrap: wrap;
340
+ }
341
+
342
+ /* Vertical layout for left/right */
343
+ .section-tools-toolbar--left .tools-buttons,
344
+ .section-tools-toolbar--right .tools-buttons {
345
+ flex-direction: column;
346
+ flex-wrap: nowrap;
347
+ }
348
+
320
349
  .tool-button {
321
350
  display: flex;
322
351
  align-items: center;
323
- gap: 0.5rem;
324
- padding: 0.5rem 1rem;
352
+ justify-content: center;
353
+ width: 2rem;
354
+ height: 2rem;
355
+ padding: 0.25rem;
325
356
  background-color: var(--pie-background, #ffffff);
326
- border: 1px solid var(--pie-border, #d0d0d0);
327
- border-radius: 4px;
357
+ border: 1px solid var(--pie-border, #ccc);
358
+ border-radius: 0.25rem;
328
359
  cursor: pointer;
329
- font-size: 0.875rem;
330
360
  color: var(--pie-text, #333);
331
- white-space: nowrap;
332
361
  transition: all 0.15s ease;
333
362
  }
334
363
 
335
- .tool-button:hover:not(:disabled) {
336
- background-color: var(--pie-secondary-background, #f5f5f5);
337
- transform: translateY(-1px);
338
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
364
+ .tool-button svg {
365
+ width: 100%;
366
+ height: 100%;
339
367
  }
340
368
 
341
- .tool-button:active:not(:disabled) {
342
- transform: translateY(0);
343
- box-shadow: none;
369
+ .tool-button:hover:not(:disabled) {
370
+ background-color: var(--pie-secondary-background, #f5f5f5);
371
+ border-color: var(--pie-border-hover, #999);
344
372
  }
345
373
 
346
374
  .tool-button.active {
@@ -363,17 +391,28 @@
363
391
  cursor: not-allowed;
364
392
  }
365
393
 
366
- .tool-icon {
367
- font-size: 1.25rem;
368
- line-height: 1;
369
- }
394
+ /* Responsive: Adapt for mobile devices */
395
+ @media (max-width: 768px) {
396
+ /* On mobile, force bottom position for better UX */
397
+ .section-tools-toolbar--left,
398
+ .section-tools-toolbar--right {
399
+ flex-direction: row;
400
+ min-width: unset;
401
+ min-height: 60px;
402
+ padding: 0.75rem 1rem;
403
+ border-left: none;
404
+ border-right: none;
405
+ border-top: 1px solid var(--pie-border, #e0e0e0);
406
+ }
370
407
 
371
- .tool-label {
372
- font-size: 0.875rem;
373
- font-weight: 500;
408
+ .section-tools-toolbar--left .tools-buttons,
409
+ .section-tools-toolbar--right .tools-buttons {
410
+ flex-direction: row;
411
+ flex-wrap: wrap;
412
+ }
374
413
  }
375
414
 
376
- /* Responsive: Hide labels on narrow screens */
415
+ /* Hide labels on narrow screens */
377
416
  @media (max-width: 640px) {
378
417
  .tool-label {
379
418
  display: none;
@@ -381,6 +420,21 @@
381
420
 
382
421
  .tool-button {
383
422
  padding: 0.5rem;
423
+ min-width: 2.75rem; /* 44px - WCAG 2.5.2 Level A minimum */
424
+ min-height: 2.75rem;
384
425
  }
385
426
  }
427
+
428
+ /* Screen reader only content */
429
+ .sr-only {
430
+ position: absolute;
431
+ width: 1px;
432
+ height: 1px;
433
+ padding: 0;
434
+ margin: -1px;
435
+ overflow: hidden;
436
+ clip: rect(0, 0, 0, 0);
437
+ white-space: nowrap;
438
+ border-width: 0;
439
+ }
386
440
  </style>