tessera-learn 0.0.1

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 (71) hide show
  1. package/AGENTS.md +1228 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -0
  4. package/dist/plugin/index.d.ts +7 -0
  5. package/dist/plugin/index.d.ts.map +1 -0
  6. package/dist/plugin/index.js +1239 -0
  7. package/dist/plugin/index.js.map +1 -0
  8. package/package.json +77 -0
  9. package/src/archiver.d.ts +27 -0
  10. package/src/components/Accordion.svelte +32 -0
  11. package/src/components/AccordionItem.svelte +144 -0
  12. package/src/components/Audio.svelte +38 -0
  13. package/src/components/Callout.svelte +81 -0
  14. package/src/components/Carousel.svelte +194 -0
  15. package/src/components/CarouselSlide.svelte +32 -0
  16. package/src/components/DefaultLayout.svelte +108 -0
  17. package/src/components/FillInTheBlank.svelte +345 -0
  18. package/src/components/Image.svelte +47 -0
  19. package/src/components/Matching.svelte +513 -0
  20. package/src/components/MultipleChoice.svelte +363 -0
  21. package/src/components/Quiz.svelte +569 -0
  22. package/src/components/RevealModal.svelte +228 -0
  23. package/src/components/Sorting.svelte +663 -0
  24. package/src/components/Video.svelte +118 -0
  25. package/src/components/index.ts +15 -0
  26. package/src/components/quiz-payload.ts +71 -0
  27. package/src/components/util.ts +24 -0
  28. package/src/index.ts +56 -0
  29. package/src/plugin/export.ts +264 -0
  30. package/src/plugin/index.ts +464 -0
  31. package/src/plugin/layout.ts +55 -0
  32. package/src/plugin/manifest.ts +330 -0
  33. package/src/plugin/quiz.ts +65 -0
  34. package/src/plugin/validation.ts +838 -0
  35. package/src/runtime/App.svelte +435 -0
  36. package/src/runtime/ErrorPage.svelte +14 -0
  37. package/src/runtime/LoadingSkeleton.svelte +26 -0
  38. package/src/runtime/Sidebar.svelte +76 -0
  39. package/src/runtime/access.ts +55 -0
  40. package/src/runtime/adapters/cmi5.ts +341 -0
  41. package/src/runtime/adapters/discovery.ts +38 -0
  42. package/src/runtime/adapters/index.ts +99 -0
  43. package/src/runtime/adapters/retry.ts +284 -0
  44. package/src/runtime/adapters/scorm12.ts +172 -0
  45. package/src/runtime/adapters/scorm2004.ts +162 -0
  46. package/src/runtime/adapters/web.ts +62 -0
  47. package/src/runtime/contexts.ts +76 -0
  48. package/src/runtime/duration.ts +29 -0
  49. package/src/runtime/hooks.svelte.ts +543 -0
  50. package/src/runtime/interaction-format.ts +132 -0
  51. package/src/runtime/interaction.ts +96 -0
  52. package/src/runtime/navigation.svelte.ts +117 -0
  53. package/src/runtime/persistence.ts +56 -0
  54. package/src/runtime/progress.svelte.ts +168 -0
  55. package/src/runtime/quiz-policy.ts +227 -0
  56. package/src/runtime/slugify.ts +17 -0
  57. package/src/runtime/types.ts +92 -0
  58. package/src/runtime/xapi/agent-rules.ts +93 -0
  59. package/src/runtime/xapi/client.ts +133 -0
  60. package/src/runtime/xapi/derive-actor.ts +90 -0
  61. package/src/runtime/xapi/publisher.ts +604 -0
  62. package/src/runtime/xapi/registry.ts +38 -0
  63. package/src/runtime/xapi/setup.ts +250 -0
  64. package/src/runtime/xapi/types.ts +106 -0
  65. package/src/runtime/xapi/uuid.ts +21 -0
  66. package/src/runtime/xapi/validation.ts +71 -0
  67. package/src/runtime/xapi/version.ts +23 -0
  68. package/src/virtual.d.ts +16 -0
  69. package/styles/base.css +194 -0
  70. package/styles/layout.css +408 -0
  71. package/styles/theme.css +36 -0
