@pie-players/pie-assessment-toolkit 0.2.9 → 0.2.10
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/components/ItemToolBar.custom-element.js +413 -88
- package/dist/components/PieAssessmentToolkit.custom-element.js +128 -41
- package/dist/components/SectionToolBar.custom-element.js +162 -0
- package/dist/components/section-toolbar-element.d.ts +3 -0
- package/dist/components/section-toolbar-element.d.ts.map +1 -0
- package/dist/components/section-toolbar-element.js +2 -0
- package/dist/components/section-toolbar-element.js.map +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +1 -1
- package/dist/context/assessment-toolkit-context.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.d.ts +1 -1
- package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
- package/dist/services/HighlightCoordinator.d.ts +9 -0
- package/dist/services/HighlightCoordinator.d.ts.map +1 -1
- package/dist/services/HighlightCoordinator.js +196 -55
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/TTSService.d.ts +18 -0
- package/dist/services/TTSService.d.ts.map +1 -1
- package/dist/services/TTSService.js +343 -105
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts +14 -2
- package/dist/services/ToolRegistry.d.ts.map +1 -1
- package/dist/services/ToolRegistry.js +2 -1
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +21 -1
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +106 -2
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.d.ts +5 -11
- package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
- package/dist/services/createDefaultToolRegistry.js +5 -48
- package/dist/services/createDefaultToolRegistry.js.map +1 -1
- package/dist/services/interfaces.d.ts +1 -1
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/pnp-standard-features.d.ts +1 -1
- package/dist/services/section-controller-types.d.ts +5 -0
- package/dist/services/section-controller-types.d.ts.map +1 -1
- package/dist/services/tool-config-defaults.d.ts +14 -0
- package/dist/services/tool-config-defaults.d.ts.map +1 -0
- package/dist/services/tool-config-defaults.js +23 -0
- package/dist/services/tool-config-defaults.js.map +1 -0
- package/dist/services/tool-context.d.ts.map +1 -1
- package/dist/services/tool-context.js +50 -3
- package/dist/services/tool-context.js.map +1 -1
- package/dist/services/tool-instance-id.d.ts +1 -3
- package/dist/services/tool-instance-id.d.ts.map +1 -1
- package/dist/services/tool-instance-id.js +4 -10
- package/dist/services/tool-instance-id.js.map +1 -1
- package/dist/services/toolbar-items.d.ts +26 -0
- package/dist/services/toolbar-items.d.ts.map +1 -0
- package/dist/services/toolbar-items.js +10 -0
- package/dist/services/toolbar-items.js.map +1 -0
- package/dist/services/tools-config-normalizer.d.ts.map +1 -1
- package/dist/services/tools-config-normalizer.js +5 -6
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/services/tts/browser-provider.d.ts.map +1 -1
- package/dist/services/tts/browser-provider.js +201 -34
- package/dist/services/tts/browser-provider.js.map +1 -1
- package/dist/services/tts/text-processing.d.ts +17 -0
- package/dist/services/tts/text-processing.d.ts.map +1 -0
- package/dist/services/tts/text-processing.js +144 -0
- package/dist/services/tts/text-processing.js.map +1 -0
- package/dist/tools/default-tool-module-loaders.d.ts.map +1 -1
- package/dist/tools/default-tool-module-loaders.js +1 -7
- package/dist/tools/default-tool-module-loaders.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.d.ts +7 -2
- package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
- package/dist/tools/registrations/accessibility-tools.js +21 -15
- package/dist/tools/registrations/accessibility-tools.js.map +1 -1
- package/dist/tools/registrations/calculator.d.ts.map +1 -1
- package/dist/tools/registrations/calculator.js +18 -10
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/dist/tools/registrations/interaction-tools.js +4 -4
- package/dist/tools/registrations/interaction-tools.js.map +1 -1
- package/dist/tools/registrations/measurement-tools.js +4 -4
- package/dist/tools/registrations/measurement-tools.js.map +1 -1
- package/dist/tools/registrations/subject-specific-tools.js +4 -4
- package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
- package/dist/tools/registrations/tts.d.ts.map +1 -1
- package/dist/tools/registrations/tts.js +161 -17
- package/dist/tools/registrations/tts.js.map +1 -1
- package/dist/tools/tool-tag-map.d.ts.map +1 -1
- package/dist/tools/tool-tag-map.js +2 -1
- package/dist/tools/tool-tag-map.js.map +1 -1
- package/package.json +11 -7
- package/src/components/ItemToolBar.svelte +254 -42
- package/src/components/PieAssessmentToolkit.svelte +139 -46
- package/src/components/SectionToolBar.svelte +58 -0
- package/dist/services/tts/provider-interface.d.ts +0 -131
- package/dist/services/tts/provider-interface.d.ts.map +0 -1
- package/dist/services/tts/provider-interface.js +0 -10
- package/dist/services/tts/provider-interface.js.map +0 -1
- package/src/components/PNPProvenanceViewer.svelte +0 -270
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-assessment-toolkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./dist/components/pie-assessment-toolkit-element.d.ts",
|
|
34
34
|
"import": "./dist/components/pie-assessment-toolkit-element.js"
|
|
35
35
|
},
|
|
36
|
+
"./components/section-toolbar-element": {
|
|
37
|
+
"types": "./dist/components/section-toolbar-element.d.ts",
|
|
38
|
+
"import": "./dist/components/section-toolbar-element.js"
|
|
39
|
+
},
|
|
36
40
|
"./services/pnp-standard-features": {
|
|
37
41
|
"types": "./dist/services/pnp-standard-features.d.ts",
|
|
38
42
|
"import": "./dist/services/pnp-standard-features.js"
|
|
@@ -60,12 +64,12 @@
|
|
|
60
64
|
"test": "bun test"
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
|
-
"@pie-players/pie-calculator": "0.1.
|
|
64
|
-
"@pie-players/pie-calculator-desmos": "0.1.
|
|
65
|
-
"@pie-players/pie-context": "0.1.
|
|
66
|
-
"@pie-players/pie-players-shared": "0.2.
|
|
67
|
-
"@pie-players/pie-tts": "0.1.
|
|
68
|
-
"@pie-players/tts-client-server": "0.2.
|
|
67
|
+
"@pie-players/pie-calculator": "0.1.5",
|
|
68
|
+
"@pie-players/pie-calculator-desmos": "0.1.6",
|
|
69
|
+
"@pie-players/pie-context": "0.1.2",
|
|
70
|
+
"@pie-players/pie-players-shared": "0.2.6",
|
|
71
|
+
"@pie-players/pie-tts": "0.1.5",
|
|
72
|
+
"@pie-players/tts-client-server": "0.2.5",
|
|
69
73
|
"daisyui": "^5.5.18"
|
|
70
74
|
},
|
|
71
75
|
"peerDependencies": {
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
tag: 'pie-item-toolbar',
|
|
4
4
|
shadow: 'open',
|
|
5
5
|
props: {
|
|
6
|
+
level: { type: 'String', attribute: 'level' },
|
|
7
|
+
scopeId: { type: 'String', attribute: 'scope-id' },
|
|
6
8
|
itemId: { type: 'String', attribute: 'item-id' },
|
|
9
|
+
sectionId: { type: 'String', attribute: 'section-id' },
|
|
7
10
|
catalogId: { type: 'String', attribute: 'catalog-id' },
|
|
8
11
|
tools: { type: 'String', attribute: 'tools' },
|
|
9
12
|
contentKind: { type: 'String', attribute: 'content-kind' },
|
|
13
|
+
position: { type: 'String', attribute: 'position' },
|
|
10
14
|
size: { type: 'String', attribute: 'size' },
|
|
11
15
|
language: { type: 'String', attribute: 'language' },
|
|
12
16
|
|
|
@@ -16,7 +20,8 @@
|
|
|
16
20
|
pnpResolver: { type: 'Object', reflect: false },
|
|
17
21
|
assessment: { type: 'Object', reflect: false },
|
|
18
22
|
itemRef: { type: 'Object', reflect: false },
|
|
19
|
-
item: { type: 'Object', reflect: false }
|
|
23
|
+
item: { type: 'Object', reflect: false },
|
|
24
|
+
hostButtons: { type: 'Object', reflect: false }
|
|
20
25
|
}
|
|
21
26
|
}}
|
|
22
27
|
/>
|
|
@@ -38,6 +43,12 @@
|
|
|
38
43
|
connectAssessmentToolkitShellContext,
|
|
39
44
|
} from '../context/runtime-context-consumer.js';
|
|
40
45
|
import type { ToolRegistry, ToolToolbarRenderResult, ToolbarContext } from '../services/ToolRegistry.js';
|
|
46
|
+
import {
|
|
47
|
+
isExternalIconUrl,
|
|
48
|
+
isInlineSvgIcon,
|
|
49
|
+
isToolbarLinkItem,
|
|
50
|
+
type ToolbarItem
|
|
51
|
+
} from '../services/toolbar-items.js';
|
|
41
52
|
import type { PNPToolResolver } from '../services/PNPToolResolver.js';
|
|
42
53
|
import { createDefaultToolRegistry } from '../services/createDefaultToolRegistry.js';
|
|
43
54
|
import { DEFAULT_TOOL_MODULE_LOADERS } from '../tools/default-tool-module-loaders.js';
|
|
@@ -48,7 +59,7 @@
|
|
|
48
59
|
} from '../services/tools-config-normalizer.js';
|
|
49
60
|
import { createScopedToolId, parseScopedToolId } from '../services/tool-instance-id.js';
|
|
50
61
|
import type { AssessmentEntity, AssessmentItemRef, ItemEntity } from '@pie-players/pie-players-shared/types';
|
|
51
|
-
import type { ElementToolContext, ItemToolContext } from '../services/tool-context.js';
|
|
62
|
+
import type { ElementToolContext, ItemToolContext, ToolLevel, ToolContext } from '../services/tool-context.js';
|
|
52
63
|
|
|
53
64
|
const isBrowser = typeof window !== 'undefined';
|
|
54
65
|
const fallbackToolRegistry = createDefaultToolRegistry({
|
|
@@ -59,30 +70,40 @@
|
|
|
59
70
|
|
|
60
71
|
// Props
|
|
61
72
|
let {
|
|
73
|
+
level = 'item' as ToolLevel,
|
|
74
|
+
scopeId = '',
|
|
62
75
|
itemId = '',
|
|
76
|
+
sectionId = '',
|
|
63
77
|
catalogId = '',
|
|
64
78
|
tools = 'calculator,textToSpeech,answerEliminator',
|
|
65
79
|
contentKind = 'assessment-item',
|
|
80
|
+
position = 'bottom' as 'top' | 'right' | 'bottom' | 'left' | 'none',
|
|
66
81
|
scopeElement = null,
|
|
67
82
|
toolRegistry = null,
|
|
68
83
|
pnpResolver = null,
|
|
69
84
|
assessment = null,
|
|
70
85
|
itemRef = null,
|
|
71
86
|
item = null,
|
|
87
|
+
hostButtons = [] as ToolbarItem[],
|
|
72
88
|
class: className = '',
|
|
73
89
|
size = 'md' as 'sm' | 'md' | 'lg',
|
|
74
90
|
language = 'en-US'
|
|
75
91
|
}: {
|
|
92
|
+
level?: ToolLevel;
|
|
93
|
+
scopeId?: string;
|
|
76
94
|
itemId?: string;
|
|
95
|
+
sectionId?: string;
|
|
77
96
|
catalogId?: string;
|
|
78
97
|
tools?: string;
|
|
79
98
|
contentKind?: string;
|
|
99
|
+
position?: 'top' | 'right' | 'bottom' | 'left' | 'none';
|
|
80
100
|
scopeElement?: HTMLElement | null;
|
|
81
101
|
toolRegistry?: ToolRegistry | null;
|
|
82
102
|
pnpResolver?: PNPToolResolver | null;
|
|
83
103
|
assessment?: AssessmentEntity | null;
|
|
84
104
|
itemRef?: AssessmentItemRef | null;
|
|
85
105
|
item?: ItemEntity | null;
|
|
106
|
+
hostButtons?: ToolbarItem[];
|
|
86
107
|
class?: string;
|
|
87
108
|
size?: 'sm' | 'md' | 'lg';
|
|
88
109
|
language?: string;
|
|
@@ -111,11 +132,22 @@
|
|
|
111
132
|
const effectiveTTSService = $derived(runtimeContext?.ttsService);
|
|
112
133
|
const effectiveElementToolStateStore = $derived(runtimeContext?.elementToolStateStore);
|
|
113
134
|
const effectiveAssessmentId = $derived(runtimeContext?.assessmentId ?? '');
|
|
114
|
-
const effectiveSectionId = $derived(runtimeContext?.sectionId ?? '');
|
|
115
135
|
const effectiveItemId = $derived(itemId || shellContext?.itemId || '');
|
|
116
136
|
const effectiveCanonicalItemId = $derived(shellContext?.canonicalItemId || effectiveItemId);
|
|
117
|
-
const
|
|
118
|
-
const effectiveContentKind = $derived(contentKind || shellContext?.contentKind || 'assessment-item');
|
|
137
|
+
const effectiveSectionId = $derived(sectionId || runtimeContext?.sectionId || '');
|
|
138
|
+
const effectiveContentKind = $derived(contentKind || shellContext?.contentKind || (level === 'section' ? 'section' : 'assessment-item'));
|
|
139
|
+
const effectiveLevel = $derived.by((): ToolLevel => {
|
|
140
|
+
if (level && level !== 'item') return level;
|
|
141
|
+
if (effectiveContentKind === 'rubric-block-stimulus') return 'passage';
|
|
142
|
+
return 'item';
|
|
143
|
+
});
|
|
144
|
+
const effectiveScopeId = $derived.by(() => {
|
|
145
|
+
if (scopeId) return scopeId;
|
|
146
|
+
if (effectiveLevel === 'section') return effectiveSectionId || runtimeContext?.sectionId || 'default-section';
|
|
147
|
+
if (effectiveLevel === 'assessment') return runtimeContext?.assessmentId || 'default-assessment';
|
|
148
|
+
return effectiveCanonicalItemId || effectiveItemId || 'default-item';
|
|
149
|
+
});
|
|
150
|
+
const effectiveCatalogId = $derived(catalogId || effectiveScopeId);
|
|
119
151
|
const effectiveItem = $derived((item as ItemEntity | null) || (shellContext?.item as ItemEntity | null) || null);
|
|
120
152
|
|
|
121
153
|
// Effective registry for visibility and metadata ownership.
|
|
@@ -129,9 +161,11 @@
|
|
|
129
161
|
const coordinatorConfig = runtimeContext?.toolkitCoordinator?.config?.tools as any;
|
|
130
162
|
return normalizeToolsConfig(coordinatorConfig || {});
|
|
131
163
|
});
|
|
132
|
-
const placementLevel = $derived.by(() =>
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
const placementLevel = $derived.by(() => {
|
|
165
|
+
if (effectiveLevel === 'section') return 'section';
|
|
166
|
+
if (effectiveLevel === 'passage' || effectiveContentKind === 'rubric-block-stimulus') return 'passage';
|
|
167
|
+
return 'item';
|
|
168
|
+
});
|
|
135
169
|
const placementAllowedToolIds = $derived.by(() => {
|
|
136
170
|
const resolved = resolveToolsForLevel(effectiveToolsConfig, placementLevel);
|
|
137
171
|
return effectiveToolRegistry.normalizeToolIds(resolved).filter(Boolean);
|
|
@@ -150,33 +184,56 @@
|
|
|
150
184
|
});
|
|
151
185
|
|
|
152
186
|
const contentReady = $derived.by(() => {
|
|
187
|
+
if (effectiveLevel === 'section' || effectiveLevel === 'assessment') return true;
|
|
153
188
|
const config = (effectiveItem as ItemEntity | null)?.config;
|
|
154
189
|
return !!(effectiveItem && config && typeof config === 'object');
|
|
155
190
|
});
|
|
156
191
|
|
|
157
192
|
const toolContext = $derived.by((): ItemToolContext | null => {
|
|
193
|
+
if (effectiveLevel === 'section') {
|
|
194
|
+
return {
|
|
195
|
+
level: 'section',
|
|
196
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
197
|
+
section: {} as any
|
|
198
|
+
} as ToolContext as ItemToolContext;
|
|
199
|
+
}
|
|
200
|
+
if (effectiveLevel === 'assessment') {
|
|
201
|
+
return {
|
|
202
|
+
level: 'assessment',
|
|
203
|
+
assessment: (assessment || {}) as AssessmentEntity
|
|
204
|
+
} as ToolContext as ItemToolContext;
|
|
205
|
+
}
|
|
158
206
|
if (!contentReady || !effectiveItem) {
|
|
159
207
|
return null;
|
|
160
208
|
}
|
|
209
|
+
if (effectiveLevel === 'passage') {
|
|
210
|
+
return {
|
|
211
|
+
level: 'passage',
|
|
212
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
213
|
+
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
214
|
+
passage: effectiveItem as any
|
|
215
|
+
} as ToolContext as ItemToolContext;
|
|
216
|
+
}
|
|
161
217
|
return {
|
|
162
218
|
level: 'item',
|
|
163
219
|
assessment: (assessment || {}) as AssessmentEntity,
|
|
164
220
|
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
165
221
|
item: effectiveItem as ItemEntity
|
|
166
|
-
};
|
|
222
|
+
} as ToolContext as ItemToolContext;
|
|
167
223
|
});
|
|
168
224
|
|
|
169
|
-
const renderContext = $derived.by(():
|
|
225
|
+
const renderContext = $derived.by((): ToolContext => {
|
|
170
226
|
if (toolContext) return toolContext;
|
|
171
227
|
return {
|
|
172
228
|
level: 'item',
|
|
173
229
|
assessment: (assessment || {}) as AssessmentEntity,
|
|
174
230
|
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
175
231
|
item: ((effectiveItem as ItemEntity | null) || ({ id: effectiveCanonicalItemId, config: {} } as ItemEntity)) as ItemEntity
|
|
176
|
-
};
|
|
232
|
+
} as ToolContext;
|
|
177
233
|
});
|
|
178
234
|
|
|
179
235
|
const elementContexts = $derived.by((): ElementToolContext[] => {
|
|
236
|
+
if (effectiveLevel !== 'item' && effectiveLevel !== 'passage') return [];
|
|
180
237
|
if (!contentReady || !effectiveItem) return [];
|
|
181
238
|
const modelsRaw = (effectiveItem as any)?.config?.models;
|
|
182
239
|
const models = Array.isArray(modelsRaw)
|
|
@@ -197,20 +254,33 @@
|
|
|
197
254
|
|
|
198
255
|
// Pass 2: tool-owned context filtering (item + element aggregation)
|
|
199
256
|
const visibleToolIds = $derived.by(() => {
|
|
257
|
+
const levelCompatibleToolIds = allowedToolIds.filter((toolId) => {
|
|
258
|
+
const registration = effectiveToolRegistry.get(toolId);
|
|
259
|
+
return registration ? registration.supportedLevels.includes(effectiveLevel) : false;
|
|
260
|
+
});
|
|
261
|
+
if (effectiveLevel === 'section') {
|
|
262
|
+
// Section toolbars are orchestrator-driven. Tool-level relevance is often
|
|
263
|
+
// item-content dependent. Keep pass-1 as the source of truth here.
|
|
264
|
+
return allowedToolIds;
|
|
265
|
+
}
|
|
200
266
|
if (!contentReady) {
|
|
201
267
|
// Stage 1: orchestrator-level allow-list only.
|
|
202
|
-
return
|
|
268
|
+
return levelCompatibleToolIds;
|
|
203
269
|
}
|
|
204
270
|
if (!toolContext && elementContexts.length === 0) {
|
|
205
|
-
return
|
|
271
|
+
return levelCompatibleToolIds;
|
|
206
272
|
}
|
|
207
273
|
const visible = new Set<string>();
|
|
208
274
|
if (toolContext) {
|
|
209
|
-
effectiveToolRegistry
|
|
275
|
+
effectiveToolRegistry
|
|
276
|
+
.filterVisibleInContext(levelCompatibleToolIds, toolContext)
|
|
277
|
+
.forEach((tool) => visible.add(tool.toolId));
|
|
210
278
|
}
|
|
211
279
|
|
|
212
280
|
for (const context of elementContexts) {
|
|
213
|
-
effectiveToolRegistry
|
|
281
|
+
effectiveToolRegistry
|
|
282
|
+
.filterVisibleInContext(levelCompatibleToolIds, context)
|
|
283
|
+
.forEach((tool) => visible.add(tool.toolId));
|
|
214
284
|
}
|
|
215
285
|
|
|
216
286
|
return Array.from(visible);
|
|
@@ -257,10 +327,19 @@
|
|
|
257
327
|
// This keeps toolbar contracts stable across mixed registration implementations.
|
|
258
328
|
return parseScopedToolId(toolId)
|
|
259
329
|
? toolId
|
|
260
|
-
: createScopedToolId(toolId,
|
|
330
|
+
: createScopedToolId(toolId, effectiveLevel, effectiveScopeId);
|
|
261
331
|
};
|
|
262
332
|
return {
|
|
263
|
-
|
|
333
|
+
scope: {
|
|
334
|
+
level: effectiveLevel,
|
|
335
|
+
scopeId: effectiveScopeId,
|
|
336
|
+
assessmentId: runtimeContext?.assessmentId,
|
|
337
|
+
sectionId: effectiveSectionId,
|
|
338
|
+
itemId: effectiveItemId,
|
|
339
|
+
canonicalItemId: effectiveCanonicalItemId,
|
|
340
|
+
contentKind: effectiveContentKind
|
|
341
|
+
},
|
|
342
|
+
itemId: effectiveScopeId,
|
|
264
343
|
catalogId: effectiveCatalogId,
|
|
265
344
|
language,
|
|
266
345
|
ui: {
|
|
@@ -274,7 +353,13 @@
|
|
|
274
353
|
elementToolStateStore: effectiveElementToolStateStore || null,
|
|
275
354
|
toggleTool: (toolId: string) => {
|
|
276
355
|
if (!effectiveToolCoordinator) return;
|
|
277
|
-
|
|
356
|
+
const instanceToolId = toInstanceToolId(toolId);
|
|
357
|
+
// Some tools only self-register after first visibility sync.
|
|
358
|
+
// Seed a placeholder registration so first toggle always works.
|
|
359
|
+
if (!effectiveToolCoordinator.getToolState(instanceToolId)) {
|
|
360
|
+
effectiveToolCoordinator.registerTool(instanceToolId, toolId);
|
|
361
|
+
}
|
|
362
|
+
effectiveToolCoordinator.toggleTool(instanceToolId);
|
|
278
363
|
},
|
|
279
364
|
isToolVisible: (toolId: string) => {
|
|
280
365
|
if (!effectiveToolCoordinator) return false;
|
|
@@ -300,6 +385,80 @@
|
|
|
300
385
|
}
|
|
301
386
|
return rendered;
|
|
302
387
|
});
|
|
388
|
+
const normalizedHostButtons = $derived.by((): ToolbarItem[] =>
|
|
389
|
+
Array.isArray(hostButtons)
|
|
390
|
+
? hostButtons.filter((item): item is ToolbarItem => !!item && typeof item.id === 'string')
|
|
391
|
+
: []
|
|
392
|
+
);
|
|
393
|
+
const nativeToolbarItems = $derived.by((): ToolbarItem[] =>
|
|
394
|
+
renderedTools
|
|
395
|
+
.filter((renderedTool) => !!renderedTool.button)
|
|
396
|
+
.map((renderedTool) => {
|
|
397
|
+
const button = renderedTool.button!;
|
|
398
|
+
return {
|
|
399
|
+
id: renderedTool.toolId,
|
|
400
|
+
label: button.label,
|
|
401
|
+
ariaLabel: button.ariaLabel || button.label,
|
|
402
|
+
icon: button.icon,
|
|
403
|
+
tooltip: button.tooltip || button.label,
|
|
404
|
+
disabled: button.disabled,
|
|
405
|
+
active: activeToolState[renderedTool.toolId] ?? button.active ?? false,
|
|
406
|
+
onClick: () => {
|
|
407
|
+
button.onClick();
|
|
408
|
+
syncRenderedToolsState();
|
|
409
|
+
},
|
|
410
|
+
} satisfies ToolbarItem;
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
const toolbarItems = $derived.by((): ToolbarItem[] => [
|
|
414
|
+
...nativeToolbarItems,
|
|
415
|
+
...normalizedHostButtons
|
|
416
|
+
]);
|
|
417
|
+
const mountedElementsBeforeButtons = $derived.by(() =>
|
|
418
|
+
renderedTools.flatMap((renderedTool) =>
|
|
419
|
+
(renderedTool.elements || [])
|
|
420
|
+
.filter((entry) => entry.mount === 'before-buttons')
|
|
421
|
+
.map((entry) => entry.element)
|
|
422
|
+
.filter((entry): entry is HTMLElement => Boolean(entry))
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
const mountedElementsAfterButtons = $derived.by(() =>
|
|
426
|
+
renderedTools.flatMap((renderedTool) =>
|
|
427
|
+
(renderedTool.elements || [])
|
|
428
|
+
.filter((entry) => entry.mount === 'after-buttons')
|
|
429
|
+
.map((entry) => entry.element)
|
|
430
|
+
.filter((entry): entry is HTMLElement => Boolean(entry))
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
function isToolbarItemActive(item: ToolbarItem): boolean {
|
|
435
|
+
if (item.id in activeToolState) {
|
|
436
|
+
return activeToolState[item.id] === true;
|
|
437
|
+
}
|
|
438
|
+
return item.active === true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function getFallbackIconSvg(iconName: string): string | null {
|
|
442
|
+
const iconMap: Record<string, string> = {
|
|
443
|
+
calculator:
|
|
444
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 2h10a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm0 2v4h10V4H7Zm0 6v2h2v-2H7Zm4 0v2h2v-2h-2Zm4 0v2h2v-2h-2Zm-8 4v2h2v-2H7Zm4 0v2h2v-2h-2Zm4 0v2h2v-2h-2Zm-8 4v2h2v-2H7Zm4 0v2h2v-2h-2Zm4 0v2h2v-2h-2Z" fill="currentColor"/></svg>',
|
|
445
|
+
'volume-up':
|
|
446
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14 3.23v2.06A7.002 7.002 0 0 1 19 12a7 7 0 0 1-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77Zm-2 17.75V3L7 8H3v8h4l5 5Zm4.5-9a4.5 4.5 0 0 0-2.5-4.03v8.05A4.5 4.5 0 0 0 16.5 12Z" fill="currentColor"/></svg>',
|
|
447
|
+
swatch:
|
|
448
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 0 9 9c0-.55-.45-1-1-1h-2.5a1.5 1.5 0 0 1 0-3H20a1 1 0 0 0 1-1 8.99 8.99 0 0 0-9-4Zm-5.5 9A1.5 1.5 0 1 1 8 13.5 1.5 1.5 0 0 1 6.5 12Zm3-4A1.5 1.5 0 1 1 11 9.5 1.5 1.5 0 0 1 9.5 8Zm5 0A1.5 1.5 0 1 1 16 9.5 1.5 1.5 0 0 1 14.5 8Z" fill="currentColor"/></svg>',
|
|
449
|
+
'chart-bar':
|
|
450
|
+
'<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>',
|
|
451
|
+
beaker:
|
|
452
|
+
'<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>',
|
|
453
|
+
protractor:
|
|
454
|
+
'<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>',
|
|
455
|
+
'bars-3':
|
|
456
|
+
'<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>',
|
|
457
|
+
ruler:
|
|
458
|
+
'<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>'
|
|
459
|
+
};
|
|
460
|
+
return iconMap[iconName] || null;
|
|
461
|
+
}
|
|
303
462
|
|
|
304
463
|
function syncRenderedToolsState() {
|
|
305
464
|
const nextActiveState: Record<string, boolean> = {};
|
|
@@ -370,41 +529,81 @@
|
|
|
370
529
|
{#if isBrowser}
|
|
371
530
|
<div
|
|
372
531
|
class="item-toolbar {className} item-toolbar--{size}"
|
|
532
|
+
class:item-toolbar--top={position === 'top'}
|
|
533
|
+
class:item-toolbar--right={position === 'right'}
|
|
534
|
+
class:item-toolbar--bottom={position === 'bottom'}
|
|
535
|
+
class:item-toolbar--left={position === 'left'}
|
|
373
536
|
data-content-kind={effectiveContentKind}
|
|
537
|
+
data-level={effectiveLevel}
|
|
374
538
|
bind:this={toolbarRootElement}
|
|
375
539
|
>
|
|
376
|
-
{#each
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
{/if}
|
|
540
|
+
{#each mountedElementsBeforeButtons as mountedElement (`before-${mountedElement.tagName}-${mountedElement.getAttribute('tool-id') || ''}`)}
|
|
541
|
+
<span class="item-toolbar__element-host" use:mountElement={mountedElement}></span>
|
|
542
|
+
{/each}
|
|
380
543
|
|
|
381
|
-
|
|
544
|
+
{#each toolbarItems as item (item.id)}
|
|
545
|
+
{#if isToolbarLinkItem(item)}
|
|
546
|
+
<a
|
|
547
|
+
class="item-toolbar__button"
|
|
548
|
+
class:item-toolbar__button--active={isToolbarItemActive(item)}
|
|
549
|
+
href={item.disabled ? undefined : item.href}
|
|
550
|
+
target={item.target}
|
|
551
|
+
rel={item.rel}
|
|
552
|
+
aria-label={item.ariaLabel || item.label}
|
|
553
|
+
title={item.tooltip || item.label}
|
|
554
|
+
aria-disabled={item.disabled ? 'true' : undefined}
|
|
555
|
+
onclick={(event) => {
|
|
556
|
+
if (item.disabled) {
|
|
557
|
+
event.preventDefault();
|
|
558
|
+
}
|
|
559
|
+
}}
|
|
560
|
+
>
|
|
561
|
+
{#if item.icon}
|
|
562
|
+
{#if isInlineSvgIcon(item.icon)}
|
|
563
|
+
{@html item.icon}
|
|
564
|
+
{:else if isExternalIconUrl(item.icon)}
|
|
565
|
+
<img class="item-toolbar__icon-image" src={item.icon} alt="" />
|
|
566
|
+
{:else}
|
|
567
|
+
{@const fallbackIcon = getFallbackIconSvg(item.icon)}
|
|
568
|
+
{#if fallbackIcon}
|
|
569
|
+
{@html fallbackIcon}
|
|
570
|
+
{:else}
|
|
571
|
+
<i class={`icon icon-${item.icon}`} aria-hidden="true"></i>
|
|
572
|
+
{/if}
|
|
573
|
+
{/if}
|
|
574
|
+
{/if}
|
|
575
|
+
</a>
|
|
576
|
+
{:else}
|
|
382
577
|
<button
|
|
383
578
|
type="button"
|
|
384
|
-
class="item-toolbar__button
|
|
385
|
-
class:item-toolbar__button--active={
|
|
386
|
-
onclick={
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
aria-pressed={activeToolState[renderedTool.toolId] ?? renderedTool.button.active ?? false}
|
|
392
|
-
title={renderedTool.button.tooltip || renderedTool.button.label}
|
|
393
|
-
disabled={renderedTool.button.disabled}
|
|
579
|
+
class="item-toolbar__button"
|
|
580
|
+
class:item-toolbar__button--active={isToolbarItemActive(item)}
|
|
581
|
+
onclick={item.onClick}
|
|
582
|
+
aria-label={item.ariaLabel || item.label}
|
|
583
|
+
aria-pressed={isToolbarItemActive(item)}
|
|
584
|
+
title={item.tooltip || item.label}
|
|
585
|
+
disabled={item.disabled}
|
|
394
586
|
>
|
|
395
|
-
{#if
|
|
396
|
-
{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
587
|
+
{#if item.icon}
|
|
588
|
+
{#if isInlineSvgIcon(item.icon)}
|
|
589
|
+
{@html item.icon}
|
|
590
|
+
{:else if isExternalIconUrl(item.icon)}
|
|
591
|
+
<img class="item-toolbar__icon-image" src={item.icon} alt="" />
|
|
592
|
+
{:else}
|
|
593
|
+
{@const fallbackIcon = getFallbackIconSvg(item.icon)}
|
|
594
|
+
{#if fallbackIcon}
|
|
595
|
+
{@html fallbackIcon}
|
|
596
|
+
{:else}
|
|
597
|
+
<i class={`icon icon-${item.icon}`} aria-hidden="true"></i>
|
|
598
|
+
{/if}
|
|
599
|
+
{/if}
|
|
401
600
|
{/if}
|
|
402
601
|
</button>
|
|
403
602
|
{/if}
|
|
603
|
+
{/each}
|
|
404
604
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{/if}
|
|
605
|
+
{#each mountedElementsAfterButtons as mountedElement (`after-${mountedElement.tagName}-${mountedElement.getAttribute('tool-id') || ''}`)}
|
|
606
|
+
<span class="item-toolbar__element-host" use:mountElement={mountedElement}></span>
|
|
408
607
|
{/each}
|
|
409
608
|
</div>
|
|
410
609
|
{/if}
|
|
@@ -416,6 +615,18 @@
|
|
|
416
615
|
gap: 0.5rem;
|
|
417
616
|
}
|
|
418
617
|
|
|
618
|
+
.item-toolbar--top,
|
|
619
|
+
.item-toolbar--bottom {
|
|
620
|
+
flex-direction: row;
|
|
621
|
+
flex-wrap: wrap;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.item-toolbar--left,
|
|
625
|
+
.item-toolbar--right {
|
|
626
|
+
flex-direction: column;
|
|
627
|
+
align-items: stretch;
|
|
628
|
+
}
|
|
629
|
+
|
|
419
630
|
.item-toolbar__button {
|
|
420
631
|
display: flex;
|
|
421
632
|
align-items: center;
|
|
@@ -429,6 +640,7 @@
|
|
|
429
640
|
color: var(--pie-text, #333);
|
|
430
641
|
cursor: pointer;
|
|
431
642
|
transition: all 0.15s ease;
|
|
643
|
+
text-decoration: none;
|
|
432
644
|
}
|
|
433
645
|
|
|
434
646
|
.item-toolbar__element-host {
|