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,150 @@
1
+ /**
2
+ * @file conditional-display.js
3
+ * @description Declarative component for conditionally showing/hiding content based on engagement, flags, and interactions.
4
+ *
5
+ * Usage examples:
6
+ *
7
+ * Simple engagement condition:
8
+ * <div data-component="conditional" data-condition="engagement.viewAllTabs">
9
+ * Content shown after all tabs viewed
10
+ * </div>
11
+ *
12
+ * Flag condition:
13
+ * <div data-component="conditional" data-condition="flag.intro-complete">
14
+ * Content shown when flag is set
15
+ * </div>
16
+ *
17
+ * Multiple conditions (AND):
18
+ * <div data-component="conditional"
19
+ * data-conditions="engagement.viewAllTabs,flag.step1-done"
20
+ * data-mode="all">
21
+ * Content shown when both conditions met
22
+ * </div>
23
+ *
24
+ * Inverse (hide when condition met):
25
+ * <div data-component="conditional"
26
+ * data-condition="engagement.viewAllTabs"
27
+ * data-show-when="false">
28
+ * "Please view all tabs above" message (hidden when tabs viewed)
29
+ * </div>
30
+ *
31
+ * Custom display mode:
32
+ * <div data-component="conditional"
33
+ * data-condition="flag.menu-open"
34
+ * data-display="flex">
35
+ * Flexbox container shown when flag set
36
+ * </div>
37
+ */
38
+
39
+ export const schema = {
40
+ type: 'conditional',
41
+ description: 'Conditionally show/hide content based on engagement or flags',
42
+ example: `<div style="display: flex; flex-direction: column; gap: 12px;">
43
+ <div data-component="conditional" data-condition="engagement.viewAllTabs" style="padding: 12px; border: 1px dashed #94a3b8; border-radius: 6px; color: #64748b;">
44
+ <p style="margin: 0; font-size: 0.875rem;">🔀 <strong>Conditional block</strong> — this content appears when <code>engagement.viewAllTabs</code> is met.</p>
45
+ </div>
46
+ <div data-component="conditional" data-condition="flag.intro-complete" data-show-when="false" style="padding: 12px; border: 1px dashed #f59e0b; border-radius: 6px; color: #92400e;">
47
+ <p style="margin: 0; font-size: 0.875rem;">🙈 <strong>Inverse block</strong> — this content <em>hides</em> when <code>flag.intro-complete</code> is set.</p>
48
+ </div>
49
+ </div>`,
50
+ properties: {
51
+ condition: { type: 'string', dataAttribute: 'data-condition' },
52
+ conditions: { type: 'string', dataAttribute: 'data-conditions' },
53
+ mode: { type: 'string', enum: ['all', 'any'], default: 'all', dataAttribute: 'data-mode' },
54
+ showWhen: { type: 'boolean', default: true, dataAttribute: 'data-show-when' },
55
+ display: { type: 'string', default: 'block', dataAttribute: 'data-display' }
56
+ },
57
+ structure: {
58
+ container: '[data-component="conditional"]',
59
+ children: {}
60
+ }
61
+ };
62
+
63
+ export const metadata = {
64
+ category: 'ui-component',
65
+ cssFile: null,
66
+ engagementTracking: null,
67
+ emitsEvents: []
68
+ };
69
+
70
+ import { conditionalDisplay } from '../../utilities/conditional-display.js';
71
+ import { logger } from '../../utilities/logger.js';
72
+
73
+ /**
74
+ * Initializes conditional display for an element.
75
+ *
76
+ * @param {HTMLElement|string} root - The element or selector
77
+ * @param {object} options - Configuration options (optional, can use data attributes)
78
+ * @returns {object} API with cleanup method
79
+ */
80
+ export function init(root, options = {}) {
81
+ const element = resolveRoot(root);
82
+ if (!element) {
83
+ logger.fatal('ConditionalDisplay: element not found', { domain: 'ui', operation: 'initConditionalDisplay' });
84
+ return;
85
+ }
86
+
87
+ // Check if already initialized (prevent double initialization)
88
+ if (element.dataset.conditionalInitialized === 'true') {
89
+ throw new Error('[ConditionalDisplay] Element already initialized. Each element should only be initialized once.');
90
+ }
91
+
92
+ // Mark as initialized
93
+ element.dataset.conditionalInitialized = 'true';
94
+
95
+ // Read config from data attributes if no options are passed (declarative mode)
96
+ const condition = options.condition || element.dataset.condition;
97
+ const conditionsAttr = options.conditions || element.dataset.conditions;
98
+ const mode = options.mode || element.dataset.mode || 'all';
99
+ const showWhen = options.showWhen !== undefined
100
+ ? options.showWhen
101
+ : (element.dataset.showWhen !== 'false'); // Default true unless explicitly "false"
102
+ const transition = options.transition !== undefined
103
+ ? options.transition
104
+ : (element.dataset.transition !== 'false'); // Default true
105
+ const display = options.display || element.dataset.display || 'block';
106
+
107
+ // Parse conditions
108
+ let conditions;
109
+ if (conditionsAttr) {
110
+ // Multiple conditions from comma-separated string
111
+ conditions = conditionsAttr.split(',').map(c => c.trim());
112
+ } else if (condition) {
113
+ // Single condition
114
+ conditions = condition;
115
+ } else {
116
+ logger.fatal('ConditionalDisplay: No condition specified. Use data-condition or data-conditions attribute.', { domain: 'ui', operation: 'initConditionalDisplay' });
117
+ return;
118
+ }
119
+
120
+ // Set up conditional display tracking
121
+ const cleanup = conditionalDisplay.showWhen(element, conditions, {
122
+ mode,
123
+ showWhen,
124
+ transition,
125
+ display,
126
+ initialCheck: true
127
+ });
128
+
129
+ // Return API with cleanup that also removes initialized flag
130
+ return {
131
+ destroy: () => {
132
+ cleanup();
133
+ delete element.dataset.conditionalInitialized;
134
+ },
135
+ element
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Resolves a root element reference to an actual DOM element.
141
+ *
142
+ * @param {HTMLElement|string} ref - Element or selector string
143
+ * @returns {HTMLElement|null} Resolved element or null
144
+ */
145
+ function resolveRoot(ref) {
146
+ if (!ref) return null;
147
+ if (ref instanceof Element) return ref;
148
+ if (typeof ref === 'string') return document.querySelector(ref);
149
+ return null;
150
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Content Image Layout Pattern
3
+ *
4
+ * CSS-only component for text with diagram/screenshot side by side.
5
+ */
6
+
7
+ export const schema = {
8
+ type: 'content-image',
9
+ description: 'Two-column layout with text and image',
10
+ example: `<div data-component="content-image">
11
+ <div>
12
+ <h3>Visual Learning</h3>
13
+ <p>Pair text with images for a clear, engaging layout. The image column adapts to the content automatically.</p>
14
+ </div>
15
+ <div>
16
+ <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='200' fill='%23e2e8f0'%3E%3Crect width='320' height='200' rx='8'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%2394a3b8' font-family='system-ui' font-size='14'%3EImage Placeholder%3C/text%3E%3C/svg%3E" alt="Placeholder">
17
+ </div>
18
+ </div>`,
19
+ properties: {
20
+ reverse: {
21
+ type: 'boolean',
22
+ default: false,
23
+ description: 'Reverse column order (add .reverse class)'
24
+ }
25
+ },
26
+ structure: {
27
+ container: '[data-component="content-image"]',
28
+ children: {
29
+ image: { selector: 'img', required: true }
30
+ }
31
+ }
32
+ };
33
+
34
+ export const metadata = {
35
+ category: 'ui-component',
36
+ cssOnly: true,
37
+ cssFile: 'components/content-image.css'
38
+ };
39
+
40
+ /** No-op initializer — CSS-only component, registered for consistency. */
41
+ export function init() {}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @file dropdown.js
3
+ * @description Custom dropdown management with event delegation.
4
+ *
5
+ * Usage example:
6
+ * <div id="role-dropdown">
7
+ * <button class="dropdown-trigger" data-action="toggle-dropdown">Choose role</button>
8
+ * <div class="dropdown-menu">
9
+ * <button class="dropdown-item" data-action="select-item">Engineer</button>
10
+ * </div>
11
+ * </div>
12
+ * UIComponents.initDropdown('role-dropdown', { onChange: (value) => console.log(value) });
13
+ */
14
+
15
+ export const schema = {
16
+ type: 'dropdown',
17
+ description: 'Custom dropdown select with keyboard navigation',
18
+ example: `<div data-component="dropdown" class="dropdown">
19
+ <button class="dropdown-trigger" aria-haspopup="true" aria-expanded="false">Select an option</button>
20
+ <ul class="dropdown-menu" role="menu">
21
+ <li class="dropdown-item" role="menuitem" tabindex="-1">Introduction</li>
22
+ <li class="dropdown-item" role="menuitem" tabindex="-1">Components</li>
23
+ <li class="dropdown-item" role="menuitem" tabindex="-1">Interactions</li>
24
+ </ul>
25
+ </div>`,
26
+ properties: {
27
+ onChange: { type: 'function', description: 'Callback when selection changes' }
28
+ },
29
+ structure: {
30
+ container: '[data-component="dropdown"]',
31
+ children: {
32
+ trigger: { selector: '.dropdown-trigger', required: true },
33
+ menu: { selector: '.dropdown-menu', required: true },
34
+ item: { selector: '.dropdown-item', required: true, minItems: 1 }
35
+ }
36
+ }
37
+ };
38
+
39
+ export const metadata = {
40
+ category: 'ui-component',
41
+ cssFile: 'components/dropdown.css',
42
+ engagementTracking: null,
43
+ emitsEvents: ['dropdown:change']
44
+ };
45
+
46
+ import { validateDropdownActions } from '../../validation/html-validators.js';
47
+ import { logger } from '../../utilities/logger.js';
48
+
49
+ const dropdowns = new Map();
50
+
51
+ // Global click handler for closing dropdowns (attached once when first dropdown is initialized)
52
+ let globalHandlerAttached = false;
53
+
54
+ function ensureGlobalHandler() {
55
+ if (globalHandlerAttached) return;
56
+
57
+ document.addEventListener('click', (event) => {
58
+ // If the click is on a dropdown trigger, let the component handle it
59
+ if (event.target.closest('.dropdown-trigger')) {
60
+ return;
61
+ }
62
+
63
+ // Close any dropdown that is open and was not the target of the click
64
+ for (const [id, dropdown] of dropdowns.entries()) {
65
+ if (dropdown.menu.classList.contains('active') && !dropdown.element.contains(event.target)) {
66
+ closeDropdown(id);
67
+ }
68
+ }
69
+ });
70
+
71
+ globalHandlerAttached = true;
72
+ }
73
+
74
+ function resolveRoot(ref) {
75
+ if (!ref) return null;
76
+ if (ref instanceof Element) return ref;
77
+ if (typeof ref === 'string') return document.querySelector(ref);
78
+ return null;
79
+ }
80
+
81
+ export function init(root, options = {}) {
82
+ ensureGlobalHandler();
83
+
84
+ const dropdown = resolveRoot(root);
85
+ if (!dropdown) {
86
+ logger.fatal(`Dropdown with selector "${root}" not found`, { domain: 'ui', operation: 'initDropdown' });
87
+ return;
88
+ }
89
+
90
+ const dropdownId = dropdown.id;
91
+ if (!dropdownId) {
92
+ logger.fatal('Dropdown container element must have an ID.', { domain: 'ui', operation: 'initDropdown' });
93
+ return;
94
+ }
95
+
96
+ const trigger = dropdown.querySelector('.dropdown-trigger');
97
+ const menu = dropdown.querySelector('.dropdown-menu');
98
+ const items = dropdown.querySelectorAll('.dropdown-item');
99
+
100
+ if (!trigger || !menu) {
101
+ logger.fatal(`Dropdown "${dropdownId}" missing required elements (.dropdown-trigger or .dropdown-menu)`, { domain: 'ui', operation: 'initDropdown' });
102
+ return;
103
+ }
104
+
105
+ // Validate that dropdown has required data-action attributes
106
+ const validation = validateDropdownActions(dropdown);
107
+ if (!validation.valid) {
108
+ const errorMessages = validation.errors.map(e => `${e.message}: ${e.context.fix}`).join('; ');
109
+ logger.fatal(`Dropdown "${dropdownId}" validation failed: ${errorMessages}`, { domain: 'ui', operation: 'initDropdown' });
110
+ return;
111
+ }
112
+
113
+ const config = {
114
+ onChange: options.onChange || null,
115
+ ...options
116
+ };
117
+
118
+ dropdowns.set(dropdownId, { element: dropdown, trigger, menu, items, config });
119
+
120
+ // Setup ARIA attributes
121
+ trigger.setAttribute('aria-haspopup', 'listbox');
122
+ trigger.setAttribute('aria-expanded', 'false');
123
+
124
+ items.forEach((item) => {
125
+ item.setAttribute('role', 'option');
126
+ item.setAttribute('tabindex', '0');
127
+ });
128
+
129
+ // Delegated event listener for the entire dropdown
130
+ dropdown.addEventListener('click', (event) => {
131
+ const actionTarget = event.target.closest('[data-action]');
132
+ if (!actionTarget) return;
133
+
134
+ const action = actionTarget.dataset.action;
135
+
136
+ if (action === 'toggle-dropdown') {
137
+ // Close other open dropdowns first
138
+ for (const [id] of dropdowns.entries()) {
139
+ if (id !== dropdownId) closeDropdown(id);
140
+ }
141
+ toggleDropdown(dropdownId);
142
+ } else if (action === 'select-item') {
143
+ const itemIndex = Array.from(items).indexOf(actionTarget);
144
+ if (itemIndex > -1) {
145
+ selectDropdownItem(dropdownId, itemIndex);
146
+ }
147
+ }
148
+ });
149
+
150
+ // Keyboard navigation for the menu
151
+ menu.addEventListener('keydown', (event) => handleDropdownKeyboard(dropdownId, event));
152
+
153
+ return {
154
+ destroy: () => {
155
+ dropdowns.delete(dropdownId);
156
+ }
157
+ };
158
+ }
159
+
160
+ export function toggleDropdown(dropdownId) {
161
+ const entry = dropdowns.get(dropdownId);
162
+ if (!entry) return;
163
+
164
+ const isOpen = entry.menu.classList.contains('active');
165
+ if (isOpen) {
166
+ closeDropdown(dropdownId);
167
+ } else {
168
+ openDropdown(dropdownId);
169
+ }
170
+ }
171
+
172
+ export function openDropdown(dropdownId) {
173
+ const entry = dropdowns.get(dropdownId);
174
+ if (!entry) return;
175
+
176
+ // Close any other open dropdowns
177
+ for (const [id] of dropdowns.entries()) {
178
+ if (id !== dropdownId) {
179
+ closeDropdown(id);
180
+ }
181
+ }
182
+
183
+ entry.menu.classList.add('active');
184
+ entry.trigger.classList.add('active');
185
+ entry.trigger.setAttribute('aria-expanded', 'true');
186
+ }
187
+
188
+ export function closeDropdown(dropdownId) {
189
+ const entry = dropdowns.get(dropdownId);
190
+ if (!entry) return;
191
+
192
+ entry.menu.classList.remove('active');
193
+ entry.trigger.classList.remove('active');
194
+ entry.trigger.setAttribute('aria-expanded', 'false');
195
+ }
196
+
197
+ function selectDropdownItem(dropdownId, index) {
198
+ const entry = dropdowns.get(dropdownId);
199
+ if (!entry) return;
200
+
201
+ const { items, trigger, config } = entry;
202
+ const selected = items[index];
203
+
204
+ items.forEach(item => item.classList.remove('selected'));
205
+ selected.classList.add('selected');
206
+
207
+ const triggerText = trigger.querySelector('.dropdown-text') || trigger;
208
+ triggerText.textContent = selected.textContent;
209
+
210
+ closeDropdown(dropdownId);
211
+ trigger.focus(); // Return focus to the trigger
212
+
213
+ if (config.onChange) {
214
+ config.onChange(selected.dataset.value || selected.textContent, index);
215
+ }
216
+
217
+ // Dispatch a custom event for declarative usage
218
+ const event = new CustomEvent('dropdown:change', {
219
+ bubbles: true,
220
+ detail: {
221
+ value: selected.dataset.value || selected.textContent,
222
+ index: index,
223
+ text: selected.textContent
224
+ }
225
+ });
226
+ entry.element.dispatchEvent(event);
227
+ }
228
+
229
+ function handleDropdownKeyboard(dropdownId, event) {
230
+ const entry = dropdowns.get(dropdownId);
231
+ if (!entry) return;
232
+
233
+ const { items, trigger } = entry;
234
+ const currentIndex = Array.from(items).indexOf(document.activeElement);
235
+
236
+ switch (event.key) {
237
+ case 'ArrowDown':
238
+ event.preventDefault();
239
+ if (currentIndex < items.length - 1) {
240
+ items[currentIndex + 1].focus();
241
+ }
242
+ break;
243
+ case 'ArrowUp':
244
+ event.preventDefault();
245
+ if (currentIndex > 0) {
246
+ items[currentIndex - 1].focus();
247
+ }
248
+ break;
249
+ case 'Escape':
250
+ event.preventDefault();
251
+ closeDropdown(dropdownId);
252
+ trigger.focus();
253
+ break;
254
+ case 'Enter':
255
+ case ' ':
256
+ event.preventDefault();
257
+ if (currentIndex > -1) {
258
+ selectDropdownItem(dropdownId, currentIndex);
259
+ }
260
+ break;
261
+ }
262
+ }