From 261e158bfc310256ff42bb1656c344fc29fcdc43 Mon Sep 17 00:00:00 2001 From: Deeksha Deepak Date: Fri, 10 Jul 2026 14:43:34 +0530 Subject: [PATCH 1/4] fix(react): make the player responsive to its own width so the editor mobile preview renders correctly The player's responsive rules used viewport @media queries, so when a host (e.g. the Sunbird editor) rendered it in a narrow mobile-preview frame while the browser viewport stayed desktop-width, the queries never fired and the layout overflowed the frame. Switch the mobile/tablet mixins to @container queries against a new 'quml' container declared on .appShell (container-type: inline-size, so the height chain and :host freeze fix are untouched). Every responsive component is a descendant of .appShell, so layout now tracks the player's own width. When the player fills the viewport (portal, real mobile, standalone) the container width == viewport width, so breakpoints trigger identically to before. Because container-type does not reliably trap position: fixed, anchor the player's overlays to .appShell explicitly: add position: relative to .appShell and switch the mobile sections drawer, submit modal, and toast from fixed to absolute. These stay within the player box when embedded and are visually identical when the player fills the viewport. The header legend backdrop stays fixed (invisible click-catcher; absolute would scope it to its button). --- .../MainPlayer/MainPlayer.module.scss | 18 ++++++++++++++++++ .../MobileSectionsDrawer.module.scss | 6 +++++- .../SubmitModal/SubmitModal.module.scss | 6 +++++- .../src/components/Toast/Toast.module.scss | 6 +++++- .../src/styles/mixins.scss | 14 ++++++++++++-- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/projects/sunbird-quml-player-react/src/components/MainPlayer/MainPlayer.module.scss b/projects/sunbird-quml-player-react/src/components/MainPlayer/MainPlayer.module.scss index a75cb91f..69915483 100644 --- a/projects/sunbird-quml-player-react/src/components/MainPlayer/MainPlayer.module.scss +++ b/projects/sunbird-quml-player-react/src/components/MainPlayer/MainPlayer.module.scss @@ -9,6 +9,24 @@ height: 100%; width: 100%; max-width: 100%; + // Query container for the player's responsive rules (m.mobile / m.tablet). + // `inline-size` contains ONLY the inline (width) axis, so the height:100% + // chain — and the :host height:100% freeze fix — is untouched. Every + // responsive component is a descendant of this element, so they respond to + // the player's own available width instead of the browser viewport (needed + // for the editor's narrow mobile preview). + container-type: inline-size; + container-name: quml; + // Positioning context for the player's overlays. The overlays (submit modal, + // mobile sections drawer, feedback toast) are `position: absolute` and anchor + // to THIS box, so they stay within the player instead of covering the whole + // browser window when the player is embedded in a small frame (editor mobile + // preview). When the player fills the viewport (portal, real mobile, + // standalone) this box == the viewport, so it is visually identical to the + // previous `position: fixed` behavior. (container-type does not reliably + // establish a fixed-positioning containing block across browsers, so we rely + // on absolute positioning + this explicit relative anchor instead.) + position: relative; overflow-x: hidden; // hard guard: nothing scrolls the shell sideways // Overview / results / review render their screen directly in the shell (the // assessment stage scrolls via its inner .main). When the host constrains the diff --git a/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss b/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss index c6eeba7c..a879eb00 100644 --- a/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss +++ b/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss @@ -2,8 +2,12 @@ @use '../../styles/mixins' as m; // Mobile sections navigator — bottom sheet (matches the design's `sheetUp`). +// `absolute` (anchored to MainPlayer's .appShell), not `fixed`, so the sheet +// stays within the player box when embedded in a small frame (editor preview). +// When the player fills the viewport (.appShell == viewport) it is identical to +// fixed. .overlay { - position: fixed; + position: absolute; inset: 0; z-index: 1000; display: flex; diff --git a/projects/sunbird-quml-player-react/src/components/SubmitModal/SubmitModal.module.scss b/projects/sunbird-quml-player-react/src/components/SubmitModal/SubmitModal.module.scss index 6ce8e756..17bf6319 100644 --- a/projects/sunbird-quml-player-react/src/components/SubmitModal/SubmitModal.module.scss +++ b/projects/sunbird-quml-player-react/src/components/SubmitModal/SubmitModal.module.scss @@ -1,8 +1,12 @@ @use '../../styles/variables' as v; @use '../../styles/mixins' as m; +// `absolute` (anchored to MainPlayer's .appShell), not `fixed`, so the modal +// stays within the player box when embedded in a small frame (editor preview). +// When the player fills the viewport (.appShell == viewport) it is identical to +// fixed. .overlay { - position: fixed; + position: absolute; inset: 0; z-index: 1200; display: flex; diff --git a/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss b/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss index b0db9b1f..6434c1f5 100644 --- a/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss +++ b/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss @@ -1,8 +1,12 @@ @use '../../styles/variables' as v; @use '../../styles/mixins' as m; +// `absolute` (anchored to MainPlayer's .appShell), not `fixed`, so the toast +// stays within the player box when embedded in a small frame (editor preview). +// When the player fills the viewport (.appShell == viewport) it is identical to +// fixed. .viewport { - position: fixed; + position: absolute; bottom: v.$space-8; left: 50%; transform: translateX(-50%); diff --git a/projects/sunbird-quml-player-react/src/styles/mixins.scss b/projects/sunbird-quml-player-react/src/styles/mixins.scss index bbfc4ee0..03bf373b 100644 --- a/projects/sunbird-quml-player-react/src/styles/mixins.scss +++ b/projects/sunbird-quml-player-react/src/styles/mixins.scss @@ -10,8 +10,18 @@ @use 'variables' as v; // Respond below the mobile breakpoint (≤768px). +// +// Uses a CONTAINER query (against the `quml` container declared on MainPlayer's +// `.appShell`), NOT a viewport `@media`. The player is an embeddable web +// component: hosts (e.g. the Sunbird editor's mobile preview) render it in a +// narrow box while the browser viewport stays desktop-width, so viewport media +// queries never fired and the layout overflowed. Querying the player's own +// width makes it responsive to the space it's actually given. +// When the player fills the viewport (portal, real mobile, standalone) the +// container width == viewport width, so the breakpoints trigger identically to +// before — no behavior change in those environments. @mixin mobile { - @media (max-width: v.$bp-mobile) { + @container quml (max-width: #{v.$bp-mobile}) { @content; } } @@ -19,7 +29,7 @@ // Respond at tablet and below (≤1023px). Combine with `mobile` (which is more // specific / placed after) for progressive collapse: tablet 2-up → mobile 1-up. @mixin tablet { - @media (max-width: v.$bp-tablet - 1px) { + @container quml (max-width: #{v.$bp-tablet - 1px}) { @content; } } From f9e382b4d3628142c1255bca569d82ffa19d65e4 Mon Sep 17 00:00:00 2001 From: Deeksha Deepak Date: Fri, 10 Jul 2026 14:57:01 +0530 Subject: [PATCH 2/4] fix(react): score FTB/MTF all-or-nothing when partial scoring is disabled When partial scoring is off (responseProcessing.template is not MAP_RESPONSE), FTB and MTF were still scored proportionally (round(maxScore x matched/total)), so the editor's 'disable partial scoring' toggle had no effect on those types. Make the non-MAP_RESPONSE branch all-or-nothing: full marks only when every blank/pair is correct, else 0. MAP_RESPONSE (partial enabled) is unchanged, and SEQ/REO were already all-or-nothing. This is an intentional deviation from the Angular player, which keeps the proportional legacy behaviour. --- .../src/utils/score.test.ts | 16 ++++++++++------ .../src/utils/score.ts | Bin 9778 -> 10648 bytes 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/projects/sunbird-quml-player-react/src/utils/score.test.ts b/projects/sunbird-quml-player-react/src/utils/score.test.ts index c3e0cbdf..c46daf46 100644 --- a/projects/sunbird-quml-player-react/src/utils/score.test.ts +++ b/projects/sunbird-quml-player-react/src/utils/score.test.ts @@ -89,7 +89,7 @@ describe('score utils (Angular evaluateAutoScored parity, normalized to 0..1)', expect(calculateMTFScore(q, { matches: { a: '1', b: '2' } })).toBe(1); // min(6,4)/4 }); - it('MTF legacy: proportional round(maxScore × hits/total)', () => { + it('MTF partial disabled (not MAP_RESPONSE): all-or-nothing', () => { const q = mk( { response1: { @@ -100,8 +100,10 @@ describe('score utils (Angular evaluateAutoScored parity, normalized to 0..1)', }, { maxScore: 10 }, ); - // 2 of 4 → round(10 × 0.5) = 5 → 0.5 - expect(calculateMTFScore(q, { matches: { a: '1', b: '2', c: 'x', d: 'y' } })).toBe(0.5); + // 2 of 4 correct → no partial credit → 0. + expect(calculateMTFScore(q, { matches: { a: '1', b: '2', c: 'x', d: 'y' } })).toBe(0); + // All 4 correct → full marks → 1. + expect(calculateMTFScore(q, { matches: { a: '1', b: '2', c: '3', d: '4' } })).toBe(1); }); // ── FTB (ftb) ──────────────────────────────────────────────────────────── @@ -150,7 +152,7 @@ describe('score utils (Angular evaluateAutoScored parity, normalized to 0..1)', expect(calculateFTBScore(q, { responses: { response1: 'red', response2: 'red' } })).toBe(0.5); }); - it('FTB legacy: proportional round(maxScore × hits/total)', () => { + it('FTB partial disabled (not MAP_RESPONSE): all-or-nothing', () => { const q = mk( { response1: { cardinality: 'single', type: 'string', correctResponse: { value: 'cat' } }, @@ -158,8 +160,10 @@ describe('score utils (Angular evaluateAutoScored parity, normalized to 0..1)', }, { maxScore: 4 }, ); - // 1 of 2 → round(4 × 0.5) = 2 → 0.5 - expect(calculateFTBScore(q, { responses: { response1: 'cat', response2: 'fish' } })).toBe(0.5); + // 1 of 2 correct → no partial credit → 0. + expect(calculateFTBScore(q, { responses: { response1: 'cat', response2: 'fish' } })).toBe(0); + // Both correct → full marks → 1. + expect(calculateFTBScore(q, { responses: { response1: 'cat', response2: 'dog' } })).toBe(1); expect(calculateFTBScore(q, null)).toBe(0); }); diff --git a/projects/sunbird-quml-player-react/src/utils/score.ts b/projects/sunbird-quml-player-react/src/utils/score.ts index e7890079461f69c67225f140945095d4b28461fd..778dbe57c95ab79765cb22dd243c2e8b0a65729e 100644 GIT binary patch delta 1178 zcmcJO&u&sd6vk6%LPAXw-MOee(nW!^+!{9xl^7asD~4WBaG{$raOZL-9cIYP0A)p2 zK7iMS&)~usS@-}ZzK4AgJ@@h_DWS&1bXGITIo~bLgdS1_Oaq20+BSPYxu$Rc*H<6GlM1?| zXC*w-f}&Fj*4EF*p^{T=*!f82W^HIupA4Ant3_^@8WmmkfHhsFNsCiAl*A_yP<9%& z(^B(dYp`?+Cyd5_9L8BV}#T+)aohOlSo<$Y;OeV z;Cp;~oB=vO??YyMnD;VoAabi^m>aU8E;CZ?YDmoj774S#rNGEe2l@HL1tEcY9gM<`B5mCd~UjK;ot{JbR_yUv^Y{Mmp z5GgnTEq8!|bKnH%=$LNiy?O6@7(Nf4Cao^y8IhzCjzv;&Bw%9<6|eQAb!2?p-s}EK zciCNqJ!nSn7`u9CY`1^+k+EfRmS9*r(3pW_B_bAi`fVNd(@d4Vk_x$V_45F+B*?Mw zV->i7A_C;xljI4h(o7^XU2iU4PG6VKJLX`X3^ QT$lRPy7))si}l|A0#qV@SO5S3 From d79b74ceb5376006457a54c2e5b5afd3509a5c24 Mon Sep 17 00:00:00 2001 From: Deeksha Deepak Date: Fri, 10 Jul 2026 15:07:58 +0530 Subject: [PATCH 3/4] fix(react): size toast and sections drawer to the player box, not the viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the container-query responsive fix (PR #167 review). The toast max-width used 92vw and the drawer panel used max-height: 80vh — both viewport-relative, so in a narrow/short embedded frame (editor mobile preview) they could overflow the player box even though the overlays are now positioned within .appShell. Switch to container-relative units: toast max-width min(92%, 36rem) and drawer panel max-height 80% (of the absolute inset:0 overlay == .appShell). Identical to the previous values when the player fills the viewport. --- .../MobileSectionsDrawer/MobileSectionsDrawer.module.scss | 6 +++++- .../src/components/Toast/Toast.module.scss | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss b/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss index a879eb00..e2b08f70 100644 --- a/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss +++ b/projects/sunbird-quml-player-react/src/components/MobileSectionsDrawer/MobileSectionsDrawer.module.scss @@ -19,7 +19,11 @@ display: flex; flex-direction: column; width: 100%; - max-height: 80vh; + // `%` (not `vh`) so the sheet height tracks the player box, not the browser + // viewport: in a short embedded frame it can't grow taller than .appShell. + // The overlay is absolute inset:0 (== .appShell height), so 80% == 80% of the + // player; identical to `80vh` when the player fills the viewport. + max-height: 80%; overflow-y: auto; -webkit-overflow-scrolling: touch; background: v.$ivory; diff --git a/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss b/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss index 6434c1f5..5f055720 100644 --- a/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss +++ b/projects/sunbird-quml-player-react/src/components/Toast/Toast.module.scss @@ -14,7 +14,10 @@ display: flex; justify-content: center; width: max-content; - max-width: min(92vw, 36rem); + // `%` (not `vw`) so the cap tracks the player box, not the browser viewport: + // in a narrow embedded frame the toast stays within .appShell. Identical to + // `92vw` when the player fills the viewport (.appShell == viewport). + max-width: min(92%, 36rem); pointer-events: none; } From c14f8c3f65916f05c621fa503f4a724ee1b1d7e3 Mon Sep 17 00:00:00 2001 From: Deeksha Deepak Date: Fri, 10 Jul 2026 15:09:17 +0530 Subject: [PATCH 4/4] updated to 0.1.10 --- projects/sunbird-quml-player-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/sunbird-quml-player-react/package.json b/projects/sunbird-quml-player-react/package.json index 5c9b2f37..e6ae205c 100644 --- a/projects/sunbird-quml-player-react/package.json +++ b/projects/sunbird-quml-player-react/package.json @@ -1,7 +1,7 @@ { "name": "@project-sunbird/sunbird-quml-player-web-component-react", "private": true, - "version": "0.1.9", + "version": "0.1.10", "type": "module", "scripts": { "dev": "vite",