@@ -0,0 +1,228 @@
1
+ <script>
2
+ /**
3
+ * @component RevealModal
4
+ * Modal overlay triggered by user-defined trigger snippet.
5
+ *
6
+ * @prop {string} [title] - Modal label for accessibility
7
+ * @prop {import('svelte').Snippet} trigger - Trigger content snippet
8
+ * @prop {import('svelte').Snippet} content - Modal body snippet
9
+ */
10
+ import { onMount } from 'svelte';
11
+
12
+ let { trigger, content, title = '' } = $props();
13
+ let open = $state(false);
14
+ let modalRef = $state(null);
15
+ let previousFocus = null;
16
+
17
+ function openModal() {
18
+ previousFocus = document.activeElement;
19
+ open = true;
20
+ }
21
+
22
+ function closeModal() {
23
+ open = false;
24
+ if (previousFocus && typeof previousFocus.focus === 'function') {
25
+ previousFocus.focus();
26
+ }
27
+ }
28
+
29
+ function handleTriggerKey(e) {
30
+ if (e.key === 'Enter' || e.key === ' ') {
31
+ e.preventDefault();
32
+ openModal();
33
+ }
34
+ }
35
+
36
+ function handleOverlayClick(e) {
37
+ if (e.target === e.currentTarget) {
38
+ closeModal();
39
+ }
40
+ }
41
+
42
+ function handleKeydown(e) {
43
+ if (e.key === 'Escape') {
44
+ closeModal();
45
+ return;
46
+ }
47
+
48
+ // Focus trap
49
+ if (e.key === 'Tab' && modalRef) {
50
+ const focusable = modalRef.querySelectorAll(
51
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
52
+ );
53
+ if (focusable.length === 0) return;
54
+
55
+ const first = focusable[0];
56
+ const last = focusable[focusable.length - 1];
57
+
58
+ if (e.shiftKey) {
59
+ if (document.activeElement === first) {
60
+ e.preventDefault();
61
+ last.focus();
62
+ }
63
+ } else {
64
+ if (document.activeElement === last) {
65
+ e.preventDefault();
66
+ first.focus();
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ $effect(() => {
73
+ if (open) {
74
+ document.body.style.overflow = 'hidden';
75
+ // Focus the modal after render
76
+ queueMicrotask(() => {
77
+ if (modalRef) {
78
+ const firstFocusable = modalRef.querySelector(
79
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
80
+ );
81
+ if (firstFocusable) firstFocusable.focus();
82
+ else modalRef.focus();
83
+ }
84
+ });
85
+ } else {
86
+ document.body.style.overflow = '';
87
+ }
88
+ });
89
+ </script>
90
+
91
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
92
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
93
+ <div
94
+ class="tessera-reveal-trigger"
95
+ onclick={openModal}
96
+ >
97
+ {@render trigger()}
98
+ </div>
99
+
100
+ {#if open}
101
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
102
+ <div
103
+ class="tessera-modal-overlay"
104
+ onclick={handleOverlayClick}
105
+ onkeydown={handleKeydown}
106
+ >
107
+ <div
108
+ class="tessera-modal-content"
109
+ role="dialog"
110
+ aria-modal="true"
111
+ aria-label={title || 'Modal'}
112
+ bind:this={modalRef}
113
+ tabindex="-1"
114
+ >
115
+ {#if title}
116
+ <h2 class="tessera-modal-title">{title}</h2>
117
+ {/if}
118
+ <div class="tessera-modal-body">
119
+ {@render content()}
120
+ </div>
121
+ <button class="tessera-modal-close" onclick={closeModal} aria-label="Close modal">
122
+
123
+ </button>
124
+ </div>
125
+ </div>
126
+ {/if}
127
+
128
+ <style>
129
+ .tessera-reveal-trigger {
130
+ display: inline-block;
131
+ cursor: pointer;
132
+ }
133
+
134
+ .tessera-reveal-trigger:focus-visible {
135
+ box-shadow: var(--tessera-focus-ring);
136
+ outline: none;
137
+ border-radius: 4px;
138
+ }
139
+
140
+ .tessera-modal-overlay {
141
+ position: fixed;
142
+ inset: 0;
143
+ background-color: rgba(0, 0, 0, 0.5);
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ z-index: 2000;
148
+ padding: var(--tessera-spacing-lg);
149
+ animation: tessera-modal-fade-in 200ms ease;
150
+ }
151
+
152
+ .tessera-modal-content {
153
+ position: relative;
154
+ background: var(--tessera-bg);
155
+ border-radius: 12px;
156
+ padding: var(--tessera-spacing-xl);
157
+ max-width: 600px;
158
+ width: 100%;
159
+ max-height: 80vh;
160
+ overflow-y: auto;
161
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
162
+ animation: tessera-modal-slide-in 200ms ease;
163
+ }
164
+
165
+ .tessera-modal-content:focus {
166
+ outline: none;
167
+ }
168
+
169
+ .tessera-modal-title {
170
+ font-size: 1.25rem;
171
+ font-weight: 700;
172
+ color: var(--tessera-text);
173
+ margin-bottom: var(--tessera-spacing-md);
174
+ margin-top: 0;
175
+ padding-right: var(--tessera-spacing-xl);
176
+ }
177
+
178
+ .tessera-modal-body :global(p:last-child) {
179
+ margin-bottom: 0;
180
+ }
181
+
182
+ .tessera-modal-close {
183
+ position: absolute;
184
+ top: var(--tessera-spacing-md);
185
+ right: var(--tessera-spacing-md);
186
+ width: 32px;
187
+ height: 32px;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ background: none;
192
+ border: none;
193
+ font-size: 1.125rem;
194
+ color: var(--tessera-text-light);
195
+ cursor: pointer;
196
+ border-radius: 6px;
197
+ transition: background-color var(--tessera-transition-fast),
198
+ color var(--tessera-transition-fast);
199
+ }
200
+
201
+ .tessera-modal-close:hover {
202
+ background-color: var(--tessera-bg-secondary);
203
+ color: var(--tessera-text);
204
+ }
205
+
206
+ .tessera-modal-close:focus-visible {
207
+ box-shadow: var(--tessera-focus-ring);
208
+ outline: none;
209
+ }
210
+
211
+ @keyframes tessera-modal-fade-in {
212
+ from { opacity: 0; }
213
+ to { opacity: 1; }
214
+ }
215
+
216
+ @keyframes tessera-modal-slide-in {
217
+ from { transform: translateY(10px); opacity: 0; }
218
+ to { transform: translateY(0); opacity: 1; }
219
+ }
220
+
221
+ @media (max-width: 640px) {
222
+ .tessera-modal-content {
223
+ max-height: 90vh;
224
+ border-radius: 12px 12px 0 0;
225
+ align-self: flex-end;
226
+ }
227
+ }
228
+ </style>