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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- 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
|
+
}
|