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

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 (64) 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 +3 -0
  17. package/dist/controllers/SectionContentService.d.ts.map +1 -1
  18. package/dist/controllers/SectionController.d.ts +55 -1
  19. package/dist/controllers/SectionController.d.ts.map +1 -1
  20. package/dist/controllers/SectionSessionService.d.ts +0 -1
  21. package/dist/controllers/SectionSessionService.d.ts.map +1 -1
  22. package/dist/controllers/toolkit-section-contracts.d.ts +2 -28
  23. package/dist/controllers/toolkit-section-contracts.d.ts.map +1 -1
  24. package/dist/controllers/types.d.ts +97 -6
  25. package/dist/controllers/types.d.ts.map +1 -1
  26. package/dist/pie-item-player-q4jcP2lZ.js +6196 -0
  27. package/dist/pie-section-player.d.ts +0 -8
  28. package/dist/pie-section-player.d.ts.map +1 -1
  29. package/dist/pie-section-player.js +61631 -11
  30. package/dist/player-preload-CQVG0Bih.js +705 -0
  31. package/dist/utils/player-preload.d.ts +2 -0
  32. package/dist/utils/player-preload.d.ts.map +1 -0
  33. package/dist/utils/player-preload.js +8 -0
  34. package/package.json +24 -32
  35. package/src/components/ItemShellElement.svelte +119 -3
  36. package/src/components/PassageShellElement.svelte +49 -0
  37. package/src/components/PieSectionPlayerBaseElement.svelte +65 -78
  38. package/src/components/PieSectionPlayerSplitPaneElement.svelte +337 -296
  39. package/src/components/PieSectionPlayerVerticalElement.svelte +446 -0
  40. package/src/components/shared/SectionItemCard.svelte +92 -0
  41. package/src/components/shared/SectionPassageCard.svelte +88 -0
  42. package/dist/ItemRenderer-MsjF_Beu.js +0 -467
  43. package/dist/PieItemModeLayoutElement-D7oTzA9T.js +0 -316
  44. package/dist/PieSplitPanelLayoutElement-GUtJ_NlF.js +0 -246
  45. package/dist/PieVerticalLayoutElement-BoA3FO5g.js +0 -194
  46. package/dist/controllers/SectionToolkitService.d.ts +0 -24
  47. package/dist/controllers/SectionToolkitService.d.ts.map +0 -1
  48. package/dist/controllers/SessionPersistenceStrategy.d.ts +0 -15
  49. package/dist/controllers/SessionPersistenceStrategy.d.ts.map +0 -1
  50. package/dist/index.d.ts +0 -2
  51. package/dist/pie-section-player-DJ5NcwdT.js +0 -17078
  52. package/dist/runtime/runtime-event-guards.d.ts +0 -4
  53. package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
  54. package/src/PieSectionPlayer.svelte +0 -826
  55. package/src/components/ItemModeLayout.svelte +0 -172
  56. package/src/components/ItemNavigation.svelte +0 -96
  57. package/src/components/ItemPlayerBridge.svelte +0 -110
  58. package/src/components/ItemRenderer.svelte +0 -248
  59. package/src/components/ItemShell.svelte +0 -86
  60. package/src/components/layout-elements/PieItemModeLayoutElement.svelte +0 -47
  61. package/src/components/layout-elements/PieSplitPanelLayoutElement.svelte +0 -62
  62. package/src/components/layout-elements/PieVerticalLayoutElement.svelte +0 -41
  63. package/src/components/layouts/SplitPanelLayout.svelte +0 -385
  64. package/src/components/layouts/VerticalLayout.svelte +0 -193
@@ -9,7 +9,6 @@
9
9
  section: { type: "Object", reflect: false },
10
10
  sectionId: { attribute: "section-id", type: "String" },
11
11
  attemptId: { attribute: "attempt-id", type: "String" },
12
- view: { type: "String" },
13
12
  playerType: { attribute: "player-type", type: "String" },
14
13
  player: { type: "Object", reflect: false },
15
14
  lazyInit: { attribute: "lazy-init", type: "Boolean" },
@@ -20,7 +19,7 @@
20
19
  isolation: { attribute: "isolation", type: "String" },
21
20
  env: { type: "Object", reflect: false },
22
21
  iifeBundleHost: { attribute: "iife-bundle-host", type: "String" },
23
- showToolbar: { attribute: "show-toolbar", type: "Boolean" },
22
+ showToolbar: { attribute: "show-toolbar", type: "String" },
24
23
  toolbarPosition: { attribute: "toolbar-position", type: "String" },
25
24
  enabledTools: { attribute: "enabled-tools", type: "String" },
26
25
  itemToolbarTools: { attribute: "item-toolbar-tools", type: "String" },
@@ -30,135 +29,157 @@
30
29
  />
31
30
 
32
31
  <script lang="ts">
32
+ import { onMount } from "svelte";
33
33
  import "./section-player-base-element.js";
