@pie-players/pie-section-player 0.3.64 → 0.3.65

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
@@ -138,6 +138,7 @@ Both layout elements support:
138
138
  - `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).
139
139
  - `split-pane-min-region-width` (number, optional): splitpane minimum pane width in px. Clamped to 160–1200. Unset by default (split bounds stay at 20–80). (Ignored by vertical layout; supported for API parity.)
140
140
  - `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.)
141
+ - `base-heading-level` (number, optional): the heading level this player's card headings occupy, and the level every descendant's outline derives from. Clamped to 1–6; default 2. See [Heading structure](#heading-structure).
141
142
  - `show-toolbar` (boolean-like): accepts `true/false` and common string forms (`"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"`)
142
143
  - Host extension props (JS properties only): `toolRegistry`, `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`, `hooks`
143
144
 
@@ -170,6 +171,51 @@ To force splitpane stacked mode to use vertical rendering:
170
171
 
171
172
  By default, splitpane stacked mode uses tabs. The dedicated `pie-section-player-tabbed` layout also always renders passage/items tabs when passages are present.
172
173
 
174
+ ### Heading structure
175
+
176
+ The player publishes one number and every descendant derives its outline from it.
177
+ Set `base-heading-level` to the level the cards should occupy in the surrounding
178
+ page — 2 when the page has its own `<h1>` above the player, 3 when the player sits
179
+ under an `<h2>`, and so on:
180
+
181
+ ```html
182
+ <pie-section-player-splitpane base-heading-level="3"></pie-section-player-splitpane>
183
+ ```
184
+
185
+ At the default of 2 that produces:
186
+
187
+ ```
188
+ h2 Passage <- passage card heading
189
+ h3 Sea Turtles in Trouble <- passage title
190
+ h4 Danger on Land <- authored data-heading content
191
+ h2 Question 1 <- item card heading
192
+ h3 Part A <- authored data-heading content in the prompt
193
+ ```
194
+
195
+ The two content kinds derive different levels from the same number, and the
196
+ difference is deliberate. An item card's heading *is* the item's heading, so the
197
+ item player is told not to emit a screen-reader item heading of its own — one at
198
+ that level already exists, and a second would read as its sibling. A passage
199
+ card's heading is a group label, so the passage player is told to start one level
200
+ deeper, putting the passage's own title beneath it.
201
+
202
+ Authored `data-heading="headingN"` markup in passages and prompts becomes real
203
+ heading elements only when a level is published, which the player now always does.
204
+ Content authored against
205
+ [PIE-151](https://illuminate.atlassian.net/browse/PIE-151) therefore renders as
206
+ structure without any host configuration.
207
+
208
+ A host that needs the element's own screen-reader item heading — because it is not
209
+ supplying question headings of its own — overrides per player through the runtime:
210
+
211
+ ```js
212
+ sectionPlayer.runtime = { player: { includeSrHeading: true } };
213
+ ```
214
+
215
+ The pattern behind this, and the reason the value is published rather than pushed,
216
+ is in
217
+ [`docs/architecture/composition-context.md`](../../docs/architecture/composition-context.md).
218
+
173
219
  ### Tab styling hooks
174
220
 
175
221
  `pie-section-player-tabbed` and splitpane `tabbed` collapse mode expose canonical `pie-*`
@@ -234,11 +280,17 @@ than no-passage mode.
234
280
 
235
281
  ### Item card media region
236
282
 
237
- An item card can carry a `data-region="media"` region beside its content region,
238
- holding a resolved accessibility-catalog media card — today a sign-language
239
- (ASL) video, gated on the `signLanguage` PNP support. The region only appears
240
- when the item carries a matching catalog card *and* policy grants the feature, so
241
- there is no dead affordance on items without signing.
283
+ An item card can carry a `data-region="media"` region beside its content region.
284
+ It is a host surface named `item-media`, not a feature: whatever capability
285
+ declares `surfaces: ['item-media']` in its registration renders there, and this
286
+ package names none of them. The region appears only when policy grants the
287
+ capability *and* the capability's own `requiresAuthoredContent` resolves, so
288
+ there is no dead affordance on items that carry nothing.
289
+
290
+ Today's occupant is `@pie-players/pie-tool-sign-language` — a signed (ASL)
291
+ translation gated on the `signLanguage` PNP support. It is not part of the
292
+ packaged capability set: a deployment opts in by registering it on the tool
293
+ registry it passes to the player.
242
294
 
243
295
  It sits to the right of the question content and is resizable via a
244
296
  keyboard-accessible divider (`role="separator"`; arrow keys, `Home`/`End`,
@@ -246,20 +298,13 @@ keyboard-accessible divider (`role="separator"`; arrow keys, `Home`/`End`,
246
298
  the content and the divider is withdrawn. Placement is fixed in this iteration:
247
299
  there is no orientation toggle and no free repositioning.
248
300
 
249
- Signing legibility needs height for hands and face, so the video is sized by an
250
- aspect-ratio target with a height floor rather than by width alone. Hosts can
251
- retune it:
252
-
253
- - `--pie-section-player-item-media-aspect-ratio` (default `3 / 4`)
254
- - `--pie-section-player-item-media-min-height` (default `220px`)
255
- - `--pie-section-player-item-media-max-height` (default `60vh`)
256
-
257
- ```css
258
- pie-section-player-item-card {
259
- --pie-section-player-item-media-aspect-ratio: 4 / 5;
260
- --pie-section-player-item-media-min-height: 260px;
261
- }
262
- ```
301
+ This package owns the region's share of the card width and nothing inside it. A
302
+ capability mounted here sizes its own content: signing legibility needs height for
303
+ hands and face, so it is sized by an aspect-ratio target with a height floor
304
+ rather than by width alone. The `--pie-section-player-item-media-*` tokens hosts
305
+ set for that belong to `@pie-players/pie-tool-sign-language` and are documented
306
+ with their defaults in [its README](../tool-sign-language/README.md) — they keep
307
+ the `pie-section-player` prefix because hosts already set them by those names.
263
308
 
264
309
  ### API direction: CE defaults first, JS customization for advanced cases
265
310
 
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The media region a content card docks an alternate into: its surface name, and
3
+ * how wide it is.
4
+ *
5
+ * Sizing only. Which capability fills the region, and whether it has anything to
6
+ * show, belongs to the capability: the card asks the registry for whatever is
7
+ * registered on the surface below and knows nothing else about it. The
8
+ * signed-alternate resolution that used to live here moved to
9
+ * `@pie-players/pie-tool-sign-language` behind that capability's own
10
+ * `requiresAuthoredContent`.
11
+ */
12
+ /**
13
+ * Host slot for media docked beside a card's content.
14
+ *
15
+ * Named for the relationship rather than for one card kind, because item cards
16
+ * and passage cards open the same slot: an alternate is authored against a
17
+ * content node, and a passage owns content nodes exactly as an item does. A
18
+ * capability declares this surface once and reaches both.
19
+ */
20
+ export declare const CONTENT_MEDIA_SURFACE = "content-media";
21
+ /**
22
+ * Default and bounds for the media region's share of the card width.
23
+ *
24
+ * Docked media generally needs height rather than width — a person on camera
25
+ * needs room for hands and face — so the region is sized by an aspect-ratio
26
+ * target with a minimum height rather than by width alone. A flat viewport
27
+ * percentage either wastes space on a short clip or crushes it on a narrow
28
+ * device. The percentage below only decides how the card's width is split;
29
+ * legibility is defended by the mounted element's own CSS.
30
+ */
31
+ export declare const MEDIA_REGION_DEFAULT_PERCENT = 34;
32
+ export declare const MEDIA_REGION_MIN_PERCENT = 20;
33
+ export declare const MEDIA_REGION_MAX_PERCENT = 55;
34
+ /** Below this card width the split is dropped and the region stacks. */
35
+ export declare const MEDIA_REGION_STACK_BREAKPOINT_PX = 560;
36
+ export declare function clampMediaRegionPercent(value: number, min?: number, max?: number): number;
37
+ /**
38
+ * Convert a pointer drag into a media-region width percentage.
39
+ *
40
+ * Container-relative, unlike the passage/items divider's fixed 0.1%-per-pixel
41
+ * factor: the same drag has to mean the same thing in a wide card and a narrow
42
+ * one. The region is on the right, so dragging left grows it.
43
+ */
44
+ export declare function mediaRegionPercentFromDrag(args: {
45
+ startPercent: number;
46
+ deltaX: number;
47
+ containerWidthPx: number;
48
+ min?: number;
49
+ max?: number;
50
+ }): number;
@@ -1,6 +1,39 @@
1
1
  import { ItemEntity } from '@pie-players/pie-players-shared/types';
2
2
  import { SectionCompositionModel } from '../../controllers/types.js';
3
3
  import { PlayerElementParams } from './player-action.js';
4
+ /**
5
+ * Heading depth as composition context.
6
+ *
7
+ * Where an item's headings belong in a document outline is a fact about the
8
+ * page, not about the item: the same item renders under a host `<h1>` here and
9
+ * three levels down there. The section player is the publisher — it knows the
10
+ * level its own card headings occupy — and each PIE element is a resolver,
11
+ * reading the level off the player host and deriving the outline it emits.
12
+ * `docs/architecture/composition-context.md` states the pattern and its
13
+ * invariants.
14
+ *
15
+ * The two content kinds get different values from the same level, and the
16
+ * difference is not arbitrary:
17
+ *
18
+ * - An item card's heading ("Question 3") *is* the item's heading. The element
19
+ * must not emit a second one at that level, so it renders no screen-reader
20
+ * item heading and nests authored content one level below.
21
+ * - A passage card's heading ("Passage") is a group label. The passage's own
22
+ * title is real content and belongs beneath it, so the passage player's level
23
+ * is one deeper again.
24
+ *
25
+ * At the default this yields card `h2` -> passage title `h3` -> passage content
26
+ * `h4`, and question `h2` -> prompt content `h3`, which is the outline
27
+ * PIE-159 specifies.
28
+ */
29
+ export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
30
+ /** Matches the level the cards rendered at before it was configurable. */
31
+ export declare const DEFAULT_SECTION_BASE_HEADING_LEVEL: HeadingLevel;
32
+ export declare function normalizeBaseHeadingLevel(value: unknown): HeadingLevel;
33
+ /** Card heading fills the item's slot, so the element adds none of its own. */
34
+ export declare function itemHeadingContext(level: HeadingLevel): Record<string, unknown>;
35
+ /** Card heading is a group label; the passage's own title sits one below it. */
36
+ export declare function passageHeadingContext(level: HeadingLevel): Record<string, unknown>;
4
37
  export type LayoutCompositionSnapshot = {
5
38
  compositionModel: SectionCompositionModel;
6
39
  passages: SectionCompositionModel["passages"];
@@ -17,6 +50,7 @@ export declare function getPassagePlayerParams(args: {
17
50
  resolvedPlayerAttributes: Record<string, string>;
18
51
  resolvedPlayerProps: Record<string, unknown>;
19
52
  playerStrategy: string;
53
+ baseHeadingLevel?: HeadingLevel;
20
54
  }): PlayerElementParams;
21
55
  export declare function getItemPlayerParams(args: {
22
56
  item: ItemEntity;
@@ -26,6 +60,7 @@ export declare function getItemPlayerParams(args: {
26
60
  resolvedPlayerProps: Record<string, unknown>;
27
61
  playerStrategy: string;
28
62
  itemIndex?: number;
63
+ baseHeadingLevel?: HeadingLevel;
29
64
  }): PlayerElementParams;
30
65
  export declare function getCanonicalItemId(args: {
31
66
  compositionModel: SectionCompositionModel;