lightview 1.8.2 → 2.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 (262) hide show
  1. package/.codacy/cli.sh +149 -0
  2. package/.codacy/codacy.yaml +15 -0
  3. package/.github/instructions/codacy.instructions.md +72 -0
  4. package/.wranglerignore +21 -0
  5. package/README.md +1330 -19
  6. package/_headers +4 -0
  7. package/build.js +70 -0
  8. package/components/actions/button.js +151 -0
  9. package/components/actions/dropdown.js +120 -0
  10. package/components/actions/modal.js +146 -0
  11. package/components/actions/swap.js +118 -0
  12. package/components/daisyui.js +288 -0
  13. package/components/data-display/accordion.js +128 -0
  14. package/components/data-display/alert.js +112 -0
  15. package/components/data-display/avatar.js +170 -0
  16. package/components/data-display/badge.js +82 -0
  17. package/components/data-display/card.js +151 -0
  18. package/components/data-display/carousel.js +94 -0
  19. package/components/data-display/chart.js +220 -0
  20. package/components/data-display/chat.js +128 -0
  21. package/components/data-display/collapse.js +103 -0
  22. package/components/data-display/countdown.js +69 -0
  23. package/components/data-display/diff.js +111 -0
  24. package/components/data-display/kbd.js +65 -0
  25. package/components/data-display/loading.js +75 -0
  26. package/components/data-display/progress.js +79 -0
  27. package/components/data-display/radial-progress.js +88 -0
  28. package/components/data-display/skeleton.js +66 -0
  29. package/components/data-display/stats.js +159 -0
  30. package/components/data-display/table.js +146 -0
  31. package/components/data-display/timeline.js +146 -0
  32. package/components/data-display/toast.js +72 -0
  33. package/components/data-display/tooltip.js +74 -0
  34. package/components/data-input/checkbox.js +253 -0
  35. package/components/data-input/file-input.js +224 -0
  36. package/components/data-input/input.js +264 -0
  37. package/components/data-input/radio.js +338 -0
  38. package/components/data-input/range.js +204 -0
  39. package/components/data-input/rating.js +219 -0
  40. package/components/data-input/select.js +287 -0
  41. package/components/data-input/textarea.js +287 -0
  42. package/components/data-input/toggle.js +201 -0
  43. package/components/index.js +137 -0
  44. package/components/layout/divider.js +72 -0
  45. package/components/layout/drawer.js +142 -0
  46. package/components/layout/footer.js +100 -0
  47. package/components/layout/hero.js +109 -0
  48. package/components/layout/indicator.js +90 -0
  49. package/components/layout/join.js +78 -0
  50. package/components/layout/navbar.js +110 -0
  51. package/components/navigation/breadcrumbs.js +91 -0
  52. package/components/navigation/dock.js +103 -0
  53. package/components/navigation/menu.js +126 -0
  54. package/components/navigation/pagination.js +105 -0
  55. package/components/navigation/steps.js +89 -0
  56. package/components/navigation/tabs.css +177 -0
  57. package/components/navigation/tabs.js +123 -0
  58. package/components/theme/theme-switch.css +65 -0
  59. package/components/theme/theme-switch.js +177 -0
  60. package/docs/about.html +164 -0
  61. package/docs/api/computed.html +184 -0
  62. package/docs/api/effects.html +173 -0
  63. package/docs/api/elements.html +180 -0
  64. package/docs/api/enhance.html +225 -0
  65. package/docs/api/hypermedia.html +165 -0
  66. package/docs/api/index.html +178 -0
  67. package/docs/api/nav.html +18 -0
  68. package/docs/api/signals.html +136 -0
  69. package/docs/api/state.html +217 -0
  70. package/docs/assets/images/logo-favicon.svg +42 -0
  71. package/docs/assets/images/logo-static.svg +40 -0
  72. package/docs/assets/images/logo.svg +66 -0
  73. package/docs/assets/js/examplify.js +395 -0
  74. package/docs/assets/styles/site.css +1102 -0
  75. package/docs/assets/styles/themes.css +236 -0
  76. package/docs/components/accordion.html +439 -0
  77. package/docs/components/alert.html +528 -0
  78. package/docs/components/avatar.html +586 -0
  79. package/docs/components/badge.html +531 -0
  80. package/docs/components/breadcrumbs.html +278 -0
  81. package/docs/components/button.html +579 -0
  82. package/docs/components/card.html +561 -0
  83. package/docs/components/carousel.html +286 -0
  84. package/docs/components/chart-area.html +702 -0
  85. package/docs/components/chart-bar.html +782 -0
  86. package/docs/components/chart-column.html +735 -0
  87. package/docs/components/chart-line.html +794 -0
  88. package/docs/components/chart-pie.html +823 -0
  89. package/docs/components/chart.html +610 -15
  90. package/docs/components/chat.html +547 -0
  91. package/docs/components/checkbox.html +641 -0
  92. package/docs/components/collapse.html +536 -0
  93. package/docs/components/component-nav.html +53 -0
  94. package/docs/components/countdown.html +470 -0
  95. package/docs/components/diff.html +245 -0
  96. package/docs/components/divider.html +240 -0
  97. package/docs/components/dock.html +277 -0
  98. package/docs/components/drawer.html +515 -0
  99. package/docs/components/dropdown.html +479 -0
  100. package/docs/components/file-input.html +591 -0
  101. package/docs/components/footer.html +301 -0
  102. package/docs/components/gallery.html +504 -0
  103. package/docs/components/hero.html +264 -0
  104. package/docs/components/index.css +840 -0
  105. package/docs/components/index.html +735 -0
  106. package/docs/components/indicator.html +342 -0
  107. package/docs/components/input.html +644 -0
  108. package/docs/components/join.html +285 -0
  109. package/docs/components/kbd.html +322 -0
  110. package/docs/components/loading.html +521 -0
  111. package/docs/components/menu.html +461 -0
  112. package/docs/components/modal.html +639 -0
  113. package/docs/components/navbar.html +321 -0
  114. package/docs/components/pagination.html +279 -0
  115. package/docs/components/progress.html +514 -0
  116. package/docs/components/radial-progress.html +434 -0
  117. package/docs/components/radio.html +655 -0
  118. package/docs/components/range.html +611 -0
  119. package/docs/components/rating.html +642 -0
  120. package/docs/components/select.html +696 -0
  121. package/docs/components/sidebar-setup.js +93 -0
  122. package/docs/components/skeleton.html +447 -0
  123. package/docs/components/spinner.html +68 -0
  124. package/docs/components/stats.html +486 -0
  125. package/docs/components/steps.html +356 -0
  126. package/docs/components/swap.html +517 -0
  127. package/docs/components/switch.html +68 -0
  128. package/docs/components/table.html +668 -0
  129. package/docs/components/tabs.html +506 -0
  130. package/docs/components/text-input.html +68 -0
  131. package/docs/components/textarea.html +603 -0
  132. package/docs/components/timeline.html +485 -42
  133. package/docs/components/toast.html +474 -0
  134. package/docs/components/toggle.html +564 -0
  135. package/docs/components/tooltip.html +423 -0
  136. package/docs/examples/getting-started-example.html +40 -0
  137. package/docs/examples/index.html +93 -0
  138. package/docs/getting-started/index.html +739 -0
  139. package/docs/getting-started/reviews.html +23 -0
  140. package/docs/getting-started/reviews.odom +108 -0
  141. package/docs/getting-started/reviews.vdom +84 -0
  142. package/docs/index.html +132 -42
  143. package/docs/playground.html +416 -0
  144. package/docs/router.html +285 -0
  145. package/docs/styles/index.html +190 -0
  146. package/functions/_middleware.js +32 -0
  147. package/index.html +309 -0
  148. package/lightview-router.js +364 -0
  149. package/lightview-x.js +1577 -0
  150. package/lightview.js +659 -1200
  151. package/middleware/locale.js +25 -0
  152. package/middleware/markdown.js +44 -0
  153. package/middleware/notFound.js +37 -0
  154. package/package.json +27 -41
  155. package/watch.js +92 -0
  156. package/wrangler.toml +12 -0
  157. package/.idea/lightview.iml +0 -12
  158. package/.idea/modules.xml +0 -8
  159. package/.idea/vcs.xml +0 -6
  160. package/LICENSE +0 -21
  161. package/codepen-no-tabs-embed.css +0 -2
  162. package/docs/CNAME +0 -1
  163. package/docs/api.html +0 -674
  164. package/docs/blank.html +0 -10
  165. package/docs/comparedto.html +0 -89
  166. package/docs/components/chart-repl.html +0 -69
  167. package/docs/components/components.js +0 -113
  168. package/docs/components/contents.html +0 -17
  169. package/docs/components/gantt-repl.html +0 -61
  170. package/docs/components/gantt.html +0 -42
  171. package/docs/components/gauge-repl.html +0 -66
  172. package/docs/components/gauge.html +0 -20
  173. package/docs/components/orgchart-repl.html +0 -64
  174. package/docs/components/orgchart.html +0 -41
  175. package/docs/components/repl-as-src.html +0 -17
  176. package/docs/components/repl-repl.html +0 -95
  177. package/docs/components/repl.html +0 -527
  178. package/docs/components/timeline-repl.html +0 -72
  179. package/docs/components.html +0 -14
  180. package/docs/css/highlightjs.min.css +0 -9
  181. package/docs/css/tutorial.css +0 -35
  182. package/docs/examples/anchor.html +0 -11
  183. package/docs/examples/chart.html +0 -34
  184. package/docs/examples/counter.html +0 -26
  185. package/docs/examples/counter.test.mjs +0 -47
  186. package/docs/examples/counter2.html +0 -26
  187. package/docs/examples/directives.html +0 -79
  188. package/docs/examples/foreign.html +0 -50
  189. package/docs/examples/forgeinform.html +0 -98
  190. package/docs/examples/form.html +0 -61
  191. package/docs/examples/gauge.html +0 -18
  192. package/docs/examples/invalid-template-literals.html +0 -44
  193. package/docs/examples/medium/remote.html +0 -60
  194. package/docs/examples/message.html +0 -18
  195. package/docs/examples/nested.html +0 -11
  196. package/docs/examples/object-bound-form.html +0 -34
  197. package/docs/examples/remote-server.js +0 -51
  198. package/docs/examples/remote.html +0 -34
  199. package/docs/examples/remote.json +0 -1
  200. package/docs/examples/scratch.html +0 -69
  201. package/docs/examples/sensors/index.html +0 -44
  202. package/docs/examples/sensors/sensor-server.js +0 -30
  203. package/docs/examples/shared.html +0 -41
  204. package/docs/examples/template.html +0 -33
  205. package/docs/examples/timeline.html +0 -21
  206. package/docs/examples/todo.html +0 -40
  207. package/docs/examples/top.html +0 -10
  208. package/docs/examples/types.html +0 -94
  209. package/docs/examples/xor.html +0 -62
  210. package/docs/examples.html +0 -25
  211. package/docs/javascript/codejar.min.js +0 -8
  212. package/docs/javascript/highlightjs.min.js +0 -1173
  213. package/docs/javascript/isomorphic-git.js +0 -9
  214. package/docs/javascript/json5.min.js +0 -1
  215. package/docs/javascript/lightning-fs.js +0 -1
  216. package/docs/javascript/lightview.js +0 -1285
  217. package/docs/javascript/marked.min.js +0 -6
  218. package/docs/javascript/peerjs.min.js +0 -70
  219. package/docs/javascript/turndown.js +0 -973
  220. package/docs/javascript/types.js +0 -606
  221. package/docs/javascript/utils.js +0 -45
  222. package/docs/lightview.html +0 -63
  223. package/docs/old_index.html +0 -965
  224. package/docs/old_index.md +0 -1132
  225. package/docs/slidein.html +0 -51
  226. package/docs/tutorial/0-getting-started.html +0 -67
  227. package/docs/tutorial/1-intro-to-variables.html +0 -103
  228. package/docs/tutorial/10-template-components.html +0 -80
  229. package/docs/tutorial/11-linked-components.html +0 -76
  230. package/docs/tutorial/12-imported-components.html +0 -67
  231. package/docs/tutorial/13-input-binding.html +0 -94
  232. package/docs/tutorial/14-automatic-variable-creation.html +0 -74
  233. package/docs/tutorial/15-form-binding.html +0 -110
  234. package/docs/tutorial/16-if-directive.html +0 -60
  235. package/docs/tutorial/17-loop-directives.html +0 -83
  236. package/docs/tutorial/18-sanitizing-and-escaping-input.html +0 -79
  237. package/docs/tutorial/2-imported-and-exported-variables.html +0 -80
  238. package/docs/tutorial/3-data-types.html +0 -89
  239. package/docs/tutorial/4-extended-data-types.html +0 -83
  240. package/docs/tutorial/5-extended-functional-types.html +0 -96
  241. package/docs/tutorial/5.1-extended-functional-types.html +0 -79
  242. package/docs/tutorial/5.2-extended-functional-types.html +0 -70
  243. package/docs/tutorial/6-conventional-javascript.html +0 -75
  244. package/docs/tutorial/7-monitoring-with-observers.html +0 -107
  245. package/docs/tutorial/8-event-listeners.html +0 -65
  246. package/docs/tutorial/9-intro-to-components.html +0 -91
  247. package/docs/tutorial/contents.html +0 -32
  248. package/docs/tutorial/my-component.html +0 -29
  249. package/docs/tutorial/remote-value.json +0 -4
  250. package/docs/websiterepl.html +0 -46
  251. package/jest-puppeteer.config.js +0 -5
  252. package/jest.config.json +0 -12
  253. package/lightview.min.js +0 -1
  254. package/lightview_good.js +0 -1267
  255. package/lightview_optimized.js +0 -1274
  256. package/repl_hold.html +0 -320
  257. package/test/basic.html +0 -104
  258. package/test/basic.test.mjs +0 -315
  259. package/test/extended.html +0 -29
  260. package/test/extended.test.mjs +0 -448
  261. package/types.js +0 -607
  262. package/unsplash.key +0 -1
