juxscript 1.1.404 → 1.1.408

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 (154) hide show
  1. package/dist/components/button.d.ts +1 -0
  2. package/dist/components/button.d.ts.map +1 -1
  3. package/dist/components/button.js +37 -0
  4. package/dist/components/button.js.map +1 -1
  5. package/dist/components/c.d.ts +53 -0
  6. package/dist/components/c.d.ts.map +1 -0
  7. package/dist/components/c.js +127 -0
  8. package/dist/components/c.js.map +1 -0
  9. package/dist/components/charts/barChart.d.ts +119 -0
  10. package/dist/components/charts/barChart.d.ts.map +1 -0
  11. package/dist/components/charts/barChart.js +644 -0
  12. package/dist/components/charts/barChart.js.map +1 -0
  13. package/dist/components/charts/lineChart.d.ts +104 -0
  14. package/dist/components/charts/lineChart.d.ts.map +1 -0
  15. package/dist/components/charts/lineChart.js +466 -0
  16. package/dist/components/charts/lineChart.js.map +1 -0
  17. package/dist/components/charts/pieChart.d.ts +93 -0
  18. package/dist/components/charts/pieChart.d.ts.map +1 -0
  19. package/dist/components/charts/pieChart.js +397 -0
  20. package/dist/components/charts/pieChart.js.map +1 -0
  21. package/dist/components/checkbox.d.ts +2 -0
  22. package/dist/components/checkbox.d.ts.map +1 -1
  23. package/dist/components/checkbox.js +47 -0
  24. package/dist/components/checkbox.js.map +1 -1
  25. package/dist/components/flex.d.ts +91 -0
  26. package/dist/components/flex.d.ts.map +1 -0
  27. package/dist/components/flex.js +166 -0
  28. package/dist/components/flex.js.map +1 -0
  29. package/dist/components/g.d.ts +21 -0
  30. package/dist/components/g.d.ts.map +1 -0
  31. package/dist/components/g.js +52 -0
  32. package/dist/components/g.js.map +1 -0
  33. package/dist/components/input.d.ts +2 -0
  34. package/dist/components/input.d.ts.map +1 -1
  35. package/dist/components/input.js +21 -2
  36. package/dist/components/input.js.map +1 -1
  37. package/dist/components/jtable.d.ts +47 -0
  38. package/dist/components/jtable.d.ts.map +1 -0
  39. package/dist/components/jtable.js +307 -0
  40. package/dist/components/jtable.js.map +1 -0
  41. package/dist/components/link.d.ts +1 -0
  42. package/dist/components/link.d.ts.map +1 -1
  43. package/dist/components/link.js +17 -0
  44. package/dist/components/link.js.map +1 -1
  45. package/dist/components/list.d.ts +1 -0
  46. package/dist/components/list.d.ts.map +1 -1
  47. package/dist/components/list.js +18 -0
  48. package/dist/components/list.js.map +1 -1
  49. package/dist/components/menu.d.ts +108 -0
  50. package/dist/components/menu.d.ts.map +1 -0
  51. package/dist/components/menu.js +665 -0
  52. package/dist/components/menu.js.map +1 -0
  53. package/dist/components/nav.d.ts +1 -0
  54. package/dist/components/nav.d.ts.map +1 -1
  55. package/dist/components/nav.js +19 -0
  56. package/dist/components/nav.js.map +1 -1
  57. package/dist/components/radio.d.ts +1 -0
  58. package/dist/components/radio.d.ts.map +1 -1
  59. package/dist/components/radio.js +23 -0
  60. package/dist/components/radio.js.map +1 -1
  61. package/dist/components/routes.d.ts +17 -0
  62. package/dist/components/routes.d.ts.map +1 -1
  63. package/dist/components/routes.js +86 -0
  64. package/dist/components/routes.js.map +1 -1
  65. package/dist/components/select.d.ts +1 -0
  66. package/dist/components/select.d.ts.map +1 -1
  67. package/dist/components/select.js +17 -0
  68. package/dist/components/select.js.map +1 -1
  69. package/dist/components/table.d.ts +1 -0
  70. package/dist/components/table.d.ts.map +1 -1
  71. package/dist/components/table.js +20 -0
  72. package/dist/components/table.js.map +1 -1
  73. package/dist/components/tabs.d.ts +17 -1
  74. package/dist/components/tabs.d.ts.map +1 -1
  75. package/dist/components/tabs.js +50 -8
  76. package/dist/components/tabs.js.map +1 -1
  77. package/dist/components/tag.d.ts +1 -0
  78. package/dist/components/tag.d.ts.map +1 -1
  79. package/dist/components/tag.js +16 -0
  80. package/dist/components/tag.js.map +1 -1
  81. package/dist/components/widgets/calendar.d.ts +74 -0
  82. package/dist/components/widgets/calendar.d.ts.map +1 -0
  83. package/dist/components/widgets/calendar.js +308 -0
  84. package/dist/components/widgets/calendar.js.map +1 -0
  85. package/dist/components/widgets/canvas-ai.d.ts +12 -0
  86. package/dist/components/widgets/canvas-ai.d.ts.map +1 -0
  87. package/dist/components/widgets/canvas-ai.js +97 -0
  88. package/dist/components/widgets/canvas-ai.js.map +1 -0
  89. package/dist/components/widgets/canvas-compile.d.ts +36 -0
  90. package/dist/components/widgets/canvas-compile.d.ts.map +1 -0
  91. package/dist/components/widgets/canvas-compile.js +379 -0
  92. package/dist/components/widgets/canvas-compile.js.map +1 -0
  93. package/dist/components/widgets/canvas-persist.d.ts +11 -0
  94. package/dist/components/widgets/canvas-persist.d.ts.map +1 -0
  95. package/dist/components/widgets/canvas-persist.js +60 -0
  96. package/dist/components/widgets/canvas-persist.js.map +1 -0
  97. package/dist/components/widgets/canvas-registry.d.ts +42 -0
  98. package/dist/components/widgets/canvas-registry.d.ts.map +1 -0
  99. package/dist/components/widgets/canvas-registry.js +338 -0
  100. package/dist/components/widgets/canvas-registry.js.map +1 -0
  101. package/dist/components/widgets/canvas-styles.d.ts +2 -0
  102. package/dist/components/widgets/canvas-styles.d.ts.map +1 -0
  103. package/dist/components/widgets/canvas-styles.js +215 -0
  104. package/dist/components/widgets/canvas-styles.js.map +1 -0
  105. package/dist/components/widgets/canvas.d.ts +125 -0
  106. package/dist/components/widgets/canvas.d.ts.map +1 -0
  107. package/dist/components/widgets/canvas.js +1359 -0
  108. package/dist/components/widgets/canvas.js.map +1 -0
  109. package/dist/components/widgets/sidebar.d.ts +100 -0
  110. package/dist/components/widgets/sidebar.d.ts.map +1 -0
  111. package/dist/components/widgets/sidebar.js +434 -0
  112. package/dist/components/widgets/sidebar.js.map +1 -0
  113. package/dist/components/widgets/stepper.d.ts +87 -0
  114. package/dist/components/widgets/stepper.d.ts.map +1 -0
  115. package/dist/components/widgets/stepper.js +388 -0
  116. package/dist/components/widgets/stepper.js.map +1 -0
  117. package/dist/generated/jux-registry.d.ts +24 -0
  118. package/dist/generated/jux-registry.d.ts.map +1 -0
  119. package/dist/generated/jux-registry.js +90 -0
  120. package/dist/generated/jux-registry.js.map +1 -0
  121. package/dist/index.d.ts +39 -23
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +38 -24
  124. package/dist/index.js.map +1 -1
  125. package/dist/state/pageState.d.ts +6 -0
  126. package/dist/state/pageState.d.ts.map +1 -1
  127. package/dist/state/pageState.js +24 -16
  128. package/dist/state/pageState.js.map +1 -1
  129. package/dist/styles/layout-regions-observer.d.ts +7 -0
  130. package/dist/styles/layout-regions-observer.d.ts.map +1 -0
  131. package/dist/styles/layout-regions-observer.js +52 -0
  132. package/dist/styles/layout-regions-observer.js.map +1 -0
  133. package/dist/utils/colors.d.ts +0 -3
  134. package/dist/utils/colors.d.ts.map +1 -1
  135. package/dist/utils/colors.js +20 -6
  136. package/dist/utils/colors.js.map +1 -1
  137. package/dist/utils/resolveContent.d.ts +11 -0
  138. package/dist/utils/resolveContent.d.ts.map +1 -0
  139. package/dist/utils/resolveContent.js +37 -0
  140. package/dist/utils/resolveContent.js.map +1 -0
  141. package/dist/utils/theme.d.ts +58 -0
  142. package/dist/utils/theme.d.ts.map +1 -0
  143. package/dist/utils/theme.js +172 -0
  144. package/dist/utils/theme.js.map +1 -0
  145. package/dist/widgets/canvas.d.ts +3 -69
  146. package/dist/widgets/canvas.d.ts.map +1 -1
  147. package/dist/widgets/canvas.js +3 -791
  148. package/dist/widgets/canvas.js.map +1 -1
  149. package/juxconfig.example.js +19 -0
  150. package/machinery/compiler4.js +103 -9
  151. package/machinery/errors-client.js +171 -67
  152. package/machinery/jux-errors.js +218 -0
  153. package/machinery/serve.js +67 -0
  154. package/package.json +1 -1
