@pie-players/pie-section-player 0.2.12 → 0.2.13

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.
Files changed (62) hide show
  1. package/README.md +28 -568
  2. package/dist/component-definitions.d.ts +0 -3
  3. package/dist/component-definitions.d.ts.map +1 -1
  4. package/dist/components/section-player-vertical-element.d.ts +2 -0
  5. package/dist/components/section-player-vertical-element.d.ts.map +1 -0
  6. package/dist/components/shared/composition.d.ts +9 -0
  7. package/dist/components/shared/composition.d.ts.map +1 -0
  8. package/dist/components/shared/player-action.d.ts +18 -0
  9. package/dist/components/shared/player-action.d.ts.map +1 -0
  10. package/dist/components/shared/player-preload.d.ts +37 -0
  11. package/dist/components/shared/player-preload.d.ts.map +1 -0
  12. package/dist/components/shared/section-player-runtime.d.ts +104 -0
  13. package/dist/components/shared/section-player-runtime.d.ts.map +1 -0
  14. package/dist/components/shared/section-player-view-state.d.ts +24 -0
  15. package/dist/components/shared/section-player-view-state.d.ts.map +1 -0
  16. package/dist/controllers/SectionContentService.d.ts.map +1 -1
  17. package/dist/controllers/SectionController.d.ts +5 -1
  18. package/dist/controllers/SectionController.d.ts.map +1 -1
  19. package/dist/controllers/SectionSessionService.d.ts +0 -1
  20. package/dist/controllers/SectionSessionService.d.ts.map +1 -1
  21. package/dist/controllers/toolkit-section-contracts.d.ts +2 -28
  22. package/dist/controllers/toolkit-section-contracts.d.ts.map +1 -1
  23. package/dist/controllers/types.d.ts +28 -1
  24. package/dist/controllers/types.d.ts.map +1 -1
  25. package/dist/pie-item-player-B1iGN63e.js +6189 -0
  26. package/dist/pie-section-player.d.ts +0 -8
  27. package/dist/pie-section-player.d.ts.map +1 -1
  28. package/dist/pie-section-player.js +56558 -11
  29. package/dist/player-preload-CQVG0Bih.js +705 -0
  30. package/dist/utils/player-preload.d.ts +2 -0
  31. package/dist/utils/player-preload.d.ts.map +1 -0
  32. package/dist/utils/player-preload.js +8 -0
  33. package/package.json +23 -32
  34. package/src/components/ItemShellElement.svelte +10 -1
  35. package/src/components/PieSectionPlayerBaseElement.svelte +21 -78
  36. package/src/components/PieSectionPlayerSplitPaneElement.svelte +236 -295
  37. package/src/components/PieSectionPlayerVerticalElement.svelte +424 -0
  38. package/src/components/shared/SectionItemCard.svelte +92 -0
  39. package/src/components/shared/SectionPassageCard.svelte +88 -0
  40. package/dist/ItemRenderer-MsjF_Beu.js +0 -467
  41. package/dist/PieItemModeLayoutElement-D7oTzA9T.js +0 -316
  42. package/dist/PieSplitPanelLayoutElement-GUtJ_NlF.js +0 -246
  43. package/dist/PieVerticalLayoutElement-BoA3FO5g.js +0 -194
  44. package/dist/controllers/SectionToolkitService.d.ts +0 -24
  45. package/dist/controllers/SectionToolkitService.d.ts.map +0 -1
  46. package/dist/controllers/SessionPersistenceStrategy.d.ts +0 -15
  47. package/dist/controllers/SessionPersistenceStrategy.d.ts.map +0 -1
  48. package/dist/index.d.ts +0 -2
  49. package/dist/pie-section-player-DJ5NcwdT.js +0 -17078
  50. package/dist/runtime/runtime-event-guards.d.ts +0 -4
  51. package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
  52. package/src/PieSectionPlayer.svelte +0 -826
  53. package/src/components/ItemModeLayout.svelte +0 -172
  54. package/src/components/ItemNavigation.svelte +0 -96
  55. package/src/components/ItemPlayerBridge.svelte +0 -110
  56. package/src/components/ItemRenderer.svelte +0 -248
  57. package/src/components/ItemShell.svelte +0 -86
  58. package/src/components/layout-elements/PieItemModeLayoutElement.svelte +0 -47
  59. package/src/components/layout-elements/PieSplitPanelLayoutElement.svelte +0 -62
  60. package/src/components/layout-elements/PieVerticalLayoutElement.svelte +0 -41
  61. package/src/components/layouts/SplitPanelLayout.svelte +0 -385
  62. package/src/components/layouts/VerticalLayout.svelte +0 -193