@@ -0,0 +1,395 @@
1
+ // Unique ID counter for iframe identification
2
+ var examplifyIdCounter = window.examplifyIdCounter || 0;
3
+ window.examplifyIdCounter = examplifyIdCounter;
4
+
5
+ function examplify(target, options = {}) {
6
+ const { scripts, styles, modules, html, at, location = 'beforeBegin', type, height, minHeight = 100, maxHeight = Infinity, allowSameOrigin = false, language = 'js', autoRun = false } = options;
7
+ const originalContent = target.textContent;
8
+ const autoResize = !height; // Auto-resize if no explicit height is provided
9
+ const iframeId = `examplify-${++examplifyIdCounter}`;
10
+
11
+ // State
12
+ let isRunning = false;
13
+
14
+ // 2. Create controls above the target
15
+ const controls = document.createElement('div');
16
+ const editable = target.getAttribute('contenteditable') == 'true';
17
+ controls.className = 'examplify-controls';
18
+
19
+ // Controls HTML
20
+ controls.innerHTML = `
21
+ <button class="examplify-btn examplify-run" title="Run">
22
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
23
+ <path d="M8 5v14l11-7z"/>
24
+ </svg>
25
+ <span>Run</span>
26
+ </button>
27
+ <button class="examplify-btn examplify-copy" title="Copy">
28
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
29
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
30
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
31
+ </svg>
32
+ <span>Copy</span>
33
+ </button>
34
+ ${editable ? `<button class="examplify-btn examplify-reset" title="Reset" style="display: none;">
35
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
36
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
37
+ <path d="M3 3v5h5"/>
38
+ </svg>
39
+ <span>Reset</span>
40
+ </button>` : ''}
41
+ `;
42
+
43
+ // 3. Create iframe after target (initially empty)
44
+ let iframe = document.createElement('iframe');
45
+ iframe.className = 'examplify-iframe';
46
+ iframe.style.opacity = '1';
47
+ iframe.style.background = '#f9fafb'; // Light gray placeholder
48
+ iframe.style.border = '1px solid #e5e7eb';
49
+ iframe.style.transition = 'opacity 0.2s ease-in, height 0.2s ease-out';
50
+ iframe.sandbox = `${allowSameOrigin ? 'allow-same-origin ' : ''}allow-scripts`;
51
+
52
+ if (height) iframe.style.height = height;
53
+ if (minHeight) iframe.style.minHeight = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
54
+ if (maxHeight && maxHeight !== Infinity) iframe.style.maxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight;
55
+
56
+ // Set initial placeholder height if not specified, to show it exists
57
+ if (!height && !iframe.style.minHeight) {
58
+ iframe.style.height = '100px';
59
+ }
60
+
61
+ // Insert elements
62
+ const insertionPoint = (at || target);
63
+ insertionPoint.insertAdjacentElement(location, iframe);
64
+
65
+ if (target.parentElement && target.parentElement.tagName === 'PRE') {
66
+ target.parentElement.insertAdjacentElement('beforebegin', controls);
67
+ target.parentElement.classList.add('examplify-parent');
68
+ target.style.outline = 'none';
69
+ target.style.border = 'none';
70
+ } else {
71
+ target.insertAdjacentElement('beforebegin', controls);
72
+ }
73
+
74
+ // Styles
75
+ // We add styles only if not present, to avoid duplication if called multiple times
76
+ if (!document.getElementById('examplify-styles')) {
77
+ const style = document.createElement('style');
78
+ style.id = 'examplify-styles';
79
+ style.textContent = `
80
+ .examplify-controls {
81
+ display: flex;
82
+ gap: 0.5rem;
83
+ margin-bottom: 0.5rem;
84
+ }
85
+ .examplify-btn {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ gap: 0.25rem;
89
+ padding: 0.25rem 0.5rem;
90
+ font-size: 0.75rem;
91
+ border: 1px solid #e5e7eb;
92
+ border-radius: 4px;
93
+ background: #f9fafb;
94
+ cursor: pointer;
95
+ transition: all 0.2s;
96
+ color: #374151;
97
+ }
98
+ .examplify-btn:hover {
99
+ background: #f3f4f6;
100
+ border-color: #d1d5db;
101
+ }
102
+ .examplify-iframe {
103
+ width: 100%;
104
+ border: 1px solid #e5e7eb;
105
+ border-radius: 6px;
106
+ margin-top: 0.5rem;
107
+ background: white;
108
+ }
109
+ .examplify-parent {
110
+ transition: outline 0.2s;
111
+ border-radius: 4px;
112
+ }
113
+ .examplify-parent:hover {
114
+ outline: 1px solid #d1d5db;
115
+ }
116
+ `;
117
+ document.head.appendChild(style);
118
+ }
119
+
120
+ // Helper: Placeholder Content
121
+ function getPlaceholderContent() {
122
+ return `<!DOCTYPE html>
123
+ <html>
124
+ <head>
125
+ <style>
126
+ body {
127
+ margin:0; padding:0; height:100vh;
128
+ display:flex; align-items:center; justify-content:center;
129
+ background:#f9fafb; color:#6b7280;
130
+ cursor:pointer; user-select:none;
131
+ font-family:system-ui,-apple-system,sans-serif;
132
+ transition: color 0.2s;
133
+ }
134
+ body:hover { color: #374151; }
135
+ .content { display:flex; align-items:center; gap:0.5rem; font-size:0.875rem; font-weight: 500; }
136
+ </style>
137
+ </head>
138
+ <body onclick="parent.postMessage({type:'examplify-run-click', id:'${iframeId}'}, '*')">
139
+ <div class="content">
140
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
141
+ <path d="M8 5v14l11-7z"/>
142
+ </svg>
143
+ <span>Click to run</span>
144
+ </div>
145
+ </body>
146
+ </html>`;
147
+ }
148
+
149
+ // Helper: Generate Iframe Content
150
+ function getIframeContent(codeContent) {
151
+ const currentTheme = document.documentElement.getAttribute('data-theme');
152
+ const themeAttr = currentTheme ? ` data-theme="${currentTheme}"` : '';
153
+ const autoResizeScript = autoResize ? `
154
+ <script>
155
+ const frameId = '${iframeId}';
156
+ function sendHeight() {
157
+ const height = Math.max(
158
+ document.body.scrollHeight,
159
+ document.body.offsetHeight,
160
+ document.documentElement.scrollHeight,
161
+ document.documentElement.offsetHeight
162
+ );
163
+ parent.postMessage({ type: 'examplify-resize', id: frameId, height: height }, '*');
164
+ }
165
+
166
+ window.addEventListener('load', () => {
167
+ sendHeight();
168
+ setTimeout(sendHeight, 300);
169
+ setTimeout(sendHeight, 1000);
170
+ });
171
+
172
+ if (typeof ResizeObserver !== 'undefined') {
173
+ const resizeObserver = new ResizeObserver(() => sendHeight());
174
+ resizeObserver.observe(document.body);
175
+ }
176
+
177
+ const mutationObserver = new MutationObserver(() => {
178
+ setTimeout(sendHeight, 50);
179
+ });
180
+ mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true });
181
+ </script>
182
+ ` : '';
183
+
184
+ return `<!DOCTYPE html>
185
+ <html${themeAttr}>
186
+ <head>
187
+ <style>
188
+ /* Hide body until stylesheets are loaded to prevent FOUC */
189
+ body {
190
+ font-family: system-ui, -apple-system, sans-serif;
191
+ padding: 1rem;
192
+ margin: 0;
193
+ opacity: 0;
194
+ transition: opacity 0.15s ease-in;
195
+ }
196
+ body.styles-ready { opacity: 1; }
197
+ </style>
198
+ ${styles ? styles.map(href => `<link rel="stylesheet" href="${href}">`).join('\n') : ''}
199
+ <script>
200
+ // Synchronously create the stylesheet-ready promise before any modules execute
201
+ window.__stylesheetsReady = (function() {
202
+ return new Promise(resolve => {
203
+ // Use requestAnimationFrame to ensure DOM is ready for querying
204
+ requestAnimationFrame(() => {
205
+ const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
206
+ if (links.length === 0) {
207
+ resolve();
208
+ return;
209
+ }
210
+
211
+ let loaded = 0;
212
+ const checkDone = () => {
213
+ loaded++;
214
+ if (loaded >= links.length) resolve();
215
+ };
216
+
217
+ links.forEach(link => {
218
+ if (link.sheet) {
219
+ checkDone();
220
+ } else {
221
+ link.addEventListener('load', checkDone);
222
+ link.addEventListener('error', checkDone);
223
+ }
224
+ });
225
+
226
+ // Fallback timeout
227
+ setTimeout(resolve, 2000);
228
+ });
229
+ });
230
+ })();
231
+ <\/script>
232
+ ${modules ? modules.map(src => `<script type="module" src="${src}"></script>`).join('\n') : ''}
233
+ ${scripts ? scripts.map(src => `<script src="${src}"></script>`).join('\n') : ''}
234
+ <script type="module">
235
+ // Wait for stylesheets before initializing Lightview components
236
+ await window.__stylesheetsReady;
237
+ if (window.LightviewX) {
238
+ await window.LightviewX.initComponents({ shadowDefault: true });
239
+ }
240
+ </script>
241
+ </head>
242
+ <body>
243
+ ${language === 'html' ? codeContent : (html ? html : '<div id="example"></div>')}
244
+ ${language === 'html' ? '' : `<script ${type ? `type="${type}"` : ''}>
245
+ const render = (content) => {
246
+ const target = document.querySelector('#example');
247
+ target.innerHTML = '';
248
+ if (typeof content === 'string') {
249
+ target.innerHTML = content;
250
+ } else if (content && content.domEl) {
251
+ target.insertAdjacentElement('afterbegin', content.domEl);
252
+ } else if (content instanceof Node) {
253
+ target.insertAdjacentElement('afterbegin', content);
254
+ }
255
+ };
256
+ ${type === 'module' ? codeContent : `
257
+ // Wait for stylesheets before running example code
258
+ window.__stylesheetsReady.then(async () => {
259
+ ${codeContent}
260
+ });
261
+ `}
262
+ </script>`}
263
+ ${autoResizeScript}
264
+ <script>
265
+ // Reveal body and signal ready only after stylesheets are loaded
266
+ window.__stylesheetsReady.then(() => {
267
+ document.body.classList.add('styles-ready');
268
+ setTimeout(() => {
269
+ parent.postMessage({ type: 'examplify-ready', id: '${iframeId}' }, '*');
270
+ }, 50);
271
+ });
272
+
273
+ // Listen for theme changes from parent
274
+ window.addEventListener('message', (event) => {
275
+ if (event.data && event.data.type === 'theme-change' && event.data.theme) {
276
+ document.documentElement.setAttribute('data-theme', event.data.theme);
277
+ if (window.LightviewX && typeof window.LightviewX.setTheme === 'function') {
278
+ window.LightviewX.setTheme(event.data.theme);
279
+ }
280
+ }
281
+ });
282
+ </script>
283
+ </body>
284
+ </html>`;
285
+ }
286
+
287
+ // Helper: Run
288
+ function run() {
289
+ const content = getIframeContent(target.textContent);
290
+ iframe.style.background = '#fff';
291
+ iframe.srcdoc = content;
292
+ isRunning = true;
293
+ }
294
+
295
+ // Initialize: auto-run or show placeholder
296
+ if (autoRun) {
297
+ run();
298
+ } else {
299
+ iframe.srcdoc = getPlaceholderContent();
300
+ }
301
+
302
+ // Event Listeners
303
+ const runBtn = controls.querySelector('.examplify-run');
304
+ runBtn.addEventListener('click', run);
305
+
306
+ const copyBtn = controls.querySelector('.examplify-copy');
307
+ copyBtn.addEventListener('click', () => {
308
+ navigator.clipboard.writeText(target.textContent);
309
+
310
+ // Feedback
311
+ const originalHtml = copyBtn.innerHTML;
312
+ copyBtn.innerHTML = `
313
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
314
+ <polyline points="20 6 9 17 4 12"></polyline>
315
+ </svg>
316
+ <span>Copied!</span>
317
+ `;
318
+ setTimeout(() => copyBtn.innerHTML = originalHtml, 2000);
319
+ });
320
+
321
+ if (editable) {
322
+ const resetBtn = controls.querySelector('.examplify-reset');
323
+
324
+ // Reset action
325
+ resetBtn.addEventListener('click', () => {
326
+ target.textContent = originalContent;
327
+ resetBtn.style.display = 'none';
328
+ // Force reset even if running to reflect text change if any,
329
+ // but actually we want to reset to original content AND re-run
330
+ // OR go back to placeholder?
331
+ // Usually reset means "restore code to initial state".
332
+ // If the user modified code, we reset text.
333
+ // If they want to run the original code, they should click run?
334
+ // Or should we auto-run? Use Case: "Reset" usually implies "Fix my broken code".
335
+ // So re-running the original code makes sense.
336
+ if (isRunning) run();
337
+ });
338
+
339
+ // Watch for changes
340
+ target.addEventListener('input', () => {
341
+ if (target.textContent !== originalContent) {
342
+ resetBtn.style.display = 'inline-flex';
343
+ } else {
344
+ resetBtn.style.display = 'none';
345
+ }
346
+ });
347
+ }
348
+
349
+ // Global Message Listener (for resizing and run click)
350
+ window.addEventListener('message', (event) => {
351
+ if (!event.data || event.data.id !== iframeId) return;
352
+
353
+ if (event.data.type === 'examplify-resize' && autoResize) {
354
+ const h = event.data.height + 2; // buffer
355
+ iframe.style.height = Math.max(minHeight, Math.min(h, maxHeight)) + 'px';
356
+ }
357
+
358
+ if (event.data.type === 'examplify-run-click') {
359
+ run();
360
+ }
361
+ });
362
+
363
+ return { controls, iframe, target, run };
364
+ }
365
+
366
+ // Global Theme Observer (Host side)
367
+ if (typeof document !== 'undefined') {
368
+ let themeObserver = null;
369
+ const initThemeObserver = () => {
370
+ if (themeObserver) return;
371
+
372
+ themeObserver = new MutationObserver((mutations) => {
373
+ mutations.forEach((mutation) => {
374
+ if (mutation.attributeName === 'data-theme') {
375
+ const newTheme = document.documentElement.getAttribute('data-theme');
376
+ // Broadcast to all examplify iframes
377
+ document.querySelectorAll('.examplify-iframe').forEach(iframe => {
378
+ if (iframe.contentWindow) {
379
+ iframe.contentWindow.postMessage({ type: 'theme-change', theme: newTheme }, '*');
380
+ }
381
+ });
382
+ }
383
+ });
384
+ });
385
+
386
+ themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
387
+ };
388
+
389
+ // Initialize when DOM is ready
390
+ if (document.readyState === 'loading') {
391
+ document.addEventListener('DOMContentLoaded', initThemeObserver);
392
+ } else {
393
+ initThemeObserver();
394
+ }
395
+ }