@pie-players/pie-section-player 0.3.36 → 0.3.38

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/README.md CHANGED
@@ -32,6 +32,73 @@ entrypoints under `@pie-players/pie-section-player/components/*`.
32
32
  Standalone browser variants for this package are intentionally deferred; the
33
33
  current supported contract is the default bundler entrypoints under `dist`.
34
34
 
35
+ ## SectionController
36
+
37
+ `SectionController` is the domain authority inside a section player. It owns
38
+ in-section navigation state, the canonical aggregation of per-item sessions,
39
+ and the host-facing persistence snapshot. The layout custom elements
40
+ (`pie-section-player-splitpane` / `-vertical` / `-tabbed`) are transport
41
+ adapters around it. See
42
+ [`docs/section-player/controller-boundaries.md`](../../docs/section-player/controller-boundaries.md)
43
+ for the rationale behind that split, and
44
+ [`docs/section-player/client-architecture-tutorial.md`](../../docs/section-player/client-architecture-tutorial.md)
45
+ for the end-to-end walkthrough.
46
+
47
+ The handle implements `SectionControllerHandle` from
48
+ `@pie-players/pie-assessment-toolkit`; see the JSDoc on that interface for
49
+ the per-method contract.
50
+
51
+ ### Obtaining the handle
52
+
53
+ ```ts
54
+ const host = document.querySelector("pie-section-player-splitpane") as any;
55
+ const controller = await host.waitForSectionController?.(5000);
56
+ ```
57
+
58
+ `waitForSectionController(timeoutMs)` resolves when the layout CE has wired
59
+ its controller (the same anchor `pie-stage-change` reaches with
60
+ `detail.stage === "engine-ready"`). Use `getSectionController()` if you've
61
+ already passed the readiness anchor synchronously. The legacy
62
+ `section-controller-ready` event was removed; either of these helpers
63
+ replaces it.
64
+
65
+ ### Session lifecycle
66
+
67
+ A typical host flow:
68
+
69
+ ```ts
70
+ controller?.configureSessionPersistence?.({ context, strategy });
71
+ await controller?.hydrate?.();
72
+ const unsubscribe = controller?.subscribe?.(handleEvent);
73
+ // ...later, on save / unload:
74
+ await controller?.persist?.();
75
+ unsubscribe?.();
76
+ ```
77
+
78
+ `getSession()` / `applySession(session, { mode })` / `updateItemSession(itemId,
79
+ detail)` are the direct read/write surfaces and exchange the same
80
+ `SectionControllerSessionState` shape the persistence strategy load/save
81
+ methods receive. See [Item session management](#item-session-management) for
82
+ worked examples.
83
+
84
+ ### Event stream
85
+
86
+ The controller's typed event stream (`SectionControllerEvent` discriminated
87
+ union) is the single source of truth for in-section change. Hosts usually
88
+ subscribe through `ToolkitCoordinator.subscribeItemEvents` /
89
+ `subscribeSectionLifecycleEvents` (cohort-aware filtering, survives
90
+ navigation) — see [JS API example for advanced host
91
+ policy](#js-api-example-for-advanced-host-policy). Key event types:
92
+
93
+ - `item-selected` — item navigation within the current section.
94
+ - `item-session-data-changed` / `item-session-meta-changed` — per-item
95
+ session updates the persistence layer should observe.
96
+ - `content-loaded` — passage / item / rubric finished loading.
97
+ - `section-loading-complete` — every renderable in the section finished
98
+ loading.
99
+ - `section-items-complete-changed` — aggregate completion flip.
100
+ - `section-navigation-change` — the controller's section identity changed.
101
+
35
102
  ## Usage
36
103
 
37
104
  Import the custom-element registration entrypoint in consumers:
@@ -176,50 +243,25 @@ host.hooks = {
176
243
 
177
244
  Advanced CE props are still supported as escape hatches (`runtime`, `coordinator`, etc.), but hosts should prefer JS/controller composition for non-standard behavior. Note: `createSectionController` is **runtime-only** — set it on `runtime.createSectionController` rather than as a top-level CE prop (the prop alias was removed in the broad architecture review compat sweep).
178
245
 
179
- ### Focus management
180
-
181
- Section-player owns its own focus contract so hosts can integrate a
182
- "Skip to Main" affordance and keyboard navigation without re-encoding
183
- the passage-first rule.
184
-
185
- **Public focus targets.** Both card custom elements are `tabindex="-1"`
186
- (programmatically focusable, never in sequential tab order) and expose a
187
- `:focus-visible` outline:
188
-
189
- - `<pie-section-player-passage-card>` (when the section has a passage)
190
- - `<pie-section-player-item-card>`
191
-
192
- The inner `.pie-section-player-content-card[data-section-item-card]`
193
- selector is an internal back-compat hook and is not part of the public
194
- contract — prefer querying the custom element tags.
195
-
196
- **Declarative control.** The `SectionPlayerFocusPolicy.autoFocus` strategy
197
- governs focus on every navigation event (Next / Back / `navigateTo`) and is
198
- also honored by the imperative `focusStart()`:
199
-
200
- - `"start-of-content"` *(default)* — focus the passage card when present,
201
- otherwise the first item card. Matches the canonical assessment UX where
202
- Skip-to-Main and navigation land in the same place. Best for
203
- one-item-per-page layouts (splitpane, tabbed).
204
- - `"current-item"` — focus the newly-active item card (queried as
205
- `pie-section-player-item-card[is-current]`). Best for stacked/list
206
- layouts where multiple items are visible at once (vertical, custom
207
- kernel-host variants). Works in both paginated and keep-together
208
- sections — QTI 3 `keep-together` is a pagination hint only and does
209
- not disable item-level navigation or current-item tracking.
210
- - `"none"` — framework never moves focus on navigation; the host owns it
211
- entirely. `focusStart()` still moves focus (defaults to
212
- start-of-content) because hosts only call it when they *want* focus to
213
- move.
246
+ ### Host-owned focus
214
247
 
215
- ```ts
216
- const host = document.querySelector("pie-section-player-splitpane") as any;
217
- host.policies = {
218
- readiness: { mode: "progressive" },
219
- preload: { enabled: true },
220
- focus: { autoFocus: "start-of-content" },
221
- telemetry: { enabled: true },
222
- };
248
+ Section-player does not move focus on behalf of host-level affordances such
249
+ as "Skip to Main", nor does it make passage/question containers tab stops.
250
+ Hosts own page chrome, skip links, landmarks, and any special focus placement.
251
+ For example, Quiz Engine's Fixed Player shell can focus its own
252
+ `main#main-content`; the next Tab then follows the browser's natural order
253
+ into the first actionable control rendered inside the section player.
254
+
255
+ The passage and item card custom elements are content/layout surfaces, not
256
+ public focus targets. Splitpane passage content remains scrollable through the
257
+ pane's native scroll behavior, but the passage pane itself is not inserted into
258
+ sequential keyboard navigation.
259
+
260
+ ```html
261
+ <a href="#main-content" class="skip-link">Skip to Main</a>
262
+ <main id="main-content" tabindex="-1">
263
+ <pie-section-player-splitpane></pie-section-player-splitpane>
264
+ </main>
223
265
  ```
224
266
 
225
267
  **Wired policy toggles.** Each `SectionPlayerPolicies` field has a real
@@ -232,8 +274,6 @@ runtime effect; nothing in this surface is decorative.
232
274
  still mount and item-players register their own elements on demand. Use
233
275
  this to disable section pre-warm when the host already owns element
234
276
  registration end-to-end. Default: `true`.
235
- - `focus.autoFocus` — focus strategy on navigation; see "Focus management"
236
- above.
237
277
  - `telemetry.enabled` — when `false`, the layout custom elements skip
238
278
  `attachInstrumentationEventBridge` setup, so no `pie-section-*`
239
279
  telemetry events flow through the bridge. Hosts that want a different
@@ -245,29 +285,6 @@ helpers read these toggles with the documented default-true semantics, so
245
285
  host code that needs to mirror the same gate (e.g. when composing a custom
246
286
  layout host) can call them directly.
247
287
 
248
- **Imperative escape hatch (host-owned focus moments).** Every layout
249
- element (`pie-section-player-splitpane`, `-vertical`, `-tabbed`,
250
- `-kernel-host`, and `-base`) exposes a `focusStart(): boolean` method.
251
- Call it from host-owned affordances the framework cannot observe — most
252
- commonly a "Skip to Main" button in the host's ribbon:
253
-
254
- ```ts
255
- document.querySelector("pie-section-player-splitpane")?.focusStart();
256
- ```
257
-
258
- `focusStart()` honors the `autoFocus` strategy so Skip-to-Main lands
259
- wherever the host has opted in for navigation focus:
260
-
261
- - `"start-of-content"` *(default)* → passage card when present, else first
262
- item card.
263
- - `"current-item"` → the item card currently marked `is-current`, falling
264
- back to start-of-content if no current item is resolvable.
265
- - `"none"` → start-of-content (hosts call `focusStart()` precisely
266
- because they want focus to move).
267
-
268
- For Next / Back / question-number navigation the host does **not** call
269
- `focusStart()` — the `autoFocus` policy fires automatically.
270
-
271
288
  ### Navigation signals
272
289
 
273
290
  - `item-selected`: item-level navigation change within the current section in the `SectionController` broadcast stream (`itemIndex`, `currentItemId`, `totalItems`).