@pie-players/pie-section-player 0.3.28 → 0.3.30

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 (32) hide show
  1. package/README.md +192 -12
  2. package/dist/chunks/dist-24a90544.js +193 -138
  3. package/dist/chunks/{dist-Bud4G4lv-b2620662.js → dist-DIBQdekL-ec6db34a.js} +120 -66
  4. package/dist/chunks/dist-c4148994.js +14 -2
  5. package/dist/chunks/{dist-DwP27yIs-cfd207a3.js → dist-rF11IR1B-adc4857f.js} +13 -1
  6. package/dist/chunks/module-c1d68911.js +1819 -1835
  7. package/dist/chunks/player-preload-79b07366.js +6701 -3674
  8. package/dist/chunks/tool-annotation-toolbar-db5bfbec.js +1555 -1470
  9. package/dist/components/section-player-tabbed-element.d.ts +2 -0
  10. package/dist/components/section-player-tabbed-element.d.ts.map +1 -0
  11. package/dist/contracts/host-hooks.d.ts +5 -0
  12. package/dist/contracts/host-hooks.d.ts.map +1 -0
  13. package/dist/contracts/layout-contract.d.ts +3 -3
  14. package/dist/contracts/layout-contract.d.ts.map +1 -1
  15. package/dist/contracts/layout-parity-metadata.d.ts +1 -0
  16. package/dist/contracts/layout-parity-metadata.d.ts.map +1 -1
  17. package/dist/contracts/runtime-host-contract.d.ts +13 -0
  18. package/dist/contracts/runtime-host-contract.d.ts.map +1 -1
  19. package/dist/controllers/SectionController.d.ts.map +1 -1
  20. package/dist/controllers/SectionItemNavigationService.d.ts +0 -1
  21. package/dist/controllers/SectionItemNavigationService.d.ts.map +1 -1
  22. package/dist/controllers/types.d.ts +16 -0
  23. package/dist/controllers/types.d.ts.map +1 -1
  24. package/dist/pie-section-player.d.ts +4 -3
  25. package/dist/pie-section-player.d.ts.map +1 -1
  26. package/dist/pie-section-player.js +6251 -5036
  27. package/dist/policies/index.d.ts +9 -1
  28. package/dist/policies/index.d.ts.map +1 -1
  29. package/dist/policies/types.d.ts +23 -1
  30. package/dist/policies/types.d.ts.map +1 -1
  31. package/package.json +23 -14
  32. /package/dist/chunks/{defineProperty-318b8855.js → defineProperty-aab5a8e0.js} +0 -0
package/README.md CHANGED
@@ -4,6 +4,7 @@ Section rendering package with layout custom elements:
4
4
 
5
5
  - `pie-section-player-splitpane`
6
6
  - `pie-section-player-vertical`
7
+ - `pie-section-player-tabbed`
7
8
 
8
9
  The package no longer exposes the legacy `pie-section-player` layout orchestration API.
9
10
 
@@ -38,6 +39,7 @@ Import the custom-element registration entrypoint in consumers:
38
39
  ```ts
39
40
  import '@pie-players/pie-section-player/components/section-player-splitpane-element';
40
41
  import '@pie-players/pie-section-player/components/section-player-vertical-element';
42
+ import '@pie-players/pie-section-player/components/section-player-tabbed-element';
41
43
  import '@pie-players/pie-section-player/components/section-player-item-card-element';
42
44
  import '@pie-players/pie-section-player/components/section-player-passage-card-element';
43
45
  ```
@@ -60,14 +62,86 @@ Both layout elements support:
60
62
  - `debug` (boolean-like): verbose debug logging control (`"true"` enables, `"false"`/`"0"` disables)
61
63
  - `toolbar-position` (string): `top|right|bottom|left|none`
62
64
  - `narrow-layout-breakpoint` (number, optional): viewport width in px below which the layout collapses (split pane: single column; vertical: toolbar moves to top). Clamped to 400–2000; default 1100.