34
- import "./item-shell-element.js";
35
- import "./passage-shell-element.js";
36
- import "@pie-players/pie-iife-player";
37
- import "@pie-players/pie-section-tools-toolbar";
38
- import "@pie-players/pie-assessment-toolkit/components/item-toolbar-element";
39
- import "@pie-players/pie-tool-calculator-inline";
34
+ import * as SectionItemCardModule from "./shared/SectionItemCard.svelte";
35
+ import * as SectionPassageCardModule from "./shared/SectionPassageCard.svelte";
36
+ import "@pie-players/pie-toolbars/components/section-toolbar-element";
40
37
  import "@pie-players/pie-tool-calculator";
41
- import {
42
- normalizeToolsConfig,
43
- parseToolList,
44
- } from "@pie-players/pie-assessment-toolkit";
45
- import { IifeElementLoader } from "@pie-players/pie-players-shared";
38
+ import "@pie-players/pie-tool-graph";
39
+ import "@pie-players/pie-tool-periodic-table";
40
+ import "@pie-players/pie-tool-protractor";
41
+ import "@pie-players/pie-tool-line-reader";
42
+ import "@pie-players/pie-tool-ruler";
43
+ import "@pie-players/pie-tool-theme";
44
+ import type { Component } from "svelte";
46
45
  import type { SectionCompositionModel } from "../controllers/types.js";
47
- import type { AssessmentSection, ItemEntity } from "@pie-players/pie-players-shared/types";
48
-
49
- const EMPTY_COMPOSITION: SectionCompositionModel = {
50
- section: null,
51
- assessmentItemRefs: [],
52
- passages: [],
53
- items: [],
54
- rubricBlocks: [],
55
- instructions: [],
56
- currentItemIndex: 0,
57
- currentItem: null,
58
- isPageMode: false,
59
- itemSessionsByItemId: {},
60
- testAttemptSession: null,
61
- };
62
- const DEFAULT_ASSESSMENT_ID = "section-demo-direct";
63
- const DEFAULT_PLAYER_TYPE = "iife";
64
- const DEFAULT_LAZY_INIT = true;
65
- const DEFAULT_ISOLATION = "inherit";
66
- const LEGACY_RUNTIME_WARNING_KEY = "pie-section-player-splitpane:legacy-runtime-props";
67
- const warnedKeys = new Set<string>();
68
- type RuntimeConfig = {
69
- assessmentId?: string;
70
- playerType?: string;
71
- player?: Record<string, unknown> | null;
72
- lazyInit?: boolean;
73
- tools?: Record<string, unknown> | null;
74
- accessibility?: Record<string, unknown> | null;
75
- coordinator?: unknown;
76
- createSectionController?: unknown;
77
- isolation?: string;
78
- env?: Record<string, unknown>;
79
- };
46
+ import type { AssessmentSection } from "@pie-players/pie-players-shared/types";
47
+ import {
48
+ EMPTY_COMPOSITION,
49
+ } from "./shared/composition.js";
50
+ import {
51
+ createPlayerAction,
52
+ } from "./shared/player-action.js";
53
+ import {
54
+ getRenderablesSignature,
55
+ orchestratePlayerElementPreload,
56
+ type PlayerPreloadState,
57
+ } from "./shared/player-preload.js";
58
+ import {
59
+ getCanonicalItemId,
60
+ getCompositionFromEvent,
61
+ getItemPlayerParams,
62
+ getPassagePlayerParams,
63
+ } from "./shared/section-player-view-state.js";
64
+ import {
65
+ type RuntimeConfig,
66
+ mapRenderablesToItems,
67
+ resolveSectionPlayerRuntimeState,
68
+ } from "./shared/section-player-runtime.js";
69
+
70
+ const SectionItemCard = (
71
+ SectionItemCardModule as unknown as { default: Component<any, any, any> }
72
+ ).default;
73
+ const SectionPassageCard = (
74
+ SectionPassageCardModule as unknown as {
75
+ default: Component<any, any, any>;
76
+ }
77
+ ).default;
80
78
 
81
79
  let {
82
- assessmentId = DEFAULT_ASSESSMENT_ID,
80
+ assessmentId,
83
81
  runtime = null as RuntimeConfig | null,
84
82
  section = null as AssessmentSection | null,
85
83
  sectionId = "",
86
84
  attemptId = "",
87
- view = "candidate",
88
- playerType = DEFAULT_PLAYER_TYPE,
89
- player = null as Record<string, unknown> | null,
90
- lazyInit = DEFAULT_LAZY_INIT,
91
- tools = null as Record<string, unknown> | null,
92
- accessibility = null as Record<string, unknown> | null,
93
- coordinator = null as unknown,
94
- createSectionController = null as unknown,
95
- isolation = DEFAULT_ISOLATION,
96
- env = { mode: "gather", role: "student" } as Record<string, unknown>,
97
- iifeBundleHost = "https://proxy.pie-api.com/bundles",
98
- showToolbar = true,
85
+ playerType,
86
+ player,
87
+ lazyInit,
88
+ tools,
89
+ accessibility,
90
+ coordinator,
91
+ createSectionController,
92
+ isolation,
93
+ env,
94
+ iifeBundleHost,
95
+ showToolbar = "true" as boolean | string | null | undefined,
99
96
  toolbarPosition = "right",
100
97
  enabledTools = "",
101
98
  itemToolbarTools = "",
102
99
  passageToolbarTools = "",
103
100
  } = $props();
