@pie-element/categorize 11.1.0 → 11.2.0-mui-update.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/configure/CHANGELOG.md +66 -0
  3. package/configure/lib/defaults.js +2 -5
  4. package/configure/lib/defaults.js.map +1 -1
  5. package/configure/lib/design/builder.js +15 -33
  6. package/configure/lib/design/builder.js.map +1 -1
  7. package/configure/lib/design/buttons.js +44 -95
  8. package/configure/lib/design/buttons.js.map +1 -1
  9. package/configure/lib/design/categories/RowLabel.js +32 -45
  10. package/configure/lib/design/categories/RowLabel.js.map +1 -1
  11. package/configure/lib/design/categories/alternateResponses.js +102 -251
  12. package/configure/lib/design/categories/alternateResponses.js.map +1 -1
  13. package/configure/lib/design/categories/category.js +138 -208
  14. package/configure/lib/design/categories/category.js.map +1 -1
  15. package/configure/lib/design/categories/choice-preview.js +59 -126
  16. package/configure/lib/design/categories/choice-preview.js.map +1 -1
  17. package/configure/lib/design/categories/droppable-placeholder.js +76 -165
  18. package/configure/lib/design/categories/droppable-placeholder.js.map +1 -1
  19. package/configure/lib/design/categories/index.js +199 -384
  20. package/configure/lib/design/categories/index.js.map +1 -1
  21. package/configure/lib/design/choices/choice.js +160 -263
  22. package/configure/lib/design/choices/choice.js.map +1 -1
  23. package/configure/lib/design/choices/config.js +46 -98
  24. package/configure/lib/design/choices/config.js.map +1 -1
  25. package/configure/lib/design/choices/index.js +152 -236
  26. package/configure/lib/design/choices/index.js.map +1 -1
  27. package/configure/lib/design/header.js +62 -111
  28. package/configure/lib/design/header.js.map +1 -1
  29. package/configure/lib/design/index.js +632 -476
  30. package/configure/lib/design/index.js.map +1 -1
  31. package/configure/lib/design/input-header.js +97 -149
  32. package/configure/lib/design/input-header.js.map +1 -1
  33. package/configure/lib/design/utils.js +4 -15
  34. package/configure/lib/design/utils.js.map +1 -1
  35. package/configure/lib/index.js +120 -183
  36. package/configure/lib/index.js.map +1 -1
  37. package/configure/lib/main.js +31 -74
  38. package/configure/lib/main.js.map +1 -1
  39. package/configure/lib/utils.js +22 -32
  40. package/configure/lib/utils.js.map +1 -1
  41. package/configure/package.json +15 -14
  42. package/controller/CHANGELOG.md +54 -0
  43. package/controller/lib/defaults.js +2 -5
  44. package/controller/lib/defaults.js.map +1 -1
  45. package/controller/lib/index.js +238 -315
  46. package/controller/lib/index.js.map +1 -1
  47. package/controller/lib/utils.js +40 -31
  48. package/controller/lib/utils.js.map +1 -1
  49. package/controller/package.json +5 -5
  50. package/lib/categorize/categories.js +110 -164
  51. package/lib/categorize/categories.js.map +1 -1
  52. package/lib/categorize/category.js +72 -122
  53. package/lib/categorize/category.js.map +1 -1
  54. package/lib/categorize/choice.js +116 -245
  55. package/lib/categorize/choice.js.map +1 -1
  56. package/lib/categorize/choices.js +66 -131
  57. package/lib/categorize/choices.js.map +1 -1
  58. package/lib/categorize/droppable-placeholder.js +49 -103
  59. package/lib/categorize/droppable-placeholder.js.map +1 -1
  60. package/lib/categorize/grid-content.js +39 -87
  61. package/lib/categorize/grid-content.js.map +1 -1
  62. package/lib/categorize/index.js +341 -316
  63. package/lib/categorize/index.js.map +1 -1
  64. package/lib/index.js +286 -271
  65. package/lib/index.js.map +1 -1
  66. package/package.json +16 -13
  67. package/LICENSE.md +0 -5
package/lib/index.js CHANGED
@@ -1,301 +1,316 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
8
- exports["default"] = void 0;
9
-
10
- var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
-
12
- var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
13
-
14
- var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
15
-
16
- var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
17
-
18
- var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
19
-
20
- var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));
21
-
7
+ exports.default = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
22
9
  var _react = _interopRequireDefault(require("react"));
