Skip to content

Commit a67da9b

Browse files
authored
Merge pull request #15 from agencyenterprise/feature/removing-submit-button-render
Feature/removing submit button render
2 parents 3f8e5c0 + 4057ddf commit a67da9b

File tree

16 files changed

+617
-96
lines changed

16 files changed

+617
-96
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"scripts": {
1010
"build": "npm run build --workspace=@ae-studio/qti-renderer && npm run build --workspaces",
1111
"dev": "npm run build && storybook dev -p 6006",
12-
"dev:sandbox": "npm run build && concurrently -k -n \"CORE,SANDBOX\" -c \"magenta,cyan\" \"npm run dev --workspace=@qti-renderer/core\" \"cd sandbox && npm run dev\"",
12+
"dev:sandbox": "npm run build && concurrently -k -n \"CORE,SANDBOX\" -c \"magenta,cyan\" \"npm run dev --workspace=@ae-studio/qti-renderer\" \"cd sandbox && npm run dev\"",
1313
"storybook": "storybook dev -p 6006",
1414
"build-storybook": "storybook build"
1515
},

packages/qti-react/src/QtiItem.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef, useState } from 'react';
1+
import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
22
import { QtiRenderer, QtiRendererOptions } from '@ae-studio/qti-renderer';
33

44
/**
@@ -17,11 +17,30 @@ export interface QtiItemProps {
1717
options?: QtiRendererOptions;
1818
}
1919

20-
export function QtiItem({ xml, options }: QtiItemProps) {
20+
export interface QtiItemRef {
21+
submit: () => void;
22+
getSubmissionCount: () => number;
23+
}
24+
25+
export const QtiItem = forwardRef<QtiItemRef, QtiItemProps>(({ xml, options }, ref) => {
2126
const containerRef = useRef<HTMLDivElement>(null);
2227
const rendererRef = useRef<QtiRenderer | null>(null);
2328
const [, forceUpdate] = useState(0);
2429

30+
useImperativeHandle(ref, () => ({
31+
submit: () => {
32+
if (rendererRef.current) {
33+
rendererRef.current.submit();
34+
}
35+
},
36+
getSubmissionCount: () => {
37+
if (rendererRef.current) {
38+
return rendererRef.current.getSubmissionCount();
39+
}
40+
return 0;
41+
},
42+
}));
43+
2544
useEffect(() => {
2645
if (!containerRef.current) {
2746
return;
@@ -52,4 +71,4 @@ export function QtiItem({ xml, options }: QtiItemProps) {
5271
}, [xml]);
5372

5473
return <div ref={containerRef} className="qti-item-container" />;
55-
}
74+
});

packages/qti-react/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { QtiItem } from './QtiItem';
2-
export type { QtiItemProps } from './QtiItem';
2+
export type { QtiItemProps, QtiItemRef } from './QtiItem';

packages/qti-renderer/src/renderer.ts

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ export class QtiRenderer {
1111
private container: HTMLElement | null = null;
1212
private options: QtiRendererOptions;
1313

14-
private submitButtonContainer: HTMLElement | null = null;
15-
private submissionCountContainer: HTMLElement | null = null;
1614
private submissionCount: number = 0;
1715

1816
private outcomeValues: Map<string, ValueElement> = new Map();
@@ -268,27 +266,6 @@ export class QtiRenderer {
268266
container.appendChild(rendered.element);
269267
}
270268

271-
// Create submission count display
272-
this.submissionCountContainer = document.createElement('div');
273-
this.submissionCountContainer.className = 'qti-submission-count';
274-
this.updateSubmissionCountDisplay();
275-
container.appendChild(this.submissionCountContainer);
276-
277-
// Create submit button container (always visible)
278-
this.submitButtonContainer = document.createElement('div');
279-
this.submitButtonContainer.className = 'qti-submit-container';
280-
281-
const submitButton = document.createElement('button');
282-
submitButton.type = 'button';
283-
submitButton.className = 'qti-submit-button';
284-
submitButton.textContent = 'Submit';
285-
submitButton.addEventListener('click', () => {
286-
this.handleSubmit();
287-
});
288-
289-
this.submitButtonContainer.appendChild(submitButton);
290-
container.appendChild(this.submitButtonContainer);
291-
292269
// Initialize Debug View if debug mode is enabled
293270
if (this.options.debug) {
294271
this.debugView = new DebugView(this);
@@ -397,26 +374,13 @@ export class QtiRenderer {
397374
return this.processGenericElement(element);
398375
}
399376

400-
/**
401-
* Update submission count display - shows remaining attempts
402-
*/
403-
private updateSubmissionCountDisplay(): void {
404-
if (!this.submissionCountContainer) return;
405-
406-
this.submissionCountContainer.textContent = `Submissions: ${this.submissionCount}`;
407-
}
408-
409-
/**
410-
* Handle submit button click - show feedback
411-
*/
412-
private handleSubmit(): void {
413-
if (!this.container) return;
414-
415-
// Increment submission count
377+
public submit(): void {
378+
dispatchSubmitProcessEvent(this.outcomeValues, this.variables);
416379
this.submissionCount++;
417-
this.updateSubmissionCountDisplay();
380+
}
418381

419-
dispatchSubmitProcessEvent(this.outcomeValues, this.variables);
382+
public getSubmissionCount(): number {
383+
return this.submissionCount;
420384
}
421385

422386
/**

packages/qti-vanilla/src/QtiItem.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,25 @@ export class VanillaQtiItem {
6161
this.mount(xml);
6262
}
6363

64+
/**
65+
* Submit the current response
66+
*/
67+
submit(): void {
68+
if (this.renderer) {
69+
this.renderer.submit();
70+
}
71+
}
72+
73+
/**
74+
* Get the submission count
75+
*/
76+
getSubmissionCount(): number {
77+
if (this.renderer) {
78+
return this.renderer.getSubmissionCount();
79+
}
80+
return 0;
81+
}
82+
6483
/**
6584
* Destroy the renderer and clean up
6685
*/

packages/qti-vue/src/QtiItem.vue

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ const mountRenderer = () => {
4141
}
4242
};
4343
44+
const submit = () => {
45+
if (rendererRef.value) {
46+
rendererRef.value.submit();
47+
}
48+
};
49+
50+
const getSubmissionCount = () => {
51+
if (rendererRef.value) {
52+
return rendererRef.value.getSubmissionCount();
53+
}
54+
return 0;
55+
};
56+
57+
defineExpose({
58+
submit,
59+
getSubmissionCount,
60+
});
61+
4462
onMounted(() => {
4563
mountRenderer();
4664
});