104
101
 
102
+ function resolveToolbarVisibility(value: boolean | string | null | undefined): boolean {
103
+ if (typeof value === "boolean") {
104
+ return value;
105
+ }
106
+ if (value === null || value === undefined) {
107
+ return true;
108
+ }
109
+ const normalizedValue = String(value).trim().toLowerCase();
110
+ if (normalizedValue === "") {
111
+ return true;
112
+ }
113
+ if (["false", "0", "off", "no"].includes(normalizedValue)) {
114
+ return false;
115
+ }
116
+ if (["true", "1", "on", "yes"].includes(normalizedValue)) {
117
+ return true;
118
+ }
119
+ return Boolean(normalizedValue);
120
+ }
121
+
122
+ const MANAGED_OUTER_SCROLL_CLASS = "pie-outer-scrollbars-managed";
123
+ const ACTIVE_OUTER_SCROLL_CLASS = "pie-outer-scrolling";
124
+
105
125
  let compositionModel = $state<SectionCompositionModel>(EMPTY_COMPOSITION);
106
126
  let leftPanelWidth = $state(50);
107
127
  let isDragging = $state(false);
108
128
  let splitContainerElement = $state<HTMLDivElement | null>(null);
109
129
  let elementsLoaded = $state(false);
130
+ let lastPreloadSignature = $state("");
131
+ let preloadRunToken = $state(0);
110
132
 
111
133
  const passages = $derived(compositionModel.passages || []);
112
134
  const items = $derived(compositionModel.items || []);
113
- const itemSessionsByItemId = $derived(compositionModel.itemSessionsByItemId || {});
114
135
  const hasPassages = $derived(passages.length > 0);
115
- const shouldRenderToolbar = $derived(showToolbar && toolbarPosition !== "none");
136
+ const shouldRenderToolbar = $derived(
137
+ resolveToolbarVisibility(showToolbar) && toolbarPosition !== "none",
138
+ );
116
139
  const toolbarBeforeContent = $derived(
117
140
  toolbarPosition === "top" || toolbarPosition === "left",
118
141
  );
119
142
  const toolbarInline = $derived(toolbarPosition === "left" || toolbarPosition === "right");