65
+ - `content-max-width-no-passage` (number, optional): max width in px when no passages exist. Clamped to 320–2200. Unset by default (layout uses available width).
66
+ - `content-max-width-with-passage` (number, optional): max width in px when passages are present. Clamped to 320–2200. Unset by default (layout uses available width).
67
+ - `split-pane-min-region-width` (number, optional): splitpane minimum pane width in px. Clamped to 160–1200. Unset by default (legacy split bounds stay at 20–80). (Ignored by vertical layout; supported for API parity.)
68
+ - `split-pane-collapse-strategy` (string, optional): splitpane stacked-mode strategy. Supported values: `tabbed` (default) and `vertical`. (Ignored by vertical/tabbed layouts; supported for API parity.)
63
69
  - `show-toolbar` (boolean-like): accepts `true/false` and common string forms (`"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"`)
64
- - Host extension props (JS properties only): `toolRegistry`, `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`, `cardTitleFormatter`
70
+ - Host extension props (JS properties only): `toolRegistry`, `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`, `hooks`
65
71
 
66
- When viewport width is within the collapsed range (~1100px and below), inline section
67
- toolbar positions (`left`/`right`) normalize to `top` so controls remain horizontally
68
- laid out and easier to access.
72
+ When viewport width is within the collapsed range (~1100px and below), splitpane and
73
+ vertical layout hosts normalize section toolbar placement to `top`. This includes
74
+ `left`, `right`, `bottom`, and `none` values.
69
75
 
70
- `cardTitleFormatter` remains active across responsive splitpane transitions (split -> stacked and stacked -> split), because title rendering is provided through shared card context rather than layout-specific state.
76
+ `hooks.cardTitleFormatter` remains active across responsive splitpane transitions (split -> stacked and stacked -> split), because title rendering is provided through shared card context rather than layout-specific state.
77
+
78
+ To opt into PIE-117 dimensions from a host, configure:
79
+
80
+ ```html
81
+ <pie-section-player-splitpane
82
+ content-max-width-no-passage="800"
83
+ content-max-width-with-passage="1200"
84
+ split-pane-min-region-width="280"
85
+ ></pie-section-player-splitpane>
86
+ ```
87
+
88
+ Use the same max-width attributes on `pie-section-player-vertical` when you want the same no-passage/with-passage width behavior in vertical mode.
89
+
90
+ To force splitpane stacked mode to use vertical rendering:
91
+
92
+ ```html
93
+ <pie-section-player-splitpane
94
+ narrow-layout-breakpoint="1100"
95
+ split-pane-collapse-strategy="vertical"
96
+ ></pie-section-player-splitpane>
97
+ ```
98
+
99
+ By default, splitpane stacked mode uses tabs. The dedicated `pie-section-player-tabbed` layout also always renders passage/items tabs when passages are present.
100
+
101
+ ### Tab styling hooks
102
+
103
+ `pie-section-player-tabbed` and splitpane `tabbed` collapse mode expose canonical `pie-*`
104
+ hooks for theming:
105
+
106
+ - `pie-section-player-tabs`
107
+ - `pie-section-player-tab`
108
+ - `pie-section-player-tab--active`
109
+ - `pie-section-player-tab-panel`
110
+
111
+ For theme compatibility with existing passage-label patterns, tabs also expose:
112
+
113
+ - `data-pie-purpose="passage-label"` and alias class `passage-label`
114
+ - `data-pie-purpose="item-label"` and alias class `item-label`
115
+
116
+ Tab colors/spacing can be themed via CSS variables such as:
117
+ `--pie-section-player-tab-color`, `--pie-section-player-tab-active-color`,
118
+ `--pie-section-player-tab-indicator-color`, and `--pie-section-player-tab-spacing`.
119
+
120
+ ### Card header styling hooks
121
+
122
+ Passage and item cards share a common header row
123
+ (`.pie-section-player-content-card-header`, with the card-specific aliases
124
+ `.pie-section-player-passage-header` / `.pie-section-player-item-header`).
125
+
126
+ - Title and toolbar are centered vertically by default. There is no prop or
127
+ attribute for this — hosts needing a non-standard alignment should override
128
+ the selector in their own stylesheet.
129
+ - The header fill is transparent by default. Hosts/themes opt into a color
130
+ via the `--pie-section-player-card-header-background` CSS variable; the
131
+ framework does not ship a brand palette.
132
+
133
+ Example (host CSS):
134
+
135
+ ```css
136
+ pie-section-player-passage-card,
137
+ pie-section-player-item-card {
138
+ --pie-section-player-card-header-background: #c9e5e6;
139
+ }
140
+ ```
141
+
142
+ When both max-width attributes are set, the with-passage cap resolves to the greater
143
+ of the two configured values (after clamp), so with-passage mode never ends up narrower
144
+ than no-passage mode.
71
145
 
