@pie-element/ebsr 14.0.0 → 14.1.0

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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pie-element/ebsr",
3
3
  "repository": "pie-framework/pie-elements",
4
- "version": "14.0.0",
4
+ "version": "14.1.0",
5
5
  "description": "",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "dependencies": {
10
- "@pie-element/multiple-choice": "^13.0.0",
10
+ "@pie-element/multiple-choice": "^13.1.0",
11
11
  "@pie-framework/pie-player-events": "^0.1.0",
12
12
  "@pie-lib/translator": "4.0.0",
13
13
  "classnames": "^2.2.5",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "author": "pie framework developers",
18
18
  "license": "ISC",
19
- "gitHead": "ae83eee44da92ef9c135dbeeb94a889d0b14cdff",
19
+ "gitHead": "9a68a6e5ec0c5eea5873f5dbf50baa7625b9d214",
20
20
  "scripts": {
21
21
  "postpublish": "../../scripts/postpublish"
22
22
  },
package/src/index.js CHANGED
@@ -26,6 +26,38 @@ export const isSessionComplete = (session) => {
26
26
  return isNonEmptyArray(a) && isNonEmptyArray(b);
27
27
  };
28
28
 
29
+ function getPlayerAttributes(element) {
30
+ const player =
31
+ element.closest('pie-player') ||
32
+ element.closest('pie-item-player');
33
+
34
+ if (!player) {
35
+ return { baseHeadingLevel: undefined, includeSrHeading: true };
36
+ }
37
+
38
+ const getRaw = (camelCaseName, hyphenatedName, allLowerName) => {
39
+ let raw = player[camelCaseName];
40
+
41
+ // fallback in case someone sets via HTML attribute manually
42
+ if (raw == null) {
43
+ raw =
44
+ player.getAttribute(hyphenatedName) ??
45
+ player.getAttribute(allLowerName);
46
+ }
47
+
48
+ return raw;
49
+ };
50
+
51
+ const levelRaw = getRaw('baseHeadingLevel', 'base-heading-level', 'baseheadinglevel');
52
+ const level = parseInt(levelRaw, 10);
53
+ const baseHeadingLevel = Number.isFinite(level) && level >= 1 && level <= 6 ? level : undefined;
54
+
55
+ const srRaw = getRaw('includeSrHeading', 'include-sr-heading', 'includesrheading');
56
+ const includeSrHeading = srRaw == null ? true : srRaw !== false && srRaw !== 'false';
57
+
58
+ return { baseHeadingLevel, includeSrHeading };
59
+ }
60
+
29
61
  export default class Ebsr extends HTMLElement {
30
62
  constructor() {
31
63
  super();
@@ -85,6 +117,12 @@ export default class Ebsr extends HTMLElement {
85
117
  mode,
86
118
  keyMode: this._model[key].choicePrefix,
87
119
  };
120
+
121
+ // Parts of an EBSR item should not render their own SR headings —
122
+ // the EBSR element itself provides the item-level heading.
123
+ const { includeSrHeading, baseHeadingLevel } = getPlayerAttributes(this);
124
+ part.includeSrHeading = includeSrHeading;
125
+ part.baseHeadingLevel = baseHeadingLevel !== undefined ? Math.min(6, baseHeadingLevel + (includeSrHeading ? 1 : 0)) : undefined;
88
126
  }
89
127
  }
90
128
 
@@ -116,16 +154,43 @@ export default class Ebsr extends HTMLElement {
116
154
 
117
155
  connectedCallback() {
118
156
  this._render();
157
+ this._initPlayerObserver();
119
158
  this.addEventListener(SESSION_CHANGED, this.onSessionUpdated);
120
159
  }
121
160
 
122
161
  disconnectedCallback() {
162
+ this._disconnectPlayerObserver();
123
163
  this.removeEventListener(SESSION_CHANGED, this.onSessionUpdated);
124
164
  }
125
165
 
166
+ _initPlayerObserver() {
167
+ const player = this.closest('pie-player') || this.closest('pie-item-player');
168
+ if (!player) return;
169
+
170
+ this._playerObserver = new MutationObserver(() => {
171
+ this._render();
172
+ });
173
+ this._playerObserver.observe(player, {
174
+ attributes: true,
175
+ attributeFilter: ['base-heading-level', 'baseheadinglevel', 'include-sr-heading', 'includesrheading'],
176
+ });
177
+ }
178
+
179
+ _disconnectPlayerObserver() {
180
+ if (this._playerObserver) {
181
+ this._playerObserver.disconnect();
182
+ this._playerObserver = null;
183
+ }
184
+ }
185
+
126
186
  _render() {
127
187
  this.ariaLabel = 'Two-Part Question';
128
188
  this.role = 'region';
189
+
190
+ const { baseHeadingLevel: ebsrLevel, includeSrHeading } = getPlayerAttributes(this);
191
+ const headingTag = ebsrLevel ? `h${Math.min(6, ebsrLevel)}` : 'h2';
192
+ const srHeading = includeSrHeading ? `<${headingTag} class="srOnly">Two-Part Question</${headingTag}>` : '';
193
+
129
194
  this.innerHTML = `
130
195
  <style>
131
196
  .srOnly {
@@ -140,7 +205,7 @@ export default class Ebsr extends HTMLElement {
140
205
  }
141
206
  ${this._model?.extraCSSRules?.rules}
142
207
  </style>
143
- <h2 class="srOnly">Two-Part Question</h2>
208
+ ${srHeading}
144
209
  <${MC_TAG_NAME} id="a"></${MC_TAG_NAME}>
145
210
  <${MC_TAG_NAME} id="b"></${MC_TAG_NAME}>
146
211
  `;