@pie-players/pie-assessment-toolkit 0.2.9 → 0.2.11
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 +425 -93
- package/dist/components/PieAssessmentToolkit.custom-element.js +200 -46
- 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 +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.d.ts +57 -1
- package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.js +23 -0
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/registration-events.d.ts +19 -0
- package/dist/runtime/registration-events.d.ts.map +1 -1
- package/dist/runtime/registration-events.js +3 -0
- package/dist/runtime/registration-events.js.map +1 -1
- package/dist/runtime/runtime-event-guards.d.ts +0 -1
- package/dist/runtime/runtime-event-guards.d.ts.map +1 -1
- package/dist/runtime/runtime-event-guards.js +0 -5
- package/dist/runtime/runtime-event-guards.js.map +1 -1
- package/dist/runtime/session-event-emitter-policy.d.ts +12 -0
- package/dist/runtime/session-event-emitter-policy.d.ts.map +1 -0
- package/dist/runtime/session-event-emitter-policy.js +57 -0
- package/dist/runtime/session-event-emitter-policy.js.map +1 -0
- 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 +40 -2
- package/dist/services/ToolRegistry.d.ts.map +1 -1
- package/dist/services/ToolRegistry.js +22 -1
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +34 -1
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +203 -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 +7 -2
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.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 +23 -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.d.ts.map +1 -1
- package/dist/tools/registrations/interaction-tools.js +5 -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 +12 -8
- package/src/components/ItemToolBar.svelte +275 -47
- package/src/components/PieAssessmentToolkit.svelte +217 -53
- 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
|
@@ -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,54 +161,92 @@
|
|
|
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);
|
|
138
172
|
});
|
|
173
|
+
const providerConfig = $derived(
|
|
174
|
+
(effectiveToolsConfig?.providers || {}) as Record<
|
|
175
|
+
string,
|
|
176
|
+
{ enabled?: boolean } | undefined
|
|
177
|
+
>
|
|
178
|
+
);
|
|
179
|
+
const isProviderEnabled = (toolId: string): boolean =>
|
|
180
|
+
providerConfig[toolId]?.enabled !== false;
|
|
139
181
|
|
|
140
182
|
// Pass 1: determine allowed tools
|
|
141
183
|
const allowedToolIds = $derived.by(() => {
|
|
142
184
|
const configuredTools =
|
|
143
185
|
normalizedExplicitTools.length > 0 ? normalizedExplicitTools : placementAllowedToolIds;
|
|
186
|
+
const providerEnabledConfiguredTools = configuredTools.filter((toolId) =>
|
|
187
|
+
isProviderEnabled(toolId)
|
|
188
|
+
);
|
|
144
189
|
if (pnpResolver && assessment && itemRef) {
|
|
145
190
|
const allowedByPnp = pnpResolver.getAllowedToolIds(assessment, itemRef);
|
|
146
|
-
if (
|
|
147
|
-
return allowedByPnp.filter((toolId) =>
|
|
191
|
+
if (providerEnabledConfiguredTools.length === 0) return allowedByPnp;
|
|
192
|
+
return allowedByPnp.filter((toolId) =>
|
|
193
|
+
providerEnabledConfiguredTools.includes(toolId)
|
|
194
|
+
);
|
|
148
195
|
}
|
|
149
|
-
return
|
|
196
|
+
return providerEnabledConfiguredTools;
|
|
150
197
|
});
|
|
151
198
|
|
|
152
199
|
const contentReady = $derived.by(() => {
|
|
200
|
+
if (effectiveLevel === 'section' || effectiveLevel === 'assessment') return true;
|
|
153
201
|
const config = (effectiveItem as ItemEntity | null)?.config;
|
|
154
202
|
return !!(effectiveItem && config && typeof config === 'object');
|
|
155
203
|
});
|
|
156
204
|
|
|
157
205
|
const toolContext = $derived.by((): ItemToolContext | null => {
|
|
206
|
+
if (effectiveLevel === 'section') {
|
|
207
|
+
return {
|
|
208
|
+
level: 'section',
|
|
209
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
210
|
+
section: {} as any
|
|
211
|
+
} as ToolContext as ItemToolContext;
|
|
212
|
+
}
|
|
213
|
+
if (effectiveLevel === 'assessment') {
|
|
214
|
+
return {
|
|
215
|
+
level: 'assessment',
|
|
216
|
+
assessment: (assessment || {}) as AssessmentEntity
|
|
217
|
+
} as ToolContext as ItemToolContext;
|
|
218
|
+
}
|
|
158
219
|
if (!contentReady || !effectiveItem) {
|
|
159
220
|
return null;
|
|
160
221
|
}
|
|
222
|
+
if (effectiveLevel === 'passage') {
|
|
223
|
+
return {
|
|
224
|
+
level: 'passage',
|
|
225
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
226
|
+
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
227
|
+
passage: effectiveItem as any
|
|
228
|
+
} as ToolContext as ItemToolContext;
|
|
229
|
+
}
|
|
161
230
|
return {
|
|
162
231
|
level: 'item',
|
|
163
232
|
assessment: (assessment || {}) as AssessmentEntity,
|
|
164
233
|
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
165
234
|
item: effectiveItem as ItemEntity
|
|
166
|
-
};
|
|
235
|
+
} as ToolContext as ItemToolContext;
|
|
167
236
|
});
|
|
168
237
|
|
|
169
|
-
const renderContext = $derived.by(():
|
|
238
|
+
const renderContext = $derived.by((): ToolContext => {
|
|
170
239
|
if (toolContext) return toolContext;
|
|
171
240
|
return {
|
|
172
241
|
level: 'item',
|
|
173
242
|
assessment: (assessment || {}) as AssessmentEntity,
|
|
174
243
|
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
175
244
|
item: ((effectiveItem as ItemEntity | null) || ({ id: effectiveCanonicalItemId, config: {} } as ItemEntity)) as ItemEntity
|
|
176
|
-
};
|
|
245
|
+
} as ToolContext;
|
|
177
246
|
});
|
|
178
247
|
|
|
179
248
|
const elementContexts = $derived.by((): ElementToolContext[] => {
|
|
249
|
+
if (effectiveLevel !== 'item' && effectiveLevel !== 'passage') return [];
|
|
180
250
|
if (!contentReady || !effectiveItem) return [];
|
|
181
251
|
const modelsRaw = (effectiveItem as any)?.config?.models;
|
|
182
252
|
const models = Array.isArray(modelsRaw)
|
|
@@ -197,24 +267,40 @@
|
|
|
197
267
|
|
|
198
268
|
// Pass 2: tool-owned context filtering (item + element aggregation)
|
|
199
269
|
const visibleToolIds = $derived.by(() => {
|
|
270
|
+
const levelCompatibleToolIds = allowedToolIds.filter((toolId) => {
|
|
271
|
+
const registration = effectiveToolRegistry.get(toolId);
|
|
272
|
+
return registration ? registration.supportedLevels.includes(effectiveLevel) : false;
|
|
273
|
+
});
|
|
274
|
+
if (effectiveLevel === 'section') {
|
|
275
|
+
// Section toolbars are orchestrator-driven. Tool-level relevance is often
|
|
276
|
+
// item-content dependent. Keep pass-1 as the source of truth here.
|
|
277
|
+
return allowedToolIds;
|
|
278
|
+
}
|
|
200
279
|
if (!contentReady) {
|
|
201
280
|
// Stage 1: orchestrator-level allow-list only.
|
|
202
|
-
return
|
|
281
|
+
return levelCompatibleToolIds;
|
|
203
282
|
}
|
|
204
283
|
if (!toolContext && elementContexts.length === 0) {
|
|
205
|
-
return
|
|
284
|
+
return levelCompatibleToolIds;
|
|
206
285
|
}
|
|
207
286
|
const visible = new Set<string>();
|
|
208
287
|
if (toolContext) {
|
|
209
|
-
effectiveToolRegistry
|
|
288
|
+
effectiveToolRegistry
|
|
289
|
+
.filterVisibleInContext(levelCompatibleToolIds, toolContext)
|
|
290
|
+
.forEach((tool) => visible.add(tool.toolId));
|
|
210
291
|
}
|
|
211
292
|
|
|
212
293
|
for (const context of elementContexts) {
|
|
213
|
-
effectiveToolRegistry
|
|
294
|
+
effectiveToolRegistry
|
|
295
|
+
.filterVisibleInContext(levelCompatibleToolIds, context)
|
|
296
|
+
.forEach((tool) => visible.add(tool.toolId));
|
|
214
297
|
}
|
|
215
298
|
|
|
216
299
|
return Array.from(visible);
|
|
217
300
|
});
|
|
301
|
+
const toolbarVisibleToolIds = $derived.by(() =>
|
|
302
|
+
effectiveToolRegistry.filterToolIdsByActivation(visibleToolIds, 'toolbar-toggle')
|
|
303
|
+
);
|
|
218
304
|
|
|
219
305
|
// Dynamically load whatever tools are currently visible.
|
|
220
306
|
// The registry owns module loader configuration by toolId.
|
|
@@ -222,7 +308,7 @@
|
|
|
222
308
|
if (!isBrowser) return;
|
|
223
309
|
let cancelled = false;
|
|
224
310
|
void effectiveToolRegistry
|
|
225
|
-
.ensureToolModulesLoaded(
|
|
311
|
+
.ensureToolModulesLoaded(toolbarVisibleToolIds)
|
|
226
312
|
.then(() => {
|
|
227
313
|
if (!cancelled) moduleLoadVersion += 1;
|
|
228
314
|
})
|
|
@@ -257,10 +343,19 @@
|
|
|
257
343
|
// This keeps toolbar contracts stable across mixed registration implementations.
|
|
258
344
|
return parseScopedToolId(toolId)
|
|
259
345
|
? toolId
|
|
260
|
-
: createScopedToolId(toolId,
|
|
346
|
+
: createScopedToolId(toolId, effectiveLevel, effectiveScopeId);
|
|
261
347
|
};
|
|
262
348
|
return {
|
|
263
|
-
|
|
349
|
+
scope: {
|
|
350
|
+
level: effectiveLevel,
|
|
351
|
+
scopeId: effectiveScopeId,
|
|
352
|
+
assessmentId: runtimeContext?.assessmentId,
|
|
353
|
+
sectionId: effectiveSectionId,
|
|
354
|
+
itemId: effectiveItemId,
|
|
355
|
+
canonicalItemId: effectiveCanonicalItemId,
|
|
356
|
+
contentKind: effectiveContentKind
|
|
357
|
+
},
|
|
358
|
+
itemId: effectiveScopeId,
|
|
264
359
|
catalogId: effectiveCatalogId,
|
|
265
360
|
language,
|
|
266
361
|
ui: {
|
|
@@ -274,7 +369,13 @@
|
|
|
274
369
|
elementToolStateStore: effectiveElementToolStateStore || null,
|
|
275
370
|
toggleTool: (toolId: string) => {
|
|
276
371
|
if (!effectiveToolCoordinator) return;
|
|
277
|
-
|
|
372
|
+
const instanceToolId = toInstanceToolId(toolId);
|
|
373
|
+
// Some tools only self-register after first visibility sync.
|
|
374
|
+
// Seed a placeholder registration so first toggle always works.
|
|
375
|
+
if (!effectiveToolCoordinator.getToolState(instanceToolId)) {
|
|
376
|
+
effectiveToolCoordinator.registerTool(instanceToolId, toolId);
|
|
377
|
+
}
|
|
378
|
+
effectiveToolCoordinator.toggleTool(instanceToolId);
|
|
278
379
|
},
|
|
279
380
|
isToolVisible: (toolId: string) => {
|
|
280
381
|
if (!effectiveToolCoordinator) return false;
|
|
@@ -292,7 +393,7 @@
|
|
|
292
393
|
moduleLoadVersion;
|
|
293
394
|
|
|
294
395
|
const rendered: ToolToolbarRenderResult[] = [];
|
|
295
|
-
for (const toolId of
|
|
396
|
+
for (const toolId of toolbarVisibleToolIds) {
|
|
296
397
|
const result = effectiveToolRegistry.renderForToolbar(toolId, renderContext, toolbarContext);
|
|
297
398
|
if (result) {
|
|
298
399
|
rendered.push(result);
|
|
@@ -300,6 +401,80 @@
|
|
|
300
401
|
}
|
|
301
402
|
return rendered;
|
|
302
403
|
});
|
|
404
|
+
const normalizedHostButtons = $derived.by((): ToolbarItem[] =>
|
|
405
|
+
Array.isArray(hostButtons)
|
|
406
|
+
? hostButtons.filter((item): item is ToolbarItem => !!item && typeof item.id === 'string')
|
|
407
|
+
: []
|
|
408
|
+
);
|
|
409
|
+
const nativeToolbarItems = $derived.by((): ToolbarItem[] =>
|
|
410
|
+
renderedTools
|
|
411
|
+
.filter((renderedTool) => !!renderedTool.button)
|
|
412
|
+
.map((renderedTool) => {
|
|
413
|
+
const button = renderedTool.button!;
|
|
414
|
+
return {
|
|
415
|
+
id: renderedTool.toolId,
|
|
416
|
+
label: button.label,
|
|
417
|
+
ariaLabel: button.ariaLabel || button.label,
|
|
418
|
+
icon: button.icon,
|
|
419
|
+
tooltip: button.tooltip || button.label,
|
|
420
|
+
disabled: button.disabled,
|
|
421
|
+
active: activeToolState[renderedTool.toolId] ?? button.active ?? false,
|
|
422
|
+
onClick: () => {
|
|
423
|
+
button.onClick();
|
|
424
|
+
syncRenderedToolsState();
|
|
425
|
+
},
|
|
426
|
+
} satisfies ToolbarItem;
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
const toolbarItems = $derived.by((): ToolbarItem[] => [
|
|
430
|
+
...nativeToolbarItems,
|
|
431
|
+
...normalizedHostButtons
|
|
432
|
+
]);
|
|
433
|
+
const mountedElementsBeforeButtons = $derived.by(() =>
|
|
434
|
+
renderedTools.flatMap((renderedTool) =>
|
|
435
|
+
(renderedTool.elements || [])
|
|
436
|
+
.filter((entry) => entry.mount === 'before-buttons')
|
|
437
|
+
.map((entry) => entry.element)
|
|
438
|
+
.filter((entry): entry is HTMLElement => Boolean(entry))
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
const mountedElementsAfterButtons = $derived.by(() =>
|
|
442
|
+
renderedTools.flatMap((renderedTool) =>
|
|
443
|
+
(renderedTool.elements || [])
|
|
444
|
+
.filter((entry) => entry.mount === 'after-buttons')
|
|
445
|
+
.map((entry) => entry.element)
|
|
446
|
+
.filter((entry): entry is HTMLElement => Boolean(entry))
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
function isToolbarItemActive(item: ToolbarItem): boolean {
|
|
451
|
+
if (item.id in activeToolState) {
|
|
452
|
+
return activeToolState[item.id] === true;
|
|
453
|
+
}
|
|
454
|
+
return item.active === true;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getFallbackIconSvg(iconName: string): string | null {
|
|
458
|
+
const iconMap: Record<string, string> = {
|
|
459
|
+
calculator:
|
|
460
|
+
'<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>',
|
|
461
|
+
'volume-up':
|
|
462
|
+
'<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>',
|
|
463
|
+
swatch:
|
|
464
|
+
'<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>',
|
|
465
|
+
'chart-bar':
|
|
466
|
+
'<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>',
|
|
467
|
+
beaker:
|
|
468
|
+
'<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>',
|
|
469
|
+
protractor:
|
|
470
|
+
'<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>',
|
|
471
|
+
'bars-3':
|
|
472
|
+
'<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>',
|
|
473
|
+
ruler:
|
|
474
|
+
'<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>'
|
|
475
|
+
};
|
|
476
|
+
return iconMap[iconName] || null;
|
|
477
|
+
}
|
|
303
478
|
|
|
304
479
|
function syncRenderedToolsState() {
|
|
305
480
|
const nextActiveState: Record<string, boolean> = {};
|
|
@@ -370,41 +545,81 @@
|
|
|
370
545
|
{#if isBrowser}
|
|
371
546
|
<div
|
|
372
547
|
class="item-toolbar {className} item-toolbar--{size}"
|
|
548
|
+
class:item-toolbar--top={position === 'top'}
|
|
549
|
+
class:item-toolbar--right={position === 'right'}
|
|
550
|
+
class:item-toolbar--bottom={position === 'bottom'}
|
|
551
|
+
class:item-toolbar--left={position === 'left'}
|
|
373
552
|
data-content-kind={effectiveContentKind}
|
|
553
|
+
data-level={effectiveLevel}
|
|
374
554
|
bind:this={toolbarRootElement}
|
|
375
555
|
>
|
|
376
|
-
{#each
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
{/if}
|
|
556
|
+
{#each mountedElementsBeforeButtons as mountedElement (`before-${mountedElement.tagName}-${mountedElement.getAttribute('tool-id') || ''}`)}
|
|
557
|
+
<span class="item-toolbar__element-host" use:mountElement={mountedElement}></span>
|
|
558
|
+
{/each}
|
|
380
559
|
|
|
381
|
-
|
|
560
|
+
{#each toolbarItems as item (item.id)}
|
|
561
|
+
{#if isToolbarLinkItem(item)}
|
|
562
|
+
<a
|
|
563
|
+
class="item-toolbar__button"
|
|
564
|
+
class:item-toolbar__button--active={isToolbarItemActive(item)}
|
|
565
|
+
href={item.disabled ? undefined : item.href}
|
|
566
|
+
target={item.target}
|
|
567
|
+
rel={item.rel}
|
|
568
|
+
aria-label={item.ariaLabel || item.label}
|
|
569
|
+
title={item.tooltip || item.label}
|
|
570
|
+
aria-disabled={item.disabled ? 'true' : undefined}
|
|
571
|
+
onclick={(event) => {
|
|
572
|
+
if (item.disabled) {
|
|
573
|
+
event.preventDefault();
|
|
574
|
+
}
|
|
575
|
+
}}
|
|
576
|
+
>
|
|
577
|
+
{#if item.icon}
|
|
578
|
+
{#if isInlineSvgIcon(item.icon)}
|
|
579
|
+
{@html item.icon}
|
|
580
|
+
{:else if isExternalIconUrl(item.icon)}
|
|
581
|
+
<img class="item-toolbar__icon-image" src={item.icon} alt="" />
|
|
582
|
+
{:else}
|
|
583
|
+
{@const fallbackIcon = getFallbackIconSvg(item.icon)}
|
|
584
|
+
{#if fallbackIcon}
|
|
585
|
+
{@html fallbackIcon}
|
|
586
|
+
{:else}
|
|
587
|
+
<i class={`icon icon-${item.icon}`} aria-hidden="true"></i>
|
|
588
|
+
{/if}
|
|
589
|
+
{/if}
|
|
590
|
+
{/if}
|
|
591
|
+
</a>
|
|
592
|
+
{:else}
|
|
382
593
|
<button
|
|
383
594
|
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}
|
|
595
|
+
class="item-toolbar__button"
|
|
596
|
+
class:item-toolbar__button--active={isToolbarItemActive(item)}
|
|
597
|
+
onclick={item.onClick}
|
|
598
|
+
aria-label={item.ariaLabel || item.label}
|
|
599
|
+
aria-pressed={isToolbarItemActive(item)}
|
|
600
|
+
title={item.tooltip || item.label}
|
|
601
|
+
disabled={item.disabled}
|
|
394
602
|
>
|
|
395
|
-
{#if
|
|
396
|
-
{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
603
|
+
{#if item.icon}
|
|
604
|
+
{#if isInlineSvgIcon(item.icon)}
|
|
605
|
+
{@html item.icon}
|
|
606
|
+
{:else if isExternalIconUrl(item.icon)}
|
|
607
|
+
<img class="item-toolbar__icon-image" src={item.icon} alt="" />
|
|
608
|
+
{:else}
|
|
609
|
+
{@const fallbackIcon = getFallbackIconSvg(item.icon)}
|
|
610
|
+
{#if fallbackIcon}
|
|
611
|
+
{@html fallbackIcon}
|
|
612
|
+
{:else}
|
|
613
|
+
<i class={`icon icon-${item.icon}`} aria-hidden="true"></i>
|
|
614
|
+
{/if}
|
|
615
|
+
{/if}
|
|
401
616
|
{/if}
|
|
402
617
|
</button>
|
|
403
618
|
{/if}
|
|
619
|
+
{/each}
|
|
404
620
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{/if}
|
|
621
|
+
{#each mountedElementsAfterButtons as mountedElement (`after-${mountedElement.tagName}-${mountedElement.getAttribute('tool-id') || ''}`)}
|
|
622
|
+
<span class="item-toolbar__element-host" use:mountElement={mountedElement}></span>
|
|
408
623
|
{/each}
|
|
409
624
|
</div>
|
|
410
625
|
{/if}
|
|
@@ -416,6 +631,18 @@
|
|
|
416
631
|
gap: 0.5rem;
|
|
417
632
|
}
|
|
418
633
|
|
|
634
|
+
.item-toolbar--top,
|
|
635
|
+
.item-toolbar--bottom {
|
|
636
|
+
flex-direction: row;
|
|
637
|
+
flex-wrap: wrap;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.item-toolbar--left,
|
|
641
|
+
.item-toolbar--right {
|
|
642
|
+
flex-direction: column;
|
|
643
|
+
align-items: stretch;
|
|
644
|
+
}
|
|
645
|
+
|
|
419
646
|
.item-toolbar__button {
|
|
420
647
|
display: flex;
|
|
421
648
|
align-items: center;
|
|
@@ -429,6 +656,7 @@
|
|
|
429
656
|
color: var(--pie-text, #333);
|
|
430
657
|
cursor: pointer;
|
|
431
658
|
transition: all 0.15s ease;
|
|
659
|
+
text-decoration: none;
|
|
432
660
|
}
|
|
433
661
|
|
|
434
662
|
.item-toolbar__element-host {
|