72
146
  ### API direction: CE defaults first, JS customization for advanced cases
73
147
 
@@ -75,29 +149,107 @@ The intended usage model is:
75
149
 
76
150
  - **CE props for default/standard flows (roughly 90% use cases)**:
77
151
  - `assessment-id`, `section`, `section-id`, `attempt-id`, `debug`
78
- - `show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`, `enabled-tools`, `item-toolbar-tools`, `passage-toolbar-tools`
152
+ - `show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`
153
+ - `content-max-width-no-passage`, `content-max-width-with-passage`, `split-pane-min-region-width`, `split-pane-collapse-strategy`
154
+ - `enabled-tools`, `item-toolbar-tools`, `passage-toolbar-tools`
79
155
  - **JS API for advanced customization**:
80
156
  - Get the controller handle via `getSectionController()` or `waitForSectionController()`
81
157
  - Listen to `section-controller-ready`
82
158
  - Apply custom policy/gating in host code (for example, domain-specific `canNext` based on controller events like `section-items-complete-changed`)
83
159
  - Compose forward/backward eligibility in host code using `selectNavigation()` + host state; there is intentionally no separate parallel CE gating API for this
84
160
  - Inject custom toolbar tooling with `toolRegistry` and optional host button arrays (`sectionHostButtons`, `itemHostButtons`, `passageHostButtons`)
85
- - Customize item/passage card headings via `cardTitleFormatter`
161
+ - Register host callbacks via `hooks` (for example `hooks.cardTitleFormatter`)
86
162
 
87
163
  Example:
88
164
 
89
165
  ```ts
90
166
  const host = document.querySelector("pie-section-player-splitpane") as any;
91
- host.cardTitleFormatter = (context: any) => {
92
- if (context.kind === "item") {
93
- return `Question ${context.itemIndex + 1}: ${context.item?.name || context.defaultTitle}`;
94
- }
95
- return context.passage?.name || context.defaultTitle;
167
+ host.hooks = {
168
+ cardTitleFormatter: (context: any) => {
169
+ if (context.kind === "item") {
170
+ return `Question ${context.itemIndex + 1}: ${context.item?.name || context.defaultTitle}`;
171
+ }
172
+ return context.passage?.name || context.defaultTitle;
173
+ },
96
174
  };
97
175
  ```
98
176
 
99
177
  Advanced CE props are still supported as escape hatches (`runtime`, `coordinator`, `createSectionController`, etc.), but hosts should prefer JS/controller composition for non-standard behavior.