23
-
24
- var _reactDom = _interopRequireDefault(require("react-dom"));
25
-
10
+ var _client = require("react-dom/client");
26
11
  var _mathRendering = require("@pie-lib/math-rendering");
27
-
28
12
  var _renderUi = require("@pie-lib/render-ui");
29
-
30
13
  var _piePlayerEvents = require("@pie-framework/pie-player-events");
31
-
32
14
  var _categorize = _interopRequireDefault(require("./categorize"));
33
-
34
- function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
35
-
36
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
37
-
38
- var Categorize = /*#__PURE__*/function (_HTMLElement) {
39
- (0, _inherits2["default"])(Categorize, _HTMLElement);
40
-
41
- var _super = _createSuper(Categorize);
42
-
43
- function Categorize() {
44
- (0, _classCallCheck2["default"])(this, Categorize);
45
- return _super.apply(this, arguments);
46
- }
47
-
48
- (0, _createClass2["default"])(Categorize, [{
49
- key: "model",
50
- set: function set(m) {
51
- this._model = m;
52
- this.eliminateBlindAnswersFromSession();
53
- this.dispatchEvent(new _piePlayerEvents.ModelSetEvent(this.tagName.toLowerCase(), this.isComplete(), !!this._model)); // reset the audioInitialized to false since the model changed, and we might need to reinitialize the audio
54
-
55
- this._audioInitialized = false;
56
- this.render();
57
- }
58
- }, {
59
- key: "isComplete",
60
- value: function isComplete() {
61
- var _ref = this._model || {},
62
- autoplayAudioEnabled = _ref.autoplayAudioEnabled,
63
- completeAudioEnabled = _ref.completeAudioEnabled;
64
-
65
- var elementContext = this; // check audio completion if audio settings are enabled and audio actually exists
66
-
67
- if (autoplayAudioEnabled && completeAudioEnabled && !this.audioComplete) {
68
- if (elementContext) {
69
- var audio = elementContext.querySelector('audio');
70
- var isInsidePrompt = audio && audio.closest('#preview-prompt'); // only require audio completion if audio exists and is inside the prompt
71
-
72
- if (audio && isInsidePrompt) {
73
- return false;
74
- }
15
+ class Categorize extends HTMLElement {
16
+ constructor() {
17
+ super();
18
+ (0, _defineProperty2.default)(this, "_scheduleMathRender", () => {
19
+ if (this._mathRenderPending) return;
20
+ this._mathRenderPending = true;
21
+ requestAnimationFrame(() => {
22
+ if (this._mathObserver && !this._mathObserverPaused) {
23
+ this._mathObserver.disconnect();
75
24
  }
76
- }
77
-
78
- if (!this._session || !this._session.answers) {
79
- return false;
80
- }
81
-
82
- if (!Array.isArray(this._session.answers)) {
83
- return false;
84
- }
85
-
86
- return this._session.answers.some(function (answer) {
87
- return answer.choices && answer.choices.length > 0;
25
+ (0, _mathRendering.renderMath)(this);
26
+ this._mathRenderPending = false;
27
+ setTimeout(() => {
28
+ if (this._mathObserver && !this._mathObserverPaused) {
29
+ this._mathObserver.observe(this, {
30
+ childList: true,
31
+ subtree: true,
32
+ characterData: false
33
+ });
34
+ }
35
+ }, 50);
88
36
  });
89
- }
90
- }, {
91
- key: "session",
92
- get: function get() {
93
- return this._session;
94
- },
95
- set: function set(s) {
96
- if (s && !s.answers) {
97
- s.answers = [];
37
+ });
38
+ (0, _defineProperty2.default)(this, "pauseMathObserver", () => {
39
+ if (this._mathObserver) {
40
+ this._mathObserver.disconnect();
41
+ this._mathObserverPaused = true;
98
42
  }
99
-
100
- this._session = s;
101
- this.render();
102
- }
103
- }, {
104
- key: "eliminateBlindAnswersFromSession",
105
- value: function eliminateBlindAnswersFromSession() {
106
- var _ref2 = this._session || {},
107
- _ref2$answers = _ref2.answers,
108
- answers = _ref2$answers === void 0 ? [] : _ref2$answers;
109
-
110
- var _ref3 = this._model || {},
111
- _ref3$choices = _ref3.choices,
112
- choices = _ref3$choices === void 0 ? [] : _ref3$choices;
113
-
114
- var mappedChoices = choices.map(function (c) {
115
- return c.id;
116
- }) || [];
117
- var filteredAnswers = answers.map(function (answer) {
118
- var answerChoices = (answer === null || answer === void 0 ? void 0 : answer.choices) || [];
119
- answer.choices = answerChoices.filter(function (c) {
120
- return mappedChoices.includes(c);
121
- });
122
- return answer;
123
- });
124
-
125
- if (filteredAnswers.length > 0) {
126
- this.changeAnswers(filteredAnswers);
43
+ });
44
+ (0, _defineProperty2.default)(this, "resumeMathObserver", () => {
45
+ if (this._mathObserverPaused) {
46
+ this._mathObserverPaused = false;
47
+ if (this._mathObserver) {
48
+ this._mathObserver.observe(this, {
49
+ childList: true,
50
+ subtree: true,
51
+ characterData: false
52
+ });
53
+ }
127
54
  }
55
+ });
56
+ this._root = null;
57
+ this._mathObserver = null;
58
+ this._mathRenderPending = false;
59
+ }
60
+ _initMathObserver() {
61
+ if (this._mathObserver) return;
62
+ this._mathObserver = new MutationObserver(() => {
63
+ this._scheduleMathRender();
64
+ });
65
+ this._mathObserver.observe(this, {
66
+ childList: true,
67
+ subtree: true,
68
+ characterData: false
69
+ });
70
+ }
71
+ _disconnectMathObserver() {
72
+ if (this._mathObserver) {
73
+ this._mathObserver.disconnect();
74
+ this._mathObserver = null;
128
75
  }
129
- }, {
130
- key: "changeAnswers",
131
- value: function changeAnswers(answers) {
132
- this._session.answers = answers;
133
- this._session.selector = 'Mouse';
134
- this.dispatchEvent(new _piePlayerEvents.SessionChangedEvent(this.tagName.toLowerCase(), this.isComplete()));
135
- this.render();
76
+ }
77
+ set model(m) {
78
+ this._model = m;
79
+ this.eliminateBlindAnswersFromSession();
80
+ this.dispatchEvent(new _piePlayerEvents.ModelSetEvent(this.tagName.toLowerCase(), this.isComplete(), !!this._model));
81
+ // reset the audioInitialized to false since the model changed, and we might need to reinitialize the audio
82
+ this._audioInitialized = false;
83
+ this.render();
84
+ }
85
+ isComplete() {
86
+ const {
87
+ autoplayAudioEnabled,
88
+ choices,
89
+ completeAudioEnabled,
90
+ hasUnplacedChoices,
91
+ possibleResponses,
92
+ responseAreasToBeFilled
93
+ } = this._model || {};
94
+ const elementContext = this;
95
+
96
+ // check audio completion if audio settings are enabled and audio actually exists
97
+ if (autoplayAudioEnabled && completeAudioEnabled && !this.audioComplete) {
98
+ if (elementContext) {
99
+ const audio = elementContext.querySelector('audio');
100
+ const isInsidePrompt = audio && audio.closest('#preview-prompt');
101
+
102
+ // only require audio completion if audio exists and is inside the prompt
103
+ if (audio && isInsidePrompt) {
104
+ return false;
105
+ }
106
+ }
136
107
  }
137
- }, {
138
- key: "onShowCorrectToggle",
139
- value: function onShowCorrectToggle() {
140
- (0, _mathRendering.renderMath)(this);
108
+ if (!this._session || !this._session.answers) {
109
+ return false;
141
110
  }
142
- }, {
143
- key: "_createAudioInfoToast",
144
- value: function _createAudioInfoToast() {
145
- var info = document.createElement('div');
146
- info.id = 'play-audio-info';
147
- Object.assign(info.style, {
148
- position: 'absolute',
149
- top: 0,
150
- width: '100%',
151
- height: '100%',
152
- display: 'flex',
153
- justifyContent: 'center',
154
- alignItems: 'center',
155
- background: 'white',
156
- zIndex: '1000',
157
- cursor: 'pointer'
158
- });
159
- var img = document.createElement('img');
160
- img.src = _renderUi.EnableAudioAutoplayImage;
161
- img.alt = 'Click anywhere to enable audio autoplay';
162
- img.width = 500;
163
- img.height = 300;
164
- info.appendChild(img);
165
- return info;
111
+ const {
112
+ answers
113
+ } = this._session;
114
+ if (!Array.isArray(answers)) {
115
+ return false;
166
116
  }
167
- }, {
168
- key: "connectedCallback",
169
- value: function connectedCallback() {
170
- var _this = this;
171
-
172
- // Observation: audio in Chrome will have the autoplay attribute,
173
- // while other browsers will not have the autoplay attribute and will need a user interaction to play the audio
174
- // This workaround fixes the issue of audio being cached and played on any user interaction in Safari and Firefox
175
- var observer = new MutationObserver(function (mutationsList, observer) {
176
- mutationsList.forEach(function (mutation) {
177
- if (mutation.type === 'childList') {
178
- if (_this._audioInitialized) return;
179
117
 
180
- var audio = _this.querySelector('audio');
181
-
182
- var isInsidePrompt = audio && audio.closest('#preview-prompt');
183
- if (!_this._model) return;
184
- if (!_this._model.autoplayAudioEnabled) return;
185
- if (audio && !isInsidePrompt) return;
186
- if (!audio) return;
187
-
188
- var info = _this._createAudioInfoToast();
118
+ // filter answers by category and count the ones with content
119
+ const filledResponseAreas = answers.filter(answer => answer.choices.length).length;
120
+ // check if an answer choice was added to at least as many response areas
121
+ // as the number of populated response areas in the correct answer
122
+ const areResponseAreasFilled = filledResponseAreas >= responseAreasToBeFilled;
123
+ // check if multiple placements are allowed
124
+ const duplicatesAllowed = (choices || []).some(choice => choice.categoryCount === 0);
125
+ if (duplicatesAllowed) {
126
+ // an answer choice can be used multiple times
127
+ return areResponseAreasFilled;
128
+ }
189
129
 
190
- var container = _this.querySelector('#main-container');
130
+ // any correct answer have any unplaced answer choices (by the author)
131
+ if (hasUnplacedChoices) {
132
+ return areResponseAreasFilled;
133
+ }
134
+ const allAnswersIds = answers.map(answer => answer.choices).flat();
191
135
 
192
- var enableAudio = function enableAudio() {
193
- if (_this.querySelector('#play-audio-info')) {
194
- audio.play();
195
- container.removeChild(info);
196
- }
136
+ // check if any correct answer have any unplaced answer choices (by the student)
137
+ const requiredAnswersPlaced = (possibleResponses || []).some(response => response.every(val => allAnswersIds.includes(val)));
197
138
 
139
+ // true - all choices (required for a correct response) were placed into a response area
140
+ return requiredAnswersPlaced;
141
+ }
142
+ set session(s) {
143
+ if (s && !s.answers) {
144
+ s.answers = [];
145
+ }
146
+ this._session = s;
147
+ this.render();
148
+ }
149
+ get session() {
150
+ return this._session;
151
+ }
152
+ eliminateBlindAnswersFromSession() {
153
+ const {
154
+ answers = []
155
+ } = this._session || {};
156
+ const {
157
+ choices = []
158
+ } = this._model || {};
159
+ const mappedChoices = choices.map(c => c.id) || [];
160
+ const filteredAnswers = answers.map(answer => {
161
+ const answerChoices = answer?.choices || [];
162
+ answer.choices = answerChoices.filter(c => mappedChoices.includes(c));
163
+ return answer;
164
+ });
165
+ if (filteredAnswers.length > 0) {
166
+ this.changeAnswers(filteredAnswers);
167
+ }
168
+ }
169
+ changeAnswers(answers) {
170
+ this._session.answers = answers;
171
+ this._session.selector = 'Mouse';
172
+ this.dispatchEvent(new _piePlayerEvents.SessionChangedEvent(this.tagName.toLowerCase(), this.isComplete()));
173
+ this.render();
174
+ }
175
+ onShowCorrectToggle() {
176
+ (0, _mathRendering.renderMath)(this);
177
+ }
178
+ _createAudioInfoToast() {
179
+ const info = document.createElement('div');
180
+ info.id = 'play-audio-info';
181
+ Object.assign(info.style, {
182
+ position: 'absolute',
183
+ top: 0,
184
+ width: '100%',
185
+ height: '100%',
186
+ display: 'flex',
187
+ justifyContent: 'center',
188
+ alignItems: 'center',
189
+ background: 'white',
190
+ zIndex: '1000',
191
+ cursor: 'pointer'
192
+ });
193
+ const img = document.createElement('img');
194
+ img.src = _renderUi.EnableAudioAutoplayImage;
195
+ img.alt = 'Click anywhere to enable audio autoplay';
196
+ img.width = 500;
197
+ img.height = 300;
198
+ info.appendChild(img);
199
+ return info;
200
+ }
201
+ connectedCallback() {
202
+ this._initMathObserver();
203
+
204
+ // Observation: audio in Chrome will have the autoplay attribute,
205
+ // while other browsers will not have the autoplay attribute and will need a user interaction to play the audio
206
+ // This workaround fixes the issue of audio being cached and played on any user interaction in Safari and Firefox
207
+ const observer = new MutationObserver((mutationsList, observer) => {
208
+ mutationsList.forEach(mutation => {
209
+ if (mutation.type === 'childList') {
210
+ if (this._audioInitialized) return;
211
+ const audio = this.querySelector('audio');
212
+ const isInsidePrompt = audio && audio.closest('#preview-prompt');
213
+ if (!this._model) return;
214
+ if (!this._model.autoplayAudioEnabled) return;
215
+ if (audio && !isInsidePrompt) return;
216
+ if (!audio) return;
217
+ const info = this._createAudioInfoToast();
218
+ const container = this.querySelector('#main-container');
219
+ const enableAudio = () => {
220
+ if (this.querySelector('#play-audio-info')) {
221
+ audio.play();
222
+ container.removeChild(info);
223
+ }
224
+ document.removeEventListener('click', enableAudio);
225
+ };
226
+
227
+ // if the audio is paused, it means the user has not interacted with the page yet and the audio will not play
228
+ // FIX FOR SAFARI: play with a slight delay to check if autoplay was blocked
229
+ setTimeout(() => {
230
+ if (audio.paused && !this.querySelector('#play-audio-info')) {
231
+ // add info message as a toast to enable audio playback
232
+ container.appendChild(info);
233
+ document.addEventListener('click', enableAudio);
234
+ } else {
198
235
  document.removeEventListener('click', enableAudio);
199
- }; // if the audio is paused, it means the user has not interacted with the page yet and the audio will not play
200
- // FIX FOR SAFARI: play with a slight delay to check if autoplay was blocked
201
-
202
-
203
- setTimeout(function () {
204
- if (audio.paused && !_this.querySelector('#play-audio-info')) {
205
- // add info message as a toast to enable audio playback
206
- container.appendChild(info);
207
- document.addEventListener('click', enableAudio);
208
- } else {
209
- document.removeEventListener('click', enableAudio);
210
- }
211
- }, 500); // we need to listen for the playing event to remove the toast in case the audio plays because of re-rendering
212
-
213
- var handlePlaying = function handlePlaying() {
214
- //timestamp when auto-played audio started playing
215
- _this._session.audioStartTime = _this._session.audioStartTime || new Date().getTime();
216
-
217
- var info = _this.querySelector('#play-audio-info');
218
-
219
- if (info) {
220
- container.removeChild(info);
221
- }
222
-
223
- audio.removeEventListener('playing', handlePlaying);
224
- };
225
-
226
- audio.addEventListener('playing', handlePlaying); // we need to listen for the ended event to update the isComplete state
227
-
228
- var handleEnded = function handleEnded() {
229
- //timestamp when auto-played audio completed playing
230
- _this._session.audioEndTime = _this._session.audioEndTime || new Date().getTime();
231
- var _this$_session = _this._session,
232
- audioStartTime = _this$_session.audioStartTime,
233
- audioEndTime = _this$_session.audioEndTime,
234
- waitTime = _this$_session.waitTime;
235
-
236
- if (!waitTime && audioStartTime && audioEndTime) {
237
- // waitTime is elapsed time the user waited for auto-played audio to finish
238
- _this._session.waitTime = audioEndTime - audioStartTime;
239
- }
240
-
241
- _this.audioComplete = true;
242
-
243
- _this.dispatchEvent(new _piePlayerEvents.SessionChangedEvent(_this.tagName.toLowerCase(), _this.isComplete()));
244
-
245
- audio.removeEventListener('ended', handleEnded);
246
- };
247
-
248
- audio.addEventListener('ended', handleEnded); // store references to remove later
249
-
250
- _this._audio = audio;
251
- _this._handlePlaying = handlePlaying;
252
- _this._handleEnded = handleEnded;
253
- _this._enableAudio = enableAudio; // set to true to prevent multiple initializations
254
-
255
- _this._audioInitialized = true;
256
- observer.disconnect();
257
- }
258
- });
259
- });
260
- observer.observe(this, {
261
- childList: true,
262
- subtree: true
236
+ }
237
+ }, 500);
238
+
239
+ // we need to listen for the playing event to remove the toast in case the audio plays because of re-rendering
240
+ const handlePlaying = () => {
241
+ //timestamp when auto-played audio started playing
242
+ this._session.audioStartTime = this._session.audioStartTime || new Date().getTime();
243
+ const info = this.querySelector('#play-audio-info');
244
+ if (info) {
245
+ container.removeChild(info);
246
+ }
247
+ audio.removeEventListener('playing', handlePlaying);
248
+ };
249
+ audio.addEventListener('playing', handlePlaying);
250
+
251
+ // we need to listen for the ended event to update the isComplete state
252
+ const handleEnded = () => {
253
+ //timestamp when auto-played audio completed playing
254
+ this._session.audioEndTime = this._session.audioEndTime || new Date().getTime();
255
+ let {
256
+ audioStartTime,
257
+ audioEndTime,
258
+ waitTime
259
+ } = this._session;
260
+ if (!waitTime && audioStartTime && audioEndTime) {
261
+ // waitTime is elapsed time the user waited for auto-played audio to finish
262
+ this._session.waitTime = audioEndTime - audioStartTime;
263
+ }
264
+ this.audioComplete = true;
265
+ this.dispatchEvent(new _piePlayerEvents.SessionChangedEvent(this.tagName.toLowerCase(), this.isComplete()));
266
+ audio.removeEventListener('ended', handleEnded);
267
+ };
268
+ audio.addEventListener('ended', handleEnded);
269
+
270
+ // store references to remove later
271
+ this._audio = audio;
272
+ this._handlePlaying = handlePlaying;
273
+ this._handleEnded = handleEnded;
274
+ this._enableAudio = enableAudio;
275
+ // set to true to prevent multiple initializations
276
+ this._audioInitialized = true;
277
+ observer.disconnect();
278
+ }
263
279
  });
280
+ });
281
+ observer.observe(this, {
282
+ childList: true,
283
+ subtree: true
284
+ });
285
+ }
286
+ disconnectedCallback() {
287
+ this._disconnectMathObserver();
288
+ document.removeEventListener('click', this._enableAudio);
289
+ if (this._audio) {
290
+ this._audio.removeEventListener('playing', this._handlePlaying);
291
+ this._audio.removeEventListener('ended', this._handleEnded);
292
+ this._audio = null;
264
293
  }
265
- }, {
266
- key: "disconnectedCallback",
267
- value: function disconnectedCallback() {
268
- document.removeEventListener('click', this._enableAudio);
269
-
270
- if (this._audio) {
271
- this._audio.removeEventListener('playing', this._handlePlaying);
272
-
273
- this._audio.removeEventListener('ended', this._handleEnded);
274
-
275
- this._audio = null;
276
- }
294
+ if (this._root) {
295
+ this._root.unmount();
277
296
  }
278
- }, {
279
- key: "render",
280
- value: function render() {
281
- var _this2 = this;
282
-
283
- if (this._model && this._session) {
284
- var el = /*#__PURE__*/_react["default"].createElement(_categorize["default"], {
285
- model: this._model,
286
- session: this._session,
287
- onAnswersChange: this.changeAnswers.bind(this),
288
- onShowCorrectToggle: this.onShowCorrectToggle.bind(this)
289
- });
290
-
291
- _reactDom["default"].render(el, this, function () {
292
- (0, _mathRendering.renderMath)(_this2);
293
- });
297
+ }
298
+ render() {
299
+ if (this._model && this._session) {
300
+ const el = /*#__PURE__*/_react.default.createElement(_categorize.default, {
301
+ model: this._model,
302
+ session: this._session,
303
+ onAnswersChange: this.changeAnswers.bind(this),
304
+ onShowCorrectToggle: this.onShowCorrectToggle.bind(this),
305
+ pauseMathObserver: this.pauseMathObserver,
306
+ resumeMathObserver: this.resumeMathObserver
307
+ });
308
+ if (!this._root) {
309
+ this._root = (0, _client.createRoot)(this);
294
310
  }
311
+ this._root.render(el);
295
312
  }
296
- }]);
297
- return Categorize;
298
- }( /*#__PURE__*/(0, _wrapNativeSuper2["default"])(HTMLElement));
299
-
300
- exports["default"] = Categorize;
313
+ }
314
+ }
315
+ exports.default = Categorize;
301
316
  //# sourceMappingURL=index.js.map