@intellectif/lk-react 6.0.0 → 7.0.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 (100) hide show
  1. package/CHANGELOG.md +386 -0
  2. package/README.md +45 -12
  3. package/dist/{WrittenResponse-BfpzfLlR.d.cts → WrittenResponse-BJL4dGsR.d.ts} +6 -2
  4. package/dist/{WrittenResponse-C3GIWErG.d.ts → WrittenResponse-zWT-Go1g.d.cts} +6 -2
  5. package/dist/{chunk-E4MNA2IQ.js → chunk-2TW6WLBC.js} +3 -2
  6. package/dist/chunk-2TW6WLBC.js.map +1 -0
  7. package/dist/chunk-6F4X2NKL.js +1 -0
  8. package/dist/chunk-6N43WDVG.js +1 -0
  9. package/dist/chunk-6ZKVJAO6.js +540 -0
  10. package/dist/chunk-6ZKVJAO6.js.map +1 -0
  11. package/dist/chunk-A572N5G7.cjs +542 -0
  12. package/dist/chunk-A572N5G7.cjs.map +1 -0
  13. package/dist/{chunk-CPF2JJG6.cjs → chunk-AMGK7DDM.cjs} +32 -2
  14. package/dist/chunk-AMGK7DDM.cjs.map +1 -0
  15. package/dist/{chunk-UQ3BBEIT.js → chunk-BG456EM3.js} +22 -5
  16. package/dist/chunk-BG456EM3.js.map +1 -0
  17. package/dist/{chunk-YXG3UJVG.cjs → chunk-BNESCXJ5.cjs} +17 -4
  18. package/dist/chunk-BNESCXJ5.cjs.map +1 -0
  19. package/dist/{chunk-BQHDQ33H.js → chunk-CMZGT26S.js} +22 -4
  20. package/dist/chunk-CMZGT26S.js.map +1 -0
  21. package/dist/{chunk-B3FUUKZ2.js → chunk-DQNVAXG6.js} +32 -3
  22. package/dist/chunk-DQNVAXG6.js.map +1 -0
  23. package/dist/{chunk-57E3E2H4.js → chunk-J23XVR2B.js} +17 -4
  24. package/dist/chunk-J23XVR2B.js.map +1 -0
  25. package/dist/{chunk-PBVMEOWI.cjs → chunk-J4LP4CLY.cjs} +110 -29
  26. package/dist/chunk-J4LP4CLY.cjs.map +1 -0
  27. package/dist/{chunk-NEWGUDA5.cjs → chunk-KK4WGW2V.cjs} +22 -5
  28. package/dist/chunk-KK4WGW2V.cjs.map +1 -0
  29. package/dist/{chunk-R3OZ6JHQ.cjs → chunk-KPI335JJ.cjs} +22 -4
  30. package/dist/chunk-KPI335JJ.cjs.map +1 -0
  31. package/dist/{chunk-5TUTJ4R3.js → chunk-MB532SXM.js} +17 -4
  32. package/dist/chunk-MB532SXM.js.map +1 -0
  33. package/dist/chunk-PGEG7UZJ.cjs +1 -0
  34. package/dist/{chunk-IYLQSNLE.cjs → chunk-PNNUPUH4.cjs} +3 -2
  35. package/dist/chunk-PNNUPUH4.cjs.map +1 -0
  36. package/dist/{chunk-NPY2F7F6.cjs → chunk-VXOOGQ6W.cjs} +17 -4
  37. package/dist/chunk-VXOOGQ6W.cjs.map +1 -0
  38. package/dist/chunk-XOR4MPEN.cjs +1 -0
  39. package/dist/{chunk-OQGBAQL2.js → chunk-ZPVMB3LH.js} +108 -27
  40. package/dist/chunk-ZPVMB3LH.js.map +1 -0
  41. package/dist/components/ActivitySequence.cjs +8 -7
  42. package/dist/components/ActivitySequence.d.cts +29 -23
  43. package/dist/components/ActivitySequence.d.ts +29 -23
  44. package/dist/components/ActivitySequence.js +7 -6
  45. package/dist/components/FillInTheBlanks.cjs +4 -3
  46. package/dist/components/FillInTheBlanks.d.cts +1 -1
  47. package/dist/components/FillInTheBlanks.d.ts +1 -1
  48. package/dist/components/FillInTheBlanks.js +3 -2
  49. package/dist/components/MultipleChoice.cjs +4 -3
  50. package/dist/components/MultipleChoice.d.cts +2 -2
  51. package/dist/components/MultipleChoice.d.ts +2 -2
  52. package/dist/components/MultipleChoice.js +3 -2
  53. package/dist/components/StimulusPanel.cjs +4 -3
  54. package/dist/components/StimulusPanel.d.cts +19 -3
  55. package/dist/components/StimulusPanel.d.ts +19 -3
  56. package/dist/components/StimulusPanel.js +3 -2
  57. package/dist/components/WrittenResponse.cjs +4 -3
  58. package/dist/components/WrittenResponse.d.cts +3 -3
  59. package/dist/components/WrittenResponse.d.ts +3 -3
  60. package/dist/components/WrittenResponse.js +3 -2
  61. package/dist/hooks/useActivityState.cjs +1 -0
  62. package/dist/hooks/useActivityState.js +1 -0
  63. package/dist/hooks/useXAPI.cjs +3 -2
  64. package/dist/hooks/useXAPI.d.cts +31 -0
  65. package/dist/hooks/useXAPI.d.ts +31 -0
  66. package/dist/hooks/useXAPI.js +2 -1
  67. package/dist/index.cjs +23 -18
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.d.cts +3 -3
  70. package/dist/index.d.ts +3 -3
  71. package/dist/index.js +9 -8
  72. package/dist/index.js.map +1 -1
  73. package/dist/theme/ThemeProvider.cjs +10 -5
  74. package/dist/theme/ThemeProvider.d.cts +21 -1
  75. package/dist/theme/ThemeProvider.d.ts +21 -1
  76. package/dist/theme/ThemeProvider.js +2 -1
  77. package/dist/theme/skin.css +211 -8
  78. package/dist/types-eXX4hzSj.d.cts +307 -0
  79. package/dist/types-eXX4hzSj.d.ts +307 -0
  80. package/package.json +28 -5
  81. package/dist/chunk-4HZ7LQIH.cjs +0 -40
  82. package/dist/chunk-4HZ7LQIH.cjs.map +0 -1
  83. package/dist/chunk-57E3E2H4.js.map +0 -1
  84. package/dist/chunk-5TUTJ4R3.js.map +0 -1
  85. package/dist/chunk-B3FUUKZ2.js.map +0 -1
  86. package/dist/chunk-BQHDQ33H.js.map +0 -1
  87. package/dist/chunk-CPF2JJG6.cjs.map +0 -1
  88. package/dist/chunk-E4MNA2IQ.js.map +0 -1
  89. package/dist/chunk-GB5URWM4.js +0 -38
  90. package/dist/chunk-GB5URWM4.js.map +0 -1
  91. package/dist/chunk-IYLQSNLE.cjs.map +0 -1
  92. package/dist/chunk-NEWGUDA5.cjs.map +0 -1
  93. package/dist/chunk-NPY2F7F6.cjs.map +0 -1
  94. package/dist/chunk-OQGBAQL2.js.map +0 -1
  95. package/dist/chunk-PBVMEOWI.cjs.map +0 -1
  96. package/dist/chunk-R3OZ6JHQ.cjs.map +0 -1
  97. package/dist/chunk-UQ3BBEIT.js.map +0 -1
  98. package/dist/chunk-YXG3UJVG.cjs.map +0 -1
  99. package/dist/types-DlHmPLbD.d.cts +0 -165
  100. package/dist/types-DlHmPLbD.d.ts +0 -165
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":[],"mappings":";;;;AAqBA,IAAM,UAAA,GAA+C;AAAA,EACnD,IAAA,EAAM,SAAA;AAAA,EACN,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,OAAA;AAAA,EACP,KAAA,EAAO,OAAA;AAAA,EACP,KAAA,EAAO;AACT,CAAA;AAWO,SAAS,aAAA,CAAc;AAAA,EAC5B,QAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,EAA0C;AACxC,EAAA,MAAM,UAAU,KAAA,EAAM;AAEtB,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,YAAA,KAAiB,MAAA,IAAa,OAAO,QAAA,CAAS,aAAa,QAAA,EAAU;AACvE,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,YAAA,CAAa,SAAS,QAAQ,CAAA;AAAA,EACvC,CAAA,EAAG,CAAC,QAAA,CAAS,QAAA,EAAU,YAAY,CAAC,CAAA;AAEpC,EAAA,MAAM,YACJ,KAAA,KAAU,MAAA,GACN,IAAA,GACA,KAAA,CAAM,UAAU,KAAA,CAAM,IAAA,GACpB,CAAA,SAAA,EAAY,KAAA,CAAM,KAAK,CAAA,CAAA,GACvB,CAAA,UAAA,EAAa,MAAM,KAAK,CAAA,MAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AAE9C,EAAA,MAAM,MAAA,GACJ,QAAA,CAAS,KAAA,KAAU,MAAA,GACf,EAAE,iBAAA,EAAmB,OAAA,EAAQ,GAC7B,EAAE,YAAA,EAAc,UAAA,CAAW,QAAA,CAAS,IAAI,CAAA,EAAE;AAShD,EAAA,MAAM,WAAA,GAAc,SAAS,MAAA,KAAW,MAAA,GAAY,EAAE,IAAA,EAAM,QAAA,CAAS,MAAA,EAAO,GAAI,EAAC;AAEjF,EAAA,uBACE,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,aAAA,EAAc,WAAA,EAAW,SAAS,IAAA,EAAM,IAAA,EAAM,MAAA,EAAS,GAAG,MAAA,EAC1E,QAAA,EAAA;AAAA,IAAA,QAAA,CAAS,KAAA,KAAU,MAAA,mBAClB,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mBAAA,EAAoB,EAAA,EAAI,OAAA,EAAU,GAAG,WAAA,EAC/C,QAAA,EAAA,QAAA,CAAS,KAAA,EACZ,CAAA,GACE,IAAA;AAAA,IACH,cAAc,IAAA,mBAAO,GAAA,CAAC,OAAE,SAAA,EAAU,mBAAA,EAAqB,qBAAU,CAAA,GAAO,IAAA;AAAA,IACxE,QAAA,CAAS,UAAU,MAAA,mBAAY,GAAA,CAAC,iBAAc,KAAA,EAAO,QAAA,CAAS,OAAO,CAAA,GAAK,IAAA;AAAA,IAC1E,aAAa,IAAA,mBACZ,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,kBAAA;AAAA,QACV,aAAA,EAAY,MAAA;AAAA,QACX,GAAG,WAAA;AAAA,QAEJ,uBAAA,EAAyB,EAAE,MAAA,EAAQ,QAAA;AAAS;AAAA,KAC9C,GACE,QAAA,CAAS,IAAA,KAAS,MAAA,uBACnB,KAAA,EAAA,EAAI,SAAA,EAAU,kBAAA,EAAmB,aAAA,EAAY,MAAA,EAAQ,GAAG,WAAA,EACtD,QAAA,EAAA,QAAA,CAAS,MACZ,CAAA,GACE,IAAA;AAAA,IACH,QAAA,CAAS,WAAA,KAAgB,MAAA,mBACxB,GAAA,CAAC,OAAE,SAAA,EAAU,yBAAA,EAA2B,GAAG,WAAA,EACzC,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,QAAA,CAAS,WAAA,EAAY,GAC9B,CAAA,GACE;AAAA,GAAA,EACN,CAAA;AAEJ","file":"chunk-UQ3BBEIT.js","sourcesContent":["'use client';\n\nimport type { Stimulus } from '@intellectif/lk-core';\nimport { useId, useMemo } from 'react';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport type { HtmlSanitizer } from '../types.js';\n\nexport interface StimulusPanelProps {\n stimulus: Stimulus;\n /**\n * 1-based presented positions of the first and last question this material\n * serves, rendered as \"Questions 3–8\" so a learner knows how far the\n * passage carries.\n */\n range?: { first: number; last: number };\n /** Renders `stimulus.bodyHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n locale?: string;\n}\n\n/** Accessible name of the region when the stimulus has no title of its own. */\nconst KIND_LABEL: Record<Stimulus['kind'], string> = {\n text: 'Passage',\n audio: 'Recording',\n video: 'Video',\n image: 'Image',\n mixed: 'Material',\n};\n\n/**\n * Presents a shared stimulus — the passage, recording or image an item group's\n * questions refer to. A landmark region named by its title (or by its kind),\n * so a screen-reader user can jump back to the material from any question.\n *\n * Rich text follows the SDK-wide rule: `bodyHtml` is rendered only through a\n * caller-supplied sanitiser, and the plain `body` is the fallback — which the\n * schema guarantees exists whenever `bodyHtml` does.\n */\nexport function StimulusPanel({\n stimulus,\n range,\n sanitizeHtml,\n locale,\n}: StimulusPanelProps): React.JSX.Element {\n const titleId = useId();\n\n const bodyHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof stimulus.bodyHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(stimulus.bodyHtml);\n }, [stimulus.bodyHtml, sanitizeHtml]);\n\n const rangeText =\n range === undefined\n ? null\n : range.first === range.last\n ? `Question ${range.first}`\n : `Questions ${range.first}–${range.last}`;\n\n const naming =\n stimulus.title !== undefined\n ? { 'aria-labelledby': titleId }\n : { 'aria-label': KIND_LABEL[stimulus.kind] };\n\n // The AUTHORED parts carry the stimulus language; the SDK's own chrome does\n // not. Declaring `stimulus.locale` on the whole region made a screen reader\n // read \"Recording\" and \"Questions 3–5\" — English strings this package ships\n // — in the passage's voice, so a Spanish listening panel announced its own\n // name and the only orientation text in it with Spanish phonetics\n // (WCAG 3.1.2 Language of Parts). Scope the declaration to the content it\n // actually describes.\n const contentLang = stimulus.locale !== undefined ? { lang: stimulus.locale } : {};\n\n return (\n <section className=\"lk-stimulus\" data-kind={stimulus.kind} lang={locale} {...naming}>\n {stimulus.title !== undefined ? (\n <p className=\"lk-stimulus-title\" id={titleId} {...contentLang}>\n {stimulus.title}\n </p>\n ) : null}\n {rangeText !== null ? <p className=\"lk-stimulus-range\">{rangeText}</p> : null}\n {stimulus.media !== undefined ? <ActivityMedia media={stimulus.media} /> : null}\n {bodyHtml !== null ? (\n <div\n className=\"lk-stimulus-body\"\n data-format=\"html\"\n {...contentLang}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n ) : stimulus.body !== undefined ? (\n <div className=\"lk-stimulus-body\" data-format=\"text\" {...contentLang}>\n {stimulus.body}\n </div>\n ) : null}\n {stimulus.attribution !== undefined ? (\n <p className=\"lk-stimulus-attribution\" {...contentLang}>\n <cite>{stimulus.attribution}</cite>\n </p>\n ) : null}\n </section>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["useMemo","isDevelopment","validateActivity","ActivitySchemaError","useRef","useActivityState","useState","useEffect","randomSessionId","seededShuffle","score","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","jsxs","jsx","ActivityMedia","FeedbackRegion","MultipleChoice","ActivityErrorBoundary"],"mappings":"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","file":"chunk-YXG3UJVG.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type ItemOutcome,\n type LearnerResponse,\n type MultipleChoiceData,\n type MultipleChoiceLearnerResponse,\n type MultipleChoiceOption,\n type ScoringDetail,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/**\n * Selected option ids carried by a learner response. Any other response shape\n * (a caller wiring the wrong item's state through `value`) reads as \"nothing\n * selected\" rather than throwing mid-exam.\n */\nfunction selectionOf(response: LearnerResponse | undefined): string[] {\n return response?.type === 'multiple-choice' ? response.selectedOptionIds : [];\n}\n\n/**\n * Whether an option is part of the correct answer, derived from the SERVER's\n * outcome — review mode never holds an answer key of its own.\n * {@link ScoringDetail.outcome} states this unambiguously. For 0.2-era details\n * that carry only the deprecated `correct` flag (which means \"the learner\n * ACTED correctly on this option\", not \"this option is the answer\") the fact\n * is still recoverable, because we know whether the learner selected it:\n * `isCorrect = wasSelected ? correct : !correct`.\n */\nfunction isAnswerOption(detail: ScoringDetail, wasSelected: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return wasSelected ? detail.correct : !detail.correct;\n }\n}\n\n/** Announcement for `review` mode, built only from the server-supplied outcome. */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `Score ${Math.round(outcome.score * 100)}%. ${\n outcome.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return 'Not graded yet.';\n }\n return null;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the v1 behaviour).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n defaultSubmitted,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n}: MultipleChoiceProps) {\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // A `redact()` projection cannot satisfy the full content schema — the\n // answer key is gone by design — and must not be checked against the\n // strict redacted schema either, since a `reveal: 'after-submit'`\n // projection legitimately carries answer-key fields for review renders.\n if (data.redacted === true) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Uncontrolled state. `defaultValue` seeds the mount only (React convention);\n // to re-seed later, remount with a `key` or drive the component with `value`.\n const [internalSelection, setInternalSelection] = useState<string[]>(() =>\n selectionOf(defaultValue),\n );\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop CHANGE (Req 3.7). The identity guard makes the mount\n // run a no-op, which it always was before `defaultValue` existed — without\n // it this effect would wipe the seed immediately after the first render.\n // (It also makes the reset StrictMode-safe: a remount with unchanged data\n // no longer discards the learner's selection.)\n // Resets back to `defaultValue`, not to empty. Clearing looked safer — it\n // cannot carry a stale answer into a different question — but `data`\n // identity is a poor proxy for \"different question\": a parent that builds\n // entries in render (`activities={raw.map(redact)}`, the documented exam\n // pattern) hands over a new object every render, and clearing wiped every\n // RESTORED answer on the first unrelated re-render, silently. `defaultValue`\n // is by definition the caller's seed for the CURRENT data, which is what\n // FillInTheBlanks already reset to; the two now agree.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n // A data change must return the item to its SEED, submitted state included —\n // resetting to idle unlocked work the learner had already committed.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection(selectionOf(defaultValueRef.current));\n setSummary(null);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n // The seed string is unchanged from when the algorithm lived in this file,\n // so an order a consumer recorded against a seed still reproduces.\n return seededShuffle(data.options, `${seedSource}:${data.id}`);\n }, [data, shuffleSeed]);\n\n // Per-option correctness for `review`, indexed by option id. Null unless the\n // caller supplied a SCORED outcome — the only source of truth this mode has.\n const reviewDetails = useMemo(() => {\n if (renderMode !== 'review' || outcome === undefined || outcome.status !== 'scored') {\n return null;\n }\n return new Map(outcome.details.map((detail) => [detail.itemId, detail]));\n }, [renderMode, outcome]);\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support:\n // `score()` would throw from the submit handler — where no error boundary\n // can catch it — AFTER the learner has answered. Fail loudly at render\n // instead, in production too. Wiring an exam item into the self-grading\n // mode is precisely the accident `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Multiple Choice \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const isSingle = data.mode === 'single';\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const isControlled = value !== undefined;\n const selected = isControlled ? selectionOf(value) : internalSelection;\n /**\n * Whether correctness and authored feedback may be shown. Practice earns it\n * by grading locally after submit; review shows what the server already\n * graded. Exam never reveals anything — not before, not after submit.\n */\n const reveal = isReview || (renderMode === 'practice' && submitted);\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n /** Single funnel for every response change: owns state only when uncontrolled. */\n const emitChange = (selectedOptionIds: string[]): void => {\n if (!isControlled) {\n setInternalSelection(selectedOptionIds);\n }\n onChange?.({ type: 'multiple-choice', selectedOptionIds });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: MultipleChoiceLearnerResponse = {\n type: 'multiple-choice',\n selectedOptionIds: selected,\n };\n // Always first, and before anything that can throw: an exam runner must be\n // able to persist the raw response no matter what happens after.\n onSubmit?.(response);\n\n if (isExam) {\n // No score(), no answer key read, no onComplete, no xAPI — the server\n // grades. The announcement deliberately carries no correctness signal.\n complete();\n setSummary('Answer submitted.');\n fireInteraction('submitted', { selectedOptionIds: selected });\n return;\n }\n\n const scoringResult = score('multiple-choice', data as MultipleChoiceData, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n // Activity-type IRI, interaction type and correct-responses pattern\n // come from the REGISTERED descriptor, so a consumer-registered type\n // gets correct interop without touching this component.\n ...xapiDefinitionFor(data),\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `Answer submitted. Score ${Math.round(scoringResult.score * 100)}%. ${\n scoringResult.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n // Rich text renders ONLY through a caller-supplied sanitiser; without one we\n // fall back to the plain-text field, escaped by React (types.ts HtmlSanitizer).\n const questionHtml =\n sanitizeHtml !== undefined && data.questionHtml !== undefined\n ? sanitizeHtml(data.questionHtml)\n : null;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n // `data-correct` means \"this option is part of the correct answer\".\n let correctness: string | undefined;\n if (isReview) {\n const detail = reviewDetails?.get(option.id);\n correctness = detail === undefined ? undefined : String(isAnswerOption(detail, checked));\n } else if (reveal) {\n correctness = String(option.isCorrect);\n }\n return (\n <label key={option.id} className=\"lk-mc-option\" data-correct={correctness}>\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? <ActivityMedia media={data.media} /> : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n {questionHtml === null ? (\n <legend id={questionId}>{data.question}</legend>\n ) : (\n <legend\n id={questionId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is the output of the caller-supplied sanitizeHtml (types.ts HtmlSanitizer contract)\n dangerouslySetInnerHTML={{ __html: questionHtml }}\n />\n )}\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n {/* review is read-only: there is nothing left to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n Submit\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome) : summary}\n </FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n MultipleChoice as MultipleChoiceCore,\n type MultipleChoiceProps,\n} from './MultipleChoice.js';\n\nexport type { MultipleChoiceProps } from './MultipleChoice.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: MultipleChoiceProps) {\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1,165 +0,0 @@
1
- import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry } from '@intellectif/lk-core';
2
-
3
- /**
4
- * How an activity is being presented. This is the single switch that decides
5
- * whether the component may grade, reveal, or submit — the three things a
6
- * summative exam must take away from the client.
7
- *
8
- * - `practice` (default, v1 behaviour): the component owns the attempt. It
9
- * scores locally on submit, reveals correctness and feedback, and calls
10
- * `onComplete` with a full {@link ActivityResult}.
11
- * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit
12
- * emits the raw learner response through `onSubmit`; the server grades it.
13
- * Safe to render redacted data, because nothing here needs the answer key.
14
- * - `review`: read-only. Renders the learner's submitted answer, and marks
15
- * correctness only from an `outcome` the caller supplies (which the server
16
- * computed). Nothing is submittable.
17
- */
18
- type RenderMode = 'practice' | 'exam' | 'review';
19
- /**
20
- * Makes the answer-key-bearing parts of an activity optional, so a component
21
- * can render either full activity data OR a `redact()` projection with the
22
- * same props. Fields the SDK classifies `answer-key` become optional here;
23
- * a component in `exam` mode must not read them at all.
24
- */
25
- type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {
26
- scoringStrategy?: unknown;
27
- /** Present on a `redact()` projection. */
28
- redacted?: true;
29
- };
30
- /**
31
- * {@link Renderable} DISTRIBUTED over a union of activity types.
32
- *
33
- * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on
34
- * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the
35
- * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single
36
- * object type carrying only the fields every activity shares. Narrowing it
37
- * dies with it — after `if (data.type === 'multiple-choice')` the compiler
38
- * still refuses `data.options`, because the union it would narrow to no
39
- * longer exists.
40
- *
41
- * The conditional below re-distributes, so `RenderableActivity` is a real
42
- * union of per-type renderables and `.type` narrows again. Anything that
43
- * accepts "some renderable activity, I don't know which" — a custom renderer,
44
- * a sequence entry — must use THIS, not `Renderable<ActivityData>`.
45
- */
46
- type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown ? Renderable<TData> : never;
47
- /**
48
- * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,
49
- * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a
50
- * dependency and, worse, a false promise. Rich text is rendered only when you
51
- * supply this function; without it the component falls back to the plain-text
52
- * field, which is always escaped. Fail-safe by construction: the SDK never
53
- * injects HTML it was not explicitly given a sanitiser for.
54
- */
55
- type HtmlSanitizer = (html: string) => string;
56
- /**
57
- * The prop contract shared by every activity component (Req 3.1). Defined
58
- * here (React-specific) rather than in lk-core, which is React-free.
59
- *
60
- * Controlled / uncontrolled follows the React convention: pass `value` +
61
- * `onChange` to own the learner's answer (restore an in-progress attempt,
62
- * autosave a delta, drive a review); pass `defaultValue` to seed an
63
- * uncontrolled component; pass neither for the v1 behaviour.
64
- */
65
- interface ActivityProps<TData extends ActivityData = ActivityData> {
66
- /** Activity content. Accepts a `redact()` projection in `exam` mode. */
67
- data: RenderableActivity<TData>;
68
- /**
69
- * Called when the component scored the attempt itself. Only ever fires in
70
- * `practice` mode — in `exam` mode the client does not grade, so there is
71
- * no `ActivityResult` to give you; use `onSubmit`.
72
- */
73
- onComplete?: (result: ActivityResult) => void;
74
- /**
75
- * Called on submit with the raw learner response and no grade. Fires in
76
- * every mode, before `onComplete`, so an exam runner can persist the
77
- * response and let the server score it.
78
- */
79
- onSubmit?: (response: LearnerResponse) => void;
80
- /** Controlled value: the learner's current response. */
81
- value?: LearnerResponse;
82
- /** Initial response for an uncontrolled component (ignored when `value` is set). */
83
- defaultValue?: LearnerResponse;
84
- /**
85
- * Mount the component as already submitted — read at mount only, like any
86
- * `default*` prop.
87
- *
88
- * Restoring an attempt without it reopens a question the learner had already
89
- * submitted as answerable, so on a summative paper they can change and
90
- * re-submit it. `AttemptState.submittedSlotIds` is what this consumes.
91
- */
92
- defaultSubmitted?: boolean;
93
- /** Fires on every change to the learner's response. Required for a controlled component. */
94
- onChange?: (response: LearnerResponse) => void;
95
- /** Presentation mode. Defaults to `practice`. */
96
- renderMode?: RenderMode;
97
- /**
98
- * Server-computed outcome, used by `review` mode to mark correctness
99
- * without the client ever scoring. Ignored in other modes.
100
- */
101
- outcome?: ItemOutcome;
102
- /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */
103
- sanitizeHtml?: HtmlSanitizer;
104
- onInteraction?: (event: InteractionEvent) => void;
105
- /** Per-instance token overrides, applied as inline CSS vars on the root. */
106
- theme?: Partial<ThemeTokens>;
107
- locale?: string;
108
- disabled?: boolean;
109
- }
110
- /**
111
- * Bridges a server-produced `redact()` projection into the `data` prop.
112
- *
113
- * `RedactedActivityData` is deliberately index-signature typed in lk-core — it
114
- * proves a payload is learner-safe, not what shape it has — so TypeScript
115
- * cannot know it still carries the public fields a renderer needs. This is the
116
- * SDK's single, documented crossing of that gap, so an exam runner does not
117
- * have to write `as unknown as` at every call site:
118
- *
119
- * ```tsx
120
- * <MultipleChoice
121
- * data={asRenderable<MultipleChoiceData>(redactedFromServer)}
122
- * renderMode="exam"
123
- * onSubmit={persist}
124
- * />
125
- * ```
126
- *
127
- * Safe because `exam` mode reads only public fields; the answer-key fields the
128
- * type claims are exactly the ones the component is forbidden to touch there.
129
- * A future release will derive per-type redacted interfaces so this becomes
130
- * unnecessary.
131
- */
132
- declare function asRenderable<TData extends ActivityData>(redacted: RedactedActivityData): Renderable<TData>;
133
- /**
134
- * The same bridge as {@link asRenderable}, for a whole sequence: activities
135
- * and item groups as a server hands them over, ready for `<ActivitySequence>`.
136
- *
137
- * Needed for the same reason and no other. `redactItemGroup` returns
138
- * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an
139
- * index-signature type whose fields are all `unknown` — so its `question` is
140
- * not a `string` and it satisfies no per-type renderable, however the prop is
141
- * widened. Widening alone cannot fix this; a crossing point is required, and
142
- * having exactly one keeps `as unknown as` out of consumer code.
143
- *
144
- * ```tsx
145
- * const entries = await fetchExam(); // redacted, server-side
146
- * <ActivitySequence
147
- * activities={asRenderableSequence(entries)}
148
- * renderMode="exam" // REQUIRED: see below
149
- * shuffleSeed={attemptId}
150
- * onSubmit={persist}
151
- * />
152
- * ```
153
- *
154
- * Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
155
- * and the default `practice` mode grades locally — the built-in components
156
- * throw at render rather than fail at submit time, so a mis-wired exam item is
157
- * loud, not silent.
158
- */
159
- declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
160
- /** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
161
- type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
162
- redacted: true;
163
- };
164
-
165
- export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, type RenderableActivity as b, asRenderable as c, asRenderableSequence as d };
@@ -1,165 +0,0 @@
1
- import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry } from '@intellectif/lk-core';
2
-
3
- /**
4
- * How an activity is being presented. This is the single switch that decides
5
- * whether the component may grade, reveal, or submit — the three things a
6
- * summative exam must take away from the client.
7
- *
8
- * - `practice` (default, v1 behaviour): the component owns the attempt. It
9
- * scores locally on submit, reveals correctness and feedback, and calls
10
- * `onComplete` with a full {@link ActivityResult}.
11
- * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit
12
- * emits the raw learner response through `onSubmit`; the server grades it.
13
- * Safe to render redacted data, because nothing here needs the answer key.
14
- * - `review`: read-only. Renders the learner's submitted answer, and marks
15
- * correctness only from an `outcome` the caller supplies (which the server
16
- * computed). Nothing is submittable.
17
- */
18
- type RenderMode = 'practice' | 'exam' | 'review';
19
- /**
20
- * Makes the answer-key-bearing parts of an activity optional, so a component
21
- * can render either full activity data OR a `redact()` projection with the
22
- * same props. Fields the SDK classifies `answer-key` become optional here;
23
- * a component in `exam` mode must not read them at all.
24
- */
25
- type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {
26
- scoringStrategy?: unknown;
27
- /** Present on a `redact()` projection. */
28
- redacted?: true;
29
- };
30
- /**
31
- * {@link Renderable} DISTRIBUTED over a union of activity types.
32
- *
33
- * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on
34
- * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the
35
- * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single
36
- * object type carrying only the fields every activity shares. Narrowing it
37
- * dies with it — after `if (data.type === 'multiple-choice')` the compiler
38
- * still refuses `data.options`, because the union it would narrow to no
39
- * longer exists.
40
- *
41
- * The conditional below re-distributes, so `RenderableActivity` is a real
42
- * union of per-type renderables and `.type` narrows again. Anything that
43
- * accepts "some renderable activity, I don't know which" — a custom renderer,
44
- * a sequence entry — must use THIS, not `Renderable<ActivityData>`.
45
- */
46
- type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown ? Renderable<TData> : never;
47
- /**
48
- * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,
49
- * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a
50
- * dependency and, worse, a false promise. Rich text is rendered only when you
51
- * supply this function; without it the component falls back to the plain-text
52
- * field, which is always escaped. Fail-safe by construction: the SDK never
53
- * injects HTML it was not explicitly given a sanitiser for.
54
- */
55
- type HtmlSanitizer = (html: string) => string;
56
- /**
57
- * The prop contract shared by every activity component (Req 3.1). Defined
58
- * here (React-specific) rather than in lk-core, which is React-free.
59
- *
60
- * Controlled / uncontrolled follows the React convention: pass `value` +
61
- * `onChange` to own the learner's answer (restore an in-progress attempt,
62
- * autosave a delta, drive a review); pass `defaultValue` to seed an
63
- * uncontrolled component; pass neither for the v1 behaviour.
64
- */
65
- interface ActivityProps<TData extends ActivityData = ActivityData> {
66
- /** Activity content. Accepts a `redact()` projection in `exam` mode. */
67
- data: RenderableActivity<TData>;
68
- /**
69
- * Called when the component scored the attempt itself. Only ever fires in
70
- * `practice` mode — in `exam` mode the client does not grade, so there is
71
- * no `ActivityResult` to give you; use `onSubmit`.
72
- */
73
- onComplete?: (result: ActivityResult) => void;
74
- /**
75
- * Called on submit with the raw learner response and no grade. Fires in
76
- * every mode, before `onComplete`, so an exam runner can persist the
77
- * response and let the server score it.
78
- */
79
- onSubmit?: (response: LearnerResponse) => void;
80
- /** Controlled value: the learner's current response. */
81
- value?: LearnerResponse;
82
- /** Initial response for an uncontrolled component (ignored when `value` is set). */
83
- defaultValue?: LearnerResponse;
84
- /**
85
- * Mount the component as already submitted — read at mount only, like any
86
- * `default*` prop.
87
- *
88
- * Restoring an attempt without it reopens a question the learner had already
89
- * submitted as answerable, so on a summative paper they can change and
90
- * re-submit it. `AttemptState.submittedSlotIds` is what this consumes.
91
- */
92
- defaultSubmitted?: boolean;
93
- /** Fires on every change to the learner's response. Required for a controlled component. */
94
- onChange?: (response: LearnerResponse) => void;
95
- /** Presentation mode. Defaults to `practice`. */
96
- renderMode?: RenderMode;
97
- /**
98
- * Server-computed outcome, used by `review` mode to mark correctness
99
- * without the client ever scoring. Ignored in other modes.
100
- */
101
- outcome?: ItemOutcome;
102
- /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */
103
- sanitizeHtml?: HtmlSanitizer;
104
- onInteraction?: (event: InteractionEvent) => void;
105
- /** Per-instance token overrides, applied as inline CSS vars on the root. */
106
- theme?: Partial<ThemeTokens>;
107
- locale?: string;
108
- disabled?: boolean;
109
- }
110
- /**
111
- * Bridges a server-produced `redact()` projection into the `data` prop.
112
- *
113
- * `RedactedActivityData` is deliberately index-signature typed in lk-core — it
114
- * proves a payload is learner-safe, not what shape it has — so TypeScript
115
- * cannot know it still carries the public fields a renderer needs. This is the
116
- * SDK's single, documented crossing of that gap, so an exam runner does not
117
- * have to write `as unknown as` at every call site:
118
- *
119
- * ```tsx
120
- * <MultipleChoice
121
- * data={asRenderable<MultipleChoiceData>(redactedFromServer)}
122
- * renderMode="exam"
123
- * onSubmit={persist}
124
- * />
125
- * ```
126
- *
127
- * Safe because `exam` mode reads only public fields; the answer-key fields the
128
- * type claims are exactly the ones the component is forbidden to touch there.
129
- * A future release will derive per-type redacted interfaces so this becomes
130
- * unnecessary.
131
- */
132
- declare function asRenderable<TData extends ActivityData>(redacted: RedactedActivityData): Renderable<TData>;
133
- /**
134
- * The same bridge as {@link asRenderable}, for a whole sequence: activities
135
- * and item groups as a server hands them over, ready for `<ActivitySequence>`.
136
- *
137
- * Needed for the same reason and no other. `redactItemGroup` returns
138
- * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an
139
- * index-signature type whose fields are all `unknown` — so its `question` is
140
- * not a `string` and it satisfies no per-type renderable, however the prop is
141
- * widened. Widening alone cannot fix this; a crossing point is required, and
142
- * having exactly one keeps `as unknown as` out of consumer code.
143
- *
144
- * ```tsx
145
- * const entries = await fetchExam(); // redacted, server-side
146
- * <ActivitySequence
147
- * activities={asRenderableSequence(entries)}
148
- * renderMode="exam" // REQUIRED: see below
149
- * shuffleSeed={attemptId}
150
- * onSubmit={persist}
151
- * />
152
- * ```
153
- *
154
- * Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
155
- * and the default `practice` mode grades locally — the built-in components
156
- * throw at render rather than fail at submit time, so a mis-wired exam item is
157
- * loud, not silent.
158
- */
159
- declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
160
- /** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
161
- type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
162
- redacted: true;
163
- };
164
-
165
- export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, type RenderableActivity as b, asRenderable as c, asRenderableSequence as d };