@pie-players/pie-players-shared 0.2.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 (233) hide show
  1. package/dist/config/profile.d.ts +15 -0
  2. package/dist/config/profile.d.ts.map +1 -0
  3. package/dist/config/profile.js +27 -0
  4. package/dist/config/profile.js.map +1 -0
  5. package/dist/i18n/index.d.ts +13 -0
  6. package/dist/i18n/index.d.ts.map +1 -0
  7. package/dist/i18n/index.js +12 -0
  8. package/dist/i18n/index.js.map +1 -0
  9. package/dist/i18n/loader.d.ts +36 -0
  10. package/dist/i18n/loader.d.ts.map +1 -0
  11. package/dist/i18n/loader.js +133 -0
  12. package/dist/i18n/loader.js.map +1 -0
  13. package/dist/i18n/scripts/check-coverage.d.ts +16 -0
  14. package/dist/i18n/scripts/check-coverage.d.ts.map +1 -0
  15. package/dist/i18n/scripts/check-coverage.js +262 -0
  16. package/dist/i18n/scripts/check-coverage.js.map +1 -0
  17. package/dist/i18n/scripts/scan-hardcoded.d.ts +16 -0
  18. package/dist/i18n/scripts/scan-hardcoded.d.ts.map +1 -0
  19. package/dist/i18n/scripts/scan-hardcoded.js +266 -0
  20. package/dist/i18n/scripts/scan-hardcoded.js.map +1 -0
  21. package/dist/i18n/simple-i18n.d.ts +69 -0
  22. package/dist/i18n/simple-i18n.d.ts.map +1 -0
  23. package/dist/i18n/simple-i18n.js +199 -0
  24. package/dist/i18n/simple-i18n.js.map +1 -0
  25. package/dist/i18n/translations/ar/common.json +36 -0
  26. package/dist/i18n/translations/ar/toolkit.json +48 -0
  27. package/dist/i18n/translations/ar/tools.json +109 -0
  28. package/dist/i18n/translations/en/common.json +36 -0
  29. package/dist/i18n/translations/en/toolkit.json +48 -0
  30. package/dist/i18n/translations/en/tools.json +109 -0
  31. package/dist/i18n/translations/es/common.json +36 -0
  32. package/dist/i18n/translations/es/toolkit.json +48 -0
  33. package/dist/i18n/translations/es/tools.json +109 -0
  34. package/dist/i18n/translations/zh/common.json +36 -0
  35. package/dist/i18n/translations/zh/toolkit.json +48 -0
  36. package/dist/i18n/translations/zh/tools.json +109 -0
  37. package/dist/i18n/types.d.ts +58 -0
  38. package/dist/i18n/types.d.ts.map +1 -0
  39. package/dist/i18n/types.js +8 -0
  40. package/dist/i18n/types.js.map +1 -0
  41. package/dist/i18n/use-i18n-standalone.svelte.d.ts +87 -0
  42. package/dist/i18n/use-i18n-standalone.svelte.d.ts.map +1 -0
  43. package/dist/i18n/use-i18n-standalone.svelte.js +151 -0
  44. package/dist/i18n/use-i18n-standalone.svelte.js.map +1 -0
  45. package/dist/i18n/use-i18n.svelte.d.ts +67 -0
  46. package/dist/i18n/use-i18n.svelte.d.ts.map +1 -0
  47. package/dist/i18n/use-i18n.svelte.js +144 -0
  48. package/dist/i18n/use-i18n.svelte.js.map +1 -0
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +11 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/instrumentation/index.d.ts +53 -0
  54. package/dist/instrumentation/index.d.ts.map +1 -0
  55. package/dist/instrumentation/index.js +53 -0
  56. package/dist/instrumentation/index.js.map +1 -0
  57. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +197 -0
  58. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts.map +1 -0
  59. package/dist/instrumentation/providers/BaseInstrumentationProvider.js +267 -0
  60. package/dist/instrumentation/providers/BaseInstrumentationProvider.js.map +1 -0
  61. package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts +106 -0
  62. package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts.map +1 -0
  63. package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js +182 -0
  64. package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js.map +1 -0
  65. package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts +170 -0
  66. package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts.map +1 -0
  67. package/dist/instrumentation/providers/DataDogInstrumentationProvider.js +183 -0
  68. package/dist/instrumentation/providers/DataDogInstrumentationProvider.js.map +1 -0
  69. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +86 -0
  70. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts.map +1 -0
  71. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +135 -0
  72. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js.map +1 -0
  73. package/dist/instrumentation/providers/index.d.ts +12 -0
  74. package/dist/instrumentation/providers/index.d.ts.map +1 -0
  75. package/dist/instrumentation/providers/index.js +12 -0
  76. package/dist/instrumentation/providers/index.js.map +1 -0
  77. package/dist/instrumentation/types.d.ts +348 -0
  78. package/dist/instrumentation/types.d.ts.map +1 -0
  79. package/dist/instrumentation/types.js +9 -0
  80. package/dist/instrumentation/types.js.map +1 -0
  81. package/dist/loader-config.d.ts +76 -0
  82. package/dist/loader-config.d.ts.map +1 -0
  83. package/dist/loader-config.js +12 -0
  84. package/dist/loader-config.js.map +1 -0
  85. package/dist/loaders/ElementLoader.d.ts +72 -0
  86. package/dist/loaders/ElementLoader.d.ts.map +1 -0
  87. package/dist/loaders/ElementLoader.js +52 -0
  88. package/dist/loaders/ElementLoader.js.map +1 -0
  89. package/dist/loaders/EsmElementLoader.d.ts +67 -0
  90. package/dist/loaders/EsmElementLoader.d.ts.map +1 -0
  91. package/dist/loaders/EsmElementLoader.js +71 -0
  92. package/dist/loaders/EsmElementLoader.js.map +1 -0
  93. package/dist/loaders/IifeElementLoader.d.ts +61 -0
  94. package/dist/loaders/IifeElementLoader.d.ts.map +1 -0
  95. package/dist/loaders/IifeElementLoader.js +63 -0
  96. package/dist/loaders/IifeElementLoader.js.map +1 -0
  97. package/dist/loaders/index.d.ts +28 -0
  98. package/dist/loaders/index.d.ts.map +1 -0
  99. package/dist/loaders/index.js +25 -0
  100. package/dist/loaders/index.js.map +1 -0
  101. package/dist/object/index.d.ts +12 -0
  102. package/dist/object/index.d.ts.map +1 -0
  103. package/dist/object/index.js +40 -0
  104. package/dist/object/index.js.map +1 -0
  105. package/dist/pie/asset-handler.d.ts +64 -0
  106. package/dist/pie/asset-handler.d.ts.map +1 -0
  107. package/dist/pie/asset-handler.js +238 -0
  108. package/dist/pie/asset-handler.js.map +1 -0
  109. package/dist/pie/component-context.d.ts +22 -0
  110. package/dist/pie/component-context.d.ts.map +1 -0
  111. package/dist/pie/component-context.js +30 -0
  112. package/dist/pie/component-context.js.map +1 -0
  113. package/dist/pie/config.d.ts +39 -0
  114. package/dist/pie/config.d.ts.map +1 -0
  115. package/dist/pie/config.js +174 -0
  116. package/dist/pie/config.js.map +1 -0
  117. package/dist/pie/configure-initialization.d.ts +35 -0
  118. package/dist/pie/configure-initialization.d.ts.map +1 -0
  119. package/dist/pie/configure-initialization.js +141 -0
  120. package/dist/pie/configure-initialization.js.map +1 -0
  121. package/dist/pie/esm-loader.d.ts +93 -0
  122. package/dist/pie/esm-loader.d.ts.map +1 -0
  123. package/dist/pie/esm-loader.js +308 -0
  124. package/dist/pie/esm-loader.js.map +1 -0
  125. package/dist/pie/iife-loader.d.ts +76 -0
  126. package/dist/pie/iife-loader.d.ts.map +1 -0
  127. package/dist/pie/iife-loader.js +303 -0
  128. package/dist/pie/iife-loader.js.map +1 -0
  129. package/dist/pie/index.d.ts +31 -0
  130. package/dist/pie/index.d.ts.map +1 -0
  131. package/dist/pie/index.js +34 -0
  132. package/dist/pie/index.js.map +1 -0
  133. package/dist/pie/initialization.d.ts +40 -0
  134. package/dist/pie/initialization.d.ts.map +1 -0
  135. package/dist/pie/initialization.js +349 -0
  136. package/dist/pie/initialization.js.map +1 -0
  137. package/dist/pie/logger.d.ts +64 -0
  138. package/dist/pie/logger.d.ts.map +1 -0
  139. package/dist/pie/logger.js +45 -0
  140. package/dist/pie/logger.js.map +1 -0
  141. package/dist/pie/math-rendering.d.ts +69 -0
  142. package/dist/pie/math-rendering.d.ts.map +1 -0
  143. package/dist/pie/math-rendering.js +98 -0
  144. package/dist/pie/math-rendering.js.map +1 -0
  145. package/dist/pie/overrides.d.ts +43 -0
  146. package/dist/pie/overrides.d.ts.map +1 -0
  147. package/dist/pie/overrides.js +146 -0
  148. package/dist/pie/overrides.js.map +1 -0
  149. package/dist/pie/player-initializer.d.ts +55 -0
  150. package/dist/pie/player-initializer.d.ts.map +1 -0
  151. package/dist/pie/player-initializer.js +123 -0
  152. package/dist/pie/player-initializer.js.map +1 -0
  153. package/dist/pie/registry.d.ts +11 -0
  154. package/dist/pie/registry.d.ts.map +1 -0
  155. package/dist/pie/registry.js +21 -0
  156. package/dist/pie/registry.js.map +1 -0
  157. package/dist/pie/resource-monitor.d.ts +208 -0
  158. package/dist/pie/resource-monitor.d.ts.map +1 -0
  159. package/dist/pie/resource-monitor.js +969 -0
  160. package/dist/pie/resource-monitor.js.map +1 -0
  161. package/dist/pie/scoring.d.ts +17 -0
  162. package/dist/pie/scoring.d.ts.map +1 -0
  163. package/dist/pie/scoring.js +84 -0
  164. package/dist/pie/scoring.js.map +1 -0
  165. package/dist/pie/types.d.ts +136 -0
  166. package/dist/pie/types.d.ts.map +1 -0
  167. package/dist/pie/types.js +52 -0
  168. package/dist/pie/types.js.map +1 -0
  169. package/dist/pie/updates.d.ts +20 -0
  170. package/dist/pie/updates.d.ts.map +1 -0
  171. package/dist/pie/updates.js +175 -0
  172. package/dist/pie/updates.js.map +1 -0
  173. package/dist/pie/use-resource-monitor.svelte.d.ts +56 -0
  174. package/dist/pie/use-resource-monitor.svelte.d.ts.map +1 -0
  175. package/dist/pie/use-resource-monitor.svelte.js +117 -0
  176. package/dist/pie/use-resource-monitor.svelte.js.map +1 -0
  177. package/dist/pie/utils.d.ts +44 -0
  178. package/dist/pie/utils.d.ts.map +1 -0
  179. package/dist/pie/utils.js +74 -0
  180. package/dist/pie/utils.js.map +1 -0
  181. package/dist/types/custom-elements.d.ts +183 -0
  182. package/dist/types/custom-elements.d.ts.map +1 -0
  183. package/dist/types/custom-elements.js +8 -0
  184. package/dist/types/custom-elements.js.map +1 -0
  185. package/dist/types/index.d.ts +761 -0
  186. package/dist/types/index.d.ts.map +1 -0
  187. package/dist/types/index.js +120 -0
  188. package/dist/types/index.js.map +1 -0
  189. package/dist/types/search.d.ts +105 -0
  190. package/dist/types/search.d.ts.map +1 -0
  191. package/dist/types/search.js +12 -0
  192. package/dist/types/search.js.map +1 -0
  193. package/dist/types/transform.d.ts +48 -0
  194. package/dist/types/transform.d.ts.map +1 -0
  195. package/dist/types/transform.js +21 -0
  196. package/dist/types/transform.js.map +1 -0
  197. package/dist/ui/focus-trap.d.ts +10 -0
  198. package/dist/ui/focus-trap.d.ts.map +1 -0
  199. package/dist/ui/focus-trap.js +30 -0
  200. package/dist/ui/focus-trap.js.map +1 -0
  201. package/dist/ui/safe-storage.d.ts +3 -0
  202. package/dist/ui/safe-storage.d.ts.map +1 -0
  203. package/dist/ui/safe-storage.js +21 -0
  204. package/dist/ui/safe-storage.js.map +1 -0
  205. package/package.json +118 -0
  206. package/src/components/PieItemPlayer.svelte +604 -0
  207. package/src/components/PiePreviewLayout.svelte +144 -0
  208. package/src/components/PiePreviewToggle.svelte +110 -0
  209. package/src/components/PieSpinner.svelte +85 -0
  210. package/src/components/ToolSettingsButton.svelte +31 -0
  211. package/src/components/ToolSettingsPanel.svelte +90 -0
  212. package/src/components/index.ts +6 -0
  213. package/src/i18n/README.md +223 -0
  214. package/src/i18n/index.ts +26 -0
  215. package/src/i18n/loader.ts +156 -0
  216. package/src/i18n/scripts/check-coverage.ts +345 -0
  217. package/src/i18n/scripts/scan-hardcoded.ts +342 -0
  218. package/src/i18n/simple-i18n.ts +236 -0
  219. package/src/i18n/translations/ar/common.json +36 -0
  220. package/src/i18n/translations/ar/toolkit.json +48 -0
  221. package/src/i18n/translations/ar/tools.json +109 -0
  222. package/src/i18n/translations/en/common.json +36 -0
  223. package/src/i18n/translations/en/toolkit.json +48 -0
  224. package/src/i18n/translations/en/tools.json +109 -0
  225. package/src/i18n/translations/es/common.json +36 -0
  226. package/src/i18n/translations/es/toolkit.json +48 -0
  227. package/src/i18n/translations/es/tools.json +109 -0
  228. package/src/i18n/translations/zh/common.json +36 -0
  229. package/src/i18n/translations/zh/toolkit.json +48 -0
  230. package/src/i18n/translations/zh/tools.json +109 -0
  231. package/src/i18n/types.ts +66 -0
  232. package/src/i18n/use-i18n-standalone.svelte.ts +184 -0
  233. package/src/i18n/use-i18n.svelte.ts +163 -0