@@ -1,792 +1,4 @@
1
- import { pageState } from '../state/pageState.js';
2
- import generateId from '../utils/idgen.js';
3
- import { btn, button } from '../components/button.js';
4
- // Dynamic imports for all component factories — add as you have them
5
- // We build a registry so the evaluated code can reference them
6
- let componentRegistry = {};
7
- function getComponentRegistry() {
8
- // Lazy-build once
9
- if (Object.keys(componentRegistry).length > 0)
10
- return componentRegistry;
11
- componentRegistry = {
12
- btn, button,
13
- // Add other component factories here as they exist:
14
- // input, select, checkbox, radio, link, h1, p, span, div,
15
- // list, table, nav, sidebar, calendar, stepper, tabs,
16
- };
17
- return componentRegistry;
18
- }
19
- const JUX_COMPLETIONS = [
20
- { label: 'btn', detail: '(id, opts?) → Button', insertText: "btn('my-btn')", kind: 'function' },
21
- { label: 'input', detail: '(id, opts?) → Input', insertText: "input('my-input', { label: 'Name' })", kind: 'function' },
22
- { label: 'select', detail: '(id, opts?) → Select', insertText: "select('my-select', { label: 'Choose' })", kind: 'function' },
23
- { label: 'checkbox', detail: '(id, opts?) → Checkbox', insertText: "checkbox('my-check', { label: 'Agree' })", kind: 'function' },
24
- { label: 'radio', detail: '(id, opts?) → Radio', insertText: "radio('my-radio', { label: 'Pick one' })", kind: 'function' },
25
- { label: 'link', detail: '(id, opts?) → Link', insertText: "link('my-link', { href: '#' })", kind: 'function' },
26
- { label: 'h1', detail: '(id, opts?) → Heading', insertText: "h1('my-heading')", kind: 'function' },
27
- { label: 'p', detail: '(id, opts?) → Paragraph', insertText: "p('my-paragraph')", kind: 'function' },
28
- { label: 'span', detail: '(id, opts?) → Span', insertText: "span('my-span')", kind: 'function' },
29
- { label: 'div', detail: '(id, opts?) → Div', insertText: "div('my-div')", kind: 'function' },
30
- { label: 'list', detail: '(id, opts?) → List', insertText: "list('my-list')", kind: 'function' },
31
- { label: 'table', detail: '(id, opts?) → Table', insertText: "table('my-table')", kind: 'function' },
32
- { label: 'nav', detail: '(id, opts?) → Nav', insertText: "nav('my-nav')", kind: 'function' },
33
- { label: 'sidebar', detail: '(id, opts?) → Sidebar', insertText: "sidebar('my-sidebar', { title: 'Nav' })", kind: 'function' },
34
- { label: 'calendar', detail: '(id, opts?) → Calendar', insertText: "calendar('my-calendar')", kind: 'function' },
35
- { label: 'stepper', detail: '(id, opts?) → Stepper', insertText: "stepper('my-stepper')", kind: 'function' },
36
- { label: 'tabs', detail: '(id, opts?) → Tabs', insertText: "tabs('my-tabs')", kind: 'function' },
37
- ];
38
- const LAYOUT_COMPLETIONS = [
39
- { label: 'c', detail: "(w, h, padding) → Container", insertText: "c('100%', 'auto', '16px')", kind: 'function' },
40
- { label: 'g', detail: "(children[]) → Group", insertText: "g([\n \n])", kind: 'function' },
41
- { label: 'flex', detail: "(dir, gap) → Flex", insertText: "flex('row', '8px')", kind: 'function' },
42
- { label: 'flexRow', detail: "(gap) → Flex Row", insertText: "flexRow('8px')", kind: 'function' },
43
- { label: 'flexCol', detail: "(gap) → Flex Column", insertText: "flexCol('8px')", kind: 'function' },
44
- ];
45
- const STATE_PROPS = [
46
- { label: 'value', detail: 'Current value', insertText: 'value', kind: 'property' },
47
- { label: 'content', detail: 'Text content', insertText: 'content', kind: 'property' },
48
- { label: 'visible', detail: 'Show/hide (bool)', insertText: 'visible', kind: 'property' },
49
- { label: 'disabled', detail: 'Disabled (bool)', insertText: 'disabled', kind: 'property' },
50
- { label: 'click', detail: 'Click event flag', insertText: 'click', kind: 'property' },
51
- { label: 'change', detail: 'Change event flag', insertText: 'change', kind: 'property' },
52
- { label: 'focus', detail: 'Focus event flag', insertText: 'focus', kind: 'property' },
53
- { label: 'hover', detail: 'Hover state flag', insertText: 'hover', kind: 'property' },
54
- { label: 'class', detail: 'CSS classes', insertText: 'class', kind: 'property' },
55
- { label: 'style', detail: 'Inline styles', insertText: 'style', kind: 'property' },
56
- ];
57
- const KEYWORD_COMPLETIONS = [
58
- { label: 'const', detail: 'Declare constant', insertText: 'const ', kind: 'keyword' },
59
- { label: 'let', detail: 'Declare variable', insertText: 'let ', kind: 'keyword' },
60
- { label: 'if', detail: 'Conditional', insertText: 'if () {\n \n}', kind: 'keyword' },
61
- { label: 'pageState', detail: 'Reactive state', insertText: "pageState['']", kind: 'variable' },
62
- { label: 'jux', detail: 'JUX component API', insertText: 'jux.', kind: 'variable' },
63
- ];
64
- const DEFAULT_CODE = `// Build your page here — real components, real behavior
65
- const heading = jux.h1('hello-world');
66
- const description = jux.p('welcome-message');
67
-
68
- const nameInput = jux.input('user-name', {
69
- label: 'Your Name',
70
- placeholder: 'Enter your name...'
71
- });
72
-
73
- const submitBtn = jux.btn('click-me-you-will-like-it');
74
-
75
- // Reactivity
76
- if (pageState['click-me-you-will-like-it'].click) {
77
- pageState['welcome-message'].content = 'Hello, ' + pageState['user-name'].value + '!';
78
- }
79
- `;
80
- function compileAndRender(code, previewEl, registry) {
81
- // Strip import lines — we inject the API directly
82
- const cleanedLines = code.split('\n').map(line => {
83
- const trimmed = line.trim();
84
- if (trimmed.startsWith('import '))
85
- return '// ' + trimmed;
86
- return line;
87
- });
88
- const cleanedCode = cleanedLines.join('\n');
89
- // Build a jux proxy that wraps each factory to override target → previewEl
90
- const juxProxy = {};
91
- const knownFactories = [
92
- 'btn', 'button', 'input', 'select', 'checkbox', 'radio',
93
- 'link', 'h1', 'h2', 'h3', 'p', 'span', 'div', 'tag',
94
- 'list', 'table', 'nav', 'sidebar', 'calendar', 'stepper', 'tabs',
95
- ];
96
- let componentCount = 0;
97
- for (const name of knownFactories) {
98
- const realFactory = registry[name];
99
- if (realFactory) {
100
- juxProxy[name] = (id, opts = {}) => {
101
- componentCount++;
102
- opts.target = previewEl.id;
103
- return realFactory(id, opts);
104
- };
105
- }
106
- else {
107
- juxProxy[name] = (id, opts = {}) => {
108
- componentCount++;
109
- const el = document.createElement('div');
110
- el.className = 'jcv-stub';
111
- el.setAttribute('data-id', id);
112
- el.setAttribute('data-type', name);
113
- const niceName = id.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
114
- el.innerHTML = `<span class="jcv-stub-type">${name}</span><span class="jcv-stub-id">${id}</span><span class="jcv-stub-content">${opts.content || niceName}</span>`;
115
- previewEl.appendChild(el);
116
- const fluent = {
117
- id, opts, _element: el,
118
- getElement: () => el,
119
- getValue: () => opts.content || niceName,
120
- setValue: (v) => { const c = el.querySelector('.jcv-stub-content'); if (c)
121
- c.textContent = v; return fluent; },
122
- render: () => fluent, into: () => fluent,
123
- content: (v) => { const c = el.querySelector('.jcv-stub-content'); if (c)
124
- c.textContent = v; return fluent; },
125
- get state() { return pageState[id]; },
126
- };
127
- if (pageState.__register)
128
- pageState.__register(fluent);
129
- return new Proxy(fluent, {
130
- get(target, prop) {
131
- if (prop in target)
132
- return target[prop];
133
- if (typeof prop === 'string' && prop !== 'then' && prop !== 'toJSON' && prop !== Symbol.toPrimitive) {
134
- console.warn(`[JUX IDE] Unknown method .${prop}() on ${name}('${id}')`);
135
- const warn = document.createElement('div');
136
- warn.className = 'jcv-live-warn';
137
- warn.textContent = `⚠ .${prop}() is not a valid method on ${name}`;
138
- previewEl.appendChild(warn);
139
- return () => target;
140
- }
141
- return undefined;
142
- }
143
- });
144
- };
145
- }
146
- }
147
- const layoutStubs = {
148
- c: () => ({ type: 'container' }),
149
- g: () => ({ type: 'group' }),
150
- flex: () => ({ type: 'flex' }),
151
- flexRow: () => ({ type: 'flexRow' }),
152
- flexCol: () => ({ type: 'flexCol' }),
153
- };
154
- const fnArgs = ['jux', 'pageState', 'c', 'g', 'flex', 'flexRow', 'flexCol', 'console'];
155
- const fnVals = [juxProxy, pageState, layoutStubs.c, layoutStubs.g, layoutStubs.flex, layoutStubs.flexRow, layoutStubs.flexCol, console];
156
- // ── Step 1: Try full parse first (fast path) ──
157
- try {
158
- const fn = new Function(...fnArgs, cleanedCode);
159
- fn(...fnVals);
160
- return { success: true, componentCount };
161
- }
162
- catch (fullErr) {
163
- // Fall through to incremental execution
164
- }
165
- // ── Step 2: Incremental statement execution ──
166
- // Split into logical statements and execute one-by-one
167
- const statements = splitIntoStatements(cleanedLines);
168
- let lastErrorLine;
169
- let lastError;
170
- let lastErrorContext;
171
- for (const stmt of statements) {
172
- if (!stmt.code.trim() || stmt.code.trim().startsWith('//'))
173
- continue;
174
- try {
175
- const fn = new Function(...fnArgs, stmt.code);
176
- fn(...fnVals);
177
- }
178
- catch (err) {
179
- // Determine exact line within the original source
180
- lastErrorLine = stmt.startLine;
181
- // Try to narrow it down for multi-line statements
182
- const stackMatch = err.stack?.match(/<anonymous>:(\d+)/);
183
- if (stackMatch) {
184
- const offsetInStmt = parseInt(stackMatch[1], 10) - 2; // Function wrapper adds ~2 lines
185
- if (offsetInStmt >= 0) {
186
- lastErrorLine = stmt.startLine + offsetInStmt;
187
- }
188
- }
189
- lastError = err.message || String(err);
190
- // Build context: 2 lines before, error line, 2 lines after
191
- const originalLines = code.split('\n');
192
- const errIdx = lastErrorLine - 1; // 0-based
193
- const contextStart = Math.max(0, errIdx - 2);
194
- const contextEnd = Math.min(originalLines.length - 1, errIdx + 2);
195
- lastErrorContext = [];
196
- for (let i = contextStart; i <= contextEnd; i++) {
197
- const marker = i === errIdx ? '→' : ' ';
198
- lastErrorContext.push(`${marker} ${i + 1} │ ${originalLines[i]}`);
199
- }
200
- // Stop executing further statements but keep what already rendered
201
- break;
202
- }
203
- }
204
- if (lastError) {
205
- return {
206
- success: false,
207
- componentCount,
208
- error: lastError,
209
- errorLine: lastErrorLine,
210
- errorContext: lastErrorContext,
211
- };
212
- }
213
- return { success: true, componentCount };
214
- }
215
- /**
216
- * Split lines into executable statement chunks.
217
- * Tracks brace/bracket/paren depth to group multi-line statements.
218
- */
219
- function splitIntoStatements(lines) {
220
- const statements = [];
221
- let current = '';
222
- let startLine = 1;
223
- let depth = 0;
224
- for (let i = 0; i < lines.length; i++) {
225
- const line = lines[i];
226
- const trimmed = line.trim();
227
- if (!current && (!trimmed || trimmed.startsWith('//'))) {
228
- // Standalone empty/comment line — skip
229
- continue;
230
- }
231
- if (!current) {
232
- startLine = i + 1; // 1-based
233
- }
234
- current += (current ? '\n' : '') + line;
235
- // Track depth
236
- for (const ch of trimmed) {
237
- if (ch === '(' || ch === '{' || ch === '[')
238
- depth++;
239
- else if (ch === ')' || ch === '}' || ch === ']')
240
- depth--;
241
- }
242
- // Statement is complete when depth returns to 0 and line ends with ; or } or is a standalone expression
243
- if (depth <= 0) {
244
- if (trimmed.endsWith(';') || trimmed.endsWith('}') || depth === 0) {
245
- statements.push({ code: current, startLine });
246
- current = '';
247
- depth = 0;
248
- }
249
- }
250
- }
251
- // Flush remaining (possibly incomplete statement — this will error)
252
- if (current.trim()) {
253
- statements.push({ code: current, startLine });
254
- }
255
- return statements;
256
- }
257
- let canvasStylesInjected = false;
258
- // ═══════════════════════════════════════════════════════════
259
- // CANVAS IDE CLASS
260
- // ═══════════════════════════════════════════════════════════
261
- class Canvas {
262
- constructor(id, options = {}) {
263
- this._el = null;
264
- this._editorEl = null;
265
- this._lineNumEl = null;
266
- this._previewEl = null;
267
- this._statusEl = null;
268
- this._errorEl = null;
269
- this._autocompleteEl = null;
270
- this._onChange = null;
271
- this._compileTimer = null;
272
- this._knownIds = [];
273
- this.id = id || generateId('canvas');
274
- this._previewId = this.id + '-preview';
275
- this._opts = {
276
- title: options.title || 'JUX IDE',
277
- width: options.width || '100%',
278
- height: options.height || '100vh',
279
- codePanelWidth: options.codePanelWidth || '50%',
280
- ...options,
281
- };
282
- }
283
- _rerenderCanvas() { this._compile(); }
284
- // ═══════════════════════════════════════════════════════
285
- // FLUENT API
286
- // ═══════════════════════════════════════════════════════
287
- title(val) { this._opts.title = val; return this; }
288
- width(val) { this._opts.width = val; return this; }
289
- height(val) { this._opts.height = val; return this; }
290
- codePanelWidth(val) { this._opts.codePanelWidth = val; return this; }
291
- style(val) { this._opts.style = val; return this; }
292
- class(val) { this._opts.class = val; return this; }
293
- onChange(fn) { this._onChange = fn; return this; }
294
- // ═══════════════════════════════════════════════════════
295
- // PAGESTATE COMPAT
296
- // ═══════════════════════════════════════════════════════
297
- getValue() { return this._editorEl?.value || ''; }
298
- setValue(val) { if (this._editorEl) {
299
- this._editorEl.value = val;
300
- this._onInput();
301
- } return this; }
302
- getElement() { return this._el; }
303
- getTitle() { return this._opts.title; }
304
- setTitle(val) { return this.title(val); }
305
- setWidth(val) { return this.width(val); }
306
- setHeight(val) { return this.height(val); }
307
- get state() { return pageState[this.id]; }
308
- // ═══════════════════════════════════════════════════════
309
- // RENDER
310
- // ═══════════════════════════════════════════════════════
311
- render(target) {
312
- this._injectStyles();
313
- this._el = this._buildDOM();
314
- let container = null;
315
- if (target && typeof target === 'object' && 'element' in target)
316
- container = target.element;
317
- else if (target instanceof HTMLElement)
318
- container = target;
319
- else if (typeof target === 'string')
320
- container = document.getElementById(target) || document.querySelector(target);
321
- else
322
- container = document.getElementById('app');
323
- if (container && this._el)
324
- container.appendChild(this._el);
325
- requestAnimationFrame(() => this._compile());
326
- return this;
327
- }
328
- into(target) { return this.render(target); }
329
- destroy() { pageState.__unregister(this.id); this._el?.remove(); }
330
- generateFullCode() { return this._editorEl?.value || ''; }
331
- // ═══════════════════════════════════════════════════════
332
- // INTERNAL — DOM
333
- // ═══════════════════════════════════════════════════════
334
- _buildDOM() {
335
- const root = document.createElement('div');
336
- root.id = this.id;
337
- root.className = 'jcv-ide' + (this._opts.class ? ' ' + this._opts.class : '');
338
- root.style.cssText = `width:${this._opts.width};height:${this._opts.height};${this._opts.style || ''}`;
339
- // ── Toolbar ──
340
- const toolbar = document.createElement('div');
341
- toolbar.className = 'jcv-toolbar';
342
- const titleEl = document.createElement('span');
343
- titleEl.className = 'jcv-toolbar-title';
344
- titleEl.textContent = this._opts.title;
345
- toolbar.appendChild(titleEl);
346
- const spacer = document.createElement('span');
347
- spacer.style.flex = '1';
348
- toolbar.appendChild(spacer);
349
- const formatBtn = this._toolbarBtn('Format', () => this._format());
350
- toolbar.appendChild(formatBtn);
351
- const runBtn = this._toolbarBtn('▶ Run', () => this._compile());
352
- runBtn.style.background = 'rgba(166,227,161,.15)';
353
- toolbar.appendChild(runBtn);
354
- const copyBtn = this._toolbarBtn('Copy', () => {
355
- navigator.clipboard?.writeText(this._editorEl?.value || '');
356
- copyBtn.textContent = '✓ Copied';
357
- setTimeout(() => copyBtn.textContent = 'Copy', 1500);
358
- });
359
- toolbar.appendChild(copyBtn);
360
- this._statusEl = document.createElement('span');
361
- this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--ok';
362
- this._statusEl.textContent = '● Ready';
363
- toolbar.appendChild(this._statusEl);
364
- root.appendChild(toolbar);
365
- // ── Split Pane ──
366
- const splitPane = document.createElement('div');
367
- splitPane.className = 'jcv-split';
368
- // Left: Code editor
369
- const codePanel = document.createElement('div');
370
- codePanel.className = 'jcv-code-panel';
371
- codePanel.style.width = this._opts.codePanelWidth;
372
- const editorWrap = document.createElement('div');
373
- editorWrap.className = 'jcv-editor-wrap';
374
- this._lineNumEl = document.createElement('div');
375
- this._lineNumEl.className = 'jcv-line-nums';
376
- editorWrap.appendChild(this._lineNumEl);
377
- const editorContainer = document.createElement('div');
378
- editorContainer.className = 'jcv-editor-container';
379
- this._editorEl = document.createElement('textarea');
380
- this._editorEl.className = 'jcv-editor';
381
- this._editorEl.value = this._opts.initialCode || DEFAULT_CODE;
382
- this._editorEl.spellcheck = false;
383
- this._editorEl.setAttribute('autocomplete', 'off');
384
- this._editorEl.setAttribute('autocorrect', 'off');
385
- this._editorEl.setAttribute('autocapitalize', 'off');
386
- this._editorEl.addEventListener('input', () => this._onInput());
387
- this._editorEl.addEventListener('scroll', () => this._syncScroll());
388
- this._editorEl.addEventListener('keydown', (e) => this._onKeyDown(e));
389
- this._editorEl.addEventListener('click', () => this._hideAutocomplete());
390
- editorContainer.appendChild(this._editorEl);
391
- // Error gutter overlay
392
- this._errorEl = document.createElement('div');
393
- this._errorEl.className = 'jcv-error-bar';
394
- this._errorEl.style.display = 'none';
395
- editorContainer.appendChild(this._errorEl);
396
- // Autocomplete popup
397
- this._autocompleteEl = document.createElement('div');
398
- this._autocompleteEl.className = 'jcv-autocomplete';
399
- this._autocompleteEl.style.display = 'none';
400
- editorContainer.appendChild(this._autocompleteEl);
401
- editorWrap.appendChild(editorContainer);
402
- codePanel.appendChild(editorWrap);
403
- splitPane.appendChild(codePanel);
404
- // Splitter
405
- const handle = document.createElement('div');
406
- handle.className = 'jcv-splitter';
407
- handle.addEventListener('mousedown', (e) => this._startResize(e, codePanel, splitPane));
408
- splitPane.appendChild(handle);
409
- // Right: Preview — this is a REAL render target
410
- const previewPanel = document.createElement('div');
411
- previewPanel.className = 'jcv-preview-panel';
412
- const previewHeader = document.createElement('div');
413
- previewHeader.className = 'jcv-preview-header';
414
- previewHeader.textContent = 'Live Preview';
415
- previewPanel.appendChild(previewHeader);
416
- this._previewEl = document.createElement('div');
417
- this._previewEl.id = this._previewId;
418
- this._previewEl.className = 'jcv-preview-area';
419
- previewPanel.appendChild(this._previewEl);
420
- splitPane.appendChild(previewPanel);
421
- root.appendChild(splitPane);
422
- return root;
423
- }
424
- _toolbarBtn(label, onClick) {
425
- const btn = document.createElement('button');
426
- btn.className = 'jcv-toolbar-btn';
427
- btn.textContent = label;
428
- btn.addEventListener('click', onClick);
429
- return btn;
430
- }
431
- // ═══════════════════════════════════════════════════════
432
- // EDITOR LOGIC
433
- // ═══════════════════════════════════════════════════════
434
- _onInput() {
435
- this._updateLineNumbers();
436
- this._scheduleCompile();
437
- this._checkAutocomplete();
438
- }
439
- _updateLineNumbers() {
440
- if (!this._lineNumEl || !this._editorEl)
441
- return;
442
- const count = this._editorEl.value.split('\n').length;
443
- let html = '';
444
- for (let i = 1; i <= count; i++)
445
- html += `<div>${i}</div>`;
446
- this._lineNumEl.innerHTML = html;
447
- }
448
- _syncScroll() {
449
- if (!this._editorEl || !this._lineNumEl)
450
- return;
451
- this._lineNumEl.scrollTop = this._editorEl.scrollTop;
452
- }
453
- _scheduleCompile() {
454
- if (this._compileTimer)
455
- clearTimeout(this._compileTimer);
456
- this._compileTimer = setTimeout(() => this._compile(), 400);
457
- }
458
- _compile() {
459
- if (!this._editorEl || !this._previewEl || !this._statusEl || !this._errorEl)
460
- return;
461
- // Clear preview
462
- this._previewEl.innerHTML = '';
463
- this._errorEl.style.display = 'none';
464
- const code = this._editorEl.value;
465
- const registry = getComponentRegistry();
466
- const result = compileAndRender(code, this._previewEl, registry);
467
- // Extract known IDs
468
- this._knownIds = [];
469
- this._previewEl.querySelectorAll('[data-id]').forEach(el => {
470
- const id = el.getAttribute('data-id');
471
- if (id)
472
- this._knownIds.push(id);
473
- });
474
- this._previewEl.querySelectorAll('[id]').forEach(el => {
475
- if (el.id && el.id !== this._previewId && !this._knownIds.includes(el.id)) {
476
- this._knownIds.push(el.id);
477
- }
478
- });
479
- if (result.success) {
480
- this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--ok';
481
- this._statusEl.textContent = `● ${result.componentCount} component${result.componentCount !== 1 ? 's' : ''}`;
482
- }
483
- else {
484
- this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--err';
485
- this._statusEl.textContent = result.errorLine ? `● Error:${result.errorLine}` : '● Error';
486
- // Rich error display in preview
487
- const errBlock = document.createElement('div');
488
- errBlock.className = 'jcv-live-error-block';
489
- const errTitle = document.createElement('div');
490
- errTitle.className = 'jcv-live-error-title';
491
- errTitle.textContent = result.errorLine
492
- ? `Error on line ${result.errorLine}`
493
- : 'Syntax Error';
494
- errBlock.appendChild(errTitle);
495
- const errMsg = document.createElement('div');
496
- errMsg.className = 'jcv-live-error-msg';
497
- errMsg.textContent = result.error || 'Unknown error';
498
- errBlock.appendChild(errMsg);
499
- if (result.errorContext && result.errorContext.length > 0) {
500
- const errCtx = document.createElement('pre');
501
- errCtx.className = 'jcv-live-error-ctx';
502
- errCtx.textContent = result.errorContext.join('\n');
503
- errBlock.appendChild(errCtx);
504
- }
505
- this._previewEl.appendChild(errBlock);
506
- // Error bar in editor
507
- this._errorEl.style.display = 'block';
508
- this._errorEl.textContent = result.errorLine
509
- ? `Line ${result.errorLine}: ${result.error}`
510
- : result.error || 'Error';
511
- }
512
- if (this._onChange)
513
- this._onChange({ code, result });
514
- }
515
- _format() {
516
- if (!this._editorEl)
517
- return;
518
- const lines = this._editorEl.value.split('\n');
519
- let indent = 0;
520
- const formatted = lines.map(line => {
521
- const trimmed = line.trim();
522
- if (!trimmed)
523
- return '';
524
- if (trimmed.startsWith('}') || trimmed.startsWith(']'))
525
- indent = Math.max(0, indent - 1);
526
- const result = ' '.repeat(indent) + trimmed;
527
- if (trimmed.endsWith('{') || trimmed.endsWith('['))
528
- indent++;
529
- return result;
530
- });
531
- this._editorEl.value = formatted.join('\n');
532
- this._onInput();
533
- }
534
- // ═══════════════════════════════════════════════════════
535
- // AUTOCOMPLETE / INTELLISENSE
536
- // ═══════════════════════════════════════════════════════
537
- _checkAutocomplete() {
538
- if (!this._editorEl)
539
- return;
540
- const pos = this._editorEl.selectionStart;
541
- const text = this._editorEl.value.substring(0, pos);
542
- let items = [];
543
- let replaceFrom = pos;
544
- const juxDot = text.match(/jux\.(\w*)$/);
545
- if (juxDot) {
546
- const filter = juxDot[1].toLowerCase();
547
- items = JUX_COMPLETIONS.filter(c => c.label.toLowerCase().startsWith(filter));
548
- replaceFrom = pos - juxDot[1].length;
549
- }
550
- const stateDot = text.match(/pageState\['[^']*'\]\.(\w*)$/);
551
- if (stateDot) {
552
- const filter = stateDot[1].toLowerCase();
553
- items = STATE_PROPS.filter(c => c.label.toLowerCase().startsWith(filter));
554
- replaceFrom = pos - stateDot[1].length;
555
- }
556
- const stateId = text.match(/pageState\['([^']*)$/);
557
- if (stateId && !stateDot) {
558
- const filter = stateId[1].toLowerCase();
559
- items = this._knownIds
560
- .filter(id => id.toLowerCase().startsWith(filter))
561
- .map(id => ({ label: id, detail: 'Component ID', insertText: id, kind: 'variable' }));
562
- replaceFrom = pos - stateId[1].length;
563
- }
564
- const lineStart = text.match(/(?:^|\n)\s*(\w*)$/);
565
- if (lineStart && !juxDot && !stateDot && !stateId && lineStart[1].length >= 1) {
566
- const filter = lineStart[1].toLowerCase();
567
- items = [...LAYOUT_COMPLETIONS, ...KEYWORD_COMPLETIONS].filter(c => c.label.toLowerCase().startsWith(filter));
568
- replaceFrom = pos - lineStart[1].length;
569
- }
570
- if (items.length > 0)
571
- this._showAutocomplete(items, replaceFrom);
572
- else
573
- this._hideAutocomplete();
574
- }
575
- _showAutocomplete(items, replaceFrom) {
576
- if (!this._autocompleteEl || !this._editorEl)
577
- return;
578
- this._autocompleteEl.innerHTML = '';
579
- this._autocompleteEl.style.display = 'block';
580
- const coords = this._getCaretCoords();
581
- this._autocompleteEl.style.top = (coords.top + 20) + 'px';
582
- this._autocompleteEl.style.left = coords.left + 'px';
583
- const maxShow = Math.min(items.length, 8);
584
- for (let i = 0; i < maxShow; i++) {
585
- const item = items[i];
586
- const row = document.createElement('div');
587
- row.className = 'jcv-ac-item' + (i === 0 ? ' jcv-ac-item--active' : '');
588
- const icon = document.createElement('span');
589
- icon.className = 'jcv-ac-icon';
590
- icon.textContent = item.kind === 'function' ? 'ƒ' : item.kind === 'property' ? '◆' : item.kind === 'keyword' ? '⬥' : '◇';
591
- row.appendChild(icon);
592
- const label = document.createElement('span');
593
- label.className = 'jcv-ac-label';
594
- label.textContent = item.label;
595
- row.appendChild(label);
596
- const detail = document.createElement('span');
597
- detail.className = 'jcv-ac-detail';
598
- detail.textContent = item.detail;
599
- row.appendChild(detail);
600
- row.addEventListener('mousedown', (e) => { e.preventDefault(); this._acceptCompletion(item, replaceFrom); });
601
- this._autocompleteEl.appendChild(row);
602
- }
603
- this._autocompleteEl.__items = items.slice(0, maxShow);
604
- this._autocompleteEl.__activeIndex = 0;
605
- this._autocompleteEl.__replaceFrom = replaceFrom;
606
- }
607
- _hideAutocomplete() {
608
- if (this._autocompleteEl)
609
- this._autocompleteEl.style.display = 'none';
610
- }
611
- _acceptCompletion(item, replaceFrom) {
612
- if (!this._editorEl)
613
- return;
614
- const before = this._editorEl.value.substring(0, replaceFrom);
615
- const after = this._editorEl.value.substring(this._editorEl.selectionStart);
616
- this._editorEl.value = before + item.insertText + after;
617
- const newPos = replaceFrom + item.insertText.length;
618
- this._editorEl.selectionStart = newPos;
619
- this._editorEl.selectionEnd = newPos;
620
- this._editorEl.focus();
621
- this._hideAutocomplete();
622
- this._onInput();
623
- }
624
- _onKeyDown(e) {
625
- if (!this._autocompleteEl || this._autocompleteEl.style.display === 'none') {
626
- if (e.key === 'Tab') {
627
- e.preventDefault();
628
- const start = this._editorEl.selectionStart;
629
- const end = this._editorEl.selectionEnd;
630
- this._editorEl.value = this._editorEl.value.substring(0, start) + ' ' + this._editorEl.value.substring(end);
631
- this._editorEl.selectionStart = this._editorEl.selectionEnd = start + 2;
632
- this._onInput();
633
- }
634
- return;
635
- }
636
- const acData = this._autocompleteEl;
637
- const items = acData.__items || [];
638
- let active = acData.__activeIndex || 0;
639
- const replaceFrom = acData.__replaceFrom || 0;
640
- if (e.key === 'ArrowDown') {
641
- e.preventDefault();
642
- active = Math.min(active + 1, items.length - 1);
643
- this._setActiveCompletion(active);
644
- }
645
- else if (e.key === 'ArrowUp') {
646
- e.preventDefault();
647
- active = Math.max(active - 1, 0);
648
- this._setActiveCompletion(active);
649
- }
650
- else if (e.key === 'Enter' || e.key === 'Tab') {
651
- e.preventDefault();
652
- if (items[active])
653
- this._acceptCompletion(items[active], replaceFrom);
654
- }
655
- else if (e.key === 'Escape') {
656
- e.preventDefault();
657
- this._hideAutocomplete();
658
- }
659
- }
660
- _setActiveCompletion(index) {
661
- if (!this._autocompleteEl)
662
- return;
663
- this._autocompleteEl.__activeIndex = index;
664
- this._autocompleteEl.querySelectorAll('.jcv-ac-item').forEach((el, i) => {
665
- el.classList.toggle('jcv-ac-item--active', i === index);
666
- });
667
- }
668
- _getCaretCoords() {
669
- if (!this._editorEl)
670
- return { top: 0, left: 0 };
671
- const text = this._editorEl.value.substring(0, this._editorEl.selectionStart);
672
- const lines = text.split('\n');
673
- const lineIndex = lines.length - 1;
674
- const colIndex = lines[lineIndex].length;
675
- return {
676
- top: lineIndex * 20 - this._editorEl.scrollTop,
677
- left: Math.min(colIndex * 7.8, 350) + 40,
678
- };
679
- }
680
- // ═══════════════════════════════════════════════════════
681
- // RESIZE HANDLE
682
- // ═══════════════════════════════════════════════════════
683
- _startResize(e, codePanel, splitPane) {
684
- e.preventDefault();
685
- const startX = e.clientX;
686
- const startWidth = codePanel.offsetWidth;
687
- const totalWidth = splitPane.offsetWidth;
688
- const onMove = (e) => {
689
- const delta = e.clientX - startX;
690
- const newWidth = Math.max(200, Math.min(totalWidth - 200, startWidth + delta));
691
- codePanel.style.width = newWidth + 'px';
692
- codePanel.style.flex = 'none';
693
- };
694
- const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
695
- document.addEventListener('mousemove', onMove);
696
- document.addEventListener('mouseup', onUp);
697
- }
698
- // ═══════════════════════════════════════════════════════
699
- // STYLES
700
- // ═══════════════════════════════════════════════════════
701
- _injectStyles() {
702
- if (canvasStylesInjected)
703
- return;
704
- canvasStylesInjected = true;
705
- const s = document.createElement('style');
706
- s.id = 'jux-canvas-styles';
707
- s.textContent = `
708
- .jcv-ide{
709
- --jcv-bg:#fff;--jcv-fg:#09090b;--jcv-card:#fff;
710
- --jcv-muted:#f4f4f5;--jcv-muted-fg:#71717a;--jcv-border:#e4e4e7;
711
- --jcv-primary:#18181b;--jcv-primary-fg:#fafafa;--jcv-accent:#f4f4f5;
712
- --jcv-ring:#18181b;--jcv-destructive:#ef4444;
713
- --jcv-radius:8px;--jcv-radius-sm:6px;--jcv-radius-xs:4px;
714
- --jcv-shadow-sm:0 1px 2px 0 rgba(0,0,0,.05);
715
- --jcv-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);
716
- --jcv-editor-bg:#1e1e2e;--jcv-editor-fg:#cdd6f4;--jcv-editor-line:#313244;
717
- --jcv-editor-gutter:#6c7086;--jcv-editor-cursor:#f5e0dc;
718
- font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;
719
- display:flex;flex-direction:column;background:var(--jcv-bg);color:var(--jcv-fg);
720
- border:1px solid var(--jcv-border);border-radius:var(--jcv-radius);overflow:hidden;
721
- }
722
-
723
- /* Toolbar */
724
- .jcv-toolbar{display:flex;align-items:center;gap:8px;padding:6px 12px;background:var(--jcv-primary);color:var(--jcv-primary-fg);border-bottom:1px solid rgba(255,255,255,.1);min-height:36px}
725
- .jcv-toolbar-title{font-weight:700;font-size:13px;letter-spacing:-.01em}
726
- .jcv-toolbar-btn{border:1px solid rgba(255,255,255,.2);background:transparent;color:var(--jcv-primary-fg);padding:4px 12px;font-size:11px;font-weight:500;border-radius:var(--jcv-radius-xs);cursor:pointer;font-family:inherit;transition:background .12s}
727
- .jcv-toolbar-btn:hover{background:rgba(255,255,255,.1)}
728
- .jcv-toolbar-status{font-size:11px;font-weight:600;padding:0 8px}
729
- .jcv-toolbar-status--ok{color:#a6e3a1}
730
- .jcv-toolbar-status--err{color:#f38ba8}
731
-
732
- /* Split */
733
- .jcv-split{flex:1;display:flex;overflow:hidden}
734
- .jcv-code-panel{display:flex;flex-direction:column;overflow:hidden;min-width:200px}
735
- .jcv-preview-panel{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:200px;background:var(--jcv-bg)}
736
- .jcv-splitter{width:4px;background:var(--jcv-border);cursor:col-resize;flex-shrink:0;transition:background .12s}
737
- .jcv-splitter:hover{background:var(--jcv-ring)}
738
-
739
- /* Editor — single textarea, no overlay */
740
- .jcv-editor-wrap{flex:1;display:flex;overflow:hidden;background:var(--jcv-editor-bg)}
741
- .jcv-line-nums{width:44px;padding:12px 0;text-align:right;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:13px;line-height:20px;color:var(--jcv-editor-gutter);background:var(--jcv-editor-bg);overflow:hidden;user-select:none;flex-shrink:0;border-right:1px solid var(--jcv-editor-line)}
742
- .jcv-line-nums div{padding:0 8px 0 0}
743
- .jcv-editor-container{flex:1;position:relative;overflow:hidden}
744
- .jcv-editor{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;padding:12px;margin:0;border:none;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:13px;line-height:20px;white-space:pre;word-wrap:normal;overflow:auto;box-sizing:border-box;tab-size:2;-moz-tab-size:2;color:var(--jcv-editor-fg);caret-color:var(--jcv-editor-cursor);background:var(--jcv-editor-bg);resize:none;outline:none}
745
- .jcv-editor::selection{background:rgba(137,180,250,.3)}
746
-
747
- /* Error bar */
748
- .jcv-error-bar{position:absolute;bottom:0;left:0;right:0;padding:4px 12px;font-size:11px;font-family:ui-monospace,monospace;background:#45273a;color:#f38ba8;border-top:1px solid #f38ba855;z-index:5}
749
-
750
- /* Autocomplete */
751
- .jcv-autocomplete{position:absolute;z-index:10;background:#1e1e2e;border:1px solid var(--jcv-editor-line);border-radius:var(--jcv-radius-xs);box-shadow:0 4px 12px rgba(0,0,0,.3);max-height:200px;overflow-y:auto;min-width:260px}
752
- .jcv-ac-item{display:flex;align-items:center;gap:6px;padding:4px 10px;cursor:pointer;font-size:12px;font-family:ui-monospace,monospace;color:var(--jcv-editor-fg)}
753
- .jcv-ac-item:hover,.jcv-ac-item--active{background:rgba(137,180,250,.15)}
754
- .jcv-ac-icon{width:16px;text-align:center;font-size:11px;color:#89b4fa;flex-shrink:0}
755
- .jcv-ac-label{font-weight:600;flex-shrink:0}
756
- .jcv-ac-detail{color:var(--jcv-editor-gutter);font-size:11px;margin-left:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
757
-
758
- /* Preview */
759
- .jcv-preview-header{padding:8px 14px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--jcv-muted-fg);border-bottom:1px solid var(--jcv-border);background:var(--jcv-card)}
760
- .jcv-preview-area{flex:1;overflow:auto;padding:24px;display:flex;flex-direction:column;gap:12px;align-items:flex-start}
761
-
762
- /* Errors and warnings in preview */
763
- .jcv-live-error{color:var(--jcv-destructive);font-size:12px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--jcv-radius-xs);margin-top:8px;font-family:ui-monospace,monospace;width:100%;box-sizing:border-box}
764
- .jcv-live-warn{color:#ca8a04;font-size:11px;padding:4px 8px;background:#fefce8;border:1px solid #fef08a;border-radius:var(--jcv-radius-xs);font-family:ui-monospace,monospace;width:100%;box-sizing:border-box}
765
-
766
- /* Rich error block in preview */
767
- .jcv-live-error-block{background:#fef2f2;border:1px solid #fecaca;border-radius:var(--jcv-radius-sm);overflow:hidden;width:100%;box-sizing:border-box;margin-top:4px}
768
- .jcv-live-error-title{padding:6px 12px;font-size:12px;font-weight:700;color:#dc2626;background:#fee2e2;border-bottom:1px solid #fecaca}
769
- .jcv-live-error-msg{padding:8px 12px;font-size:12px;font-family:ui-monospace,monospace;color:#991b1b}
770
- .jcv-live-error-ctx{margin:0;padding:8px 12px;font-size:11px;line-height:18px;font-family:ui-monospace,monospace;background:#fff5f5;color:#71717a;border-top:1px solid #fecaca;overflow-x:auto;white-space:pre}
771
- .jcv-live-error-ctx .jcv-err-line{color:#dc2626;font-weight:700}
772
-
773
- /* Component stubs (for components not yet imported) */
774
- .jcv-stub{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--jcv-muted);border:1px dashed var(--jcv-border);border-radius:var(--jcv-radius-sm);font-size:13px;color:var(--jcv-fg)}
775
- .jcv-stub-type{font-size:10px;font-weight:700;text-transform:uppercase;color:var(--jcv-muted-fg);background:var(--jcv-bg);padding:1px 6px;border-radius:3px;border:1px solid var(--jcv-border)}
776
- .jcv-stub-id{font-size:11px;color:var(--jcv-muted-fg);font-family:ui-monospace,monospace}
777
- .jcv-stub-content{font-weight:500}
778
- `;
779
- document.head.appendChild(s);
780
- }
781
- }
782
- // ═══════════════════════════════════════════════════════════
783
- // FACTORY
784
- // ═══════════════════════════════════════════════════════════
785
- export function canvas(id, options = {}) {
786
- const cv = new Canvas(id, options);
787
- pageState.__register(cv);
788
- return cv;
789
- }
790
- export { Canvas };
791
- export default canvas;
1
+ // Re-export from canonical location
2
+ export { canvas, Canvas } from '../components/widgets/canvas.js';
3
+ export { canvas as default } from '../components/widgets/canvas.js';
792
4
  //# sourceMappingURL=canvas.js.map