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,422 @@
1
+ /**
2
+ * @file html-validators.js
3
+ * @description Runtime HTML validation for rendered slide content.
4
+ * Checks for common authoring issues that cause page reloads or break component wiring.
5
+ * Single consumer: view-manager.js (runs after each slide render).
6
+ */
7
+
8
+ // ============================================================================
9
+ // HTML Structure Validation Helpers
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Validates buttons inside forms have explicit type attributes to prevent page reloads.
14
+ * @param {HTMLElement} element - The element to validate
15
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
16
+ */
17
+ export function validateFormButtons(element) {
18
+ const errors = [];
19
+ const forms = element.querySelectorAll('form');
20
+
21
+ forms.forEach((form) => {
22
+ const buttons = form.querySelectorAll('button:not([type])');
23
+ if (buttons.length > 0) {
24
+ errors.push({
25
+ message: `Found ${buttons.length} button(s) inside a form without a 'type' attribute`,
26
+ context: {
27
+ form: form.outerHTML.substring(0, 200) + '...',
28
+ buttons: Array.from(buttons).map(btn => btn.outerHTML),
29
+ fix: 'Add type="button" to buttons that should not submit the form, or type="submit" for submit buttons.'
30
+ }
31
+ });
32
+ }
33
+ });
34
+
35
+ return {
36
+ valid: errors.length === 0,
37
+ errors
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Validates that buttons with data-action inside forms have explicit type attributes.
43
+ * @param {HTMLElement} element - The element to validate
44
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
45
+ */
46
+ export function validateActionButtonsInForms(element) {
47
+ const errors = [];
48
+ const actionElements = element.querySelectorAll('[data-action]');
49
+
50
+ actionElements.forEach(actionEl => {
51
+ if (actionEl.tagName === 'BUTTON' && actionEl.closest('form') && !actionEl.hasAttribute('type')) {
52
+ errors.push({
53
+ message: 'Button with data-action inside a form is missing type attribute',
54
+ context: {
55
+ button: actionEl.outerHTML,
56
+ action: actionEl.getAttribute('data-action'),
57
+ fix: 'Add type="button" to prevent the button from submitting the form.'
58
+ }
59
+ });
60
+ }
61
+ });
62
+
63
+ return {
64
+ valid: errors.length === 0,
65
+ errors
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Validates that anchor tags don't have dangerous href values that cause page reloads.
71
+ * @param {HTMLElement} element - The element to validate
72
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
73
+ */
74
+ export function validateAnchorTags(element) {
75
+ const errors = [];
76
+ const dangerousAnchors = element.querySelectorAll('a[href="#"], a[href=""]');
77
+
78
+ if (dangerousAnchors.length > 0) {
79
+ errors.push({
80
+ message: `Found ${dangerousAnchors.length} anchor tag(s) with dangerous href attribute`,
81
+ context: {
82
+ anchors: Array.from(dangerousAnchors).map(a => a.outerHTML),
83
+ fix: 'For internal navigation, use NavigationActions.goToSlide(). For other actions, use a <button type="button">. For external links, use a valid URL.'
84
+ }
85
+ });
86
+ }
87
+
88
+ return {
89
+ valid: errors.length === 0,
90
+ errors
91
+ };
92
+ }
93
+
94
+ // ============================================================================
95
+ // UI Component Validation Helpers
96
+ // ============================================================================
97
+
98
+ /**
99
+ * Required data-action patterns for UI components.
100
+ * @constant {Object}
101
+ */
102
+ export const UI_COMPONENT_ACTIONS = {
103
+ dropdown: {
104
+ trigger: 'toggle-dropdown',
105
+ item: 'select-item'
106
+ },
107
+ tabs: {
108
+ button: 'select-tab'
109
+ },
110
+ modal: {
111
+ close: 'close-modal'
112
+ },
113
+ interaction: {
114
+ check: 'check-answer',
115
+ reset: 'reset'
116
+ }
117
+ };
118
+
119
+ /**
120
+ * Validates dropdown components have required data-action attributes.
121
+ * @param {HTMLElement} element - The element to validate (should contain dropdowns)
122
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
123
+ */
124
+ export function validateDropdownActions(element) {
125
+ const errors = [];
126
+ const dropdowns = element.querySelectorAll('.custom-dropdown, [class*="dropdown"]');
127
+
128
+ dropdowns.forEach(dropdown => {
129
+ // Check for dropdown triggers without data-action
130
+ const triggers = dropdown.querySelectorAll('.dropdown-trigger, [class*="dropdown-trigger"]');
131
+ triggers.forEach(trigger => {
132
+ if (!trigger.hasAttribute('data-action')) {
133
+ errors.push({
134
+ message: 'Dropdown trigger is missing data-action attribute',
135
+ context: {
136
+ element: trigger.outerHTML,
137
+ expected: `data-action="${UI_COMPONENT_ACTIONS.dropdown.trigger}"`,
138
+ fix: 'Add data-action="toggle-dropdown" to the dropdown trigger button.'
139
+ }
140
+ });
141
+ } else {
142
+ const action = trigger.getAttribute('data-action');
143
+ if (action !== UI_COMPONENT_ACTIONS.dropdown.trigger) {
144
+ errors.push({
145
+ message: `Dropdown trigger has incorrect data-action="${action}"`,
146
+ context: {
147
+ element: trigger.outerHTML,
148
+ actual: action,
149
+ expected: UI_COMPONENT_ACTIONS.dropdown.trigger,
150
+ fix: `Change to data-action="${UI_COMPONENT_ACTIONS.dropdown.trigger}".`
151
+ }
152
+ });
153
+ }
154
+ }
155
+ });
156
+
157
+ // Check for dropdown items without data-action
158
+ const items = dropdown.querySelectorAll('.dropdown-item, [class*="dropdown-item"]');
159
+ items.forEach(item => {
160
+ if (!item.hasAttribute('data-action')) {
161
+ errors.push({
162
+ message: 'Dropdown item is missing data-action attribute',
163
+ context: {
164
+ element: item.outerHTML,
165
+ expected: `data-action="${UI_COMPONENT_ACTIONS.dropdown.item}"`,
166
+ fix: 'Add data-action="select-item" to the dropdown item.'
167
+ }
168
+ });
169
+ } else {
170
+ const action = item.getAttribute('data-action');
171
+ if (action !== UI_COMPONENT_ACTIONS.dropdown.item) {
172
+ errors.push({
173
+ message: `Dropdown item has incorrect data-action="${action}"`,
174
+ context: {
175
+ element: item.outerHTML,
176
+ actual: action,
177
+ expected: UI_COMPONENT_ACTIONS.dropdown.item,
178
+ fix: `Change to data-action="${UI_COMPONENT_ACTIONS.dropdown.item}".`
179
+ }
180
+ });
181
+ }
182
+ }
183
+ });
184
+ });
185
+
186
+ return {
187
+ valid: errors.length === 0,
188
+ errors
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Validates tab components have required data-action attributes.
194
+ * @param {HTMLElement} element - The element to validate (should contain tabs)
195
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
196
+ */
197
+ export function validateTabActions(element) {
198
+ const errors = [];
199
+
200
+ // Look for tab containers
201
+ const tabContainers = element.querySelectorAll('.tabs, [role="tablist"]');
202
+
203
+ tabContainers.forEach(container => {
204
+ const tabButtons = container.querySelectorAll('.tab-button, [role="tab"]');
205
+
206
+ tabButtons.forEach(button => {
207
+ if (!button.hasAttribute('data-action')) {
208
+ errors.push({
209
+ message: 'Tab button is missing data-action attribute',
210
+ context: {
211
+ element: button.outerHTML,
212
+ expected: `data-action="${UI_COMPONENT_ACTIONS.tabs.button}"`,
213
+ fix: 'Add data-action="select-tab" to the tab button.'
214
+ }
215
+ });
216
+ } else {
217
+ const action = button.getAttribute('data-action');
218
+ if (action !== UI_COMPONENT_ACTIONS.tabs.button) {
219
+ errors.push({
220
+ message: `Tab button has incorrect data-action="${action}"`,
221
+ context: {
222
+ element: button.outerHTML,
223
+ actual: action,
224
+ expected: UI_COMPONENT_ACTIONS.tabs.button,
225
+ fix: `Change to data-action="${UI_COMPONENT_ACTIONS.tabs.button}".`
226
+ }
227
+ });
228
+ }
229
+ }
230
+ });
231
+ });
232
+
233
+ return {
234
+ valid: errors.length === 0,
235
+ errors
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Validates modal components have required data-action attributes.
241
+ * @param {HTMLElement} element - The element to validate (should contain modals)
242
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
243
+ */
244
+ export function validateModalActions(element) {
245
+ const errors = [];
246
+ const modals = element.querySelectorAll('.modal, [role="dialog"]');
247
+
248
+ modals.forEach(modal => {
249
+ // Check for close buttons (both .modal-close and any button meant to close)
250
+ const closeButtons = modal.querySelectorAll('.modal-close, button[aria-label*="close" i], button[aria-label*="dismiss" i]');
251
+
252
+ closeButtons.forEach(button => {
253
+ if (!button.hasAttribute('data-action')) {
254
+ errors.push({
255
+ message: 'Modal close button is missing data-action attribute',
256
+ context: {
257
+ element: button.outerHTML,
258
+ expected: `data-action="${UI_COMPONENT_ACTIONS.modal.close}"`,
259
+ fix: 'Add data-action="close-modal" to the modal close button.'
260
+ }
261
+ });
262
+ } else {
263
+ const action = button.getAttribute('data-action');
264
+ if (action !== UI_COMPONENT_ACTIONS.modal.close) {
265
+ errors.push({
266
+ message: `Modal close button has incorrect data-action="${action}"`,
267
+ context: {
268
+ element: button.outerHTML,
269
+ actual: action,
270
+ expected: UI_COMPONENT_ACTIONS.modal.close,
271
+ fix: `Change to data-action="${UI_COMPONENT_ACTIONS.modal.close}".`
272
+ }
273
+ });
274
+ }
275
+ }
276
+ });
277
+ });
278
+
279
+ return {
280
+ valid: errors.length === 0,
281
+ errors
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Validates interaction components have required data-action attributes.
287
+ * @param {HTMLElement} element - The element to validate (should contain interactions)
288
+ * @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
289
+ */
290
+ export function validateInteractionActions(element) {
291
+ const errors = [];
292
+
293
+ // Look for interaction containers (common patterns)
294
+ const interactions = element.querySelectorAll('[data-interaction-type], .interaction, [class*="interaction-"]');
295
+
296
+ interactions.forEach(interaction => {
297
+ const interactionId = interaction.id || interaction.dataset.interaction || 'unknown';
298
+
299
+ // Check for check-answer buttons
300
+ const checkButtons = interaction.querySelectorAll('.check-answer, button[class*="check"]');
301
+ checkButtons.forEach(button => {
302
+ if (!button.hasAttribute('data-action')) {
303
+ errors.push({
304
+ message: 'Interaction check button is missing data-action attribute',
305
+ context: {
306
+ interactionId,
307
+ element: button.outerHTML,
308
+ expected: `data-action="${UI_COMPONENT_ACTIONS.interaction.check}"`,
309
+ fix: 'Add data-action="check-answer" and data-interaction="[id]" to the check button.'
310
+ }
311
+ });
312
+ } else {
313
+ const action = button.getAttribute('data-action');
314
+ if (action !== UI_COMPONENT_ACTIONS.interaction.check && action !== UI_COMPONENT_ACTIONS.interaction.reset) {
315
+ errors.push({
316
+ message: `Interaction button has incorrect data-action="${action}"`,
317
+ context: {
318
+ interactionId,
319
+ element: button.outerHTML,
320
+ actual: action,
321
+ expected: `"${UI_COMPONENT_ACTIONS.interaction.check}" or "${UI_COMPONENT_ACTIONS.interaction.reset}"`,
322
+ fix: 'Use data-action="check-answer" or data-action="reset".'
323
+ }
324
+ });
325
+ }
326
+ }
327
+
328
+ // Verify data-interaction attribute is present
329
+ if (!button.hasAttribute('data-interaction')) {
330
+ errors.push({
331
+ message: 'Interaction button is missing data-interaction attribute',
332
+ context: {
333
+ interactionId,
334
+ element: button.outerHTML,
335
+ fix: 'Add data-interaction="[id]" to identify which interaction this button controls.'
336
+ }
337
+ });
338
+ }
339
+ });
340
+ });
341
+
342
+ return {
343
+ valid: errors.length === 0,
344
+ errors
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Validates all UI components in an element for required data-action attributes.
350
+ * This function checks all component types (dropdowns, tabs, modals, interactions).
351
+ * @param {HTMLElement} element - The element to validate
352
+ * @param {string} [viewName=''] - Optional view name for error context
353
+ * @returns {{valid: boolean, errors: Array<{type: string, message: string, context: Object}>}} Validation result
354
+ */
355
+ export function validateUIComponentActions(element, viewName = '') {
356
+ const allErrors = [];
357
+
358
+ // Validate dropdowns
359
+ const dropdownResult = validateDropdownActions(element);
360
+ dropdownResult.errors.forEach(error => {
361
+ allErrors.push({ type: 'dropdown', viewName, ...error });
362
+ });
363
+
364
+ // Validate tabs
365
+ const tabResult = validateTabActions(element);
366
+ tabResult.errors.forEach(error => {
367
+ allErrors.push({ type: 'tabs', viewName, ...error });
368
+ });
369
+
370
+ // Validate modals
371
+ const modalResult = validateModalActions(element);
372
+ modalResult.errors.forEach(error => {
373
+ allErrors.push({ type: 'modal', viewName, ...error });
374
+ });
375
+
376
+ // Validate interactions
377
+ const interactionResult = validateInteractionActions(element);
378
+ interactionResult.errors.forEach(error => {
379
+ allErrors.push({ type: 'interaction', viewName, ...error });
380
+ });
381
+
382
+ return {
383
+ valid: allErrors.length === 0,
384
+ errors: allErrors
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Validates rendered HTML content for common issues that cause page reloads or errors.
390
+ * This is the master validation function that checks both HTML structure and UI components.
391
+ * @param {HTMLElement} element - The element to validate
392
+ * @param {string} [viewName=''] - Optional view name for error context
393
+ * @returns {{valid: boolean, errors: Array<{type: string, message: string, context: Object}>}} Validation result
394
+ */
395
+ export function validateRenderedHTML(element, viewName = '') {
396
+ const allErrors = [];
397
+
398
+ // Validate HTML structure (forms, buttons, anchors)
399
+ const formButtonResult = validateFormButtons(element);
400
+ formButtonResult.errors.forEach(error => {
401
+ allErrors.push({ type: 'form-button', viewName, ...error });
402
+ });
403
+
404
+ const actionButtonResult = validateActionButtonsInForms(element);
405
+ actionButtonResult.errors.forEach(error => {
406
+ allErrors.push({ type: 'action-button', viewName, ...error });
407
+ });
408
+
409
+ const anchorResult = validateAnchorTags(element);
410
+ anchorResult.errors.forEach(error => {
411
+ allErrors.push({ type: 'anchor', viewName, ...error });
412
+ });
413
+
414
+ // Validate UI components
415
+ const uiComponentResult = validateUIComponentActions(element, viewName);
416
+ allErrors.push(...uiComponentResult.errors);
417
+
418
+ return {
419
+ valid: allErrors.length === 0,
420
+ errors: allErrors
421
+ };
422
+ }