@@ -0,0 +1,144 @@
1
+ <!--
2
+ PiePreviewLayout - Layout container for authoring with preview
3
+
4
+ Combines PiePreviewToggle with conditional rendering of PieItemPlayer instances.
5
+ Switches between authoring mode (configure elements) and preview mode (regular player elements).
6
+ -->
7
+ <script lang="ts">
8
+ import { tick } from 'svelte';
9
+ import type { LoaderConfig } from '../loader-config';
10
+ import { DEFAULT_LOADER_CONFIG } from '../loader-config';
11
+ import { createPieLogger, isGlobalDebugEnabled } from '../pie/logger';
12
+ import { BundleType } from '../pie/types';
13
+ import type { ConfigEntity, Env, ImageHandler, SoundHandler } from '../types';
14
+ import PieItemPlayer from './PieItemPlayer.svelte';
15
+ import PiePreviewToggle from './PiePreviewToggle.svelte';
16
+
17
+ const logger = createPieLogger('pie-preview-layout', () => isGlobalDebugEnabled());
18
+
19
+ // Props using Svelte 5 runes
20
+ let {
21
+ mode = $bindable('author' as 'author' | 'preview'),
22
+ itemConfig,
23
+ passageConfig = null,
24
+ configuration = {} as Record<string, any>,
25
+ env = { mode: 'gather', role: 'student' } as Env,
26
+ session = [] as any[],
27
+ addCorrectResponse = false,
28
+ customClassname = '',
29
+ passageContainerClass = '',
30
+ containerClass = '',
31
+ // Legacy: authoring uses editor.js, preview uses player/client-player.
32
+ // For backwards-compat, keep `bundleType` but allow per-mode overrides.
33
+ bundleType = BundleType.player,
34
+ bundleTypeAuthor = BundleType.editor,
35
+ bundleTypePreview = BundleType.player,
36
+ loaderConfig = DEFAULT_LOADER_CONFIG as LoaderConfig,
37
+ // Asset handler callbacks
38
+ onInsertImage,
39
+ onDeleteImage,
40
+ onInsertSound,
41
+ onDeleteSound,
42
+ // Event callbacks
43
+ onLoadComplete,
44
+ onPlayerError,
45
+ onSessionChanged,
46
+ onModelUpdated
47
+ }: {
48
+ mode?: 'author' | 'preview';
49
+ itemConfig: ConfigEntity;
50
+ passageConfig?: ConfigEntity | null;
51
+ configuration?: Record<string, any>;
52
+ env?: Env;
53
+ session?: any[];
54
+ addCorrectResponse?: boolean;
55
+ customClassname?: string;
56
+ passageContainerClass?: string;
57
+ containerClass?: string;
58
+ bundleType?: BundleType;
59
+ bundleTypeAuthor?: BundleType;
60
+ bundleTypePreview?: BundleType;
61
+ loaderConfig?: LoaderConfig;
62
+ // Asset handlers
63
+ onInsertImage?: (handler: ImageHandler) => void;
64
+ onDeleteImage?: (src: string, done: (err?: Error) => void) => void;
65
+ onInsertSound?: (handler: SoundHandler) => void;
66
+ onDeleteSound?: (src: string, done: (err?: Error) => void) => void;
67
+ // Event callbacks
68
+ onLoadComplete?: (detail?: any) => void;
69
+ onPlayerError?: (detail?: any) => void;
70
+ onSessionChanged?: (detail?: any) => void;
71
+ onModelUpdated?: (detail?: any) => void;
72
+ } = $props();
73
+
74
+ // Track current mode state
75
+ let currentMode = $state(mode);
76
+
77
+ // Handle mode changes from toggle
78
+ function handleModeChange(newMode: 'author' | 'preview') {
79
+ logger.debug('[PiePreviewLayout] Mode changed:', newMode);
80
+ currentMode = newMode;
81
+ mode = newMode; // Update parent via bindable
82
+ }
83
+
84
+ // Derive player mode from current mode
85
+ const playerMode = $derived.by(() => {
86
+ return currentMode === 'preview' ? 'view' : 'author';
87
+ });
88
+
89
+ // Derive bundle type from current mode (author vs preview).
90
+ // Prefer per-mode overrides; fall back to `bundleType` for older callers.
91
+ const effectiveBundleType = $derived.by(() => {
92
+ if (currentMode === 'preview') return bundleTypePreview ?? bundleType;
93
+ return bundleTypeAuthor ?? BundleType.editor;
94
+ });
95
+
96
+ // Key to force re-mount when switching modes
97
+ const playerKey = $derived.by(() => {
98
+ return `${currentMode}-${Date.now()}`;
99
+ });
100
+ </script>
101
+
102
+ <div class="pie-preview-layout">
103
+ <PiePreviewToggle mode={currentMode} onModeChange={handleModeChange} />
104
+
105
+ <div class="preview-content" role="tabpanel" id="{currentMode}-panel" aria-labelledby="{currentMode}-tab">
106
+ {#key playerKey}
107
+ <PieItemPlayer
108
+ {itemConfig}
109
+ {passageConfig}
110
+ {env}
111
+ {session}
112
+ {addCorrectResponse}
113
+ {customClassname}
114
+ {passageContainerClass}
115
+ {containerClass}
116
+ bundleType={effectiveBundleType}
117
+ {loaderConfig}
118
+ mode={playerMode}
119
+ {configuration}
120
+ {onInsertImage}
121
+ {onDeleteImage}
122
+ {onInsertSound}
123
+ {onDeleteSound}
124
+ {onLoadComplete}
125
+ {onPlayerError}
126
+ {onSessionChanged}
127
+ {onModelUpdated}
128
+ />
129
+ {/key}
130
+ </div>
131
+ </div>
132
+
133
+ <style>
134
+ .pie-preview-layout {
135
+ display: block;
136
+ width: 100%;
137
+ }
138
+
139
+ .preview-content {
140
+ display: block;
141
+ width: 100%;
142
+ padding: 1rem 0;
143
+ }
144
+ </style>
@@ -0,0 +1,110 @@
1
+ <!--
2
+ PiePreviewToggle - Toggle between authoring and preview modes
3
+
4
+ Simple tab-based interface for switching between author and preview views.
5
+ Emits mode changes via callback prop.
6
+ -->
7
+ <script lang="ts">
8
+ import { createPieLogger, isGlobalDebugEnabled } from '../pie/logger';
9
+
10
+ const logger = createPieLogger('pie-preview-toggle', () => isGlobalDebugEnabled());
11
+
12
+ // Props using Svelte 5 runes
13
+ let {
14
+ mode = 'author' as 'author' | 'preview',
15
+ onModeChange
16
+ }: {
17
+ mode?: 'author' | 'preview';
18
+ onModeChange?: (mode: 'author' | 'preview') => void;
19
+ } = $props();
20
+
21
+ // Handle mode change
22
+ function handleModeChange(newMode: 'author' | 'preview') {
23
+ logger.debug('[PiePreviewToggle] Mode changed to:', newMode);
24
+
25
+ if (onModeChange) {
26
+ onModeChange(newMode);
27
+ }
28
+
29
+ // Also dispatch DOM event for backward compatibility
30
+ const event = new CustomEvent('mode-changed', {
31
+ detail: { mode: newMode },
32
+ bubbles: true,
33
+ composed: true
34
+ });
35
+ dispatchEvent(event);
36
+ }
37
+ </script>
38
+
39
+ <div class="pie-preview-toggle">
40
+ <div class="toggle-tabs" role="tablist">
41
+ <button
42
+ type="button"
43
+ role="tab"
44
+ class="toggle-tab"
45
+ class:active={mode === 'author'}
46
+ aria-selected={mode === 'author'}
47
+ aria-controls="author-panel"
48
+ onclick={() => handleModeChange('author')}
49
+ >
50
+ Author
51
+ </button>
52
+ <button
53
+ type="button"
54
+ role="tab"
55
+ class="toggle-tab"
56
+ class:active={mode === 'preview'}
57
+ aria-selected={mode === 'preview'}
58
+ aria-controls="preview-panel"
59
+ onclick={() => handleModeChange('preview')}
60
+ >
61
+ Preview
62
+ </button>
63
+ </div>
64
+ </div>
65
+
66
+ <style>
67
+ .pie-preview-toggle {
68
+ display: block;
69
+ width: 100%;
70
+ margin-bottom: 1rem;
71
+ }
72
+
73
+ .toggle-tabs {
74
+ display: flex;
75
+ border-bottom: 2px solid #e0e0e0;
76
+ gap: 0.5rem;
77
+ }
78
+
79
+ .toggle-tab {
80
+ padding: 0.75rem 1.5rem;
81
+ background: transparent;
82
+ border: none;
83
+ border-bottom: 3px solid transparent;
84
+ cursor: pointer;
85
+ font-size: 1rem;
86
+ font-weight: 500;
87
+ color: #666;
88
+ transition: all 0.2s ease;
89
+ margin-bottom: -2px;
90
+ }
91
+
92
+ .toggle-tab:hover {
93
+ color: #333;
94
+ background: rgba(0, 0, 0, 0.05);
95
+ }
96
+
97
+ .toggle-tab:focus {
98
+ outline: 2px solid #1976d2;
99
+ outline-offset: 2px;
100
+ }
101
+
102
+ .toggle-tab.active {
103
+ color: #1976d2;
104
+ border-bottom-color: #1976d2;
105
+ }
106
+
107
+ .toggle-tab:active {
108
+ transform: translateY(1px);
109
+ }
110
+ </style>
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ export let fixed: boolean = false;
3
+ </script>
4
+
5
+ <div class="pie-loading {fixed ? 'fixed' : ''}"></div>
6
+
7
+ <style>
8
+ .pie-loading {
9
+ position: absolute;
10
+ height: 100%;
11
+ width: 100%;
12
+ background-color: #fff;
13
+ bottom: 0;
14
+ left: 0;
15
+ right: 0;
16
+ top: 0;
17
+ z-index: 9999;
18
+ opacity: 0.4;
19
+ }
20
+
21
+ .pie-loading.fixed {
22
+ position: fixed;
23
+ }
24
+
25
+ .pie-loading:before {
26
+ content: "";
27
+ background-color: rgba(0, 0, 0, 0);
28
+ border: 5px solid rgba(63, 81, 181, 0.9);
29
+ opacity: 0.9;
30
+ border-right: 5px solid rgba(0, 0, 0, 0);
31
+ border-left: 5px solid rgba(0, 0, 0, 0);
32
+ border-radius: 50px;
33
+ box-shadow: 0 0 35px rgba(63, 81, 181, 0.9);
34
+ width: 50px;
35
+ height: 50px;
36
+ animation: spinPulse 1s infinite linear;
37
+ margin: -25px 0 0 -25px;
38
+ position: absolute;
39
+ top: 50%;
40
+ left: 50%;
41
+ }
42
+
43
+ .pie-loading:after {
44
+ content: "";
45
+ background-color: rgba(0, 0, 0, 0);
46
+ border: 5px solid rgba(63, 81, 181, 0.9);
47
+ opacity: 0.9;
48
+ border-left: 5px solid rgba(0, 0, 0, 0);
49
+ border-right: 5px solid rgba(0, 0, 0, 0);
50
+ border-radius: 50px;
51
+ box-shadow: 0 0 15px rgba(63, 81, 181, 0.9);
52
+ width: 30px;
53
+ height: 30px;
54
+ animation: spinoffPulse 1s infinite linear;
55
+ margin: -15px 0 0 -15px;
56
+ position: absolute;
57
+ top: 50%;
58
+ left: 50%;
59
+ }
60
+
61
+ @keyframes spinPulse {
62
+ 0% {
63
+ transform: rotate(160deg);
64
+ opacity: 0;
65
+ box-shadow: 0 0 1px rgba(63, 81, 181, 0.9);
66
+ }
67
+ 50% {
68
+ transform: rotate(145deg);
69
+ opacity: 1;
70
+ }
71
+ 100% {
72
+ transform: rotate(-320deg);
73
+ opacity: 0;
74
+ }
75
+ }
76
+
77
+ @keyframes spinoffPulse {
78
+ 0% {
79
+ transform: rotate(0deg);
80
+ }
81
+ 100% {
82
+ transform: rotate(360deg);
83
+ }
84
+ }
85
+ </style>
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ let {
3
+ buttonEl = null as HTMLButtonElement | null,
4
+ onClick,
5
+ ariaLabel = 'Settings',
6
+ active = false
7
+ }: {
8
+ buttonEl?: HTMLButtonElement | null;
9
+ onClick: () => void;
10
+ ariaLabel?: string;
11
+ active?: boolean;
12
+ } = $props();
13
+ </script>
14
+
15
+ <button
16
+ bind:this={buttonEl}
17
+ type="button"
18
+ class="btn btn-ghost btn-sm btn-circle"
19
+ aria-label={ariaLabel}
20
+ aria-pressed={active}
21
+ onclick={onClick}
22
+ >
23
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
24
+ <path
25
+ stroke-linecap="round"
26
+ stroke-linejoin="round"
27
+ d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
28
+ />
29
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
30
+ </svg>
31
+ </button>
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ let {
5
+ open,
6
+ title = 'Settings',
7
+ onClose,
8
+ anchorEl = null
9
+ }: {
10
+ open: boolean;
11
+ title?: string;
12
+ onClose: () => void;
13
+ anchorEl?: HTMLElement | null;
14
+ } = $props();
15
+
16
+ let panelEl = $state<HTMLDivElement | null>(null);
17
+ let panelPosition = $state<{ top: number; left?: number; right?: number } | null>(null);
18
+
19
+ function onKeyDown(e: KeyboardEvent) {
20
+ if (e.key === 'Escape') onClose();
21
+ }
22
+
23
+ $effect(() => {
24
+ if (open) queueMicrotask(() => panelEl?.focus?.());
25
+ });
26
+
27
+ // Calculate position based on anchor element
28
+ $effect(() => {
29
+ if (open && anchorEl) {
30
+ const anchorRect = anchorEl.getBoundingClientRect();
31
+ const panelWidth = 320; // w-80 = 20rem = 320px
32
+ const spacing = 8; // Gap from anchor
33
+
34
+ // Try to position to the right of anchor first
35
+ const rightPosition = anchorRect.right + spacing;
36
+ const hasSpaceOnRight = rightPosition + panelWidth <= window.innerWidth;
37
+
38
+ if (hasSpaceOnRight) {
39
+ // Position to the right of anchor
40
+ panelPosition = {
41
+ top: anchorRect.top,
42
+ left: rightPosition
43
+ };
44
+ } else {
45
+ // Position to the left of anchor
46
+ panelPosition = {
47
+ top: anchorRect.top,
48
+ right: window.innerWidth - anchorRect.left + spacing
49
+ };
50
+ }
51
+ } else {
52
+ panelPosition = null;
53
+ }
54
+ });
55
+
56
+ onMount(() => {
57
+ const onDocClick = (e: MouseEvent) => {
58
+ if (!open) return;
59
+ const target = e.target as Node | null;
60
+ if (!target) return;
61
+ if (panelEl?.contains(target)) return;
62
+ if (anchorEl?.contains(target)) return;
63
+ onClose();
64
+ };
65
+
66
+ document.addEventListener('mousedown', onDocClick);
67
+ return () => document.removeEventListener('mousedown', onDocClick);
68
+ });
69
+ </script>
70
+
71
+ {#if open}
72
+ <div class="fixed inset-0" aria-hidden="true" onmousedown={onClose} style="z-index: 4000;"></div>
73
+ <div
74
+ bind:this={panelEl}
75
+ class="tool-settings-panel fixed w-80 rounded-box bg-base-100 shadow p-3 text-base-content"
76
+ role="dialog"
77
+ aria-label={title}
78
+ tabindex="-1"
79
+ onkeydown={onKeyDown}
80
+ style="z-index: 4100; {panelPosition ? `top: ${panelPosition.top}px; ${panelPosition.left !== undefined ? `left: ${panelPosition.left}px;` : `right: ${panelPosition.right}px;`}` : 'top: 4rem; right: 1rem;'}"
81
+ >
82
+ <div class="flex items-center justify-between gap-2 mb-2">
83
+ <h2 class="font-semibold text-sm">{title}</h2>
84
+ <button type="button" class="btn btn-ghost btn-xs" onclick={onClose} aria-label="Close settings">Close</button>
85
+ </div>
86
+ <div class="text-sm">
87
+ <slot />
88
+ </div>
89
+ </div>
90
+ {/if}
@@ -0,0 +1,6 @@
1
+ export { default as PieItemPlayer } from "./PieItemPlayer.svelte";
2
+ export { default as PiePreviewLayout } from "./PiePreviewLayout.svelte";
3
+ export { default as PiePreviewToggle } from "./PiePreviewToggle.svelte";
4
+ export { default as PieSpinner } from "./PieSpinner.svelte";
5
+ export { default as ToolSettingsButton } from "./ToolSettingsButton.svelte";
6
+ export { default as ToolSettingsPanel } from "./ToolSettingsPanel.svelte";
@@ -0,0 +1,223 @@
1
+ # PIE Players i18n System
2
+
3
+ Comprehensive internationalization system with support for English, Spanish, Chinese, and Arabic (RTL).
4
+
5
+ ## Usage Patterns
6
+
7
+ ### Pattern 1: Standalone Components (Recommended for Individual Components)
8
+
9
+ Use `useI18nStandalone()` when building standalone components (tools, players) that don't require the full toolkit architecture.
10
+
11
+ ```svelte
12
+ <script lang="ts">
13
+ import { useI18nStandalone } from '@pie-framework/pie-players-shared/i18n';
14
+
15
+ // Simple setup - no service injection needed
16
+ const i18n = useI18nStandalone({
17
+ locale: 'en', // Optional: defaults to browser language
18
+ debug: false // Optional: enable debug logging
19
+ });
20
+ </script>
21
+
22
+ <div dir={i18n.direction}>
23
+ <button>{i18n.t('common.save')}</button>
24
+ <span>{i18n.tn('assessment.questions', 10)}</span>
25
+
26
+ <!-- Change locale dynamically -->
27
+ <button onclick={() => i18n.setLocale('es')}>Español</button>
28
+ <button onclick={() => i18n.setLocale('ar')}>العربية</button>
29
+ </div>
30
+ ```
31
+
32
+ **Benefits:**
33
+ - ✅ No service architecture required
34
+ - ✅ Self-contained - manages its own I18nService instance
35
+ - ✅ Full feature support (hybrid loading, RTL, pluralization)
36
+ - ✅ Perfect for reusable components and tools
37
+
38
+ ### Pattern 2: Integrated with Assessment Toolkit
39
+
40
+ Use `useI18n()` when working within the full assessment toolkit where centralized locale management is needed.
41
+
42
+ ```svelte
43
+ <script lang="ts">
44
+ import { useI18n } from '@pie-framework/pie-players-shared/i18n';
45
+
46
+ // Receive service from parent/context
47
+ let { player } = $props();
48
+ const i18n = useI18n(() => player.getI18nService());
49
+ </script>
50
+
51
+ <div dir={i18n.direction}>
52
+ <button>{i18n.t('common.save')}</button>
53
+ <span>{i18n.tn('assessment.questions', totalQuestions)}</span>
54
+ </div>
55
+ ```
56
+
57
+ **Benefits:**
58
+ - ✅ Centralized locale management across entire application
59
+ - ✅ Locale preferences from student profile/IEP
60
+ - ✅ Shared service instance - locale changes sync everywhere
61
+ - ✅ Integration with accommodation system
62
+
63
+ ### Pattern 3: Direct Service Usage (Advanced)
64
+
65
+ For non-Svelte contexts or advanced use cases:
66
+
67
+ ```typescript
68
+ import { SimpleI18n, BUNDLED_TRANSLATIONS, loadTranslations } from '@pie-framework/pie-players-shared/i18n';
69
+
70
+ const i18n = new SimpleI18n({
71
+ locale: 'en',
72
+ bundledTranslations: BUNDLED_TRANSLATIONS,
73
+ loadTranslations,
74
+ });
75
+
76
+ await i18n.initialize({ locale: 'es' });
77
+
78
+ console.log(i18n.t('common.save')); // "Guardar"
79
+ console.log(i18n.getDirection()); // "ltr"
80
+
81
+ // Subscribe to changes
82
+ const unsubscribe = i18n.subscribe(() => {
83
+ console.log('Locale changed to:', i18n.getLocale());
84
+ });
85
+ ```
86
+
87
+ ## Translation Functions
88
+
89
+ ### `t(key, params?)` - Basic Translation
90
+
91
+ ```typescript
92
+ i18n.t('common.save') // "Save"
93
+ i18n.t('common.cancel') // "Cancel"
94
+
95
+ // With interpolation
96
+ i18n.t('assessment.question_of', { current: 5, total: 20 })
97
+ // "Question 5 of 20"
98
+ ```
99
+
100
+ ### `tn(key, count, params?)` - Pluralization
101
+
102
+ ```typescript
103
+ i18n.tn('assessment.questions', 1) // "1 Question"
104
+ i18n.tn('assessment.questions', 10) // "10 Questions"
105
+
106
+ // With additional params
107
+ i18n.tn('common.item', count, { type: 'folder' })
108
+ ```
109
+
110
+ ## Available Translations
111
+
112
+ ### Common Namespace (`common.*`)
113
+ - Buttons: `save`, `cancel`, `close`, `back`, `next`, `previous`, `submit`
114
+ - States: `loading`, `error`, `retry`
115
+ - Common words: `question`, `item`, `character` (all with pluralization)
116
+
117
+ ### Assessment Namespace (`assessment.*`)
118
+ - `title` - Assessment title
119
+ - `questions` - Question count (plural)
120
+ - `question_of` - "Question X of Y"
121
+ - `student_name` - Student name label
122
+ - `fullscreen`, `exit_fullscreen` - Fullscreen controls
123
+
124
+ ### Accommodation Namespace (`accommodation.*`)
125
+ - `audio`, `audio_aria` - Audio/TTS controls
126
+ - `contrast`, `contrast_aria` - Contrast controls
127
+
128
+ ### Navigation Namespace (`navigation.*`)
129
+ - `back`, `next`, `previous`, `submit` - Navigation buttons
130
+ - `navigate_to` - "Navigate to question X"
131
+ - `section` - Section label
132
+
133
+ ### Tool Namespace (`tool.*`)
134
+ - Tool names: `calculator`, `graph`, `periodic_table`, etc.
135
+ - TTS controls: `tts.speak`, `tts.pause`, `tts.stop`, `tts.rate`
136
+ - Calculator types: `calculator.basic`, `calculator.scientific`, `calculator.graphing`
137
+ - Color schemes: `color_scheme.default`, `color_scheme.high_contrast`, `color_scheme.dark`
138
+
139
+ ## Supported Languages
140
+
141
+ - **English (en)** - Bundled (~15KB)
142
+ - **Spanish (es)** - Lazy-loaded (~12KB)
143
+ - **Chinese (zh)** - Lazy-loaded (~12KB)
144
+ - **Arabic (ar)** - Lazy-loaded (~12KB) with RTL support
145
+
146
+ ## RTL Support
147
+
148
+ Arabic automatically switches to RTL mode:
149
+
150
+ ```svelte
151
+ <script>
152
+ const i18n = useI18nStandalone({ locale: 'ar' });
153
+ </script>
154
+
155
+ <!-- direction automatically set to "rtl" -->
156
+ <div dir={i18n.direction}>
157
+ {i18n.t('assessment.title')} <!-- التقييم -->
158
+ </div>
159
+ ```
160
+
161
+ ## API Reference
162
+
163
+ ### Composable Return Value
164
+
165
+ ```typescript
166
+ {
167
+ // Reactive getters
168
+ locale: string; // Current locale (e.g., 'en', 'es')
169
+ direction: 'ltr' | 'rtl'; // Text direction
170
+ isLoading: boolean; // Locale loading state
171
+ availableLocales: string[]; // List of loaded locales
172
+
173
+ // Methods
174
+ t(key: string, params?: Record<string, any>): string;
175
+ tn(key: string, count: number, params?: Record<string, any>): string;
176
+ setLocale(locale: string): Promise<void>;
177
+ isLocaleLoaded(locale: string): boolean;
178
+ hasKey(key: string): boolean;
179
+ }
180
+ ```
181
+
182
+ ## When to Use Each Pattern
183
+
184
+ | Scenario | Use Pattern |
185
+ |----------|-------------|
186
+ | Standalone tool component | `useI18nStandalone()` |
187
+ | Reusable UI component | `useI18nStandalone()` |
188
+ | Individual player | `useI18nStandalone()` |
189
+ | Full assessment application | `useI18n()` |
190
+ | Student profile integration | `useI18n()` |
191
+ | IEP/504 locale requirements | `useI18n()` |
192
+ | Non-Svelte code | `SimpleI18n` class |
193
+
194
+ ## Performance
195
+
196
+ - **Initial load**: ~15KB (English bundled)
197
+ - **Lazy loading**: ~12KB per additional language (cached)
198
+ - **Hybrid strategy**: Only loads languages when needed
199
+ - **Service reuse**: `useI18nStandalone()` creates one service per component
200
+
201
+ ## Adding New Translations
202
+
203
+ Translation files are located at:
204
+ ```
205
+ packages/players-shared/src/i18n/translations/
206
+ ├── en/ (common.json, toolkit.json, tools.json)
207
+ ├── es/ (common.json, toolkit.json, tools.json)
208
+ ├── zh/ (common.json, toolkit.json, tools.json)
209
+ └── ar/ (common.json, toolkit.json, tools.json)
210
+ ```
211
+
212
+ Format:
213
+ ```json
214
+ {
215
+ "common": {
216
+ "save": "Save",
217
+ "question": {
218
+ "one": "Question",
219
+ "other": "Questions"
220
+ }
221
+ }
222
+ }
223
+ ```