juxscript 1.1.404 → 1.1.409

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 +38 -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
@@ -0,0 +1,1359 @@
1
+ import { pageState } from '../../state/pageState.js';
2
+ import generateId from '../../utils/idgen.js';
3
+ import { parseCode } from '../../utils/codeparser.js';
4
+ import { injectCanvasStyles } from './canvas-styles.js';
5
+ import { JUX_COMPLETIONS, LAYOUT_COMPLETIONS, STATE_PROPS, KEYWORD_COMPLETIONS, COMPONENT_REGISTRY, UTILITY_REGISTRY, METHOD_SIGNATURES, LAYOUT_METHODS, SNIPPET_CATEGORIES, } from './canvas-registry.js';
6
+ import { compileAndRender } from './canvas-compile.js';
7
+ import { DEFAULT_TABS, saveTabs, loadTabs } from './canvas-persist.js';
8
+ import { aiSuggest } from './canvas-ai.js';
9
+ const STATE_KEEP_KEYS = new Set([
10
+ '__watch', '__unwatch', '__register', '__unregister',
11
+ '__notify', '__clear', '__clearWatchers', '__listeners', '__components', '__store',
12
+ ]);
13
+ class Canvas {
14
+ constructor(id, options = {}) {
15
+ this._el = null;
16
+ this._editorEl = null;
17
+ this._highlightEl = null;
18
+ this._lineNumEl = null;
19
+ this._previewEl = null;
20
+ this._statusEl = null;
21
+ this._errorEl = null;
22
+ this._autocompleteEl = null;
23
+ this._tabBarEl = null;
24
+ this._pageSidebarEl = null;
25
+ this._onChange = null;
26
+ this._compileTimer = null;
27
+ this._persistTimer = null;
28
+ this._knownIds = [];
29
+ this._isCompiling = false;
30
+ this._clickInterceptAttached = false;
31
+ this._tabs = [];
32
+ this._activeTabId = '';
33
+ this._currentRoute = '/';
34
+ this._savedNavigateTo = null;
35
+ this._aiFabEl = null;
36
+ this._aiChatEl = null;
37
+ this._aiLoading = false;
38
+ this._layout = 'left';
39
+ this._sidebarCollapsed = false;
40
+ this._splitEl = null;
41
+ this._codePanelEl = null;
42
+ this._previewPanelEl = null;
43
+ this._splitterEl = null;
44
+ this._sidebarWrapEl = null;
45
+ this._bodyEl = null;
46
+ this._snippetMenuEl = null;
47
+ this._snippetMenuLine = -1;
48
+ this.id = id || generateId('canvas');
49
+ this._previewId = this.id + '-preview';
50
+ this._savedNavigateTo = window.navigateTo || null;
51
+ this._opts = {
52
+ title: options.title || 'JUX IDE',
53
+ width: options.width || '100%',
54
+ height: options.height || '100vh',
55
+ codePanelWidth: options.codePanelWidth || '50%',
56
+ ...options,
57
+ };
58
+ this._appName = this._opts.title;
59
+ if (options.tabs?.length) {
60
+ this._tabs = options.tabs.map(t => ({ ...t }));
61
+ }
62
+ else if (options.initialCode) {
63
+ this._tabs = [{ id: 'main', label: 'main.jux', code: options.initialCode, isLayout: false, route: '/' }];
64
+ }
65
+ else {
66
+ this._tabs = DEFAULT_TABS.map(t => ({ ...t }));
67
+ }
68
+ this._activeTabId = this._tabs[0]?.id || '';
69
+ }
70
+ // ── Public fluent API ────────────────────────────────────────────────
71
+ _rerenderCanvas() { this._compile(); }
72
+ title(val) { this._opts.title = val; return this; }
73
+ width(val) { this._opts.width = val; return this; }
74
+ height(val) { this._opts.height = val; return this; }
75
+ codePanelWidth(val) { this._opts.codePanelWidth = val; return this; }
76
+ style(val) { this._opts.style = val; return this; }
77
+ class(val) { this._opts.class = val; return this; }
78
+ onChange(fn) { this._onChange = fn; return this; }
79
+ getValue() { return this._editorEl?.value || ''; }
80
+ setValue(val) { if (this._editorEl) {
81
+ this._editorEl.value = val;
82
+ this._onInput();
83
+ } return this; }
84
+ getElement() { return this._el; }
85
+ getTitle() { return this._opts.title; }
86
+ setTitle(val) { return this.title(val); }
87
+ setWidth(val) { return this.width(val); }
88
+ setHeight(val) { return this.height(val); }
89
+ get state() { return pageState[this.id]; }
90
+ generateFullCode() { return this._editorEl?.value || ''; }
91
+ render(target) {
92
+ injectCanvasStyles();
93
+ this._el = this._buildDOM();
94
+ let cont = null;
95
+ if (target && typeof target === 'object' && 'element' in target)
96
+ cont = target.element;
97
+ else if (target instanceof HTMLElement)
98
+ cont = target;
99
+ else if (typeof target === 'string')
100
+ cont = document.getElementById(target) || document.querySelector(target);
101
+ else
102
+ cont = document.getElementById('app');
103
+ if (cont && this._el)
104
+ cont.appendChild(this._el);
105
+ this._updateLineNumbers();
106
+ this._renderTabBar();
107
+ this._loadPersistedTabs().then(() => {
108
+ this._renderTabBar();
109
+ if (this._editorEl) {
110
+ const tab = this._getActiveTab();
111
+ if (tab)
112
+ this._editorEl.value = tab.code;
113
+ this._updateLineNumbers();
114
+ this._updateHighlight();
115
+ }
116
+ requestAnimationFrame(() => this._compile());
117
+ });
118
+ return this;
119
+ }
120
+ into(target) { return this.render(target); }
121
+ destroy() {
122
+ if (this._savedNavigateTo) {
123
+ window.navigateTo = this._savedNavigateTo;
124
+ }
125
+ pageState.__unregister(this.id);
126
+ // Remove our children from layout regions, but don't touch their styles
127
+ const navHeader = document.getElementById('app-nav-header');
128
+ if (navHeader)
129
+ navHeader.innerHTML = '';
130
+ const appSidebar = document.getElementById('app-sidebar');
131
+ if (appSidebar) {
132
+ appSidebar.innerHTML = '';
133
+ appSidebar.style.removeProperty('display');
134
+ }
135
+ // Reset CSS vars we may have set for sidebar width
136
+ document.documentElement.style.removeProperty('--app-sidebar-width');
137
+ document.documentElement.style.removeProperty('--app-nav-header-height');
138
+ this._el?.remove();
139
+ }
140
+ // ── Persistence ──────────────────────────────────────────────────────
141
+ async _loadPersistedTabs() {
142
+ const saved = await loadTabs(this._appName);
143
+ if (saved?.length) {
144
+ this._tabs = saved;
145
+ this._activeTabId = this._tabs[0]?.id || '';
146
+ }
147
+ }
148
+ _persistTabs() {
149
+ if (this._persistTimer)
150
+ clearTimeout(this._persistTimer);
151
+ this._persistTimer = setTimeout(() => {
152
+ const tab = this._getActiveTab();
153
+ if (tab && this._editorEl)
154
+ tab.code = this._editorEl.value;
155
+ saveTabs(this._appName, this._tabs);
156
+ }, 300);
157
+ }
158
+ // ── Tab management ───────────────────────────────────────────────────
159
+ _getActiveTab() {
160
+ return this._tabs.find(t => t.id === this._activeTabId);
161
+ }
162
+ _switchTab(tabId) {
163
+ const current = this._getActiveTab();
164
+ if (current && this._editorEl)
165
+ current.code = this._editorEl.value;
166
+ this._activeTabId = tabId;
167
+ const tab = this._getActiveTab();
168
+ if (tab && this._editorEl) {
169
+ this._editorEl.value = tab.code;
170
+ this._updateLineNumbers();
171
+ this._updateHighlight();
172
+ }
173
+ this._renderTabBar();
174
+ if (tab && !tab.isLayout && tab.route)
175
+ this._currentRoute = tab.route;
176
+ this._scheduleCompile();
177
+ }
178
+ _addTab() {
179
+ const existingRoutes = this._tabs.filter(t => !t.isLayout).map(t => t.route);
180
+ let newRoute = '/new-page';
181
+ let counter = 1;
182
+ while (existingRoutes.includes(newRoute))
183
+ newRoute = `/new-page-${counter++}`;
184
+ const slug = newRoute.replace(/^\//, '');
185
+ const newTab = {
186
+ id: `tab-${Date.now()}`, label: `${slug}.jux`, isLayout: false, route: newRoute,
187
+ code: `// ${slug} page (route: ${newRoute})\nconst heading = jux.h1('${slug}-title', { content: '${slug.charAt(0).toUpperCase() + slug.slice(1)}' });\n`,
188
+ };
189
+ this._tabs.push(newTab);
190
+ this._persistTabs();
191
+ this._switchTab(newTab.id);
192
+ }
193
+ _closeTab(tabId) {
194
+ const tab = this._tabs.find(t => t.id === tabId);
195
+ if (!tab || tab.isLayout)
196
+ return;
197
+ if (this._tabs.filter(t => !t.isLayout).length <= 1)
198
+ return;
199
+ this._tabs = this._tabs.filter(t => t.id !== tabId);
200
+ this._persistTabs();
201
+ if (this._activeTabId === tabId)
202
+ this._switchTab(this._tabs[0].id);
203
+ else
204
+ this._renderTabBar();
205
+ this._scheduleCompile();
206
+ }
207
+ _renameTab(tabId) {
208
+ const tab = this._tabs.find(t => t.id === tabId);
209
+ if (!tab || tab.isLayout)
210
+ return;
211
+ const itemEl = this._pageSidebarEl?.querySelector(`.jcv-page-item[data-tab-id="${tabId}"]`);
212
+ if (!itemEl)
213
+ return;
214
+ const labelEl = itemEl.querySelector('.jcv-page-item-label');
215
+ if (!labelEl)
216
+ return;
217
+ const input = document.createElement('input');
218
+ input.type = 'text';
219
+ input.className = 'jcv-page-rename-input';
220
+ input.value = tab.label.replace(/\.jux$/, '');
221
+ input.style.cssText = 'background:#313244;color:#cdd6f4;border:1px solid #89b4fa;border-radius:3px;font-size:12px;font-family:ui-monospace,monospace;padding:1px 4px;width:100%;box-sizing:border-box;outline:none;';
222
+ const originalLabel = tab.label;
223
+ const originalRoute = tab.route;
224
+ const commit = () => {
225
+ const raw = input.value.trim();
226
+ if (!raw) {
227
+ cancel();
228
+ return;
229
+ }
230
+ const slug = raw.replace(/\.jux$/, '').replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
231
+ const newLabel = slug + '.jux';
232
+ const newRoute = '/' + slug;
233
+ const duplicate = this._tabs.find(t => t.id !== tabId && !t.isLayout && t.route === newRoute);
234
+ if (duplicate) {
235
+ input.style.borderColor = '#f38ba8';
236
+ input.focus();
237
+ return;
238
+ }
239
+ tab.label = newLabel;
240
+ tab.route = newRoute;
241
+ if (this._currentRoute === originalRoute) {
242
+ this._currentRoute = newRoute;
243
+ }
244
+ this._persistTabs();
245
+ this._renderTabBar();
246
+ this._scheduleCompile(true);
247
+ };
248
+ const cancel = () => {
249
+ tab.label = originalLabel;
250
+ tab.route = originalRoute;
251
+ this._renderTabBar();
252
+ };
253
+ input.addEventListener('keydown', (e) => {
254
+ if (e.key === 'Enter') {
255
+ e.preventDefault();
256
+ commit();
257
+ }
258
+ if (e.key === 'Escape') {
259
+ e.preventDefault();
260
+ cancel();
261
+ }
262
+ });
263
+ input.addEventListener('blur', () => commit());
264
+ labelEl.textContent = '';
265
+ labelEl.appendChild(input);
266
+ input.focus();
267
+ input.select();
268
+ }
269
+ _renderTabBar() {
270
+ if (!this._pageSidebarEl)
271
+ return;
272
+ this._pageSidebarEl.innerHTML = '';
273
+ for (const tab of this._tabs) {
274
+ const item = document.createElement('button');
275
+ item.type = 'button';
276
+ item.className = 'jcv-page-item'
277
+ + (tab.id === this._activeTabId ? ' jcv-page-item--active' : '')
278
+ + (tab.isLayout ? ' jcv-page-item--layout' : '');
279
+ item.setAttribute('data-tab-id', tab.id);
280
+ const icon = document.createElement('span');
281
+ icon.className = 'jcv-page-item-icon';
282
+ icon.textContent = tab.isLayout ? '\u25B3' : '\u25CB';
283
+ item.appendChild(icon);
284
+ const labelEl = document.createElement('span');
285
+ labelEl.className = 'jcv-page-item-label';
286
+ labelEl.textContent = tab.label;
287
+ item.appendChild(labelEl);
288
+ item.addEventListener('click', (e) => {
289
+ if (e.target.classList.contains('jcv-page-item-close'))
290
+ return;
291
+ if (e.target.tagName === 'INPUT')
292
+ return;
293
+ this._switchTab(tab.id);
294
+ });
295
+ if (!tab.isLayout) {
296
+ item.addEventListener('dblclick', (e) => {
297
+ e.preventDefault();
298
+ e.stopPropagation();
299
+ this._renameTab(tab.id);
300
+ });
301
+ }
302
+ if (!tab.isLayout && this._tabs.filter(t => !t.isLayout).length > 1) {
303
+ const x = document.createElement('span');
304
+ x.className = 'jcv-page-item-close';
305
+ x.textContent = '\u00D7';
306
+ x.addEventListener('click', (e) => { e.stopPropagation(); this._closeTab(tab.id); });
307
+ item.appendChild(x);
308
+ }
309
+ this._pageSidebarEl.appendChild(item);
310
+ }
311
+ const addBtn = document.createElement('button');
312
+ addBtn.type = 'button';
313
+ addBtn.className = 'jcv-page-sidebar-add';
314
+ const addIcon = document.createElement('span');
315
+ addIcon.className = 'jcv-page-sidebar-add-icon';
316
+ addIcon.textContent = '+';
317
+ addBtn.appendChild(addIcon);
318
+ const addLabel = document.createElement('span');
319
+ addLabel.textContent = 'New Page';
320
+ addBtn.appendChild(addLabel);
321
+ addBtn.title = 'Add page';
322
+ addBtn.addEventListener('click', () => this._addTab());
323
+ this._pageSidebarEl.appendChild(addBtn);
324
+ }
325
+ // ── Navigation ───────────────────────────────────────────────────────
326
+ _navigatePreview(path) {
327
+ if (this._isCompiling)
328
+ return;
329
+ this._currentRoute = path;
330
+ const matching = this._tabs.find(t => t.route === path && !t.isLayout);
331
+ if (matching) {
332
+ this._pageSidebarEl?.querySelectorAll('.jcv-page-item').forEach(el => {
333
+ el.classList.toggle('jcv-page-item--routed', el.getAttribute('data-tab-id') === matching.id);
334
+ });
335
+ }
336
+ this._compile();
337
+ }
338
+ // ── DOM construction ─────────────────────────────────────────────────
339
+ _buildDOM() {
340
+ // Populate #app-nav-header with the toolbar (append only — no inline style overrides)
341
+ const navHeader = document.getElementById('app-nav-header');
342
+ if (navHeader) {
343
+ navHeader.innerHTML = '';
344
+ navHeader.appendChild(this._buildToolbar());
345
+ // Set the CSS var so #app offsets correctly
346
+ document.documentElement.style.setProperty('--app-nav-header-height', '56px');
347
+ }
348
+ // Populate #app-sidebar with the page sidebar (append only — no inline style overrides)
349
+ // Guard against duplicate #app-sidebar elements in the DOM
350
+ const allSidebars = document.querySelectorAll('#app-sidebar');
351
+ if (allSidebars.length > 1) {
352
+ // Keep only the first one, remove duplicates
353
+ for (let i = 1; i < allSidebars.length; i++) {
354
+ allSidebars[i].remove();
355
+ }
356
+ }
357
+ const appSidebar = document.getElementById('app-sidebar');
358
+ if (appSidebar) {
359
+ appSidebar.innerHTML = '';
360
+ // Clear any rogue inline display:none set by precompile/router/destroy
361
+ appSidebar.style.removeProperty('display');
362
+ appSidebar.appendChild(this._buildPageSidebar());
363
+ }
364
+ // Set sidebar width via CSS var only — no inline styles on #app-sidebar
365
+ document.documentElement.style.setProperty('--app-sidebar-width', '180px');
366
+ // Do NOT touch #app's inline styles — layout-regions.css handles positioning
367
+ // via the CSS vars we just set above.
368
+ const root = document.createElement('div');
369
+ root.id = this.id;
370
+ root.className = 'jcv-ide' + (this._opts.class ? ' ' + this._opts.class : '');
371
+ root.style.cssText = `width:100%;height:100%;${this._opts.style || ''}`;
372
+ const splitPane = document.createElement('div');
373
+ splitPane.className = 'jcv-split';
374
+ this._splitEl = splitPane;
375
+ const codePanel = this._buildCodePanel();
376
+ codePanel.style.width = this._opts.codePanelWidth;
377
+ this._codePanelEl = codePanel;
378
+ splitPane.appendChild(codePanel);
379
+ const handle = document.createElement('div');
380
+ handle.className = 'jcv-splitter';
381
+ this._splitterEl = handle;
382
+ handle.addEventListener('mousedown', (e) => this._startResize(e, codePanel, splitPane));
383
+ splitPane.appendChild(handle);
384
+ splitPane.appendChild(this._buildPreviewPanel());
385
+ root.appendChild(splitPane);
386
+ root.style.position = 'relative';
387
+ root.appendChild(this._buildAiFab());
388
+ root.appendChild(this._buildAiChat());
389
+ return root;
390
+ }
391
+ _buildToolbar() {
392
+ const toolbar = document.createElement('div');
393
+ toolbar.className = 'jcv-toolbar';
394
+ const titleEl = document.createElement('span');
395
+ titleEl.className = 'jcv-toolbar-title';
396
+ titleEl.textContent = this._opts.title;
397
+ toolbar.appendChild(titleEl);
398
+ const layoutGroup = document.createElement('div');
399
+ layoutGroup.className = 'jcv-layout-group';
400
+ const eyeSvg = '<svg width="12" height="8" viewBox="0 0 12 8" fill="currentColor" style="display:block"><path d="M6 0C3 0 .7 2 0 4c.7 2 3 4 6 4s5.3-2 6-4c-.7-2-3-4-6-4zm0 6.5c-1.4 0-2.5-1.1-2.5-2.5S4.6 1.5 6 1.5 8.5 2.6 8.5 4 7.4 6.5 6 6.5z"/><circle cx="6" cy="4" r="1"/></svg>';
401
+ const codeHtml = '<span class="jcv-li-c">&lt;/&gt;</span>';
402
+ const previewHtml = `<span class="jcv-li-p">${eyeSvg}</span>`;
403
+ const layouts = [
404
+ { key: 'left', title: 'Code Left / Preview Right', icon: `<span class="jcv-li">${codeHtml}${previewHtml}</span>` },
405
+ { key: 'right', title: 'Preview Left / Code Right', icon: `<span class="jcv-li">${previewHtml}${codeHtml}</span>` },
406
+ { key: 'top', title: 'Code Top / Preview Bottom', icon: `<span class="jcv-li jcv-li--v">${codeHtml}${previewHtml}</span>` },
407
+ { key: 'bottom', title: 'Preview Top / Code Bottom', icon: `<span class="jcv-li jcv-li--v">${previewHtml}${codeHtml}</span>` },
408
+ ];
409
+ for (const l of layouts) {
410
+ const btn = document.createElement('button');
411
+ btn.type = 'button';
412
+ btn.className = 'jcv-layout-btn' + (this._layout === l.key ? ' jcv-layout-btn--active' : '');
413
+ btn.title = l.title;
414
+ btn.innerHTML = l.icon;
415
+ btn.setAttribute('data-layout', l.key);
416
+ btn.addEventListener('click', () => this._setLayout(l.key));
417
+ layoutGroup.appendChild(btn);
418
+ }
419
+ toolbar.appendChild(layoutGroup);
420
+ const sidebarToggle = document.createElement('button');
421
+ sidebarToggle.type = 'button';
422
+ sidebarToggle.className = 'jcv-toolbar-btn jcv-sidebar-toggle';
423
+ sidebarToggle.title = 'Toggle Pages sidebar';
424
+ sidebarToggle.textContent = '\u2630';
425
+ sidebarToggle.addEventListener('click', () => this._toggleSidebar());
426
+ toolbar.appendChild(sidebarToggle);
427
+ const spacer = document.createElement('span');
428
+ spacer.style.flex = '1';
429
+ toolbar.appendChild(spacer);
430
+ toolbar.appendChild(this._toolbarBtn('Format', () => this._format()));
431
+ const runBtn = this._toolbarBtn('\u25B6 Run', () => this._compile());
432
+ runBtn.style.background = 'rgba(166,227,161,.15)';
433
+ toolbar.appendChild(runBtn);
434
+ const copyBtn = this._toolbarBtn('Copy', () => {
435
+ navigator.clipboard?.writeText(this._editorEl?.value || '');
436
+ copyBtn.textContent = '\u2713 Copied';
437
+ setTimeout(() => copyBtn.textContent = 'Copy', 1500);
438
+ });
439
+ toolbar.appendChild(copyBtn);
440
+ this._statusEl = document.createElement('span');
441
+ this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--ok';
442
+ this._statusEl.textContent = '\u25CF Ready';
443
+ toolbar.appendChild(this._statusEl);
444
+ return toolbar;
445
+ }
446
+ _buildPageSidebar() {
447
+ const sidebar = document.createElement('div');
448
+ sidebar.className = 'jcv-page-sidebar';
449
+ const header = document.createElement('div');
450
+ header.className = 'jcv-page-sidebar-header';
451
+ header.textContent = 'Pages';
452
+ sidebar.appendChild(header);
453
+ this._pageSidebarEl = document.createElement('div');
454
+ this._pageSidebarEl.className = 'jcv-page-sidebar-list';
455
+ sidebar.appendChild(this._pageSidebarEl);
456
+ this._tabBarEl = this._pageSidebarEl;
457
+ return sidebar;
458
+ }
459
+ _buildCodePanel() {
460
+ const panel = document.createElement('div');
461
+ panel.className = 'jcv-code-panel';
462
+ const editorWrap = document.createElement('div');
463
+ editorWrap.className = 'jcv-editor-wrap';
464
+ this._lineNumEl = document.createElement('div');
465
+ this._lineNumEl.className = 'jcv-line-nums';
466
+ editorWrap.appendChild(this._lineNumEl);
467
+ const editorContainer = document.createElement('div');
468
+ editorContainer.className = 'jcv-editor-container';
469
+ this._highlightEl = document.createElement('div');
470
+ this._highlightEl.className = 'jcv-highlight';
471
+ this._highlightEl.setAttribute('aria-hidden', 'true');
472
+ editorContainer.appendChild(this._highlightEl);
473
+ this._editorEl = document.createElement('textarea');
474
+ this._editorEl.className = 'jcv-editor';
475
+ this._editorEl.value = this._getActiveTab()?.code || '';
476
+ this._editorEl.spellcheck = false;
477
+ this._editorEl.setAttribute('autocomplete', 'off');
478
+ this._editorEl.setAttribute('autocorrect', 'off');
479
+ this._editorEl.setAttribute('autocapitalize', 'off');
480
+ this._editorEl.addEventListener('input', () => this._onInput());
481
+ this._editorEl.addEventListener('scroll', () => this._syncScroll());
482
+ this._editorEl.addEventListener('keydown', (e) => this._onKeyDown(e));
483
+ this._editorEl.addEventListener('click', () => { this._hideAutocomplete(); this._hideSnippetMenu(); });
484
+ editorContainer.appendChild(this._editorEl);
485
+ this._errorEl = document.createElement('div');
486
+ this._errorEl.className = 'jcv-error-bar';
487
+ this._errorEl.style.display = 'none';
488
+ editorContainer.appendChild(this._errorEl);
489
+ this._autocompleteEl = document.createElement('div');
490
+ this._autocompleteEl.className = 'jcv-autocomplete';
491
+ this._autocompleteEl.style.display = 'none';
492
+ editorContainer.appendChild(this._autocompleteEl);
493
+ editorWrap.appendChild(editorContainer);
494
+ this._snippetMenuEl = document.createElement('div');
495
+ this._snippetMenuEl.className = 'jcv-snippet-menu';
496
+ this._snippetMenuEl.style.display = 'none';
497
+ editorWrap.appendChild(this._snippetMenuEl);
498
+ panel.appendChild(editorWrap);
499
+ return panel;
500
+ }
501
+ _buildPreviewPanel() {
502
+ const panel = document.createElement('div');
503
+ panel.className = 'jcv-preview-panel';
504
+ this._previewPanelEl = panel;
505
+ const header = document.createElement('div');
506
+ header.className = 'jcv-preview-header';
507
+ const title = document.createElement('span');
508
+ title.textContent = 'Live Preview';
509
+ header.appendChild(title);
510
+ const routeIndicator = document.createElement('span');
511
+ routeIndicator.className = 'jcv-route-indicator';
512
+ routeIndicator.id = `${this.id}-route-indicator`;
513
+ routeIndicator.textContent = this._currentRoute;
514
+ header.appendChild(routeIndicator);
515
+ panel.appendChild(header);
516
+ this._previewEl = document.createElement('div');
517
+ this._previewEl.id = this._previewId;
518
+ this._previewEl.className = 'jcv-preview-area';
519
+ // ── Mirror the full layout-regions.css structure ──
520
+ // Mirrors: #app-nav-header
521
+ const regionNavHeader = document.createElement('header');
522
+ regionNavHeader.className = 'jcv-region-nav-header';
523
+ regionNavHeader.id = `${this._previewId}-nav-header`;
524
+ this._previewEl.appendChild(regionNavHeader);
525
+ // Body row: sidebar | main-col (toolbar + content) | sidebar-right
526
+ const regionBody = document.createElement('div');
527
+ regionBody.className = 'jcv-region-body';
528
+ // Mirrors: #app-sidebar
529
+ const regionSidebar = document.createElement('div');
530
+ regionSidebar.className = 'jcv-region-sidebar';
531
+ regionSidebar.id = `${this._previewId}-sidebar`;
532
+ regionBody.appendChild(regionSidebar);
533
+ // Main column: toolbar + content (mirrors #app area)
534
+ const regionMain = document.createElement('main');
535
+ regionMain.className = 'jcv-region-main';
536
+ regionMain.id = `${this._previewId}-app`;
537
+ // Mirrors: #app-toolbar
538
+ const regionToolbar = document.createElement('div');
539
+ regionToolbar.className = 'jcv-region-toolbar';
540
+ regionToolbar.id = `${this._previewId}-toolbar`;
541
+ regionMain.appendChild(regionToolbar);
542
+ // Mirrors: #app content area
543
+ const regionContent = document.createElement('div');
544
+ regionContent.className = 'jcv-region-content';
545
+ regionContent.id = `${this._previewId}-content`;
546
+ regionMain.appendChild(regionContent);
547
+ regionBody.appendChild(regionMain);
548
+ // Mirrors: #app-sidebar-right
549
+ const regionSidebarRight = document.createElement('div');
550
+ regionSidebarRight.className = 'jcv-region-sidebar-right';
551
+ regionSidebarRight.id = `${this._previewId}-sidebar-right`;
552
+ regionBody.appendChild(regionSidebarRight);
553
+ this._previewEl.appendChild(regionBody);
554
+ // Mirrors: #app-nav-footer
555
+ const regionNavFooter = document.createElement('footer');
556
+ regionNavFooter.className = 'jcv-region-nav-footer';
557
+ regionNavFooter.id = `${this._previewId}-nav-footer`;
558
+ this._previewEl.appendChild(regionNavFooter);
559
+ // Mirrors: #app-statusbar
560
+ const regionStatusbar = document.createElement('div');
561
+ regionStatusbar.className = 'jcv-region-statusbar';
562
+ regionStatusbar.id = `${this._previewId}-statusbar`;
563
+ this._previewEl.appendChild(regionStatusbar);
564
+ // Portal regions (absolute within preview)
565
+ const regionToast = document.createElement('div');
566
+ regionToast.className = 'jcv-region-toast';
567
+ regionToast.id = `${this._previewId}-toast`;
568
+ this._previewEl.appendChild(regionToast);
569
+ const regionModal = document.createElement('div');
570
+ regionModal.className = 'jcv-region-modal';
571
+ regionModal.id = `${this._previewId}-modal`;
572
+ this._previewEl.appendChild(regionModal);
573
+ const regionDrawer = document.createElement('div');
574
+ regionDrawer.className = 'jcv-region-drawer';
575
+ regionDrawer.id = `${this._previewId}-drawer`;
576
+ this._previewEl.appendChild(regionDrawer);
577
+ panel.appendChild(this._previewEl);
578
+ return panel;
579
+ }
580
+ _toolbarBtn(label, onClick) {
581
+ const b = document.createElement('button');
582
+ b.className = 'jcv-toolbar-btn';
583
+ b.textContent = label;
584
+ b.addEventListener('click', onClick);
585
+ return b;
586
+ }
587
+ // ── Editor helpers ───────────────────────────────────────────────────
588
+ _onInput() {
589
+ const tab = this._getActiveTab();
590
+ if (tab && this._editorEl)
591
+ tab.code = this._editorEl.value;
592
+ this._updateLineNumbers();
593
+ this._updateHighlight();
594
+ this._scheduleCompile();
595
+ this._checkAutocomplete();
596
+ this._persistTabs();
597
+ }
598
+ _updateLineNumbers() {
599
+ if (!this._lineNumEl || !this._editorEl)
600
+ return;
601
+ const count = this._editorEl.value.split('\n').length;
602
+ this._lineNumEl.innerHTML = '';
603
+ for (let i = 1; i <= count; i++) {
604
+ const div = document.createElement('div');
605
+ const addBtn = document.createElement('button');
606
+ addBtn.type = 'button';
607
+ addBtn.className = 'jcv-line-add';
608
+ addBtn.textContent = '+';
609
+ addBtn.title = 'Insert component';
610
+ addBtn.addEventListener('mousedown', (e) => {
611
+ e.preventDefault();
612
+ e.stopPropagation();
613
+ this._toggleSnippetMenu(i, addBtn);
614
+ });
615
+ div.appendChild(addBtn);
616
+ div.appendChild(document.createTextNode(String(i)));
617
+ this._lineNumEl.appendChild(div);
618
+ }
619
+ }
620
+ _syncScroll() {
621
+ if (!this._editorEl || !this._lineNumEl)
622
+ return;
623
+ this._lineNumEl.scrollTop = this._editorEl.scrollTop;
624
+ if (this._highlightEl) {
625
+ this._highlightEl.scrollTop = this._editorEl.scrollTop;
626
+ this._highlightEl.scrollLeft = this._editorEl.scrollLeft;
627
+ }
628
+ }
629
+ _updateHighlight() {
630
+ if (!this._highlightEl || !this._editorEl)
631
+ return;
632
+ try {
633
+ const lines = parseCode(this._editorEl.value);
634
+ this._highlightEl.innerHTML = lines.map(l => l.html).join('\n') + '\n';
635
+ }
636
+ catch (_) {
637
+ const escaped = this._editorEl.value
638
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
639
+ this._highlightEl.innerHTML = escaped + '\n';
640
+ }
641
+ }
642
+ _format() {
643
+ if (!this._editorEl)
644
+ return;
645
+ let indent = 0;
646
+ const formatted = this._editorEl.value.split('\n').map(line => {
647
+ const trimmed = line.trim();
648
+ if (!trimmed)
649
+ return '';
650
+ if (trimmed.startsWith('}') || trimmed.startsWith(']'))
651
+ indent = Math.max(0, indent - 1);
652
+ const res = ' '.repeat(indent) + trimmed;
653
+ if (trimmed.endsWith('{') || trimmed.endsWith('['))
654
+ indent++;
655
+ return res;
656
+ });
657
+ this._editorEl.value = formatted.join('\n');
658
+ this._onInput();
659
+ }
660
+ _scheduleCompile(immediate = false) {
661
+ if (this._compileTimer)
662
+ clearTimeout(this._compileTimer);
663
+ const delay = immediate ? 150 : 1200;
664
+ this._compileTimer = setTimeout(() => this._compile(), delay);
665
+ }
666
+ // ── Compile & render ─────────────────────────────────────────────────
667
+ _compile() {
668
+ if (!this._editorEl || !this._previewEl || !this._statusEl || !this._errorEl)
669
+ return;
670
+ if (this._isCompiling)
671
+ return;
672
+ this._isCompiling = true;
673
+ window.__juxCanvasCompiling = true;
674
+ const activeTab = this._getActiveTab();
675
+ if (activeTab && this._editorEl)
676
+ activeTab.code = this._editorEl.value;
677
+ if (this._savedNavigateTo) {
678
+ window.navigateTo = this._savedNavigateTo;
679
+ }
680
+ // Clear all mirrored layout regions
681
+ const regionNavHeader = this._previewEl.querySelector('.jcv-region-nav-header');
682
+ const regionSidebar = this._previewEl.querySelector('.jcv-region-sidebar');
683
+ const regionSidebarRight = this._previewEl.querySelector('.jcv-region-sidebar-right');
684
+ const regionToolbar = this._previewEl.querySelector('.jcv-region-toolbar');
685
+ const regionContent = this._previewEl.querySelector('.jcv-region-content');
686
+ const regionNavFooter = this._previewEl.querySelector('.jcv-region-nav-footer');
687
+ const regionStatusbar = this._previewEl.querySelector('.jcv-region-statusbar');
688
+ const regionToast = this._previewEl.querySelector('.jcv-region-toast');
689
+ const regionModal = this._previewEl.querySelector('.jcv-region-modal');
690
+ const regionDrawer = this._previewEl.querySelector('.jcv-region-drawer');
691
+ if (regionNavHeader)
692
+ regionNavHeader.innerHTML = '';
693
+ if (regionSidebar)
694
+ regionSidebar.innerHTML = '';
695
+ if (regionSidebarRight)
696
+ regionSidebarRight.innerHTML = '';
697
+ if (regionToolbar)
698
+ regionToolbar.innerHTML = '';
699
+ if (regionContent)
700
+ regionContent.innerHTML = '';
701
+ if (regionNavFooter)
702
+ regionNavFooter.innerHTML = '';
703
+ if (regionStatusbar)
704
+ regionStatusbar.innerHTML = '';
705
+ if (regionToast)
706
+ regionToast.innerHTML = '';
707
+ if (regionModal)
708
+ regionModal.innerHTML = '';
709
+ if (regionDrawer)
710
+ regionDrawer.innerHTML = '';
711
+ this._errorEl.style.display = 'none';
712
+ for (const key of Object.keys(pageState)) {
713
+ if (!STATE_KEEP_KEYS.has(key))
714
+ delete pageState[key];
715
+ }
716
+ const layoutTab = this._tabs.find(t => t.isLayout);
717
+ const routeTab = this._tabs.find(t => !t.isLayout && t.route === this._currentRoute);
718
+ const fallbackTab = this._tabs.find(t => !t.isLayout);
719
+ const layoutCode = layoutTab ? layoutTab.code : '';
720
+ const pageCode = (routeTab || fallbackTab).code;
721
+ let combinedCode = '';
722
+ if (layoutCode.trim())
723
+ combinedCode += `{\n${layoutCode}\n}\n\n`;
724
+ combinedCode += `{\n${pageCode}\n}`;
725
+ const routeIndicator = document.getElementById(`${this.id}-route-indicator`);
726
+ if (routeIndicator)
727
+ routeIndicator.textContent = this._currentRoute;
728
+ const contentDiv = regionContent || this._previewEl;
729
+ const result = compileAndRender(combinedCode, this._previewEl, (path) => this._navigatePreview(path), contentDiv, this._tabs, {
730
+ sidebarTarget: regionSidebar || undefined,
731
+ sidebarRightTarget: regionSidebarRight || undefined,
732
+ headerTarget: regionNavHeader || undefined,
733
+ footerTarget: regionNavFooter || undefined,
734
+ toolbarTarget: regionToolbar || undefined,
735
+ statusbarTarget: regionStatusbar || undefined,
736
+ toastTarget: regionToast || undefined,
737
+ modalTarget: regionModal || undefined,
738
+ drawerTarget: regionDrawer || undefined,
739
+ });
740
+ if (!this._clickInterceptAttached) {
741
+ this._clickInterceptAttached = true;
742
+ this._previewEl.addEventListener('click', (e) => {
743
+ const linkEl = e.target.closest('a[href], [data-path]');
744
+ if (linkEl) {
745
+ e.preventDefault();
746
+ e.stopPropagation();
747
+ const p = linkEl.getAttribute('data-path') || linkEl.getAttribute('href') || '';
748
+ if (p.startsWith('/'))
749
+ this._navigatePreview(p);
750
+ }
751
+ }, true);
752
+ }
753
+ this._knownIds = [];
754
+ this._previewEl.querySelectorAll('[data-id]').forEach(el => {
755
+ const eid = el.getAttribute('data-id');
756
+ if (eid)
757
+ this._knownIds.push(eid);
758
+ });
759
+ this._previewEl.querySelectorAll('[id]').forEach(el => {
760
+ if (el.id && el.id !== this._previewId && !this._knownIds.includes(el.id))
761
+ this._knownIds.push(el.id);
762
+ });
763
+ this._renderCompileResult(result, contentDiv);
764
+ if (this._onChange)
765
+ this._onChange({ code: combinedCode, result });
766
+ this._isCompiling = false;
767
+ window.__juxCanvasCompiling = false;
768
+ }
769
+ _renderCompileResult(result, contentDiv) {
770
+ if (!this._statusEl || !this._errorEl)
771
+ return;
772
+ if (result.success) {
773
+ this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--ok';
774
+ this._statusEl.textContent = `\u25CF ${result.componentCount} component${result.componentCount !== 1 ? 's' : ''} \u00B7 ${this._currentRoute}`;
775
+ }
776
+ else {
777
+ this._statusEl.className = 'jcv-toolbar-status jcv-toolbar-status--err';
778
+ this._statusEl.textContent = '\u25CF Error';
779
+ const errBlock = document.createElement('div');
780
+ errBlock.className = 'jcv-live-error-block';
781
+ const errTitle = document.createElement('div');
782
+ errTitle.className = 'jcv-live-error-title';
783
+ errTitle.textContent = 'Compile Error';
784
+ errBlock.appendChild(errTitle);
785
+ const errMsg = document.createElement('div');
786
+ errMsg.className = 'jcv-live-error-msg';
787
+ errMsg.textContent = result.error || 'Unknown error';
788
+ errBlock.appendChild(errMsg);
789
+ const errHint = document.createElement('div');
790
+ errHint.className = 'jcv-live-error-ctx';
791
+ errHint.textContent = '\u2139\uFE0F Check the browser console (F12) for full details.';
792
+ errBlock.appendChild(errHint);
793
+ contentDiv.appendChild(errBlock);
794
+ this._errorEl.style.display = 'block';
795
+ this._errorEl.textContent = result.error || 'Error';
796
+ }
797
+ }
798
+ // ── Autocomplete ─────────────────────────────────────────────────────
799
+ _checkAutocomplete() {
800
+ if (!this._editorEl)
801
+ return;
802
+ const pos = this._editorEl.selectionStart;
803
+ const text = this._editorEl.value.substring(0, pos);
804
+ let items = [];
805
+ let replaceFrom = pos;
806
+ const fullCode = this._editorEl.value;
807
+ const varMap = {};
808
+ const assignRx = /(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?jux\.(\w+)\s*\(/g;
809
+ let am;
810
+ while ((am = assignRx.exec(fullCode)) !== null) {
811
+ varMap[am[1]] = am[2];
812
+ }
813
+ const juxDot = text.match(/jux\.(\w*)$/);
814
+ if (juxDot) {
815
+ const f = juxDot[1].toLowerCase();
816
+ items = JUX_COMPLETIONS.filter(ci => ci.label.toLowerCase().startsWith(f));
817
+ replaceFrom = pos - juxDot[1].length;
818
+ }
819
+ const stateDot = text.match(/pageState\['[^']*'\]\.(\w*)$/);
820
+ if (stateDot) {
821
+ const f = stateDot[1].toLowerCase();
822
+ items = STATE_PROPS.filter(ci => ci.label.toLowerCase().startsWith(f));
823
+ replaceFrom = pos - stateDot[1].length;
824
+ }
825
+ const stateId = text.match(/pageState\['([^']*)$/);
826
+ if (stateId && !stateDot) {
827
+ const f = stateId[1].toLowerCase();
828
+ items = this._knownIds.filter(k => k.toLowerCase().startsWith(f))
829
+ .map(k => ({ label: k, detail: 'Component ID', insertText: k, kind: 'variable' }));
830
+ replaceFrom = pos - stateId[1].length;
831
+ }
832
+ if (!juxDot && !stateDot && !stateId) {
833
+ let compName;
834
+ let prefix = '';
835
+ let matchLen = 0;
836
+ const chainDot = text.match(/\)\.(\w*)$/);
837
+ if (chainDot) {
838
+ prefix = chainDot[1].toLowerCase();
839
+ matchLen = chainDot[1].length;
840
+ const juxCalls = [...text.matchAll(/jux\.(\w+)\s*\(/g)];
841
+ if (juxCalls.length > 0) {
842
+ compName = juxCalls[juxCalls.length - 1][1];
843
+ }
844
+ if (!compName) {
845
+ const curLine = text.split('\n').pop() || '';
846
+ const varStart = curLine.match(/^\s*(?:(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?)?(\w+)\s*\./);
847
+ if (varStart && varMap[varStart[1]]) {
848
+ compName = varMap[varStart[1]];
849
+ }
850
+ }
851
+ }
852
+ if (!chainDot) {
853
+ const varDot = text.match(/(\w+)\.(\w*)$/);
854
+ if (varDot && varDot[1] !== 'jux' && varDot[1] !== 'pageState' && varMap[varDot[1]]) {
855
+ compName = varMap[varDot[1]];
856
+ prefix = varDot[2].toLowerCase();
857
+ matchLen = varDot[2].length;
858
+ }
859
+ }
860
+ if (compName) {
861
+ const entry = COMPONENT_REGISTRY[compName];
862
+ const utilEntry = UTILITY_REGISTRY[compName];
863
+ const methods = entry?.methods || utilEntry?.methods || LAYOUT_METHODS[compName] || [];
864
+ if (methods.length > 0) {
865
+ items = methods
866
+ .filter(m => m.toLowerCase().startsWith(prefix))
867
+ .map(m => ({
868
+ label: m,
869
+ detail: 'Method',
870
+ insertText: METHOD_SIGNATURES[m] || `${m}()`,
871
+ kind: 'method',
872
+ }));
873
+ replaceFrom = pos - matchLen;
874
+ }
875
+ }
876
+ }
877
+ const lineStart = text.match(/(?:^|\n)\s*(\w*)$/);
878
+ if (lineStart && !juxDot && !stateDot && !stateId && lineStart[1].length >= 1 && items.length === 0) {
879
+ const f = lineStart[1].toLowerCase();
880
+ items = [...LAYOUT_COMPLETIONS, ...KEYWORD_COMPLETIONS].filter(ci => ci.label.toLowerCase().startsWith(f));
881
+ replaceFrom = pos - lineStart[1].length;
882
+ }
883
+ if (items.length > 0)
884
+ this._showAutocomplete(items, replaceFrom);
885
+ else
886
+ this._hideAutocomplete();
887
+ }
888
+ _showAutocomplete(items, replaceFrom) {
889
+ if (!this._autocompleteEl || !this._editorEl)
890
+ return;
891
+ this._autocompleteEl.innerHTML = '';
892
+ this._autocompleteEl.style.display = 'block';
893
+ const coords = this._getCaretCoords();
894
+ this._autocompleteEl.style.top = (coords.top + 20) + 'px';
895
+ this._autocompleteEl.style.left = coords.left + 'px';
896
+ const maxShow = Math.min(items.length, 8);
897
+ for (let i = 0; i < maxShow; i++) {
898
+ const item = items[i];
899
+ const row = document.createElement('div');
900
+ row.className = 'jcv-ac-item' + (i === 0 ? ' jcv-ac-item--active' : '');
901
+ const icon = document.createElement('span');
902
+ icon.className = 'jcv-ac-icon';
903
+ icon.textContent = item.kind === 'function' ? '\u0192' : item.kind === 'property' ? '\u25C6' : item.kind === 'keyword' ? '\u2B25' : '\u25C7';
904
+ row.appendChild(icon);
905
+ const lbl = document.createElement('span');
906
+ lbl.className = 'jcv-ac-label';
907
+ lbl.textContent = item.label;
908
+ row.appendChild(lbl);
909
+ const det = document.createElement('span');
910
+ det.className = 'jcv-ac-detail';
911
+ det.textContent = item.detail;
912
+ row.appendChild(det);
913
+ row.addEventListener('mousedown', (e) => { e.preventDefault(); this._acceptCompletion(item, replaceFrom); });
914
+ this._autocompleteEl.appendChild(row);
915
+ }
916
+ const ac = this._autocompleteEl;
917
+ ac.__items = items.slice(0, maxShow);
918
+ ac.__activeIndex = 0;
919
+ ac.__replaceFrom = replaceFrom;
920
+ }
921
+ _hideAutocomplete() {
922
+ if (this._autocompleteEl)
923
+ this._autocompleteEl.style.display = 'none';
924
+ }
925
+ // ── Snippet insertion menu ───────────────────────────────────────────
926
+ _toggleSnippetMenu(lineNumber, anchorBtn) {
927
+ if (this._snippetMenuEl?.style.display !== 'none' && this._snippetMenuLine === lineNumber) {
928
+ this._hideSnippetMenu();
929
+ return;
930
+ }
931
+ this._showSnippetMenu(lineNumber, anchorBtn);
932
+ }
933
+ _showSnippetMenu(lineNumber, anchorBtn) {
934
+ if (!this._snippetMenuEl)
935
+ return;
936
+ this._snippetMenuLine = lineNumber;
937
+ const wrapRect = this._snippetMenuEl.parentElement.getBoundingClientRect();
938
+ const btnRect = anchorBtn.getBoundingClientRect();
939
+ const top = btnRect.top - wrapRect.top;
940
+ this._snippetMenuEl.style.top = top + 'px';
941
+ this._snippetMenuEl.style.left = '4px';
942
+ this._snippetMenuEl.style.display = 'flex';
943
+ this._renderSnippetMenu('');
944
+ }
945
+ _hideSnippetMenu() {
946
+ if (this._snippetMenuEl) {
947
+ this._snippetMenuEl.style.display = 'none';
948
+ this._snippetMenuLine = -1;
949
+ }
950
+ }
951
+ _renderSnippetMenu(filter) {
952
+ if (!this._snippetMenuEl)
953
+ return;
954
+ this._snippetMenuEl.innerHTML = '';
955
+ const header = document.createElement('div');
956
+ header.className = 'jcv-snippet-header';
957
+ const headerIcon = document.createElement('span');
958
+ headerIcon.className = 'jcv-snippet-header-icon';
959
+ headerIcon.textContent = '+';
960
+ header.appendChild(headerIcon);
961
+ const searchInput = document.createElement('input');
962
+ searchInput.type = 'text';
963
+ searchInput.className = 'jcv-snippet-search';
964
+ searchInput.placeholder = 'Search components\u2026';
965
+ searchInput.value = filter;
966
+ searchInput.addEventListener('input', () => {
967
+ this._renderSnippetMenu(searchInput.value);
968
+ });
969
+ searchInput.addEventListener('keydown', (e) => {
970
+ if (e.key === 'Escape') {
971
+ e.preventDefault();
972
+ this._hideSnippetMenu();
973
+ this._editorEl?.focus();
974
+ }
975
+ });
976
+ header.appendChild(searchInput);
977
+ this._snippetMenuEl.appendChild(header);
978
+ const listEl = document.createElement('div');
979
+ listEl.className = 'jcv-snippet-list';
980
+ const lowerFilter = filter.toLowerCase();
981
+ for (const cat of SNIPPET_CATEGORIES) {
982
+ const matchedItems = cat.items.filter(item => item.name.toLowerCase().includes(lowerFilter) || item.detail.toLowerCase().includes(lowerFilter));
983
+ if (matchedItems.length === 0)
984
+ continue;
985
+ const catLabel = document.createElement('div');
986
+ catLabel.className = 'jcv-snippet-cat';
987
+ catLabel.textContent = cat.label;
988
+ listEl.appendChild(catLabel);
989
+ for (const item of matchedItems) {
990
+ const row = document.createElement('div');
991
+ row.className = 'jcv-snippet-item';
992
+ const icon = document.createElement('span');
993
+ icon.className = 'jcv-snippet-item-icon';
994
+ icon.textContent = '\u0192';
995
+ row.appendChild(icon);
996
+ const lbl = document.createElement('span');
997
+ lbl.className = 'jcv-snippet-item-label';
998
+ lbl.textContent = item.name;
999
+ row.appendChild(lbl);
1000
+ const det = document.createElement('span');
1001
+ det.className = 'jcv-snippet-item-detail';
1002
+ det.textContent = item.detail;
1003
+ row.appendChild(det);
1004
+ row.addEventListener('mousedown', (e) => {
1005
+ e.preventDefault();
1006
+ this._insertSnippetAtLine(this._snippetMenuLine, item.snippet);
1007
+ });
1008
+ listEl.appendChild(row);
1009
+ }
1010
+ }
1011
+ this._snippetMenuEl.appendChild(listEl);
1012
+ requestAnimationFrame(() => searchInput.focus());
1013
+ }
1014
+ _insertSnippetAtLine(lineNumber, snippet) {
1015
+ if (!this._editorEl)
1016
+ return;
1017
+ const lines = this._editorEl.value.split('\n');
1018
+ const insertIndex = Math.min(lineNumber, lines.length);
1019
+ // Detect indentation from the target line
1020
+ const targetLine = lines[insertIndex - 1] || '';
1021
+ const indent = targetLine.match(/^(\s*)/)?.[1] || '';
1022
+ // Apply indentation to snippet lines
1023
+ const snippetLines = snippet.split('\n');
1024
+ const indentedSnippet = snippetLines.map((line, i) => i === 0 ? indent + line : (line ? indent + line : line)).join('\n');
1025
+ // Insert after the target line
1026
+ lines.splice(insertIndex, 0, indentedSnippet);
1027
+ this._editorEl.value = lines.join('\n');
1028
+ // Position cursor at end of inserted snippet
1029
+ let cursorPos = 0;
1030
+ for (let i = 0; i <= insertIndex; i++)
1031
+ cursorPos += lines[i].length + 1;
1032
+ cursorPos += indentedSnippet.length;
1033
+ this._editorEl.selectionStart = this._editorEl.selectionEnd = Math.min(cursorPos, this._editorEl.value.length);
1034
+ this._editorEl.focus();
1035
+ this._hideSnippetMenu();
1036
+ this._onInput();
1037
+ }
1038
+ _acceptCompletion(item, replaceFrom) {
1039
+ if (!this._editorEl)
1040
+ return;
1041
+ const before = this._editorEl.value.substring(0, replaceFrom);
1042
+ const after = this._editorEl.value.substring(this._editorEl.selectionStart);
1043
+ this._editorEl.value = before + item.insertText + after;
1044
+ const newPos = replaceFrom + item.insertText.length;
1045
+ this._editorEl.selectionStart = this._editorEl.selectionEnd = newPos;
1046
+ this._editorEl.focus();
1047
+ this._hideAutocomplete();
1048
+ this._onInput();
1049
+ }
1050
+ _onKeyDown(e) {
1051
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
1052
+ e.preventDefault();
1053
+ this._toggleAiChat();
1054
+ return;
1055
+ }
1056
+ if (!this._autocompleteEl || this._autocompleteEl.style.display === 'none') {
1057
+ if (e.key === 'Tab') {
1058
+ e.preventDefault();
1059
+ const s = this._editorEl.selectionStart, en = this._editorEl.selectionEnd;
1060
+ this._editorEl.value = this._editorEl.value.substring(0, s) + ' ' + this._editorEl.value.substring(en);
1061
+ this._editorEl.selectionStart = this._editorEl.selectionEnd = s + 2;
1062
+ this._onInput();
1063
+ }
1064
+ if (e.key === 'Enter' || e.key === ';' || e.key === '}' || e.key === ')' || e.key === ']') {
1065
+ if (this._compileTimer)
1066
+ clearTimeout(this._compileTimer);
1067
+ this._compileTimer = setTimeout(() => this._compile(), 150);
1068
+ }
1069
+ return;
1070
+ }
1071
+ const ac = this._autocompleteEl;
1072
+ const acItems = ac.__items || [];
1073
+ let active = ac.__activeIndex || 0;
1074
+ const replFrom = ac.__replaceFrom || 0;
1075
+ if (e.key === 'ArrowDown') {
1076
+ e.preventDefault();
1077
+ active = Math.min(active + 1, acItems.length - 1);
1078
+ this._setActiveCompletion(active);
1079
+ }
1080
+ else if (e.key === 'ArrowUp') {
1081
+ e.preventDefault();
1082
+ active = Math.max(active - 1, 0);
1083
+ this._setActiveCompletion(active);
1084
+ }
1085
+ else if (e.key === 'Enter' || e.key === 'Tab') {
1086
+ e.preventDefault();
1087
+ if (acItems[active])
1088
+ this._acceptCompletion(acItems[active], replFrom);
1089
+ }
1090
+ else if (e.key === 'Escape') {
1091
+ e.preventDefault();
1092
+ this._hideAutocomplete();
1093
+ }
1094
+ }
1095
+ _setActiveCompletion(index) {
1096
+ if (!this._autocompleteEl)
1097
+ return;
1098
+ this._autocompleteEl.__activeIndex = index;
1099
+ this._autocompleteEl.querySelectorAll('.jcv-ac-item').forEach((el, i) => el.classList.toggle('jcv-ac-item--active', i === index));
1100
+ }
1101
+ _getCaretCoords() {
1102
+ if (!this._editorEl)
1103
+ return { top: 0, left: 0 };
1104
+ const lines = this._editorEl.value.substring(0, this._editorEl.selectionStart).split('\n');
1105
+ return {
1106
+ top: (lines.length - 1) * 20 - this._editorEl.scrollTop,
1107
+ left: Math.min(lines[lines.length - 1].length * 7.8, 350) + 40,
1108
+ };
1109
+ }
1110
+ // ── AI Assist ────────────────────────────────────────────────────────
1111
+ _buildAiFab() {
1112
+ const fab = document.createElement('button');
1113
+ fab.className = 'jcv-ai-fab';
1114
+ fab.innerHTML = '\u2728';
1115
+ fab.title = 'AI Assist (Ctrl+K)';
1116
+ fab.addEventListener('click', () => this._toggleAiChat());
1117
+ this._aiFabEl = fab;
1118
+ return fab;
1119
+ }
1120
+ _buildAiChat() {
1121
+ const chat = document.createElement('div');
1122
+ chat.className = 'jcv-ai-chat';
1123
+ const header = document.createElement('div');
1124
+ header.className = 'jcv-ai-header';
1125
+ header.innerHTML = '<span>\u2728</span><span>AI Assist</span>';
1126
+ const badge = document.createElement('span');
1127
+ badge.className = 'jcv-ai-badge';
1128
+ badge.textContent = 'Claude';
1129
+ header.appendChild(badge);
1130
+ const shortcut = document.createElement('span');
1131
+ shortcut.className = 'jcv-ai-shortcut';
1132
+ shortcut.textContent = 'Ctrl+K';
1133
+ header.appendChild(shortcut);
1134
+ const closeBtn = document.createElement('button');
1135
+ closeBtn.className = 'jcv-ai-close';
1136
+ closeBtn.textContent = '\u00D7';
1137
+ closeBtn.addEventListener('click', () => this._closeAiChat());
1138
+ header.appendChild(closeBtn);
1139
+ chat.appendChild(header);
1140
+ const inputWrap = document.createElement('div');
1141
+ inputWrap.className = 'jcv-ai-input-wrap';
1142
+ const aiInput = document.createElement('textarea');
1143
+ aiInput.className = 'jcv-ai-input';
1144
+ aiInput.placeholder = 'Ask AI to write JUX code\u2026';
1145
+ aiInput.rows = 2;
1146
+ aiInput.addEventListener('keydown', (e) => {
1147
+ if (e.key === 'Enter' && !e.shiftKey) {
1148
+ e.preventDefault();
1149
+ this._submitAiPrompt();
1150
+ }
1151
+ if (e.key === 'Escape')
1152
+ this._closeAiChat();
1153
+ });
1154
+ inputWrap.appendChild(aiInput);
1155
+ const sendBtn = document.createElement('button');
1156
+ sendBtn.className = 'jcv-ai-send';
1157
+ sendBtn.textContent = 'Send';
1158
+ sendBtn.addEventListener('click', () => this._submitAiPrompt());
1159
+ inputWrap.appendChild(sendBtn);
1160
+ chat.appendChild(inputWrap);
1161
+ const status = document.createElement('div');
1162
+ status.className = 'jcv-ai-status';
1163
+ chat.appendChild(status);
1164
+ const result = document.createElement('div');
1165
+ result.className = 'jcv-ai-result';
1166
+ chat.appendChild(result);
1167
+ const actions = document.createElement('div');
1168
+ actions.className = 'jcv-ai-actions';
1169
+ const insertBtn = document.createElement('button');
1170
+ insertBtn.className = 'jcv-ai-action-btn';
1171
+ insertBtn.textContent = '\u2713 Insert at cursor';
1172
+ insertBtn.addEventListener('click', () => this._insertAiResult());
1173
+ actions.appendChild(insertBtn);
1174
+ const replaceBtn = document.createElement('button');
1175
+ replaceBtn.className = 'jcv-ai-action-btn jcv-ai-action-btn--secondary';
1176
+ replaceBtn.textContent = 'Replace all';
1177
+ replaceBtn.addEventListener('click', () => this._replaceWithAiResult());
1178
+ actions.appendChild(replaceBtn);
1179
+ chat.appendChild(actions);
1180
+ this._aiChatEl = chat;
1181
+ return chat;
1182
+ }
1183
+ _toggleAiChat() {
1184
+ if (this._aiChatEl?.classList.contains('jcv-ai-chat--open')) {
1185
+ this._closeAiChat();
1186
+ }
1187
+ else {
1188
+ this._openAiChat();
1189
+ }
1190
+ }
1191
+ _openAiChat() {
1192
+ if (!this._aiChatEl)
1193
+ return;
1194
+ this._aiChatEl.classList.add('jcv-ai-chat--open');
1195
+ if (this._aiFabEl)
1196
+ this._aiFabEl.classList.add('jcv-ai-fab--hidden');
1197
+ requestAnimationFrame(() => {
1198
+ const input = this._aiChatEl?.querySelector('.jcv-ai-input');
1199
+ input?.focus();
1200
+ });
1201
+ }
1202
+ _closeAiChat() {
1203
+ if (!this._aiChatEl)
1204
+ return;
1205
+ this._aiChatEl.classList.remove('jcv-ai-chat--open');
1206
+ if (this._aiFabEl)
1207
+ this._aiFabEl.classList.remove('jcv-ai-fab--hidden');
1208
+ this._editorEl?.focus();
1209
+ }
1210
+ async _submitAiPrompt() {
1211
+ if (this._aiLoading || !this._aiChatEl)
1212
+ return;
1213
+ const aiInput = this._aiChatEl.querySelector('.jcv-ai-input');
1214
+ const sendBtn = this._aiChatEl.querySelector('.jcv-ai-send');
1215
+ const status = this._aiChatEl.querySelector('.jcv-ai-status');
1216
+ const result = this._aiChatEl.querySelector('.jcv-ai-result');
1217
+ const actions = this._aiChatEl.querySelector('.jcv-ai-actions');
1218
+ const prompt = aiInput?.value.trim();
1219
+ if (!prompt)
1220
+ return;
1221
+ this._aiLoading = true;
1222
+ sendBtn.disabled = true;
1223
+ status.textContent = '\u23F3 Asking Claude\u2026';
1224
+ status.className = 'jcv-ai-status jcv-ai-status--visible jcv-ai-status--loading';
1225
+ result.className = 'jcv-ai-result';
1226
+ result.textContent = '';
1227
+ actions.className = 'jcv-ai-actions';
1228
+ const code = this._editorEl?.value || '';
1229
+ const cursorLine = code.substring(0, this._editorEl?.selectionStart || 0).split('\n').length;
1230
+ const res = await aiSuggest({ prompt, code, cursorLine });
1231
+ this._aiLoading = false;
1232
+ sendBtn.disabled = false;
1233
+ if (res.error) {
1234
+ status.textContent = '\u26A0 ' + res.error;
1235
+ status.className = 'jcv-ai-status jcv-ai-status--visible jcv-ai-status--error';
1236
+ }
1237
+ else {
1238
+ status.textContent = '\u2705 Suggestion ready';
1239
+ status.className = 'jcv-ai-status jcv-ai-status--visible';
1240
+ result.textContent = res.suggestion;
1241
+ result.className = 'jcv-ai-result jcv-ai-result--visible';
1242
+ actions.className = 'jcv-ai-actions jcv-ai-actions--visible';
1243
+ }
1244
+ }
1245
+ _insertAiResult() {
1246
+ const result = this._aiChatEl?.querySelector('.jcv-ai-result');
1247
+ if (!result || !this._editorEl)
1248
+ return;
1249
+ const text = result.textContent || '';
1250
+ const pos = this._editorEl.selectionStart;
1251
+ const before = this._editorEl.value.substring(0, pos);
1252
+ const after = this._editorEl.value.substring(pos);
1253
+ const insert = (before.length > 0 && !before.endsWith('\n') ? '\n' : '') + text + '\n';
1254
+ this._editorEl.value = before + insert + after;
1255
+ this._editorEl.selectionStart = this._editorEl.selectionEnd = pos + insert.length;
1256
+ this._onInput();
1257
+ this._closeAiChat();
1258
+ }
1259
+ _replaceWithAiResult() {
1260
+ const result = this._aiChatEl?.querySelector('.jcv-ai-result');
1261
+ if (!result || !this._editorEl)
1262
+ return;
1263
+ this._editorEl.value = result.textContent || '';
1264
+ this._editorEl.selectionStart = this._editorEl.selectionEnd = this._editorEl.value.length;
1265
+ this._onInput();
1266
+ this._closeAiChat();
1267
+ }
1268
+ // ── Layout orientation ─────────────────────────────────────────────
1269
+ _setLayout(layout) {
1270
+ if (this._layout === layout)
1271
+ return;
1272
+ this._layout = layout;
1273
+ if (!this._splitEl || !this._codePanelEl || !this._previewPanelEl || !this._splitterEl)
1274
+ return;
1275
+ this._el?.querySelectorAll('.jcv-layout-btn').forEach(b => {
1276
+ b.classList.toggle('jcv-layout-btn--active', b.getAttribute('data-layout') === layout);
1277
+ });
1278
+ const split = this._splitEl;
1279
+ const code = this._codePanelEl;
1280
+ const preview = this._previewPanelEl;
1281
+ const handle = this._splitterEl;
1282
+ const isVertical = layout === 'top' || layout === 'bottom';
1283
+ code.style.width = '';
1284
+ code.style.height = '';
1285
+ code.style.flex = '';
1286
+ while (split.firstChild)
1287
+ split.removeChild(split.firstChild);
1288
+ if (isVertical) {
1289
+ split.classList.add('jcv-split--vertical');
1290
+ handle.className = 'jcv-splitter jcv-splitter--horizontal';
1291
+ code.style.height = '50%';
1292
+ }
1293
+ else {
1294
+ split.classList.remove('jcv-split--vertical');
1295
+ handle.className = 'jcv-splitter';
1296
+ code.style.width = this._opts.codePanelWidth;
1297
+ }
1298
+ if (layout === 'left' || layout === 'top') {
1299
+ split.appendChild(code);
1300
+ split.appendChild(handle);
1301
+ split.appendChild(preview);
1302
+ }
1303
+ else {
1304
+ split.appendChild(preview);
1305
+ split.appendChild(handle);
1306
+ split.appendChild(code);
1307
+ }
1308
+ }
1309
+ _toggleSidebar() {
1310
+ this._sidebarCollapsed = !this._sidebarCollapsed;
1311
+ // Control #app-sidebar width through the CSS var that layout-regions.css reads
1312
+ document.documentElement.style.setProperty('--app-sidebar-width', this._sidebarCollapsed ? '0px' : '180px');
1313
+ }
1314
+ // ── Resize ───────────────────────────────────────────────────────────
1315
+ _startResize(e, codePanel, splitPane) {
1316
+ e.preventDefault();
1317
+ const isVertical = this._layout === 'top' || this._layout === 'bottom';
1318
+ if (isVertical) {
1319
+ const startY = e.clientY, startHeight = codePanel.offsetHeight, totalHeight = splitPane.offsetHeight;
1320
+ const reversed = this._layout === 'bottom';
1321
+ const onMove = (ev) => {
1322
+ const delta = ev.clientY - startY;
1323
+ const newHeight = Math.max(100, Math.min(totalHeight - 100, startHeight + (reversed ? -delta : delta)));
1324
+ codePanel.style.height = newHeight + 'px';
1325
+ codePanel.style.flex = 'none';
1326
+ };
1327
+ const onUp = () => {
1328
+ document.removeEventListener('mousemove', onMove);
1329
+ document.removeEventListener('mouseup', onUp);
1330
+ };
1331
+ document.addEventListener('mousemove', onMove);
1332
+ document.addEventListener('mouseup', onUp);
1333
+ }
1334
+ else {
1335
+ const startX = e.clientX, startWidth = codePanel.offsetWidth, totalWidth = splitPane.offsetWidth;
1336
+ const reversed = this._layout === 'right';
1337
+ const onMove = (ev) => {
1338
+ const delta = ev.clientX - startX;
1339
+ const newWidth = Math.max(200, Math.min(totalWidth - 200, startWidth + (reversed ? -delta : delta)));
1340
+ codePanel.style.width = newWidth + 'px';
1341
+ codePanel.style.flex = 'none';
1342
+ };
1343
+ const onUp = () => {
1344
+ document.removeEventListener('mousemove', onMove);
1345
+ document.removeEventListener('mouseup', onUp);
1346
+ };
1347
+ document.addEventListener('mousemove', onMove);
1348
+ document.addEventListener('mouseup', onUp);
1349
+ }
1350
+ }
1351
+ }
1352
+ export function canvas(id, options = {}) {
1353
+ const cv = new Canvas(id, options);
1354
+ pageState.__register(cv);
1355
+ return cv;
1356
+ }
1357
+ export { Canvas };
1358
+ export default canvas;
1359
+ //# sourceMappingURL=canvas.js.map