packages/qti-vue/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QtiRendererOptions } from '@qti-renderer/core';
1+
import { QtiRendererOptions } from '@ae-studio/qti-renderer';
22

33
/**
44
* Props interface for QtiItem Vue component

sandbox/index.html

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,26 @@ <h2>QTI XML Input</h2>
2828
<div class="controls">
2929
<button id="validate-btn">Validate XML</button>
3030
<button id="render-btn">Render QTI</button>
31+
<label class="toggle-control">
32+
<input type="checkbox" id="custom-css-toggle">
33+
<span class="toggle-slider"></span>
34+
<span class="toggle-label">Custom CSS</span>
35+
</label>
3136
</div>
3237
<div id="validation-result"></div>
3338
</div>
39+
<div class="resizer" id="dragMe"></div>
3440
<div class="preview-pane">
35-
<h2>Preview</h2>
36-
<div id="qti-container"></div>
41+
<div class="preview-header">
42+
<h2>Preview</h2>
43+
</div>
44+
<div class="renderer-wrapper">
45+
<div id="qti-container"></div>
46+
</div>
47+
<div class="controls preview-controls">
48+
<span id="submission-count">Submissions: 0</span>
49+
<button id="submit-btn" class="primary-btn">Submit Answer</button>
50+
</div>
3751
</div>
3852
</div>
3953
</div>

sandbox/src/custom-renderer.css

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* Custom Renderer CSS */
2+
.qti-item-body {
3+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
4+
line-height: 1.6;
5+
color: #1a1a1a;
6+
max-width: 100%;
7+
margin: 0 auto;
8+
}
9+
10+
.qti-choice-interaction {
11+
margin-top: 2rem;
12+
border: none;
13+
padding: 0;
14+
}
15+
16+
.qti-prompt {
17+
font-size: 1.25rem;
18+
font-weight: 600;
19+
margin-bottom: 1.5rem;
20+
color: #111;
21+
line-height: 1.4;
22+
}
23+
24+
ul.qti-choices-list {
25+
list-style-type: none;
26+
padding: 0;
27+
margin: 0;
28+
display: flex;
29+
flex-direction: column;
30+
gap: 12px;
31+
}
32+
33+
li.qti-simple-choice {
34+
background-color: #fff;
35+
border: 1px solid #e1e4e8;
36+
padding: 16px 20px;
37+
border-radius: 12px;
38+
cursor: pointer;
39+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
40+
display: flex;
41+
align-items: center;
42+
position: relative;
43+
font-size: 1rem;
44+
}
45+
46+
li.qti-simple-choice:hover {
47+
background-color: #f8f9fa;
48+
border-color: #cbd5e1;
49+
transform: translateY(-1px);
50+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
51+
}
52+
53+
li.qti-simple-choice:active {
54+
transform: translateY(0);
55+
}
56+
57+
/* Hide the default radio/checkbox */
58+
li.qti-simple-choice input {
59+
appearance: none;
60+
-webkit-appearance: none;
61+
width: 20px;
62+
height: 20px;
63+
border: 2px solid #cbd5e1;
64+
border-radius: 50%; /* Default to circle (radio style), JS/Renderer handles type */
65+
margin-right: 16px;
66+
position: relative;
67+
flex-shrink: 0;
68+
transition: all 0.2s ease;
69+
}
70+
71+
/* Checkbox specific style if distinguishable, usually controlled by attribute on interaction */
72+
/* For now we style the input generically */
73+
74+
li.qti-simple-choice input:checked {
75+
border-color: #2563eb;
76+
background-color: #2563eb;
77+
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
78+
background-size: 100% 100%;
79+
background-position: center;
80+
background-repeat: no-repeat;
81+
}
82+
83+
/* Style the label inside */
84+
label.qti-simple-choice {
85+
cursor: pointer;
86+
flex: 1;
87+
font-weight: 500;
88+
color: #374151;
89+
}
90+
91+
/* Selected state for the row */
92+
li.qti-simple-choice:has(input:checked) {
93+
background-color: #eff6ff;
94+
border-color: #bfdbfe;
95+
box-shadow: 0 0 0 1px #bfdbfe;
96+
}
97+
98+
li.qti-simple-choice:has(input:checked) label.qti-simple-choice {
99+
color: #1e40af;
100+
}
101+
102+
/* Custom styling for specific elements */
103+
.qti-underline {
104+
text-decoration: underline;
105+
text-decoration-color: #2563eb;
106+
text-decoration-thickness: 2px;
107+
text-underline-offset: 4px;
108+
}
109+
110+
.qti-visually-hidden {
111+
position: absolute;
112+
width: 1px;
113+
height: 1px;
114+
padding: 0;
115+
margin: -1px;
116+
overflow: hidden;
117+
clip: rect(0, 0, 0, 0);
118+
white-space: nowrap;
119+
border: 0;
120+
}
121+
122+
/* Feedback Styles */
123+
.qti-modal-feedback {
124+
margin-top: 1.5rem;
125+
padding: 1rem 1.25rem;
126+
background-color: #f8f9fa;
127+
border: 1px solid #e9ecef;
128+
border-radius: 8px;
129+
color: #495057;
130+
font-size: 0.95rem;
131+
}
132+
133+
.qti-modal-feedback:empty {
134+
display: none;
135+
margin: 0;
136+
padding: 0;
137+
border: none;
138+
}
139+
140+
.qti-feedback-inline {
141+
display: inline-block;
142+
margin-left: 0.5rem;
143+
padding: 0.2rem 0.5rem;
144+
background-color: #f1f3f5;
145+
border-radius: 4px;
146+
font-size: 0.9em;
147+
color: #495057;
148+
vertical-align: middle;
149+
}
150+
151+
.qti-feedback-inline:empty {
152+
display: none;
153+
margin: 0;
154+
padding: 0;
155+
}

0 commit comments

Comments
 (0)