@pie-players/pie-tool-answer-eliminator 0.2.8 → 0.2.10
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/adapters/multiple-choice-adapter.ts +62 -14
- package/answer-eliminator-core.ts +12 -85
- package/dist/adapters/multiple-choice-adapter.d.ts +8 -0
- package/dist/adapters/multiple-choice-adapter.d.ts.map +1 -1
- package/dist/answer-eliminator-core.d.ts +3 -8
- package/dist/answer-eliminator-core.d.ts.map +1 -1
- package/dist/strategies/mask-strategy.d.ts +7 -2
- package/dist/strategies/mask-strategy.d.ts.map +1 -1
- package/dist/strategies/strikethrough-strategy.d.ts +10 -2
- package/dist/strategies/strikethrough-strategy.d.ts.map +1 -1
- package/dist/tool-answer-eliminator.js +2647 -1443
- package/dist/vite.config.d.ts.map +1 -1
- package/package.json +15 -6
- package/strategies/mask-strategy.ts +33 -42
- package/strategies/strikethrough-strategy.ts +53 -69
- package/tool-answer-eliminator.svelte +77 -104
- package/dist/tool-answer-eliminator.js.map +0 -1
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
<svelte:options
|
|
2
2
|
customElement={{
|
|
3
3
|
tag: 'pie-tool-answer-eliminator',
|
|
4
|
-
shadow: '
|
|
4
|
+
shadow: 'open',
|
|
5
5
|
props: {
|
|
6
6
|
visible: { type: 'Boolean', attribute: 'visible' },
|
|
7
7
|
toolId: { type: 'String', attribute: 'tool-id' },
|
|
8
8
|
strategy: { type: 'String', attribute: 'strategy' },
|
|
9
9
|
alwaysOn: { type: 'Boolean', attribute: 'always-on' },
|
|
10
10
|
buttonAlignment: { type: 'String', attribute: 'button-alignment' },
|
|
11
|
-
coordinator: { type: 'Object' },
|
|
12
11
|
scopeElement: { type: 'Object', reflect: false },
|
|
13
12
|
|
|
14
13
|
// Store integration (JS properties only)
|
|
@@ -46,9 +45,17 @@
|
|
|
46
45
|
|
|
47
46
|
<script lang="ts">
|
|
48
47
|
|
|
49
|
-
import
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
import {
|
|
49
|
+
connectToolRuntimeContext,
|
|
50
|
+
connectToolShellContext,
|
|
51
|
+
ZIndexLayer,
|
|
52
|
+
} from '@pie-players/pie-assessment-toolkit';
|
|
53
|
+
import type {
|
|
54
|
+
AssessmentToolkitShellContext,
|
|
55
|
+
AssessmentToolkitRuntimeContext,
|
|
56
|
+
IToolCoordinator,
|
|
57
|
+
} from '@pie-players/pie-assessment-toolkit';
|
|
58
|
+
import { onMount } from 'svelte';
|
|
52
59
|
import { AnswerEliminatorCore } from './answer-eliminator-core.js';
|
|
53
60
|
|
|
54
61
|
// Props
|
|
@@ -58,7 +65,6 @@ import { onDestroy, onMount } from 'svelte';
|
|
|
58
65
|
strategy = 'strikethrough' as 'strikethrough' | 'mask' | 'gray',
|
|
59
66
|
alwaysOn = false, // Set true for profile-based accommodation
|
|
60
67
|
buttonAlignment = 'right' as 'left' | 'right' | 'inline', // Button placement: left, right, or inline with checkbox
|
|
61
|
-
coordinator,
|
|
62
68
|
scopeElement = null, // Container element to limit DOM queries (for multi-item pages)
|
|
63
69
|
|
|
64
70
|
// Store integration
|
|
@@ -70,111 +76,82 @@ import { onDestroy, onMount } from 'svelte';
|
|
|
70
76
|
strategy?: 'strikethrough' | 'mask' | 'gray';
|
|
71
77
|
alwaysOn?: boolean;
|
|
72
78
|
buttonAlignment?: 'left' | 'right' | 'inline';
|
|
73
|
-
coordinator?: IToolCoordinator;
|
|
74
79
|
scopeElement?: HTMLElement | null;
|
|
75
80
|
elementToolStateStore?: any;
|
|
76
81
|
globalElementId?: string;
|
|
77
82
|
} = $props();
|
|
78
83
|
|
|
79
84
|
// State
|
|
85
|
+
let contextHostElement = $state<HTMLElement | null>(null);
|
|
86
|
+
let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
|
|
87
|
+
let shellContext = $state<AssessmentToolkitShellContext | null>(null);
|
|
88
|
+
const coordinator = $derived(
|
|
89
|
+
runtimeContext?.toolCoordinator as IToolCoordinator | undefined,
|
|
90
|
+
);
|
|
80
91
|
let core = $state<AnswerEliminatorCore | null>(null);
|
|
81
|
-
let
|
|
82
|
-
let mutationObserver = $state<MutationObserver | null>(null);
|
|
92
|
+
let lastShellContextVersion = $state<number | null>(null);
|
|
83
93
|
|
|
84
94
|
// Track registration state
|
|
85
|
-
let
|
|
95
|
+
let registeredToolId = $state<string | null>(null);
|
|
96
|
+
let registeredCoordinator = $state<IToolCoordinator | null>(null);
|
|
86
97
|
|
|
87
98
|
// Determine if tool should be active (either toggled on OR always-on mode)
|
|
88
99
|
let isActive = $derived(alwaysOn || visible);
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
if (!
|
|
101
|
+
$effect(() => {
|
|
102
|
+
if (!contextHostElement) return;
|
|
103
|
+
return connectToolRuntimeContext(contextHostElement, (value: AssessmentToolkitRuntimeContext) => {
|
|
104
|
+
runtimeContext = value;
|
|
105
|
+
});
|
|
106
|
+
});
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
$effect(() => {
|
|
109
|
+
if (!contextHostElement) return;
|
|
110
|
+
return connectToolShellContext(contextHostElement, (value: AssessmentToolkitShellContext) => {
|
|
111
|
+
shellContext = value;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
95
114
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
function resolveQuestionRoot(): HTMLElement | null {
|
|
116
|
+
return scopeElement || shellContext?.scopeElement || null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function initializeForCurrentQuestion() {
|
|
120
|
+
if (!isActive || !core) return;
|
|
121
|
+
const questionRoot = resolveQuestionRoot();
|
|
103
122
|
|
|
104
123
|
if (!questionRoot) {
|
|
105
|
-
console.warn('[AnswerEliminator]
|
|
124
|
+
console.warn('[AnswerEliminator] Missing shell scope context for question root');
|
|
106
125
|
return;
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
core.initializeForQuestion(questionRoot
|
|
110
|
-
updateEliminatedCount();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function waitForPIEElements(callback: () => void, timeout: number = 5000) {
|
|
114
|
-
// Use scopeElement if provided, otherwise search globally
|
|
115
|
-
const searchRoot = scopeElement || document.body;
|
|
116
|
-
|
|
117
|
-
// Check if PIE elements already exist
|
|
118
|
-
const checkElements = () => {
|
|
119
|
-
const questionRoot =
|
|
120
|
-
searchRoot.querySelector('pie-player') ||
|
|
121
|
-
searchRoot.querySelector('multiple-choice') ||
|
|
122
|
-
searchRoot.querySelector('ebsr') ||
|
|
123
|
-
searchRoot.querySelector('[data-pie-element]');
|
|
124
|
-
|
|
125
|
-
if (questionRoot) {
|
|
126
|
-
// Elements found, clean up observer and execute callback
|
|
127
|
-
if (mutationObserver) {
|
|
128
|
-
mutationObserver.disconnect();
|
|
129
|
-
mutationObserver = null;
|
|
130
|
-
}
|
|
131
|
-
callback();
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Try immediately first
|
|
138
|
-
if (checkElements()) return;
|
|
139
|
-
|
|
140
|
-
// Set up MutationObserver to watch for elements within scope
|
|
141
|
-
mutationObserver = new MutationObserver(() => {
|
|
142
|
-
checkElements();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Observe the search root for added nodes
|
|
146
|
-
mutationObserver.observe(searchRoot, {
|
|
147
|
-
childList: true,
|
|
148
|
-
subtree: true
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Fallback timeout to prevent infinite observation
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
if (mutationObserver) {
|
|
154
|
-
mutationObserver.disconnect();
|
|
155
|
-
mutationObserver = null;
|
|
156
|
-
// Try one last time
|
|
157
|
-
checkElements();
|
|
158
|
-
}
|
|
159
|
-
}, timeout);
|
|
128
|
+
core.initializeForQuestion(questionRoot);
|
|
160
129
|
}
|
|
161
130
|
|
|
162
131
|
function handleItemChange() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
132
|
+
requestAnimationFrame(() => {
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
initializeForCurrentQuestion();
|
|
135
|
+
});
|
|
166
136
|
});
|
|
167
137
|
}
|
|
168
138
|
|
|
169
|
-
function updateEliminatedCount() {
|
|
170
|
-
eliminatedCount = core?.getEliminatedCount() || 0;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
139
|
// Register with coordinator when it becomes available
|
|
174
140
|
$effect(() => {
|
|
175
|
-
if (coordinator
|
|
141
|
+
if (!coordinator || !toolId) return;
|
|
142
|
+
if (
|
|
143
|
+
registeredCoordinator &&
|
|
144
|
+
registeredToolId &&
|
|
145
|
+
(registeredCoordinator !== coordinator || registeredToolId !== toolId)
|
|
146
|
+
) {
|
|
147
|
+
registeredCoordinator.unregisterTool(registeredToolId);
|
|
148
|
+
registeredCoordinator = null;
|
|
149
|
+
registeredToolId = null;
|
|
150
|
+
}
|
|
151
|
+
if (!registeredToolId) {
|
|
176
152
|
coordinator.registerTool(toolId, 'Answer Eliminator', undefined, ZIndexLayer.MODAL);
|
|
177
|
-
|
|
153
|
+
registeredCoordinator = coordinator;
|
|
154
|
+
registeredToolId = toolId;
|
|
178
155
|
}
|
|
179
156
|
});
|
|
180
157
|
|
|
@@ -194,42 +171,37 @@ import { onDestroy, onMount } from 'svelte';
|
|
|
194
171
|
core.setStoreIntegration(elementToolStateStore, globalElementId);
|
|
195
172
|
}
|
|
196
173
|
|
|
197
|
-
// Listen for question changes (PIE player emits this)
|
|
198
|
-
document.addEventListener('pie-item-changed', handleItemChange);
|
|
199
|
-
|
|
200
|
-
// Listen for custom events from answer-eliminator-core when state changes
|
|
201
|
-
document.addEventListener('answer-eliminator-state-change', () => {
|
|
202
|
-
updateEliminatedCount();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
174
|
// Initialize for current question if active, otherwise ensure clean state
|
|
206
175
|
if (isActive) {
|
|
207
|
-
|
|
208
|
-
waitForPIEElements(() => {
|
|
209
|
-
initializeForCurrentQuestion();
|
|
210
|
-
});
|
|
176
|
+
initializeForCurrentQuestion();
|
|
211
177
|
} else {
|
|
212
178
|
// If not active on mount, clear any leftover visual eliminations
|
|
213
179
|
core.cleanup();
|
|
214
180
|
}
|
|
215
181
|
|
|
216
182
|
return () => {
|
|
217
|
-
// Clean up MutationObserver if still active
|
|
218
|
-
if (mutationObserver) {
|
|
219
|
-
mutationObserver.disconnect();
|
|
220
|
-
mutationObserver = null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
183
|
core?.destroy();
|
|
224
184
|
core = null;
|
|
225
|
-
if (
|
|
226
|
-
|
|
185
|
+
if (registeredCoordinator && registeredToolId) {
|
|
186
|
+
registeredCoordinator.unregisterTool(registeredToolId);
|
|
187
|
+
registeredCoordinator = null;
|
|
188
|
+
registeredToolId = null;
|
|
227
189
|
}
|
|
228
|
-
document.removeEventListener('pie-item-changed', handleItemChange);
|
|
229
|
-
document.removeEventListener('answer-eliminator-state-change', updateEliminatedCount);
|
|
230
190
|
};
|
|
231
191
|
});
|
|
232
192
|
|
|
193
|
+
$effect(() => {
|
|
194
|
+
const shellVersion = shellContext?.contextVersion ?? null;
|
|
195
|
+
if (shellVersion === null) return;
|
|
196
|
+
if (lastShellContextVersion === null) {
|
|
197
|
+
lastShellContextVersion = shellVersion;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (shellVersion === lastShellContextVersion) return;
|
|
201
|
+
lastShellContextVersion = shellVersion;
|
|
202
|
+
handleItemChange();
|
|
203
|
+
});
|
|
204
|
+
|
|
233
205
|
// Watch for visibility changes to show/hide elimination buttons
|
|
234
206
|
$effect(() => {
|
|
235
207
|
if (core) {
|
|
@@ -248,3 +220,4 @@ import { onDestroy, onMount } from 'svelte';
|
|
|
248
220
|
|
|
249
221
|
<!-- No visible UI - tool operates entirely through injected buttons next to choices -->
|
|
250
222
|
<!-- The toolbar button visibility is managed by tool-toolbar.svelte -->
|
|
223
|
+
<div bind:this={contextHostElement} style="display: none;" aria-hidden="true"></div>
|