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", 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..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 @@ -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; @@ -15,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/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..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 @@ -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%); @@ -10,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; } 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; } } 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 e7890079..778dbe57 100644 Binary files a/projects/sunbird-quml-player-react/src/utils/score.ts and b/projects/sunbird-quml-player-react/src/utils/score.ts differ