100
178
 
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.
214
+
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
+ };
223
+ ```
224
+
225
+ **Imperative escape hatch (host-owned focus moments).** Every layout
226
+ element (`pie-section-player-splitpane`, `-vertical`, `-tabbed`,
227
+ `-kernel-host`, and `-base`) exposes a `focusStart(): boolean` method.
228
+ Call it from host-owned affordances the framework cannot observe — most
229
+ commonly a "Skip to Main" button in the host's ribbon:
230
+
231
+ ```ts
232
+ document.querySelector("pie-section-player-splitpane")?.focusStart();
233
+ ```
234
+
235
+ `focusStart()` honors the `autoFocus` strategy so Skip-to-Main lands
236
+ wherever the host has opted in for navigation focus:
237
+
238
+ - `"start-of-content"` *(default)* → passage card when present, else first
239
+ item card.
240
+ - `"current-item"` → the item card currently marked `is-current`, falling
241
+ back to start-of-content if no current item is resolvable.
242
+ - `"none"` → start-of-content (hosts call `focusStart()` precisely
243
+ because they want focus to move).
244
+
245
+ For Next / Back / question-number navigation the host does **not** call
246
+ `focusStart()` — the `autoFocus` policy fires automatically.
247
+
248
+ **Deprecation.** `SectionPlayerFocusPolicy.autoFocusFirstItem` still
249
+ works for one release (mapped onto `autoFocus`: `true` →
250
+ `"start-of-content"`, `false` → `"none"`) and emits a one-time console
251
+ warning. It will be removed in the next major. Migrate to `autoFocus`.
252
+
101
253
  ### Navigation signals
102
254
 
103
255
  - `item-selected`: item-level navigation change within the current section in the `SectionController` broadcast stream (`itemIndex`, `currentItemId`, `totalItems`).
@@ -333,6 +485,29 @@ await controller?.updateItemSession?.("q1", {
333
485
  The same controller snapshot is what the persistence strategy saves/loads.
334
486
  When a controller is reused for the same `sectionId`/`attemptId`, `updateInput()` refreshes composition input while preserving in-memory section session data.
335
487
 
488
+ ## Content trust boundary
489
+
490
+ Section-player layouts embed `<pie-item-player>` elements for each item.
491
+ Item and passage markup is sanitized by default via DOMPurify; see
492
+ [pie-item-player README](./README.md#content-trust-boundary)
493
+ property. Hosts can forward those settings through the section-player
494
+ `runtime.player` overrides — the runtime flattens these onto the embedded
495
+ `<pie-item-player>` instance, so any field not recognized by the kernel is
496
+ passed straight through as a prop/attribute:
497
+
498
+ ```ts
499
+ const runtime = {
500
+ playerType: "iife",
501
+ player: {
502
+ trustMarkup: false, // default, keeps DOMPurify on
503
+ // sanitizeMarkup: (html) => myCustomSanitize(html),
504
+ },
505
+ };
506
+ ```
507
+
508
+ Set `trustMarkup: true` only when the section payload is guaranteed to be
509
+ produced by a trusted pipeline.
510
+
336
511
  ## Exports
337
512
 
338
513
  Published exports are intentionally minimal:
@@ -340,15 +515,20 @@ Published exports are intentionally minimal:
340
515
  - `@pie-players/pie-section-player`
341
516
  - `@pie-players/pie-section-player/components/section-player-splitpane-element`
342
517
  - `@pie-players/pie-section-player/components/section-player-vertical-element`
518
+ - `@pie-players/pie-section-player/components/section-player-tabbed-element`
343
519
  - `@pie-players/pie-section-player/components/section-player-kernel-host-element`
344
520
  - `@pie-players/pie-section-player/components/section-player-shell-element`
345
521
  - `@pie-players/pie-section-player/components/section-player-item-card-element`
346
522
  - `@pie-players/pie-section-player/components/section-player-passage-card-element`
523
+ - `@pie-players/pie-section-player/components/section-player-items-pane-element`
524
+ - `@pie-players/pie-section-player/components/section-player-passages-pane-element`
347
525
  - `@pie-players/pie-section-player/contracts/layout-contract`
348
526
  - `@pie-players/pie-section-player/contracts/public-events`
349
527
  - `@pie-players/pie-section-player/contracts/runtime-host-contract`
350
528
  - `@pie-players/pie-section-player/contracts/layout-parity-metadata`
529
+ - `@pie-players/pie-section-player/contracts/host-hooks`
351
530
  - `@pie-players/pie-section-player/policies`
531
+ - `@pie-players/pie-section-player/utils/player-preload`
352
532
 
353
533
  ## Development
354
534