@@ -1,172 +0,0 @@
1
- <!--
2
- ItemModeLayout - Internal Component
3
-
4
- Renders item mode (keepTogether: false) - one item at a time with navigation.
5
- Not exposed as a web component - used internally in PieSectionPlayer.
6
- -->
7
- <script lang="ts">
8
- import type { SectionCompositionModel } from '../controllers/types.js';
9
- import ItemNavigation from './ItemNavigation.svelte';
10
- import ItemRenderer from './ItemRenderer.svelte';
11
-
12
- let {
13
- composition,
14
- env = { mode: 'gather', role: 'student' },
15
- toolbarPosition = 'right',
16
- showToolbar = true,
17
- onprevious,
18
- onnext
19
- }: {
20
- composition: SectionCompositionModel;
21
- env?: { mode: 'gather' | 'view' | 'evaluate' | 'author'; role: 'student' | 'instructor' };
22
- toolbarPosition?: 'top' | 'right' | 'bottom' | 'left' | 'none';
23
- showToolbar?: boolean;
24
- onprevious?: () => void;
25
- onnext?: () => void;
26
- } = $props();
27
-
28
- let passages = $derived(composition?.passages || []);
29
- let items = $derived(composition?.items || []);
30
- let currentIndex = $derived(composition?.currentItemIndex || 0);
31
- let totalItems = $derived(items.length);
32
- let currentItem = $derived(composition?.currentItem || items[currentIndex] || null);
33
- let itemSessionsByItemId = $derived(composition?.itemSessionsByItemId || {});
34
- let itemSession = $derived(currentItem?.id ? itemSessionsByItemId[currentItem.id] : undefined);
35
- let canPrevious = $derived(currentIndex > 0);
36
- let canNext = $derived(currentIndex < totalItems - 1);
37
- let shouldRenderToolbar = $derived(showToolbar && toolbarPosition !== 'none');
38
- let isToolbarBeforeContent = $derived(
39
- toolbarPosition === 'top' || toolbarPosition === 'left'
40
- );
41
- </script>
42
-
43
- <div class={`pie-section-player__layout-shell pie-section-player__layout-shell--${toolbarPosition}`}>
44
- {#if shouldRenderToolbar && isToolbarBeforeContent}
45
- <pie-section-tools-toolbar
46
- position={toolbarPosition}
47
- enabled-tools=""
48
- ></pie-section-tools-toolbar>
49
- {/if}
50
- <div class="pie-section-player__item-mode-layout">
51
- <!-- Passages (visible for all items) -->
52
- {#if passages.length > 0}
53
- <div class="pie-section-player__passages-section">
54
- {#each passages as passage (passage.id)}
55
- <div class="pie-section-player__passage-wrapper">
56
- <ItemRenderer
57
- item={passage}
58
- contentKind="rubric-block-stimulus"
59
- env={{ mode: 'view', role: env.role }}
60
- customClassName="pie-section-player__passage-item"
61
- />
62
- </div>
63
- {/each}
64
- </div>
65
- {/if}
66
-
67
- <!-- Current Item -->
68
- {#if currentItem}
69
- <div class="pie-section-player__current-item-section">
70
- <ItemRenderer
71
- item={currentItem}
72
- contentKind="assessment-item"
73
- {env}
74
- session={itemSession}
75
- customClassName="pie-section-player__item-content"
76
- />
77
- </div>
78
- {:else}
79
- <div class="pie-section-player__no-item">
80
- <p>No item to display</p>
81
- </div>
82
- {/if}
83
-
84
- <!-- Navigation -->
85
- <ItemNavigation
86
- {currentIndex}
87
- {totalItems}
88
- {canNext}
89
- {canPrevious}
90
- {onprevious}
91
- {onnext}
92
- />
93
- </div>
94
- {#if shouldRenderToolbar && !isToolbarBeforeContent}
95
- <pie-section-tools-toolbar
96
- position={toolbarPosition}
97
- enabled-tools=""
98
- ></pie-section-tools-toolbar>
99
- {/if}
100
- </div>
101
-
102
- <style>
103
- .pie-section-player__layout-shell {
104
- display: flex;
105
- width: 100%;
106
- height: 100%;
107
- min-height: 0;
108
- overflow: hidden;
109
- }
110
-
111
- .pie-section-player__layout-shell--top,
112
- .pie-section-player__layout-shell--bottom,
113
- .pie-section-player__layout-shell--none {
114
- flex-direction: column;
115
- }
116
-
117
- .pie-section-player__layout-shell--left,
118
- .pie-section-player__layout-shell--right {
119
- flex-direction: row;
120
- }
121
-
122
- .pie-section-player__item-mode-layout {
123
- flex: 1;
124
- display: flex;
125
- flex-direction: column;
126
- gap: 1.5rem;
127
- padding: 1rem;
128
- overflow-y: auto;
129
- }
130
-
131
- .pie-section-player__passages-section {
132
- display: flex;
133
- flex-direction: column;
134
- gap: 1rem;
135
- }
136
-
137
- .pie-section-player__passages-section :global(.pie-section-player__passage-item) {
138
- padding: 0;
139
- background: transparent;
140
- border: 0;
141
- border-radius: 0;
142
- }
143
-
144
- .pie-section-player__passage-wrapper {
145
- flex-shrink: 0;
146
- padding: 0.25rem;
147
- background: var(--pie-white, white);
148
- border: 1px solid var(--pie-border-light, #e5e7eb);
149
- border-radius: 6px;
150
- }
151
-
152
- .pie-section-player__current-item-section {
153
- padding: 0.25rem;
154
- background: var(--pie-white, white);
155
- border: 1px solid var(--pie-border-light, #e5e7eb);
156
- border-radius: 6px;
157
- min-height: 300px;
158
- }
159
-
160
- .pie-section-player__no-item {
161
- padding: 2rem;
162
- text-align: center;
163
- color: var(--pie-disabled-secondary, #999);
164
- }
165
-
166
- /* Responsive */
167
- @media (max-width: 768px) {
168
- .pie-section-player__item-mode-layout {
169
- gap: 1rem;
170
- }
171
- }
172
- </style>
@@ -1,96 +0,0 @@
1
- <!--
2
- ItemNavigation - Internal Component
3
-
4
- Navigation controls for item mode (keepTogether: false).
5
- Not exposed as a web component - used internally in PieSectionPlayer.
6
- -->
7
- <script lang="ts">
8
- let {
9
- currentIndex,
10
- totalItems,
11
- canNext,
12
- canPrevious,
13
- onprevious,
14
- onnext
15
- }: {
16
- currentIndex: number;
17
- totalItems: number;
18
- canNext: boolean;
19
- canPrevious: boolean;
20
- onprevious?: () => void;
21
- onnext?: () => void;
22
- } = $props();
23
- </script>
24
-
25
- <div class="pie-section-player__item-navigation">
26
- <button
27
- type="button"
28
- class="pie-section-player__nav-button pie-section-player__nav-button--previous"
29
- disabled={!canPrevious}
30
- onclick={onprevious}
31
- >
32
- Previous
33
- </button>
34
- <span class="pie-section-player__item-counter">
35
- {currentIndex + 1} / {totalItems}
36
- </span>
37
- <button
38
- type="button"
39
- class="pie-section-player__nav-button pie-section-player__nav-button--next"
40
- disabled={!canNext}
41
- onclick={onnext}
42
- >
43
- Next
44
- </button>
45
- </div>
46
-
47
- <style>
48
- .pie-section-player__item-navigation {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- padding: 1rem;
53
- background: var(--pie-secondary-background, #f9f9f9);
54
- border-top: 1px solid var(--pie-border-light, #e0e0e0);
55
- gap: 1rem;
56
- }
57
-
58
- .pie-section-player__nav-button {
59
- padding: 0.5rem 1rem;
60
- background: var(--pie-primary, #007bff);
61
- color: var(--pie-white, white);
62
- border: none;
63
- border-radius: 4px;
64
- cursor: pointer;
65
- font-size: 0.9rem;
66
- transition: background 0.2s;
67
- min-width: 100px;
68
- }
69
-
70
- .pie-section-player__nav-button:hover:not(:disabled) {
71
- background: var(--pie-primary-dark, #0056b3);
72
- }
73
-
74
- .pie-section-player__nav-button:disabled {
75
- background: var(--pie-disabled-secondary, #ccc);
76
- cursor: not-allowed;
77
- opacity: 0.6;
78
- }
79
-
80
- .pie-section-player__item-counter {
81
- font-size: 0.9rem;
82
- color: var(--pie-disabled, #666);
83
- font-weight: 500;
84
- }
85
-
86
- /* Responsive */
87
- @media (max-width: 768px) {
88
- .pie-section-player__item-navigation {
89
- flex-direction: column;
90
- }
91
-
92
- .pie-section-player__nav-button {
93
- width: 100%;
94
- }
95
- }
96
- </style>
@@ -1,110 +0,0 @@
1
- <script lang="ts">
2
- import type { ComponentDefinition } from "../component-definitions.js";
3
- import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
4
- import { onMount, untrack } from "svelte";
5
-
6
- let {
7
- item,
8
- env,
9
- session,
10
- hasElements,
11
- resolvedPlayerTag,
12
- resolvedPlayerDefinition,
13
- skipElementLoading = true,
14
- onsessionchanged,
15
- }: {
16
- item: ItemEntity | PassageEntity;
17
- env: {
18
- mode: "gather" | "view" | "evaluate" | "author";
19
- role: "student" | "instructor";
20
- };
21
- session: any;
22
- hasElements: boolean;
23
- resolvedPlayerTag: string;
24
- resolvedPlayerDefinition?: ComponentDefinition;
25
- skipElementLoading?: boolean;
26
- onsessionchanged?: (event: CustomEvent) => void;
27
- } = $props();
28
-
29
- let playerElement: any = $state(null);
30
- let lastConfig: any = null;
31
- let lastEnv: any = null;
32
-
33
- onMount(() => {
34
- (async () => {
35
- if (hasElements) {
36
- await resolvedPlayerDefinition?.ensureDefined?.();
37
- }
38
- })();
39
- });
40
-
41
- $effect(() => {
42
- const currentConfig = item.config;
43
- const currentEnv = env;
44
- const currentSession = session;
45
-
46
- if (!playerElement || !currentConfig || !hasElements) return;
47
-
48
- const envChanged =
49
- !lastEnv ||
50
- lastEnv.mode !== currentEnv.mode ||
51
- lastEnv.role !== currentEnv.role;
52
- if (currentConfig === lastConfig && !envChanged) return;
53
-
54
- untrack(() => {
55
- playerElement.config = currentConfig;
56
- playerElement.session = currentSession;
57
- playerElement.env = currentEnv;
58
- if (resolvedPlayerDefinition?.attributes) {
59
- for (const [name, value] of Object.entries(
60
- resolvedPlayerDefinition.attributes,
61
- )) {
62
- playerElement.setAttribute(name, value);
63
- }
64
- }
65
- if (resolvedPlayerDefinition?.props) {
66
- for (const [name, value] of Object.entries(resolvedPlayerDefinition.props)) {
67
- (playerElement as any)[name] = value;
68
- }
69
- }
70
- if (skipElementLoading) {
71
- playerElement.setAttribute("skip-element-loading", "true");
72
- (playerElement as any).skipElementLoading = true;
73
- }
74
- });
75
-
76
- lastConfig = currentConfig;
77
- lastEnv = currentEnv;
78
- });
79
-
80
- $effect(() => {
81
- if (!playerElement || !onsessionchanged) return;
82
-
83
- const handler = (event: Event) => {
84
- const customEvent = event as CustomEvent;
85
- console.debug('[ItemPlayerBridge][SessionTrace] session-changed received', {
86
- itemId: item?.id || null,
87
- envMode: env?.mode,
88
- envRole: env?.role,
89
- detailKeys:
90
- customEvent?.detail && typeof customEvent.detail === 'object'
91
- ? Object.keys(customEvent.detail)
92
- : []
93
- });
94
- onsessionchanged(event as CustomEvent);
95
- };
96
-
97
- playerElement.addEventListener("session-changed", handler);
98
- return () => {
99
- playerElement.removeEventListener("session-changed", handler);
100
- };
101
- });
102
- </script>
103
-
104
- {#if hasElements}
105
- {#key resolvedPlayerTag}
106
- <svelte:element this={resolvedPlayerTag} bind:this={playerElement}></svelte:element>
107
- {/key}
108
- {:else}
109
- {@html item.config.markup}
110
- {/if}
@@ -1,248 +0,0 @@
1
- <!--
2
- ItemRenderer - Internal Component
3
-
4
- Renders a single item using the IIFE player.
5
- Handles SSML extraction, TTS service binding, and player lifecycle.
6
- -->
7
- <script lang="ts">
8
- import {
9
- SSMLExtractor,
10
- assessmentToolkitRuntimeContext,
11
- createScopedToolId,
12
- type AssessmentToolkitRuntimeContext,
13
- } from "@pie-players/pie-assessment-toolkit";
14
- import { ContextConsumer } from "@pie-players/pie-context";
15
- import "@pie-players/pie-assessment-toolkit/components/item-toolbar-element";
16
- import {
17
- DEFAULT_PLAYER_DEFINITIONS,
18
- } from "../component-definitions.js";
19
- import ItemPlayerBridge from "./ItemPlayerBridge.svelte";
20
- import ItemShell, { type QtiContentKind } from "./ItemShell.svelte";
21
- import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
22
- import { onMount, untrack } from "svelte";
23
-
24
- type SectionPlayerRuntimeContext = AssessmentToolkitRuntimeContext & {
25
- reportSessionChanged?: (itemId: string, detail: unknown) => void;
26
- };
27
-
28
- let {
29
- item,
30
- env = { mode: "gather", role: "student" },
31
- session = { id: "", data: [] },
32
- contentKind = "assessment-item" as QtiContentKind,
33
- skipElementLoading = true,
34
- customClassName = "",
35
- onsessionchanged,
36
- }: {
37
- item: ItemEntity | PassageEntity;
38
- env?: {
39
- mode: "gather" | "view" | "evaluate" | "author";
40
- role: "student" | "instructor";
41
- };
42
- session?: any;
43
- contentKind?: QtiContentKind;
44
- skipElementLoading?: boolean;
45
- playerVersion?: string;
46
- customClassName?: string;
47
- onsessionchanged?: (event: CustomEvent) => void;
48
- } = $props();
49
-
50
- function handlePlayerSessionChanged(event: CustomEvent) {
51
- console.debug("[ItemRenderer][SessionTrace] handlePlayerSessionChanged", {
52
- itemId: item?.id || null,
53
- contentKind,
54
- hasRuntimeReporter: !!runtimeContext?.reportSessionChanged,
55
- hasOnSessionChangedProp: !!onsessionchanged,
56
- detailKeys:
57
- event?.detail && typeof event.detail === "object"
58
- ? Object.keys(event.detail)
59
- : [],
60
- });
61
- // Section item sessions are reported through runtime context to avoid
62
- // callback prop-drilling across internal layout components.
63
- if (contentKind === "assessment-item") {
64
- const itemId = item.id || "";
65
- if (itemId && runtimeContext?.reportSessionChanged) {
66
- console.debug("[ItemRenderer][SessionTrace] forwarding via runtimeContext", {
67
- itemId,
68
- });
69
- event.stopPropagation();
70
- runtimeContext.reportSessionChanged(itemId, event.detail);
71
- return;
72
- }
73
- }
74
- if (onsessionchanged) {
75
- console.debug("[ItemRenderer][SessionTrace] forwarding via onsessionchanged prop", {
76
- itemId: item?.id || null,
77
- });
78
- event.stopPropagation();
79
- onsessionchanged(event);
80
- }
81
- }
82
-
83
- // Get the DOM element reference for service binding
84
- let contextHostElement: HTMLElement | null = $state(null);
85
- let itemContentElement: HTMLElement | null = $state(null);
86
- let itemToolbarElement: HTMLElement | null = $state(null);
87
- let runtimeContext = $state<SectionPlayerRuntimeContext | null>(null);
88
- let runtimeContextConsumer: ContextConsumer<
89
- typeof assessmentToolkitRuntimeContext
90
- > | null = null;
91
-
92
- // Runtime dependencies come from assessment toolkit context.
93
- const effectiveToolkitCoordinator = $derived(runtimeContext?.toolkitCoordinator);
94
- const toolCoordinator = $derived(
95
- effectiveToolkitCoordinator?.toolCoordinator,
96
- );
97
- const catalogResolver = $derived(
98
- effectiveToolkitCoordinator?.catalogResolver,
99
- );
100
-
101
- // Consume runtime context from the section-player provider tree.
102
- $effect(() => {
103
- if (!contextHostElement) return;
104
- runtimeContextConsumer = new ContextConsumer(contextHostElement, {
105
- context: assessmentToolkitRuntimeContext,
106
- subscribe: true,
107
- onValue: (value: AssessmentToolkitRuntimeContext) => {
108
- runtimeContext = value as SectionPlayerRuntimeContext;
109
- },
110
- });
111
- runtimeContextConsumer.connect();
112
- return () => {
113
- runtimeContextConsumer?.disconnect();
114
- runtimeContextConsumer = null;
115
- };
116
- });
117
-
118
- let calculatorVisible = $state(false);
119
-
120
- let hasElements = $derived(
121
- !!(item?.config?.elements && Object.keys(item.config.elements).length > 0),
122
- );
123
-
124
- let resolvedPlayerDefinition = $derived.by(
125
- () => DEFAULT_PLAYER_DEFINITIONS["iife"],
126
- );
127
- let resolvedPlayerTag = $derived(resolvedPlayerDefinition?.tagName || "pie-iife-player");
128
-
129
- onMount(() => {
130
- // Cleanup: clear this item's catalogs on unmount.
131
- return () => {
132
- if (catalogResolver) {
133
- catalogResolver.clearItemCatalogs();
134
- }
135
- };
136
- });
137
-
138
- // Extract SSML from item config when item changes
139
- $effect(() => {
140
- if (item?.config && catalogResolver) {
141
- // Skip if already extracted
142
- if (item.config.extractedCatalogs) {
143
- catalogResolver.clearItemCatalogs();
144
- catalogResolver.addItemCatalogs(item.config.extractedCatalogs);
145
- return;
146
- }
147
-
148
- const extractor = new SSMLExtractor();
149
- const result = extractor.extractFromItemConfig(item.config);
150
-
151
- // Update config with cleaned content (SSML removed, catalog IDs added)
152
- untrack(() => {
153
- item.config = result.cleanedConfig;
154
- item.config.extractedCatalogs = result.catalogs;
155
- });
156
-
157
- // Register catalogs with resolver for TTS lookup
158
- if (result.catalogs.length > 0) {
159
- catalogResolver.clearItemCatalogs(); // Clear previous item's catalogs
160
- catalogResolver.addItemCatalogs(result.catalogs);
161
- console.debug(
162
- `[ItemRenderer] Extracted ${result.catalogs.length} SSML catalogs for item ${item.id}`,
163
- );
164
- }
165
- }
166
- });
167
-
168
- // Bind direct item contracts to item toolbar.
169
- $effect(() => {
170
- if (!itemToolbarElement) return;
171
- if (itemContentElement) {
172
- (itemToolbarElement as any).scopeElement = itemContentElement;
173
- }
174
- if (item) {
175
- (itemToolbarElement as any).item = item;
176
- }
177
- });
178
-
179
-
180
- // Subscribe to calculator visibility changes
181
- $effect(() => {
182
- if (!toolCoordinator || !item) return;
183
- const scopedCalculatorId = createScopedToolId(
184
- "calculator",
185
- "item",
186
- item.id || "unknown-item",
187
- );
188
-
189
- const unsubscribe = toolCoordinator.subscribe(() => {
190
- calculatorVisible = toolCoordinator.isToolVisible(scopedCalculatorId);
191
- });
192
-
193
- // Initial update
194
- calculatorVisible = toolCoordinator.isToolVisible(scopedCalculatorId);
195
-
196
- return unsubscribe;
197
- });
198
- </script>
199
-
200
- <div bind:this={contextHostElement} data-item-id={item.id || ""}>
201
- {#if item.config}
202
- <ItemShell
203
- {item}
204
- {contentKind}
205
- {customClassName}
206
- >
207
- <pie-item-toolbar
208
- slot="toolbar"
209
- bind:this={itemToolbarElement}
210
- item-id={item.id}
211
- catalog-id={item.id}
212
- tools="calculator,tts,answerEliminator"
213
- content-kind={contentKind}
214
- size="md"
215
- language="en-US"
216
- ></pie-item-toolbar>
217
-
218
- <div class="pie-section-player__item-content" bind:this={itemContentElement}>
219
- <ItemPlayerBridge
220
- {item}
221
- {env}
222
- {session}
223
- {hasElements}
224
- resolvedPlayerTag={resolvedPlayerTag}
225
- resolvedPlayerDefinition={resolvedPlayerDefinition}
226
- {skipElementLoading}
227
- onsessionchanged={handlePlayerSessionChanged}
228
- />
229
- </div>
230
- </ItemShell>
231
-
232
- <!-- Calculator Tool Instance (rendered outside panel for floating overlay) -->
233
- {#if item}
234
- <pie-tool-calculator
235
- visible={calculatorVisible}
236
- tool-id={createScopedToolId("calculator", "item", item.id || "unknown-item")}
237
- ></pie-tool-calculator>
238
- {/if}
239
- {/if}
240
- </div>
241
-
242
- <style>
243
- .pie-section-player__item-content {
244
- padding: 1rem;
245
- border: 1px solid var(--pie-border-light, #e5e7eb);
246
- border-radius: 4px;
247
- }
248
- </style>
@@ -1,86 +0,0 @@
1
- <script lang="ts">
2
- import {
3
- assessmentToolkitRuntimeContext,
4
- type AssessmentToolkitRuntimeContext,
5
- } from "@pie-players/pie-assessment-toolkit";
6
- import { ContextConsumer } from "@pie-players/pie-context";
7
- import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
8
-
9
- export type QtiContentKind =
10
- | "assessment-item"
11
- | "rubric-block-stimulus"
12
- | "rubric-block-instructions"
13
- | "rubric-block-rubric";
14
-
15
- let {
16
- item,
17
- contentKind = "assessment-item" as QtiContentKind,
18
- customClassName = "",
19
- }: {
20
- item: ItemEntity | PassageEntity;
21
- contentKind?: QtiContentKind;
22
- customClassName?: string;
23
- } = $props();
24
-
25
- let contextHostElement = $state<HTMLElement | null>(null);
26
- let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
27
- let runtimeContextConsumer: ContextConsumer<
28
- typeof assessmentToolkitRuntimeContext
29
- > | null = null;
30
-
31
- const effectiveAssessmentId = $derived(runtimeContext?.assessmentId ?? "");
32
- const effectiveSectionId = $derived(runtimeContext?.sectionId ?? "");
33
-
34
- $effect(() => {
35
- if (!contextHostElement) return;
36
- runtimeContextConsumer = new ContextConsumer(contextHostElement, {
37
- context: assessmentToolkitRuntimeContext,
38
- subscribe: true,
39
- onValue: (value: AssessmentToolkitRuntimeContext) => {
40
- runtimeContext = value;
41
- },
42
- });
43
- runtimeContextConsumer.connect();
44
- return () => {
45
- runtimeContextConsumer?.disconnect();
46
- runtimeContextConsumer = null;
47
- };
48
- });
49
- </script>
50
-
51
- <div
52
- bind:this={contextHostElement}
53
- class="pie-section-player__item-renderer {customClassName}"
54
- data-assessment-id={effectiveAssessmentId}
55
- data-section-id={effectiveSectionId}
56
- data-item-id={item.id}
57
- data-content-kind={contentKind}
58
- >
59
- <div class="pie-section-player__item-header">
60
- <h4 class="pie-section-player__item-title">{item.name || "Question"}</h4>
61
- <slot name="toolbar"></slot>
62
- </div>
63
- <slot></slot>
64
- </div>
65
-
66
- <style>
67
- .pie-section-player__item-renderer {
68
- display: block;
69
- margin-bottom: 0;
70
- }
71
-
72
- .pie-section-player__item-header {
73
- display: flex;
74
- align-items: center;
75
- justify-content: space-between;
76
- padding: 0.75rem 0;
77
- margin-bottom: 0.5rem;
78
- }
79
-
80
- .pie-section-player__item-title {
81
- margin: 0;
82
- font-size: 0.95rem;
83
- font-weight: 600;
84
- color: var(--pie-primary, #1976d2);
85
- }
86
- </style>