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.
- package/dist/components/button.d.ts +1 -0
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +37 -0
- package/dist/components/button.js.map +1 -1
- package/dist/components/c.d.ts +53 -0
- package/dist/components/c.d.ts.map +1 -0
- package/dist/components/c.js +127 -0
- package/dist/components/c.js.map +1 -0
- package/dist/components/charts/barChart.d.ts +119 -0
- package/dist/components/charts/barChart.d.ts.map +1 -0
- package/dist/components/charts/barChart.js +644 -0
- package/dist/components/charts/barChart.js.map +1 -0
- package/dist/components/charts/lineChart.d.ts +104 -0
- package/dist/components/charts/lineChart.d.ts.map +1 -0
- package/dist/components/charts/lineChart.js +466 -0
- package/dist/components/charts/lineChart.js.map +1 -0
- package/dist/components/charts/pieChart.d.ts +93 -0
- package/dist/components/charts/pieChart.d.ts.map +1 -0
- package/dist/components/charts/pieChart.js +397 -0
- package/dist/components/charts/pieChart.js.map +1 -0
- package/dist/components/checkbox.d.ts +2 -0
- package/dist/components/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox.js +47 -0
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/flex.d.ts +91 -0
- package/dist/components/flex.d.ts.map +1 -0
- package/dist/components/flex.js +166 -0
- package/dist/components/flex.js.map +1 -0
- package/dist/components/g.d.ts +21 -0
- package/dist/components/g.d.ts.map +1 -0
- package/dist/components/g.js +52 -0
- package/dist/components/g.js.map +1 -0
- package/dist/components/input.d.ts +2 -0
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +21 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/jtable.d.ts +47 -0
- package/dist/components/jtable.d.ts.map +1 -0
- package/dist/components/jtable.js +307 -0
- package/dist/components/jtable.js.map +1 -0
- package/dist/components/link.d.ts +1 -0
- package/dist/components/link.d.ts.map +1 -1
- package/dist/components/link.js +17 -0
- package/dist/components/link.js.map +1 -1
- package/dist/components/list.d.ts +1 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +18 -0
- package/dist/components/list.js.map +1 -1
- package/dist/components/menu.d.ts +108 -0
- package/dist/components/menu.d.ts.map +1 -0
- package/dist/components/menu.js +665 -0
- package/dist/components/menu.js.map +1 -0
- package/dist/components/nav.d.ts +1 -0
- package/dist/components/nav.d.ts.map +1 -1
- package/dist/components/nav.js +19 -0
- package/dist/components/nav.js.map +1 -1
- package/dist/components/radio.d.ts +1 -0
- package/dist/components/radio.d.ts.map +1 -1
- package/dist/components/radio.js +23 -0
- package/dist/components/radio.js.map +1 -1
- package/dist/components/routes.d.ts +17 -0
- package/dist/components/routes.d.ts.map +1 -1
- package/dist/components/routes.js +86 -0
- package/dist/components/routes.js.map +1 -1
- package/dist/components/select.d.ts +1 -0
- package/dist/components/select.d.ts.map +1 -1
- package/dist/components/select.js +17 -0
- package/dist/components/select.js.map +1 -1
- package/dist/components/table.d.ts +1 -0
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +20 -0
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.d.ts +17 -1
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +50 -8
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/tag.d.ts +1 -0
- package/dist/components/tag.d.ts.map +1 -1
- package/dist/components/tag.js +16 -0
- package/dist/components/tag.js.map +1 -1
- package/dist/components/widgets/calendar.d.ts +74 -0
- package/dist/components/widgets/calendar.d.ts.map +1 -0
- package/dist/components/widgets/calendar.js +308 -0
- package/dist/components/widgets/calendar.js.map +1 -0
- package/dist/components/widgets/canvas-ai.d.ts +12 -0
- package/dist/components/widgets/canvas-ai.d.ts.map +1 -0
- package/dist/components/widgets/canvas-ai.js +97 -0
- package/dist/components/widgets/canvas-ai.js.map +1 -0
- package/dist/components/widgets/canvas-compile.d.ts +36 -0
- package/dist/components/widgets/canvas-compile.d.ts.map +1 -0
- package/dist/components/widgets/canvas-compile.js +379 -0
- package/dist/components/widgets/canvas-compile.js.map +1 -0
- package/dist/components/widgets/canvas-persist.d.ts +11 -0
- package/dist/components/widgets/canvas-persist.d.ts.map +1 -0
- package/dist/components/widgets/canvas-persist.js +60 -0
- package/dist/components/widgets/canvas-persist.js.map +1 -0
- package/dist/components/widgets/canvas-registry.d.ts +42 -0
- package/dist/components/widgets/canvas-registry.d.ts.map +1 -0
- package/dist/components/widgets/canvas-registry.js +338 -0
- package/dist/components/widgets/canvas-registry.js.map +1 -0
- package/dist/components/widgets/canvas-styles.d.ts +2 -0
- package/dist/components/widgets/canvas-styles.d.ts.map +1 -0
- package/dist/components/widgets/canvas-styles.js +215 -0
- package/dist/components/widgets/canvas-styles.js.map +1 -0
- package/dist/components/widgets/canvas.d.ts +125 -0
- package/dist/components/widgets/canvas.d.ts.map +1 -0
- package/dist/components/widgets/canvas.js +1359 -0
- package/dist/components/widgets/canvas.js.map +1 -0
- package/dist/components/widgets/sidebar.d.ts +100 -0
- package/dist/components/widgets/sidebar.d.ts.map +1 -0
- package/dist/components/widgets/sidebar.js +434 -0
- package/dist/components/widgets/sidebar.js.map +1 -0
- package/dist/components/widgets/stepper.d.ts +87 -0
- package/dist/components/widgets/stepper.d.ts.map +1 -0
- package/dist/components/widgets/stepper.js +388 -0
- package/dist/components/widgets/stepper.js.map +1 -0
- package/dist/generated/jux-registry.d.ts +24 -0
- package/dist/generated/jux-registry.d.ts.map +1 -0
- package/dist/generated/jux-registry.js +90 -0
- package/dist/generated/jux-registry.js.map +1 -0
- package/dist/index.d.ts +39 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -24
- package/dist/index.js.map +1 -1
- package/dist/state/pageState.d.ts +6 -0
- package/dist/state/pageState.d.ts.map +1 -1
- package/dist/state/pageState.js +24 -16
- package/dist/state/pageState.js.map +1 -1
- package/dist/styles/layout-regions-observer.d.ts +7 -0
- package/dist/styles/layout-regions-observer.d.ts.map +1 -0
- package/dist/styles/layout-regions-observer.js +52 -0
- package/dist/styles/layout-regions-observer.js.map +1 -0
- package/dist/utils/colors.d.ts +0 -3
- package/dist/utils/colors.d.ts.map +1 -1
- package/dist/utils/colors.js +20 -6
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/resolveContent.d.ts +11 -0
- package/dist/utils/resolveContent.d.ts.map +1 -0
- package/dist/utils/resolveContent.js +37 -0
- package/dist/utils/resolveContent.js.map +1 -0
- package/dist/utils/theme.d.ts +58 -0
- package/dist/utils/theme.d.ts.map +1 -0
- package/dist/utils/theme.js +172 -0
- package/dist/utils/theme.js.map +1 -0
- package/dist/widgets/canvas.d.ts +3 -69
- package/dist/widgets/canvas.d.ts.map +1 -1
- package/dist/widgets/canvas.js +3 -791
- package/dist/widgets/canvas.js.map +1 -1
- package/juxconfig.example.js +19 -0
- package/machinery/compiler4.js +103 -9
- package/machinery/errors-client.js +171 -67
- package/machinery/jux-errors.js +218 -0
- package/machinery/serve.js +67 -0
- package/package.json +1 -1
package/dist/widgets/canvas.js
CHANGED
|
@@ -1,792 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|