coursecode 0.1.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 (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. package/template/vite.config.js +339 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Steps Layout Pattern
3
+ *
4
+ * CSS-only component - no JavaScript behavior, just schema for discovery and validation.
5
+ * Displays numbered step-by-step processes with visual connecting elements.
6
+ */
7
+
8
+ export const schema = {
9
+ type: 'steps',
10
+ description: 'Numbered step-by-step process display',
11
+ example: `<div data-component="steps">
12
+ <div class="step"><div class="step-number">1</div><div class="step-content"><h3>Plan</h3><p>Define objectives and outline your course structure.</p></div></div>
13
+ <div class="step"><div class="step-number">2</div><div class="step-content"><h3>Build</h3><p>Create slides with content, components, and interactions.</p></div></div>
14
+ <div class="step"><div class="step-number">3</div><div class="step-content"><h3>Deploy</h3><p>Export and upload to your LMS platform.</p></div></div>
15
+ </div>`,
16
+ properties: {
17
+ style: {
18
+ type: 'string',
19
+ enum: ['cards', 'connected', 'compact', 'connected-minimal'],
20
+ default: 'cards',
21
+ description: 'Visual style variant (data-style attribute)'
22
+ }
23
+ },
24
+ structure: {
25
+ container: '[data-component="steps"]',
26
+ children: {
27
+ step: { selector: '.step', required: true, minItems: 1 },
28
+ stepNumber: { selector: '.step-number', parent: '.step', required: true },
29
+ stepContent: { selector: '.step-content', parent: '.step', required: true }
30
+ }
31
+ }
32
+ };
33
+
34
+ export const metadata = {
35
+ category: 'ui-component',
36
+ cssOnly: true,
37
+ cssFile: 'components/steps.css'
38
+ };
39
+
40
+ /** No-op initializer — CSS-only component, registered for consistency. */
41
+ export function init() {}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @file tabs.js
3
+ * @description Accessible tab interface with keyboard navigation and event delegation.
4
+ *
5
+ * Usage example:
6
+ * <div class="tabs" id="feature-tabs">
7
+ * <button class="tab-button" data-action="select-tab" data-tab="overview" aria-controls="overview-panel">Overview</button>
8
+ * <div id="overview-panel" class="tab-content">...</div>
9
+ * </div>
10
+ * UIComponents.initTabs('#feature-tabs', { buttonSelector: '.tab-button', panelSelector: '.tab-content' });
11
+ *
12
+ * For audio in tabs, use the standalone audio player component inside the tab panel:
13
+ * <div id="overview-panel" class="tab-content">
14
+ * <div data-component="audio-player" data-audio-id="overview-audio"
15
+ * data-audio-src="audio/overview.mp3" data-audio-required="true"></div>
16
+ * <p>Panel content...</p>
17
+ * </div>
18
+ */
19
+
20
+ import { announceToScreenReader } from './index.js';
21
+ import { validateTabActions } from '../../validation/html-validators.js';
22
+ import engagementManager from '../../engagement/engagement-manager.js';
23
+ import * as NavigationState from '../../navigation/NavigationState.js';
24
+ import { logger } from '../../utilities/logger.js';
25
+
26
+ // Schema for validation, linting, and AI-assisted authoring
27
+ export const schema = {
28
+ type: 'tabs',
29
+ description: 'Accessible tab interface with keyboard navigation',
30
+ example: `<div data-component="tabs">
31
+ <div class="tab-list" role="tablist">
32
+ <button class="tab-button active" data-action="select-tab" data-tab="overview" role="tab">Overview</button>
33
+ <button class="tab-button" data-action="select-tab" data-tab="details" role="tab">Details</button>
34
+ <button class="tab-button" data-action="select-tab" data-tab="resources" role="tab">Resources</button>
35
+ </div>
36
+ <div id="overview" class="tab-content active" role="tabpanel"><p>This is the overview panel with introductory content.</p></div>
37
+ <div id="details" class="tab-content" role="tabpanel" hidden><p>Detailed information goes here with supporting data.</p></div>
38
+ <div id="resources" class="tab-content" role="tabpanel" hidden><p>Links, downloads, and additional resources.</p></div>
39
+ </div>`,
40
+ properties: {
41
+ buttonSelector: { type: 'string', default: '[role="tab"], .tab-button', description: 'Selector for tab buttons' },
42
+ panelSelector: { type: 'string', default: '[role="tabpanel"], .tab-content', description: 'Selector for tab panels' },
43
+ panelRoot: { type: 'string', description: 'Optional selector for panel container if different from tab container' },
44
+ activeClass: { type: 'string', default: 'active', description: 'CSS class for active state' }
45
+ },
46
+ structure: {
47
+ container: '[data-component="tabs"]',
48
+ children: {
49
+ button: { selector: '[data-action="select-tab"]', required: true, minItems: 1 },
50
+ panel: { selector: '.tab-content, [role="tabpanel"]', required: true }
51
+ }
52
+ }
53
+ };
54
+
55
+ export const metadata = {
56
+ category: 'ui-component',
57
+ cssFile: 'components/tabs.css',
58
+ engagementTracking: 'viewAllTabs',
59
+ emitsEvents: ['tab:selected']
60
+ };
61
+
62
+ export function init(root, options = {}) {
63
+ const container = resolveRoot(root);
64
+ if (!container) {
65
+ logger.fatal('UIComponents.initTabs: container not found', { domain: 'ui', operation: 'initTabs' });
66
+ return;
67
+ }
68
+
69
+ // Read config from data attributes if no options are passed, allowing declarative setup
70
+ const buttonSelector = options.buttonSelector || container.dataset.buttonSelector || '[role="tab"], .tab-button';
71
+ const panelSelector = options.panelSelector || container.dataset.panelSelector || '[role="tabpanel"], .tab-content';
72
+ const panelRoot = resolveRoot(options.panelRoot || container.dataset.panelRoot) || container;
73
+ const activeClass = options.activeClass || container.dataset.activeClass || 'active';
74
+ const onChange = typeof options.onChange === 'function' ? options.onChange : null;
75
+
76
+ const buttons = Array.from(container.querySelectorAll(buttonSelector));
77
+ const panels = Array.from(panelRoot.querySelectorAll(panelSelector));
78
+
79
+ if (!buttons.length) {
80
+ logger.fatal('UIComponents.initTabs: no tab buttons found', { domain: 'ui', operation: 'initTabs' });
81
+ return;
82
+ }
83
+
84
+ // Get current slide ID for engagement tracking
85
+ const currentSlideId = NavigationState.getCurrentSlideId();
86
+
87
+ // ALWAYS register tabs (engagement manager checks if tracking needed)
88
+ if (currentSlideId) {
89
+ const tabIds = buttons.map(btn =>
90
+ btn.getAttribute('aria-controls') || btn.dataset.tab
91
+ ).filter(Boolean);
92
+ engagementManager.registerTabs(currentSlideId, tabIds);
93
+ }
94
+
95
+ // Validate that buttons have required data-action attributes
96
+ const validation = validateTabActions(container);
97
+ if (!validation.valid) {
98
+ const errorDetails = validation.errors.map((error, index) =>
99
+ ` Tab button ${index + 1}: ${error.message}\n Expected: ${error.context.expected}\n Fix: ${error.context.fix}`
100
+ ).join('\n');
101
+ logger.fatal(`UIComponents.initTabs: Some tab buttons are missing data-action attributes.\n${errorDetails}`, { domain: 'ui', operation: 'initTabs' });
102
+ return;
103
+ }
104
+
105
+ // Validate that all buttons have corresponding panels
106
+ const missingPanels = [];
107
+ buttons.forEach((btn, index) => {
108
+ const targetId = btn.getAttribute('aria-controls') || btn.dataset.tab;
109
+ if (!targetId) {
110
+ missingPanels.push(`Button ${index + 1} is missing data-tab or aria-controls attribute.`);
111
+ } else {
112
+ // Use CSS.escape if available, otherwise simple fallback or assume safe ID
113
+ const escapedId = CSS.escape ? CSS.escape(targetId) : targetId;
114
+ const panel = panelRoot.querySelector(`#${escapedId}`);
115
+ if (!panel) {
116
+ missingPanels.push(`Panel not found for button ${index + 1} (target ID: #${targetId}).`);
117
+ }
118
+ }
119
+ });
120
+
121
+ if (missingPanels.length > 0) {
122
+ logger.fatal(`UIComponents.initTabs: Invalid structure in #${container.id || 'tabs'}:\n${missingPanels.join('\n')}`, { domain: 'ui', operation: 'initTabs' });
123
+ return;
124
+ }
125
+
126
+ function activateTab(button, announce = true, track = true) {
127
+ if (!button) return;
128
+
129
+ const targetPanelId = button.getAttribute('aria-controls') || button.dataset.tab || '';
130
+ const escapedId = safeSelector(targetPanelId);
131
+ const targetPanel = targetPanelId && escapedId ? panelRoot.querySelector(`#${escapedId}`) : null;
132
+
133
+ buttons.forEach(btn => {
134
+ btn.classList.remove(activeClass);
135
+ btn.setAttribute('aria-selected', 'false');
136
+ btn.setAttribute('tabindex', btn === button ? '0' : '-1');
137
+ });
138
+
139
+ panels.forEach(panel => {
140
+ panel.classList.remove(activeClass);
141
+ panel.setAttribute('hidden', 'hidden');
142
+ });
143
+
144
+ button.classList.add(activeClass);
145
+ button.setAttribute('aria-selected', 'true');
146
+
147
+ if (targetPanel) {
148
+ targetPanel.classList.add(activeClass);
149
+ targetPanel.removeAttribute('hidden');
150
+ }
151
+
152
+ // ALWAYS track tab view (engagement manager ignores if not tracking)
153
+ const slideId = NavigationState.getCurrentSlideId();
154
+ if (track && slideId && targetPanelId) {
155
+ engagementManager.trackTabView(slideId, targetPanelId);
156
+ engagementManager.saveActiveTab(slideId, targetPanelId);
157
+ }
158
+
159
+ if (announce) {
160
+ announceToScreenReader(`Switched to ${button.textContent.trim()} tab`);
161
+ }
162
+
163
+ if (onChange) {
164
+ onChange({
165
+ tabId: targetPanelId,
166
+ button,
167
+ panel: targetPanel
168
+ });
169
+ }
170
+ }
171
+
172
+ function handleClick(event) {
173
+ const button = event.target.closest('[data-action="select-tab"]');
174
+ if (button && buttons.includes(button)) {
175
+ event.preventDefault();
176
+ activateTab(button);
177
+ }
178
+ }
179
+
180
+ function handleKeydown(event) {
181
+ const currentButton = event.target.closest('[data-action="select-tab"]');
182
+ if (!currentButton || !buttons.includes(currentButton)) return;
183
+
184
+ const currentIndex = buttons.indexOf(currentButton);
185
+ if (currentIndex === -1) return;
186
+
187
+ let targetIndex = null;
188
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
189
+ targetIndex = (currentIndex + 1) % buttons.length;
190
+ } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
191
+ targetIndex = (currentIndex - 1 + buttons.length) % buttons.length;
192
+ } else if (event.key === 'Home') {
193
+ targetIndex = 0;
194
+ } else if (event.key === 'End') {
195
+ targetIndex = buttons.length - 1;
196
+ }
197
+
198
+ if (targetIndex !== null) {
199
+ event.preventDefault();
200
+ const targetButton = buttons[targetIndex];
201
+ targetButton.focus();
202
+ activateTab(targetButton);
203
+ }
204
+ }
205
+
206
+ container.addEventListener('click', handleClick);
207
+ container.addEventListener('keydown', handleKeydown);
208
+
209
+ // Restore saved tab selection, falling back to the authored default
210
+ const savedTabId = currentSlideId ? engagementManager.getActiveTab(currentSlideId) : null;
211
+ const savedButton = savedTabId
212
+ ? buttons.find(btn => (btn.getAttribute('aria-controls') || btn.dataset.tab) === savedTabId)
213
+ : null;
214
+ const activeButton = savedButton || buttons.find(btn => btn.classList.contains(activeClass)) || buttons[0];
215
+ buttons.forEach(btn => btn.setAttribute('tabindex', btn === activeButton ? '0' : '-1'));
216
+ panels.forEach(panel => panel.setAttribute('hidden', 'hidden'));
217
+ activateTab(activeButton, false, false); // Don't announce, don't track via activateTab
218
+
219
+ // Track initially active tab for engagement progress (like accordion does for open panels).
220
+ // Users who see the active tab content should get credit for viewing it.
221
+ if (currentSlideId && activeButton) {
222
+ const activeTabId = activeButton.getAttribute('aria-controls') || activeButton.dataset.tab;
223
+ if (activeTabId) {
224
+ engagementManager.trackTabView(currentSlideId, activeTabId);
225
+ }
226
+ }
227
+
228
+ return {
229
+ activate: (buttonOrId) => {
230
+ const button = typeof buttonOrId === 'string'
231
+ ? buttons.find(btn => btn.dataset.tab === buttonOrId || btn.getAttribute('aria-controls') === buttonOrId)
232
+ : buttonOrId;
233
+ activateTab(button);
234
+ },
235
+ destroy: () => {
236
+ container.removeEventListener('click', handleClick);
237
+ container.removeEventListener('keydown', handleKeydown);
238
+ }
239
+ };
240
+ }
241
+
242
+ function resolveRoot(ref) {
243
+ if (!ref) return null;
244
+ if (ref instanceof Element) return ref;
245
+ if (typeof ref === 'string') return document.querySelector(ref);
246
+ return null;
247
+ }
248
+
249
+ function safeSelector(value) {
250
+ if (!value) return '';
251
+ if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
252
+ return CSS.escape(value);
253
+ }
254
+ return value.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
255
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Timeline Layout Pattern
3
+ *
4
+ * CSS-only component - no JavaScript behavior, just schema for discovery and validation.
5
+ * Vertical timeline with alternating content and connecting lines.
6
+ */
7
+
8
+ export const schema = {
9
+ type: 'timeline',
10
+ description: 'Vertical timeline with alternating content cards',
11
+ example: `<div data-component="timeline">
12
+ <div class="timeline-item"><div class="timeline-marker"></div><div class="timeline-date">2024</div><div class="timeline-content"><h3>Project Launch</h3><p>Initial release with core features.</p></div></div>
13
+ <div class="timeline-item"><div class="timeline-marker"></div><div class="timeline-date">2025</div><div class="timeline-content"><h3>Major Update</h3><p>Added interactive components and LMS support.</p></div></div>
14
+ <div class="timeline-item"><div class="timeline-marker"></div><div class="timeline-date">2026</div><div class="timeline-content"><h3>AI Integration</h3><p>Automated course authoring with AI assistance.</p></div></div>
15
+ </div>`,
16
+ properties: {
17
+ style: {
18
+ type: 'string',
19
+ enum: ['default', 'icon', 'compact'],
20
+ default: 'default',
21
+ description: 'Visual style variant (data-timeline-style attribute)'
22
+ }
23
+ },
24
+ structure: {
25
+ container: '[data-component="timeline"]',
26
+ children: {
27
+ item: { selector: '.timeline-item', required: true, minItems: 1 },
28
+ content: { selector: '.timeline-content', parent: '.timeline-item', required: true },
29
+ marker: { selector: '.timeline-marker', parent: '.timeline-item' },
30
+ date: { selector: '.timeline-date', parent: '.timeline-item' }
31
+ }
32
+ }
33
+ };
34
+
35
+ export const metadata = {
36
+ category: 'ui-component',
37
+ cssOnly: true,
38
+ cssFile: 'components/timeline.css'
39
+ };
40
+
41
+ /** No-op initializer — CSS-only component, registered for consistency. */
42
+ export function init() {}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @file toggle-group.js
3
+ * @description Handles a group of toggle switches, aggregating their values and emitting change events.
4
+ */
5
+
6
+ export const schema = {
7
+ type: 'toggle-group',
8
+ description: 'Group of toggle switches with aggregated value tracking',
9
+ example: `<div data-component="toggle-group">
10
+ <label class="toggle-switch"><input type="checkbox" data-label="Notifications" checked><span class="toggle-slider"></span><span class="toggle-label">Notifications</span></label>
11
+ <label class="toggle-switch"><input type="checkbox" data-label="Dark Mode"><span class="toggle-slider"></span><span class="toggle-label">Dark Mode</span></label>
12
+ <label class="toggle-switch"><input type="checkbox" data-label="Auto-Save" checked><span class="toggle-slider"></span><span class="toggle-label">Auto-Save</span></label>
13
+ </div>`,
14
+ properties: {},
15
+ structure: {
16
+ container: '[data-component="toggle-group"]',
17
+ children: {
18
+ toggle: { selector: 'input[type="checkbox"]', required: true, minItems: 1 }
19
+ }
20
+ }
21
+ };
22
+
23
+ export const metadata = {
24
+ category: 'ui-component',
25
+ cssFile: 'components/toggle.css',
26
+ engagementTracking: null,
27
+ emitsEvents: ['toggle:change']
28
+ };
29
+
30
+ import { logger } from '../../utilities/logger.js';
31
+
32
+ export function init(element) {
33
+ if (!element) {
34
+ logger.fatal('ToggleGroup: Container element is required', { domain: 'ui', operation: 'initToggleGroup' });
35
+ return;
36
+ }
37
+
38
+ const toggles = element.querySelectorAll('input[type="checkbox"]');
39
+
40
+ if (toggles.length === 0) {
41
+ logger.fatal('ToggleGroup: No checkbox inputs found in container', { domain: 'ui', operation: 'initToggleGroup' });
42
+ return;
43
+ }
44
+
45
+ const handleChange = (e) => {
46
+ const target = e.target;
47
+ const label = target.dataset.label || target.nextElementSibling?.nextElementSibling?.textContent || 'Toggle';
48
+ const status = target.checked ? 'enabled' : 'disabled';
49
+
50
+ const event = new CustomEvent('toggle:change', {
51
+ bubbles: true,
52
+ detail: {
53
+ label: label,
54
+ checked: target.checked,
55
+ status: status,
56
+ value: `${label} is now ${status}`
57
+ }
58
+ });
59
+ element.dispatchEvent(event);
60
+ };
61
+
62
+ toggles.forEach(toggle => {
63
+ toggle.addEventListener('change', handleChange);
64
+ });
65
+
66
+ return {
67
+ destroy: () => {
68
+ toggles.forEach(toggle => {
69
+ toggle.removeEventListener('change', handleChange);
70
+ });
71
+ }
72
+ };
73
+ }