120
- const preloadedRenderables = $derived.by(() => [
121
- ...(passages as unknown as ItemEntity[]),
122
- ...(items as ItemEntity[]),
123
- ]);
124
- const effectiveToolsConfig = $derived.by(() => {
125
- const runtimeTools = ((runtime as RuntimeConfig | null)?.tools || tools || {}) as any;
126
- const normalized = normalizeToolsConfig(runtimeTools);
127
- const sectionTools = parseToolList(enabledTools);
128
- const itemTools = parseToolList(itemToolbarTools);
129
- const passageTools = parseToolList(passageToolbarTools);
130
- return normalizeToolsConfig({
131
- ...normalized,
132
- placement: {
133
- ...normalized.placement,
134
- section: sectionTools.length > 0 ? sectionTools : normalized.placement.section,
135
- item: itemTools.length > 0 ? itemTools : normalized.placement.item,
136
- passage: passageTools.length > 0 ? passageTools : normalized.placement.passage,
137
- },
138
- });
143
+ const preloadedRenderables = $derived.by(() =>
144
+ mapRenderablesToItems(compositionModel.renderables || []),
145
+ );
146
+ const preloadedRenderablesSignature = $derived.by(() =>
147
+ getRenderablesSignature(compositionModel.renderables || []),
148
+ );
149
+ const runtimeState = $derived.by(() =>
150
+ resolveSectionPlayerRuntimeState({
151
+ assessmentId,
152
+ playerType,
153
+ player,
154
+ lazyInit,
155
+ tools,
156
+ accessibility,
157
+ coordinator,
158
+ createSectionController,
159
+ isolation,
160
+ env,
161
+ runtime,
162
+ enabledTools,
163
+ itemToolbarTools,
164
+ passageToolbarTools,
165
+ }),
166
+ );
167
+ const effectiveRuntime = $derived(runtimeState.effectiveRuntime);
168
+ const playerRuntime = $derived(runtimeState.playerRuntime);
169
+ const resolvedPlayerDefinition = $derived(playerRuntime.resolvedPlayerDefinition);
170
+ const resolvedPlayerTag = $derived(playerRuntime.resolvedPlayerTag);
171
+ const resolvedPlayerAttributes = $derived(playerRuntime.resolvedPlayerAttributes);
172
+ const resolvedPlayerProps = $derived(playerRuntime.resolvedPlayerProps);
173
+ const resolvedPlayerEnv = $derived(playerRuntime.resolvedPlayerEnv);
174
+ const playerStrategy = $derived(playerRuntime.strategy);
175
+ const splitPanePlayerAction = createPlayerAction({
176
+ stateKey: "__splitPaneAppliedParams",
177
+ setSkipElementLoadingOnce: true,
178
+ includeSessionRefInState: true,
139
179
  });
140
- const effectiveRuntime = $derived.by(() => ({
141
- assessmentId,
142
- playerType,
143
- player,
144
- lazyInit,
145
- accessibility,
146
- coordinator,
147
- createSectionController,
148
- isolation,
149
- env,
150
- ...(runtime || {}),
151
- tools: effectiveToolsConfig,
152
- }));
153
180
 
154
181
  function handleBaseCompositionChanged(event: Event) {
155
- const detail = (event as CustomEvent<{ composition?: SectionCompositionModel }>).detail;
156
- compositionModel = detail?.composition || EMPTY_COMPOSITION;
157
- }
158
-
159
- function getSessionForItem(item: ItemEntity): unknown {
160
- const itemId = item.id || "";
161
- return itemSessionsByItemId[itemId];
182
+ compositionModel = getCompositionFromEvent(event);
162
183
  }
163
184
 
164
185
  function handleDividerMouseDown(event: MouseEvent) {
@@ -208,234 +229,205 @@
208
229
  });
209
230
 
210
231
  $effect(() => {
211
- const renderables = preloadedRenderables;
212
- if (renderables.length === 0) {
213
- elementsLoaded = true;
214
- return;
215
- }
216
-
217
- const bundleHost = String(iifeBundleHost || "").trim();
218
- if (!bundleHost) {
219
- console.warn(
220
- "[pie-section-player-splitpane] Missing iifeBundleHost for element preloading; rendering without preload.",
221
- );
222
- elementsLoaded = true;
223
- return;
224
- }
232
+ resolvedPlayerDefinition?.ensureDefined?.().catch((error) => {
233
+ console.error("[pie-section-player-splitpane] Failed to load item player component:", error);
234
+ });
235
+ });
225
236
 
226
- let cancelled = false;
227
- elementsLoaded = false;
228
- const loader = new IifeElementLoader({
229
- bundleHost,
230
- debugEnabled: () => false,
237
+ $effect(() => {
238
+ orchestratePlayerElementPreload({
239
+ componentTag: "pie-section-player-splitpane",
240
+ strategy: playerStrategy,
241
+ renderables: preloadedRenderables,
242
+ renderablesSignature: preloadedRenderablesSignature,
243
+ resolvedPlayerProps: resolvedPlayerProps as Record<string, unknown>,
244
+ resolvedPlayerEnv: resolvedPlayerEnv as Record<string, unknown>,
245
+ iifeBundleHost,
246
+ getState: () =>
247
+ ({
248
+ lastPreloadSignature,
249
+ preloadRunToken,
250
+ elementsLoaded,
251
+ }) as PlayerPreloadState,
252
+ setState: (next) => {
253
+ if (next.lastPreloadSignature !== undefined) {
254
+ lastPreloadSignature = next.lastPreloadSignature;
255
+ }
256
+ if (next.preloadRunToken !== undefined) {
257
+ preloadRunToken = next.preloadRunToken;
258
+ }
259
+ if (next.elementsLoaded !== undefined) {
260
+ elementsLoaded = next.elementsLoaded;
261
+ }
262
+ },
231
263
  });
232
- const loaderView = (env as any)?.mode === "author" ? "author" : "delivery";
233
-
234
- void loader
235
- .loadFromItems(renderables, {
236
- view: loaderView,
237
- needsControllers: true,
238
- })
239
- .then(() => {
240
- if (!cancelled) elementsLoaded = true;
241
- })
242
- .catch((error) => {
243
- console.error(
244
- "[pie-section-player-splitpane] Failed to preload PIE elements:",
245
- error,
246
- );
247
- if (!cancelled) elementsLoaded = true;
248
- });
264
+ });
249
265
 
250
- return () => {
251
- cancelled = true;
266
+ onMount(() => {
267
+ let scrollTimeout: ReturnType<typeof setTimeout> | null = null;
268
+ const html = document.documentElement;
269
+ const body = document.body;
270
+
271
+ html.classList.add(MANAGED_OUTER_SCROLL_CLASS);
272
+ body.classList.add(MANAGED_OUTER_SCROLL_CLASS);
273
+
274
+ const showOuterScrollbars = () => {
275
+ html.classList.add(ACTIVE_OUTER_SCROLL_CLASS);
276
+ body.classList.add(ACTIVE_OUTER_SCROLL_CLASS);
277
+ if (scrollTimeout) {
278
+ clearTimeout(scrollTimeout);
279
+ }
280
+ scrollTimeout = setTimeout(() => {
281
+ html.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
282
+ body.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
283
+ }, 900);
252
284
  };
253
- });
254
285
 
255
- $effect(() => {
256
- if (typeof window === "undefined" || runtime) return;
257
- const usedLegacyProps: string[] = [];
258
- if (assessmentId !== DEFAULT_ASSESSMENT_ID) usedLegacyProps.push("assessmentId");
259
- if (playerType !== DEFAULT_PLAYER_TYPE) usedLegacyProps.push("playerType");
260
- if (player !== null) usedLegacyProps.push("player");
261
- if (lazyInit !== DEFAULT_LAZY_INIT) usedLegacyProps.push("lazyInit");
262
- if (tools !== null) usedLegacyProps.push("tools");
263
- if (accessibility !== null) usedLegacyProps.push("accessibility");
264
- if (coordinator !== null) usedLegacyProps.push("coordinator");
265
- if (createSectionController !== null) usedLegacyProps.push("createSectionController");
266
- if (isolation !== DEFAULT_ISOLATION) usedLegacyProps.push("isolation");
267
- const key = `${LEGACY_RUNTIME_WARNING_KEY}:${usedLegacyProps.sort().join(",")}`;
268
- if (usedLegacyProps.length === 0 || warnedKeys.has(key)) return;
269
- warnedKeys.add(key);
270
- console.warn(
271
- `[pie-section-player-splitpane] Runtime props (${usedLegacyProps.join(", ")}) are deprecated. Prefer the \`runtime\` object prop.`,
272
- );
286
+ window.addEventListener("scroll", showOuterScrollbars, { passive: true });
287
+ return () => {
288
+ window.removeEventListener("scroll", showOuterScrollbars);
289
+ html.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
290
+ body.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
291
+ html.classList.remove(MANAGED_OUTER_SCROLL_CLASS);
292
+ body.classList.remove(MANAGED_OUTER_SCROLL_CLASS);
293
+ if (scrollTimeout) {
294
+ clearTimeout(scrollTimeout);
295
+ }
296
+ };
273
297
  });
274
298
 
275
299
  </script>
276
300
 
277
301
  <pie-section-player-base
278
302
  runtime={effectiveRuntime}
279
- {assessmentId}
280
303
  {section}
281
304
  section-id={sectionId}
282
305
  attempt-id={attemptId}
283
- {view}
284
- player-type={playerType}
285
- {player}
286
- lazy-init={lazyInit}
287
- {tools}
288
- {accessibility}
289
- {coordinator}
290
- create-section-controller={createSectionController}
291
- {isolation}
292
306
  oncomposition-changed={handleBaseCompositionChanged}
293
307
  >
294
- <div class={`player-shell player-shell--${toolbarPosition}`}>
308
+ <div
309
+ class={`pie-section-player-shell pie-section-player-shell--${toolbarPosition}`}
310
+ >
295
311
  {#if shouldRenderToolbar && toolbarBeforeContent}
296
- <pie-section-tools-toolbar
297
- class={`section-toolbar section-toolbar--${toolbarPosition}`}
312
+ <pie-section-toolbar
313
+ class={`pie-section-player-toolbar pie-section-player-toolbar--${toolbarPosition}`}
298
314
  position={toolbarPosition}
299
315
  enabled-tools={enabledTools}
300
- ></pie-section-tools-toolbar>
316
+ ></pie-section-toolbar>
301
317
  {/if}
302
318
 
303
- <div class={`layout-body ${toolbarInline ? "layout-body--inline" : ""}`}>
319
+ <div
320
+ class={`pie-section-player-layout-body ${shouldRenderToolbar && toolbarInline ? "pie-section-player-layout-body--inline" : ""}`}
321
+ >
304
322
  <div
305
- class={`split-content ${!hasPassages ? "split-content--no-passages" : ""}`}
323
+ class={`pie-section-player-split-content ${!hasPassages ? "pie-section-player-split-content--no-passages" : ""}`}
306
324
  bind:this={splitContainerElement}
307
325
  style={hasPassages
308
326
  ? `grid-template-columns: ${leftPanelWidth}% 0.5rem ${100 - leftPanelWidth - 0.5}%`
309
327
  : "grid-template-columns: 1fr"}
310
328
  >
311
329
  {#if hasPassages}
312
- <aside class="passages-pane" aria-label="Passages">
330
+ <aside class="pie-section-player-passages-pane" aria-label="Passages">
313
331
  {#if !elementsLoaded}
314
- <div class="content-card">
315
- <div class="content-card-body passage-content pie-section-player__passage-content">
332
+ <div class="pie-section-player-content-card">
333
+ <div
334
+ class="pie-section-player-content-card-body pie-section-player-passage-content pie-section-player__passage-content"
335
+ >
316
336
  Loading passage content...
317
337
  </div>
318
338
  </div>
319
339
  {:else}
320
340
  {#each passages as passage, passageIndex (passage.id || passageIndex)}
321
- <pie-passage-shell
322
- item-id={passage.id}
323
- content-kind="rubric-block-stimulus"
324
- item={passage}
325
- >
326
- <div class="content-card">
327
- <div
328
- class="content-card-header passage-header pie-section-player__passage-header"
329
- data-region="header"
330
- >
331
- <h2>Passage {passageIndex + 1}</h2>
332
- <pie-item-toolbar
333
- item-id={passage.id}
334
- catalog-id={passage.id}
335
- tools={passageToolbarTools}
336
- content-kind="rubric-block-stimulus"
337
- size="md"
338
- language="en-US"
339
- ></pie-item-toolbar>
340
- </div>
341
- <div
342
- class="content-card-body passage-content pie-section-player__passage-content"
343
- data-region="content"
344
- >
345
- <pie-iife-player
346
- config={JSON.stringify(passage.config || {})}
347
- env={JSON.stringify({ mode: "view", role: (env as any)?.role || "student" })}
348
- bundle-host={iifeBundleHost}
349
- skip-element-loading={true}
350
- ></pie-iife-player>
351
- </div>
352
- </div>
353
- </pie-passage-shell>
341
+ <SectionPassageCard
342
+ {passage}
343
+ {resolvedPlayerTag}
344
+ playerAction={splitPanePlayerAction}
345
+ playerParams={getPassagePlayerParams({
346
+ passage,
347
+ resolvedPlayerEnv,
348
+ resolvedPlayerAttributes,
349
+ resolvedPlayerProps,
350
+ playerStrategy,
351
+ })}
352
+ {passageToolbarTools}
353
+ />
354
354
  {/each}
355
355
  {/if}
356
356
  </aside>
357
357
 
358
358
  <button
359
359
  type="button"
360
- class={`split-divider ${isDragging ? "split-divider--dragging" : ""}`}
360
+ class={`pie-section-player-split-divider ${isDragging ? "pie-section-player-split-divider--dragging" : ""}`}
361
361
  onmousedown={handleDividerMouseDown}
362
362
  onkeydown={handleDividerKeyDown}
363
363
  aria-label="Resize panels"
364
364
  >
365
- <span class="split-divider-handle"></span>
365
+ <span class="pie-section-player-split-divider-handle"></span>
366
366
  </button>
367
367
  {/if}
368
368
 
369
- <main class="items-pane" aria-label="Items">
369
+ <main class="pie-section-player-items-pane" aria-label="Items">
370
370
  {#if !elementsLoaded}
371
- <div class="content-card">
372
- <div class="content-card-body item-content pie-section-player__item-content">
371
+ <div class="pie-section-player-content-card">
372
+ <div
373
+ class="pie-section-player-content-card-body pie-section-player-item-content pie-section-player__item-content"
374
+ >
373
375
  Loading section content...
374
376
  </div>
375
377
  </div>
376
378
  {:else}
377
379
  {#each items as item, itemIndex (item.id || itemIndex)}
378
- <pie-item-shell item-id={item.id} content-kind="assessment-item" item={item}>
379
- <div class="content-card">
380
- <div
381
- class="content-card-header item-header pie-section-player__item-header"
382
- data-region="header"
383
- >
384
- <h2>Question {itemIndex + 1}</h2>
385
- <pie-item-toolbar
386
- item-id={item.id}
387
- catalog-id={item.id}
388
- tools={itemToolbarTools}
389
- content-kind="assessment-item"
390
- size="md"
391
- language="en-US"
392
- ></pie-item-toolbar>
393
- </div>
394
- <div
395
- class="content-card-body item-content pie-section-player__item-content"
396
- data-region="content"
397
- >
398
- <pie-iife-player
399
- config={JSON.stringify(item.config || {})}
400
- env={JSON.stringify(env)}
401
- session={JSON.stringify(getSessionForItem(item) || { id: "", data: [] })}
402
- bundle-host={iifeBundleHost}
403
- skip-element-loading={true}
404
- ></pie-iife-player>
405
- </div>
406
- <div data-region="footer"></div>
407
- </div>
408
- </pie-item-shell>
380
+ <SectionItemCard
381
+ {item}
382
+ canonicalItemId={getCanonicalItemId({ compositionModel, item })}
383
+ {resolvedPlayerTag}
384
+ playerAction={splitPanePlayerAction}
385
+ playerParams={getItemPlayerParams({
386
+ item,
387
+ compositionModel,
388
+ resolvedPlayerEnv,
389
+ resolvedPlayerAttributes,
390
+ resolvedPlayerProps,
391
+ playerStrategy,
392
+ })}
393
+ {itemToolbarTools}
394
+ />
409
395
  {/each}
410
396
  {/if}
411
397
  </main>
412
398
  </div>
413
399
 
414
400
  {#if shouldRenderToolbar && toolbarInline && toolbarPosition === "right"}
415
- <aside class="section-toolbar-pane section-toolbar-pane--right" aria-label="Section tools">
416
- <pie-section-tools-toolbar
401
+ <aside
402
+ class="pie-section-player-toolbar-pane pie-section-player-toolbar-pane--right"
403
+ aria-label="Section tools"
404
+ >
405
+ <pie-section-toolbar
417
406
  position="right"
418
407
  enabled-tools={enabledTools}
419
- ></pie-section-tools-toolbar>
408
+ ></pie-section-toolbar>
420
409
  </aside>
421
410
  {/if}
422
411
 
423
412
  {#if shouldRenderToolbar && toolbarInline && toolbarPosition === "left"}
424
- <aside class="section-toolbar-pane section-toolbar-pane--left" aria-label="Section tools">
425
- <pie-section-tools-toolbar
413
+ <aside
414
+ class="pie-section-player-toolbar-pane pie-section-player-toolbar-pane--left"
415
+ aria-label="Section tools"
416
+ >
417
+ <pie-section-toolbar
426
418
  position="left"
427
419
  enabled-tools={enabledTools}
428
- ></pie-section-tools-toolbar>
420
+ ></pie-section-toolbar>
429
421
  </aside>
430
422
  {/if}
431
423
  </div>
432
424
 
433
425
  {#if shouldRenderToolbar && !toolbarBeforeContent && !toolbarInline}
434
- <pie-section-tools-toolbar
435
- class={`section-toolbar section-toolbar--${toolbarPosition}`}
426
+ <pie-section-toolbar
427
+ class={`pie-section-player-toolbar pie-section-player-toolbar--${toolbarPosition}`}
436
428
  position={toolbarPosition}
437
429
  enabled-tools={enabledTools}
438
- ></pie-section-tools-toolbar>
430
+ ></pie-section-toolbar>
439
431
  {/if}
440
432
  </div>
441
433
  </pie-section-player-base>
@@ -450,41 +442,43 @@
450
442
  overflow: hidden;
451
443
  }
452
444
 
453
- .player-shell {
445
+ .pie-section-player-shell {
454
446
  display: flex;
455
447
  flex-direction: column;
456
448
  height: 100%;
457
449
  min-height: 0;
458
450
  overflow: hidden;
451
+ background: var(--pie-background-dark, #ecedf1);
459
452
  }
460
453
 
461
- .player-shell--left,
462
- .player-shell--right {
454
+ .pie-section-player-shell--left,
455
+ .pie-section-player-shell--right {
463
456
  flex-direction: row;
464
457
  }
465
458
 
466
- .player-shell--left .layout-body--inline {
459
+ .pie-section-player-shell--left .pie-section-player-layout-body--inline {
467
460
  order: 2;
468
461
  }
469
462
 
470
- .player-shell--left .section-toolbar-pane--left {
463
+ .pie-section-player-shell--left .pie-section-player-toolbar-pane--left {
471
464
  order: 1;
472
465
  }
473
466
 
474
- .layout-body {
467
+ .pie-section-player-layout-body {
475
468
  display: grid;
476
469
  grid-template-columns: minmax(0, 1fr);
477
470
  flex: 1;
478
471
  min-height: 0;
479
472
  overflow: hidden;
473
+ background: var(--pie-background-dark, #ecedf1);
480
474
  }
481
475
 
482
- .layout-body--inline {
476
+ .pie-section-player-layout-body--inline {
483
477
  grid-template-columns: minmax(0, 1fr) auto;
484
478
  gap: 1rem;
485
479
  }
486
480
 
487
- .split-content {
481
+ .pie-section-player-split-content {
488
482
  display: grid;
489
483
  gap: 0;
490
484
  min-height: 0;
@@ -492,12 +486,12 @@
492
486
  overflow: hidden;
493
487
  }
494
488
 
495
- .split-content--no-passages .items-pane {
496
- padding-left: 0;
489
+ .pie-section-player-split-content--no-passages .pie-section-player-items-pane {
490
+ padding-left: 0.5rem;
497
491
  }
498
492
 
499
- .passages-pane,
500
- .items-pane {
493
+ .pie-section-player-passages-pane,
494
+ .pie-section-player-items-pane {
501
495
  height: 100%;
502
496
  max-height: 100%;
503
497
  min-height: 0;
@@ -508,24 +502,36 @@
508
502
  display: flex;
509
503
  flex-direction: column;
510
504
  gap: 1rem;
505
+ padding: 0.5rem;
506
+ box-sizing: border-box;
507
+ background: var(--pie-background-dark, #ecedf1);
511
508
  }
512
509
 
513
- .section-toolbar-pane {
510
+ .pie-section-player-toolbar-pane {
514
511
  min-height: 0;
515
512
  overflow: auto;
513
+ padding: 0.5rem;
514
+ box-sizing: border-box;
515
+ background: var(--pie-background-dark, #ecedf1);
516
516
  }
517
517
 
518
- .section-toolbar-pane--right {
518
+ .pie-section-player-toolbar-pane--right {
519
519
  border-left: 1px solid var(--pie-border-light, #e5e7eb);
520
- padding-left: 0.5rem;
521
520
  }
522
521
 
523
- .section-toolbar-pane--left {
522
+ .pie-section-player-toolbar-pane--left {
524
523
  border-right: 1px solid var(--pie-border-light, #e5e7eb);
525
- padding-right: 0.5rem;
526
524
  }
527
525
 
528
- .split-divider {
526
+ .pie-section-player-toolbar {
527
+ margin: 0.5rem;
528
+ }
529
+
530
+ .pie-section-player-toolbar-pane pie-section-toolbar {
531
+ margin: 0.5rem;
532
+ }
533
+
534
+ .pie-section-player-split-divider {
529
535
  border: none;
530
536
  padding: 0;
531
537
  margin: 0;
@@ -544,16 +550,16 @@
544
550
  transition: background 0.2s ease;
545
551
  }
546
552
 
547
- .split-divider:hover {
553
+ .pie-section-player-split-divider:hover {
548
554
  background: var(--pie-border-light, #e5e7eb);
549
555
  }
550
556
 
551
- .split-divider:focus {
557
+ .pie-section-player-split-divider:focus {
552
558
  outline: 2px solid var(--pie-focus-checked-border, #1976d2);
553
559
  outline-offset: -2px;
554
560
  }
555
561
 
556
- .split-divider-handle {
562
+ .pie-section-player-split-divider-handle {
557
563
  position: absolute;
558
564
  inset: 0;
559
565
  margin: auto;
@@ -565,7 +571,7 @@
565
571
  pointer-events: none;
566
572
  }
567
573
 
568
- .split-divider-handle::before {
574
+ .pie-section-player-split-divider-handle::before {
569
575
  content: "";
570
576
  position: absolute;
571
577
  top: 50%;
@@ -578,25 +584,25 @@
578
584
  opacity: 0.8;
579
585
  }
580
586
 
581
- .split-divider:hover .split-divider-handle,
582
- .split-divider:focus .split-divider-handle,
583
- .split-divider--dragging .split-divider-handle {
587
+ .pie-section-player-split-divider:hover .pie-section-player-split-divider-handle,
588
+ .pie-section-player-split-divider:focus .pie-section-player-split-divider-handle,
589
+ .pie-section-player-split-divider--dragging .pie-section-player-split-divider-handle {
584
590
  background: var(--pie-primary, #1976d2);
585
591
  height: 80px;
586
592
  box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
587
593
  }
588
594
 
589
- .split-divider--dragging {
595
+ .pie-section-player-split-divider--dragging {
590
596
  background: var(--pie-primary-light, #dbeafe);
591
597
  }
592
598
 
593
- .content-card {
599
+ .pie-section-player-content-card {
594
600
  border: 1px solid var(--pie-border-light, #e5e7eb);
595
601
  border-radius: 8px;
596
- background: var(--pie-white, #fff);
602
+ background: var(--pie-background, #fff);
597
603
  }
598
604
 
599
- .content-card-header {
605
+ .pie-section-player-content-card-header {
600
606
  display: flex;
601
607
  align-items: center;
602
608
  justify-content: space-between;
@@ -604,36 +610,71 @@
604
610
  border-bottom: 1px solid var(--pie-border-light, #e5e7eb);
605
611
  }
606
612
 
607
- .content-card-header h2 {
608
- margin: 0;
609
- font-size: 0.95rem;
610
- font-weight: 600;
613
+ .pie-section-player-content-card-body {
614
+ padding: 1rem;
611
615
  }
612
616
 
613
- .content-card-body {
614
- padding: 1rem;
617
+ :global(html.pie-outer-scrollbars-managed),
618
+ :global(body.pie-outer-scrollbars-managed) {
619
+ scrollbar-width: auto;
620
+ scrollbar-color: transparent transparent;
621
+ }
622
+
623
+ :global(html.pie-outer-scrollbars-managed.pie-outer-scrolling),
624
+ :global(body.pie-outer-scrollbars-managed.pie-outer-scrolling) {
625
+ scrollbar-color: #c1c1c1 #f1f1f1;
626
+ }
627
+
628
+ :global(html.pie-outer-scrollbars-managed::-webkit-scrollbar),
629
+ :global(body.pie-outer-scrollbars-managed::-webkit-scrollbar) {
630
+ width: 0;
631
+ height: 0;
632
+ background: transparent;
633
+ }
634
+
635
+ :global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar),
636
+ :global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar) {
637
+ width: 8px;
638
+ height: 8px;
639
+ }
640
+
641
+ :global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-track),
642
+ :global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-track) {
643
+ background: #f1f1f1;
644
+ border-radius: 4px;
645
+ }
646
+
647
+ :global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb),
648
+ :global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb) {
649
+ background: #c1c1c1;
650
+ border-radius: 4px;
651
+ }
652
+
653
+ :global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb:hover),
654
+ :global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb:hover) {
655
+ background: #a1a1a1;
615
656
  }
616
657
 
617
658
  @media (max-width: 1100px) {
618
- .player-shell--left,
619
- .player-shell--right {
659
+ .pie-section-player-shell--left,
660
+ .pie-section-player-shell--right {
620
661
  flex-direction: column;
621
662
  }
622
663
 
623
- .layout-body--inline {
664
+ .pie-section-player-layout-body--inline {
624
665
  grid-template-columns: 1fr;
625
666
  }
626
667
 
627
- .split-content {
668
+ .pie-section-player-split-content {
628
669
  grid-template-columns: 1fr !important;
629
670
  }
630
671
 
631
- .split-divider {
672
+ .pie-section-player-split-divider {
632
673
  display: none;
633
674
  }
634
675
 
635
- .section-toolbar-pane--left,
636
- .section-toolbar-pane--right {
676
+ .pie-section-player-toolbar-pane--left,
677
+ .pie-section-player-toolbar-pane--right {
637
678
  border: none;
638
679
  padding: 0;
639
680
  }