juxscript 1.1.401 → 1.1.402

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.
@@ -0,0 +1,641 @@
1
+ import { pageState } from '../state/pageState.js';
2
+ import generateId from '../utils/idgen.js';
3
+ // ═══════════════════════════════════════════════════════════
4
+ // DEFAULT INVENTORY
5
+ // ═══════════════════════════════════════════════════════════
6
+ const DEFAULT_INVENTORY = [
7
+ // Shapes
8
+ { type: 'c', label: 'Container (c)', category: 'shapes' },
9
+ { type: 'g', label: 'Group (g)', category: 'shapes' },
10
+ { type: 'flex', label: 'Flex', category: 'shapes' },
11
+ { type: 'flexRow', label: 'Flex Row', category: 'shapes' },
12
+ { type: 'flexCol', label: 'Flex Col', category: 'shapes' },
13
+ // Widgets
14
+ { type: 'sidebar', label: 'Sidebar', category: 'widgets' },
15
+ { type: 'calendar', label: 'Calendar', category: 'widgets' },
16
+ { type: 'stepper', label: 'Stepper', category: 'widgets' },
17
+ { type: 'tabs', label: 'Tabs', category: 'widgets' },
18
+ // Components (primitives)
19
+ { type: 'button', label: 'Button', category: 'components' },
20
+ { type: 'input', label: 'Input', category: 'components' },
21
+ { type: 'select', label: 'Select', category: 'components' },
22
+ { type: 'checkbox', label: 'Checkbox', category: 'components' },
23
+ { type: 'radio', label: 'Radio', category: 'components' },
24
+ { type: 'list', label: 'List', category: 'components' },
25
+ { type: 'table', label: 'Table', category: 'components' },
26
+ { type: 'nav', label: 'Nav', category: 'components' },
27
+ { type: 'link', label: 'Link', category: 'components' },
28
+ { type: 'tag', label: 'Tag', category: 'components' },
29
+ { type: 'h1', label: 'H1', category: 'components' },
30
+ { type: 'p', label: 'Paragraph', category: 'components' },
31
+ { type: 'span', label: 'Span', category: 'components' },
32
+ ];
33
+ // ═══════════════════════════════════════════════════════════
34
+ // CODE TEMPLATES
35
+ // ═══════════════════════════════════════════════════════════
36
+ function defaultCode(type, id) {
37
+ switch (type) {
38
+ case 'c': return `c('100%', 'auto', '16px')`;
39
+ case 'g': return `g([])`;
40
+ case 'flex': return `flex('row', '8px')`;
41
+ case 'flexRow': return `flexRow('8px')`;
42
+ case 'flexCol': return `flexCol('8px')`;
43
+ case 'sidebar': return `jux.sidebar('${id}').title('Nav').section('Pages', ['Home', 'About']).render()`;
44
+ case 'calendar': return `jux.calendar('${id}').render()`;
45
+ case 'stepper': return `jux.stepper('${id}').render()`;
46
+ case 'tabs': return `jux.tabs('${id}', { items: [{ label: 'Tab 1' }, { label: 'Tab 2' }] })`;
47
+ case 'button': return `jux.btn('${id}', { content: 'Click me' })`;
48
+ case 'input': return `jux.input('${id}', { label: 'Label', placeholder: 'Type...' })`;
49
+ case 'select': return `jux.select('${id}', { label: 'Choose', items: [{ label: 'A', value: 'a' }] })`;
50
+ case 'checkbox': return `jux.checkbox('${id}', { label: 'Check me' })`;
51
+ case 'radio': return `jux.radio('${id}', { label: 'Options', items: ['A', 'B'] })`;
52
+ case 'list': return `jux.list('${id}')`;
53
+ case 'table': return `jux.table('${id}', { columns: ['Name', 'Value'] })`;
54
+ case 'nav': return `jux.nav('${id}', { items: ['Home', 'About'] })`;
55
+ case 'link': return `jux.link('${id}', { content: 'Link', href: '#' })`;
56
+ case 'tag': return `jux.div('${id}', { content: 'Content' })`;
57
+ case 'h1': return `jux.h1('${id}', { content: 'Heading' })`;
58
+ case 'p': return `jux.p('${id}', { content: 'Paragraph text' })`;
59
+ case 'span': return `jux.span('${id}', { content: 'Inline text' })`;
60
+ default: return `jux.${type}('${id}')`;
61
+ }
62
+ }
63
+ let canvasStylesInjected = false;
64
+ // ═══════════════════════════════════════════════════════════
65
+ // CANVAS CLASS
66
+ // ═══════════════════════════════════════════════════════════
67
+ class Canvas {
68
+ constructor(id, options = {}) {
69
+ this._el = null;
70
+ this._items = [];
71
+ this._selectedId = null;
72
+ this._activeTab = 'design';
73
+ this._onChange = null;
74
+ this._canvasEl = null;
75
+ this._inventoryEl = null;
76
+ this._propertiesEl = null;
77
+ this._counter = 0;
78
+ this.id = id || generateId('canvas');
79
+ this._opts = {
80
+ title: options.title || 'Canvas Builder',
81
+ width: options.width || '100%',
82
+ height: options.height || '100vh',
83
+ inventoryWidth: options.inventoryWidth || '220px',
84
+ propertiesWidth: options.propertiesWidth || '280px',
85
+ ...options,
86
+ };
87
+ }
88
+ // ═══════════════════════════════════════════════════════════
89
+ // FLUENT API
90
+ // ═══════════════════════════════════════════════════════════
91
+ title(val) { this._opts.title = val; return this; }
92
+ width(val) { this._opts.width = val; return this; }
93
+ height(val) { this._opts.height = val; return this; }
94
+ inventoryWidth(val) { this._opts.inventoryWidth = val; return this; }
95
+ propertiesWidth(val) { this._opts.propertiesWidth = val; return this; }
96
+ style(val) { this._opts.style = val; return this; }
97
+ class(val) { this._opts.class = val; return this; }
98
+ onChange(fn) {
99
+ this._onChange = fn;
100
+ return this;
101
+ }
102
+ // ═══════════════════════════════════════════════════════════
103
+ // PAGESTATE
104
+ // ═══════════════════════════════════════════════════════════
105
+ getValue() { return this._selectedId; }
106
+ setValue(val) { this._selectItem(val); return this; }
107
+ getElement() { return this._el; }
108
+ getTitle() { return this._opts.title; }
109
+ getWidth() { return this._opts.width; }
110
+ getHeight() { return this._opts.height; }
111
+ getInventoryWidth() { return this._opts.inventoryWidth; }
112
+ getPropertiesWidth() { return this._opts.propertiesWidth; }
113
+ getClass() { return this._opts.class || ''; }
114
+ getStyle() { return this._opts.style || ''; }
115
+ getItems() { return [...this._items]; }
116
+ getSelectedItem() { return this._items.find(i => i.id === this._selectedId) || null; }
117
+ setTitle(val) { return this.title(val); }
118
+ setWidth(val) { return this.width(val); }
119
+ setHeight(val) { return this.height(val); }
120
+ setInventoryWidth(val) { return this.inventoryWidth(val); }
121
+ setPropertiesWidth(val) { return this.propertiesWidth(val); }
122
+ setClass(val) { return this.class(val); }
123
+ setStyle(val) { return this.style(val); }
124
+ get state() { return pageState[this.id]; }
125
+ // ═══════════════════════════════════════════════════════════
126
+ // ACTIONS
127
+ // ═══════════════════════════════════════════════════════════
128
+ addItemToCanvas(type, category, x = 20, y = 20) {
129
+ this._counter++;
130
+ const itemId = `${type}-${this._counter}`;
131
+ const entry = DEFAULT_INVENTORY.find(e => e.type === type);
132
+ const item = {
133
+ id: itemId,
134
+ type,
135
+ category,
136
+ label: entry?.label || type,
137
+ x,
138
+ y,
139
+ code: defaultCode(type, itemId),
140
+ };
141
+ this._items.push(item);
142
+ this._renderCanvasItems();
143
+ this._selectItem(itemId);
144
+ if (this._onChange)
145
+ this._onChange(item);
146
+ return this;
147
+ }
148
+ removeItemFromCanvas(itemId) {
149
+ this._items = this._items.filter(i => i.id !== itemId);
150
+ if (this._selectedId === itemId)
151
+ this._selectedId = null;
152
+ this._renderCanvasItems();
153
+ this._renderProperties();
154
+ if (this._onChange)
155
+ this._onChange(null);
156
+ return this;
157
+ }
158
+ updateItemCode(itemId, code) {
159
+ const item = this._items.find(i => i.id === itemId);
160
+ if (item)
161
+ item.code = code;
162
+ return this;
163
+ }
164
+ // ═══════════════════════════════════════════════════════════
165
+ // RENDER
166
+ // ═══════════════════════════════════════════════════════════
167
+ render(target) {
168
+ this._injectStyles();
169
+ this._el = this._buildDOM();
170
+ let container = null;
171
+ if (target && typeof target === 'object' && 'element' in target)
172
+ container = target.element;
173
+ else if (target instanceof HTMLElement)
174
+ container = target;
175
+ else if (typeof target === 'string')
176
+ container = document.getElementById(target) || document.querySelector(target);
177
+ else
178
+ container = document.getElementById('app');
179
+ if (container && this._el) {
180
+ container.appendChild(this._el);
181
+ }
182
+ return this;
183
+ }
184
+ into(target) { return this.render(target); }
185
+ destroy() {
186
+ pageState.__unregister(this.id);
187
+ this._el?.remove();
188
+ }
189
+ // ═══════════════════════════════════════════════════════════
190
+ // INTERNAL — DOM
191
+ // ═══════════════════════════════════════════════════════════
192
+ _buildDOM() {
193
+ const el = document.createElement('div');
194
+ el.id = this.id;
195
+ el.className = 'jux-canvas' + (this._opts.class ? ' ' + this._opts.class : '');
196
+ el.style.cssText = `width:${this._opts.width};height:${this._opts.height};display:flex;${this._opts.style || ''}`;
197
+ // ── Inventory Panel ──
198
+ this._inventoryEl = this._buildInventoryPanel();
199
+ el.appendChild(this._inventoryEl);
200
+ // ── Canvas Area ──
201
+ const canvasWrapper = document.createElement('div');
202
+ canvasWrapper.className = 'jux-canvas-center';
203
+ canvasWrapper.style.cssText = `flex:1;display:flex;flex-direction:column;overflow:hidden;`;
204
+ // Header
205
+ const header = document.createElement('div');
206
+ header.className = 'jux-canvas-header';
207
+ header.textContent = this._opts.title;
208
+ canvasWrapper.appendChild(header);
209
+ // Canvas drop area
210
+ this._canvasEl = document.createElement('div');
211
+ this._canvasEl.className = 'jux-canvas-area';
212
+ this._canvasEl.setAttribute('data-empty', 'true');
213
+ const emptyMsg = document.createElement('div');
214
+ emptyMsg.className = 'jux-canvas-empty';
215
+ emptyMsg.textContent = 'Drag components from the inventory or click to add';
216
+ this._canvasEl.appendChild(emptyMsg);
217
+ // Drop handling
218
+ this._canvasEl.addEventListener('dragover', (e) => {
219
+ e.preventDefault();
220
+ this._canvasEl.classList.add('jux-canvas-area--dragover');
221
+ });
222
+ this._canvasEl.addEventListener('dragleave', () => {
223
+ this._canvasEl.classList.remove('jux-canvas-area--dragover');
224
+ });
225
+ this._canvasEl.addEventListener('drop', (e) => {
226
+ e.preventDefault();
227
+ this._canvasEl.classList.remove('jux-canvas-area--dragover');
228
+ const type = e.dataTransfer?.getData('text/plain');
229
+ const category = e.dataTransfer?.getData('application/x-category');
230
+ if (type && category) {
231
+ const rect = this._canvasEl.getBoundingClientRect();
232
+ this.addItemToCanvas(type, category, e.clientX - rect.left, e.clientY - rect.top);
233
+ }
234
+ });
235
+ canvasWrapper.appendChild(this._canvasEl);
236
+ el.appendChild(canvasWrapper);
237
+ // ── Properties Panel ──
238
+ this._propertiesEl = this._buildPropertiesPanel();
239
+ el.appendChild(this._propertiesEl);
240
+ return el;
241
+ }
242
+ // ── Inventory ──
243
+ _buildInventoryPanel() {
244
+ const panel = document.createElement('div');
245
+ panel.className = 'jux-canvas-inventory';
246
+ panel.style.width = this._opts.inventoryWidth;
247
+ const panelTitle = document.createElement('div');
248
+ panelTitle.className = 'jux-canvas-panel-title';
249
+ panelTitle.textContent = 'Inventory';
250
+ panel.appendChild(panelTitle);
251
+ const categories = [
252
+ { key: 'shapes', label: 'Shapes', icon: '◻' },
253
+ { key: 'widgets', label: 'Widgets', icon: '⚙' },
254
+ { key: 'components', label: 'Components', icon: '◈' },
255
+ ];
256
+ for (const cat of categories) {
257
+ const section = document.createElement('div');
258
+ section.className = 'jux-canvas-inv-section';
259
+ const sectionLabel = document.createElement('div');
260
+ sectionLabel.className = 'jux-canvas-inv-label';
261
+ sectionLabel.textContent = `${cat.icon} ${cat.label}`;
262
+ section.appendChild(sectionLabel);
263
+ const entries = DEFAULT_INVENTORY.filter(e => e.category === cat.key);
264
+ for (const entry of entries) {
265
+ const item = document.createElement('div');
266
+ item.className = 'jux-canvas-inv-item';
267
+ item.textContent = entry.label;
268
+ item.setAttribute('draggable', 'true');
269
+ item.addEventListener('dragstart', (e) => {
270
+ e.dataTransfer?.setData('text/plain', entry.type);
271
+ e.dataTransfer?.setData('application/x-category', entry.category);
272
+ item.classList.add('jux-canvas-inv-item--dragging');
273
+ });
274
+ item.addEventListener('dragend', () => {
275
+ item.classList.remove('jux-canvas-inv-item--dragging');
276
+ });
277
+ // Click to add
278
+ item.addEventListener('click', () => {
279
+ this.addItemToCanvas(entry.type, entry.category);
280
+ });
281
+ section.appendChild(item);
282
+ }
283
+ panel.appendChild(section);
284
+ }
285
+ return panel;
286
+ }
287
+ // ── Properties Panel ──
288
+ _buildPropertiesPanel() {
289
+ const panel = document.createElement('div');
290
+ panel.className = 'jux-canvas-properties';
291
+ panel.style.width = this._opts.propertiesWidth;
292
+ const panelTitle = document.createElement('div');
293
+ panelTitle.className = 'jux-canvas-panel-title';
294
+ panelTitle.textContent = 'Properties';
295
+ panel.appendChild(panelTitle);
296
+ const content = document.createElement('div');
297
+ content.className = 'jux-canvas-props-content';
298
+ content.innerHTML = '<div class="jux-canvas-props-empty">Select a component</div>';
299
+ panel.appendChild(content);
300
+ return panel;
301
+ }
302
+ // ── Canvas Items ──
303
+ _renderCanvasItems() {
304
+ if (!this._canvasEl)
305
+ return;
306
+ this._canvasEl.innerHTML = '';
307
+ if (this._items.length === 0) {
308
+ this._canvasEl.setAttribute('data-empty', 'true');
309
+ const emptyMsg = document.createElement('div');
310
+ emptyMsg.className = 'jux-canvas-empty';
311
+ emptyMsg.textContent = 'Drag components from the inventory or click to add';
312
+ this._canvasEl.appendChild(emptyMsg);
313
+ return;
314
+ }
315
+ this._canvasEl.removeAttribute('data-empty');
316
+ for (const item of this._items) {
317
+ const el = document.createElement('div');
318
+ el.className = 'jux-canvas-item' + (item.id === this._selectedId ? ' jux-canvas-item--selected' : '');
319
+ el.setAttribute('data-item-id', item.id);
320
+ el.style.cssText = `left:${item.x}px;top:${item.y}px;`;
321
+ // Category badge
322
+ const badge = document.createElement('span');
323
+ badge.className = `jux-canvas-item-badge jux-canvas-item-badge--${item.category}`;
324
+ badge.textContent = item.category === 'shapes' ? '◻' : item.category === 'widgets' ? '⚙' : '◈';
325
+ el.appendChild(badge);
326
+ const label = document.createElement('span');
327
+ label.className = 'jux-canvas-item-label';
328
+ label.textContent = item.label;
329
+ el.appendChild(label);
330
+ // Remove button
331
+ const removeBtn = document.createElement('span');
332
+ removeBtn.className = 'jux-canvas-item-remove';
333
+ removeBtn.textContent = '×';
334
+ removeBtn.addEventListener('click', (e) => {
335
+ e.stopPropagation();
336
+ this.removeItemFromCanvas(item.id);
337
+ });
338
+ el.appendChild(removeBtn);
339
+ // Select on click
340
+ el.addEventListener('click', () => this._selectItem(item.id));
341
+ // Drag to reposition
342
+ el.setAttribute('draggable', 'true');
343
+ el.addEventListener('dragstart', (e) => {
344
+ e.dataTransfer?.setData('application/x-canvas-item', item.id);
345
+ e.dataTransfer.effectAllowed = 'move';
346
+ });
347
+ this._canvasEl.appendChild(el);
348
+ }
349
+ // Internal reposition drop handler
350
+ this._canvasEl.addEventListener('drop', (e) => {
351
+ const itemId = e.dataTransfer?.getData('application/x-canvas-item');
352
+ if (itemId) {
353
+ const movedItem = this._items.find(i => i.id === itemId);
354
+ if (movedItem) {
355
+ const rect = this._canvasEl.getBoundingClientRect();
356
+ movedItem.x = e.clientX - rect.left;
357
+ movedItem.y = e.clientY - rect.top;
358
+ this._renderCanvasItems();
359
+ this._selectItem(itemId);
360
+ }
361
+ }
362
+ });
363
+ }
364
+ // ── Selection & Properties ──
365
+ _selectItem(itemId) {
366
+ this._selectedId = itemId;
367
+ this._activeTab = 'design';
368
+ // Update selection highlight
369
+ if (this._canvasEl) {
370
+ this._canvasEl.querySelectorAll('.jux-canvas-item').forEach(el => {
371
+ el.classList.toggle('jux-canvas-item--selected', el.getAttribute('data-item-id') === itemId);
372
+ });
373
+ }
374
+ this._renderProperties();
375
+ if (this._onChange) {
376
+ this._onChange(this._items.find(i => i.id === itemId) || null);
377
+ }
378
+ }
379
+ _renderProperties() {
380
+ if (!this._propertiesEl)
381
+ return;
382
+ const content = this._propertiesEl.querySelector('.jux-canvas-props-content');
383
+ if (!content)
384
+ return;
385
+ const item = this._items.find(i => i.id === this._selectedId);
386
+ if (!item) {
387
+ content.innerHTML = '<div class="jux-canvas-props-empty">Select a component</div>';
388
+ return;
389
+ }
390
+ content.innerHTML = '';
391
+ // ── Tab Bar ──
392
+ const tabBar = document.createElement('div');
393
+ tabBar.className = 'jux-canvas-tab-bar';
394
+ const tabs = [
395
+ { key: 'design', label: 'Design' },
396
+ { key: 'code', label: 'Code' },
397
+ { key: 'state', label: 'State' },
398
+ ];
399
+ for (const tab of tabs) {
400
+ const btn = document.createElement('button');
401
+ btn.className = 'jux-canvas-tab' + (tab.key === this._activeTab ? ' jux-canvas-tab--active' : '');
402
+ btn.textContent = tab.label;
403
+ btn.addEventListener('click', () => {
404
+ this._activeTab = tab.key;
405
+ this._renderProperties();
406
+ });
407
+ tabBar.appendChild(btn);
408
+ }
409
+ content.appendChild(tabBar);
410
+ // ── Tab Content ──
411
+ const tabContent = document.createElement('div');
412
+ tabContent.className = 'jux-canvas-tab-content';
413
+ if (this._activeTab === 'design') {
414
+ this._renderDesignTab(tabContent, item);
415
+ }
416
+ else if (this._activeTab === 'code') {
417
+ this._renderCodeTab(tabContent, item);
418
+ }
419
+ else {
420
+ this._renderStateTab(tabContent, item);
421
+ }
422
+ content.appendChild(tabContent);
423
+ }
424
+ _renderDesignTab(container, item) {
425
+ // Header
426
+ const header = document.createElement('div');
427
+ header.className = 'jux-canvas-prop-header';
428
+ header.innerHTML = `<strong>${item.label}</strong><span class="jux-canvas-prop-id">${item.id}</span>`;
429
+ container.appendChild(header);
430
+ // Properties
431
+ const props = this._getPropertiesForType(item.type);
432
+ for (const prop of props) {
433
+ const row = document.createElement('div');
434
+ row.className = 'jux-canvas-prop-row';
435
+ const label = document.createElement('label');
436
+ label.className = 'jux-canvas-prop-label';
437
+ label.textContent = prop.name;
438
+ row.appendChild(label);
439
+ const input = document.createElement('input');
440
+ input.className = 'jux-canvas-prop-input';
441
+ input.type = prop.inputType || 'text';
442
+ input.value = prop.defaultValue || '';
443
+ input.placeholder = prop.placeholder || '';
444
+ input.addEventListener('change', () => {
445
+ // Update the code template with new value
446
+ this._updatePropertyInCode(item, prop.name, input.value);
447
+ });
448
+ row.appendChild(input);
449
+ container.appendChild(row);
450
+ }
451
+ }
452
+ _renderCodeTab(container, item) {
453
+ const label = document.createElement('div');
454
+ label.className = 'jux-canvas-code-label';
455
+ label.textContent = 'Component Code';
456
+ container.appendChild(label);
457
+ const textarea = document.createElement('textarea');
458
+ textarea.className = 'jux-canvas-code-editor';
459
+ textarea.value = item.code;
460
+ textarea.spellcheck = false;
461
+ textarea.addEventListener('change', () => {
462
+ this.updateItemCode(item.id, textarea.value);
463
+ });
464
+ container.appendChild(textarea);
465
+ // Full page code preview
466
+ const previewLabel = document.createElement('div');
467
+ previewLabel.className = 'jux-canvas-code-label';
468
+ previewLabel.style.marginTop = '12px';
469
+ previewLabel.textContent = 'Full Page Code';
470
+ container.appendChild(previewLabel);
471
+ const preview = document.createElement('pre');
472
+ preview.className = 'jux-canvas-code-preview';
473
+ preview.textContent = this._generateFullCode();
474
+ container.appendChild(preview);
475
+ }
476
+ _renderStateTab(container, item) {
477
+ const header = document.createElement('div');
478
+ header.className = 'jux-canvas-prop-header';
479
+ header.innerHTML = `<strong>PageState</strong><span class="jux-canvas-prop-id">pageState['${item.id}']</span>`;
480
+ container.appendChild(header);
481
+ // Available state properties
482
+ const stateProps = [
483
+ { prop: 'value', desc: 'Current value of the component' },
484
+ { prop: 'content', desc: 'Text content' },
485
+ { prop: 'visible', desc: 'Visibility toggle' },
486
+ { prop: 'disabled', desc: 'Disabled state' },
487
+ { prop: 'click', desc: 'Click event flag' },
488
+ { prop: 'change', desc: 'Change event flag' },
489
+ { prop: 'focus', desc: 'Focus event flag' },
490
+ { prop: 'hover', desc: 'Hover state flag' },
491
+ ];
492
+ for (const sp of stateProps) {
493
+ const row = document.createElement('div');
494
+ row.className = 'jux-canvas-state-row';
495
+ row.innerHTML = `<code>pageState['${item.id}'].${sp.prop}</code><span class="jux-canvas-state-desc">${sp.desc}</span>`;
496
+ container.appendChild(row);
497
+ }
498
+ // Interaction example
499
+ const exLabel = document.createElement('div');
500
+ exLabel.className = 'jux-canvas-code-label';
501
+ exLabel.style.marginTop = '12px';
502
+ exLabel.textContent = 'Example Interaction';
503
+ container.appendChild(exLabel);
504
+ const example = document.createElement('pre');
505
+ example.className = 'jux-canvas-code-preview';
506
+ example.textContent = this._generateInteractionExample(item);
507
+ container.appendChild(example);
508
+ }
509
+ // ── Property Definitions ──
510
+ _getPropertiesForType(type) {
511
+ const common = [
512
+ { name: 'class', placeholder: 'CSS class' },
513
+ { name: 'style', placeholder: 'CSS inline style' },
514
+ ];
515
+ switch (type) {
516
+ case 'button':
517
+ return [{ name: 'content', placeholder: 'Button text' }, { name: 'variant', placeholder: 'default | outline | ghost' }, ...common];
518
+ case 'input':
519
+ return [{ name: 'label', placeholder: 'Field label' }, { name: 'placeholder', placeholder: 'Placeholder' }, { name: 'value', placeholder: 'Default value' }, ...common];
520
+ case 'select':
521
+ return [{ name: 'label', placeholder: 'Field label' }, { name: 'placeholder', placeholder: 'Placeholder' }, ...common];
522
+ case 'checkbox':
523
+ return [{ name: 'label', placeholder: 'Checkbox label' }, ...common];
524
+ case 'radio':
525
+ return [{ name: 'label', placeholder: 'Group label' }, ...common];
526
+ case 'h1':
527
+ case 'p':
528
+ case 'span':
529
+ case 'tag':
530
+ return [{ name: 'content', placeholder: 'Text content' }, { name: 'tag', placeholder: 'HTML tag' }, ...common];
531
+ case 'link':
532
+ return [{ name: 'content', placeholder: 'Link text' }, { name: 'href', placeholder: 'URL' }, ...common];
533
+ case 'c':
534
+ return [{ name: 'width', placeholder: '100%' }, { name: 'height', placeholder: 'auto' }, { name: 'padding', placeholder: '16px' }, ...common];
535
+ case 'flex':
536
+ case 'flexRow':
537
+ case 'flexCol':
538
+ return [{ name: 'gap', placeholder: '8px' }, { name: 'justify', placeholder: 'flex-start' }, { name: 'align', placeholder: 'stretch' }, ...common];
539
+ case 'sidebar':
540
+ return [{ name: 'title', placeholder: 'App Name' }, { name: 'width', placeholder: '260px' }, ...common];
541
+ case 'tabs':
542
+ return [{ name: 'defaultTab', placeholder: 'First tab ID' }, ...common];
543
+ default:
544
+ return common;
545
+ }
546
+ }
547
+ _updatePropertyInCode(item, propName, value) {
548
+ const escapedValue = JSON.stringify(value).slice(1, -1);
549
+ const escapedProp = propName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
550
+ if (item.code.includes(`${propName}:`)) {
551
+ item.code = item.code.replace(new RegExp(`${escapedProp}:\\s*'[^']*'`), `${propName}: '${escapedValue}'`);
552
+ }
553
+ }
554
+ // ── Code Generation ──
555
+ _generateFullCode() {
556
+ const lines = [
557
+ "import { jux, pageState, c, g, flex } from 'juxscript';",
558
+ '',
559
+ ];
560
+ for (const item of this._items) {
561
+ lines.push(`const ${item.id.replace(/-/g, '_')} = ${item.code};`);
562
+ }
563
+ return lines.join('\n');
564
+ }
565
+ _generateInteractionExample(item) {
566
+ const others = this._items.filter(i => i.id !== item.id);
567
+ if (others.length === 0) {
568
+ return `if (pageState['${item.id}'].click) {\n console.log('${item.label} clicked!');\n}`;
569
+ }
570
+ const other = others[0];
571
+ return `if (pageState['${item.id}'].click) {\n pageState['${other.id}'].value = 'Updated!';\n}`;
572
+ }
573
+ // ═══════════════════════════════════════════════════════════
574
+ // STYLES
575
+ // ═══════════════════════════════════════════════════════════
576
+ _injectStyles() {
577
+ if (canvasStylesInjected)
578
+ return;
579
+ canvasStylesInjected = true;
580
+ const s = document.createElement('style');
581
+ s.id = 'jux-canvas-styles';
582
+ s.textContent = `
583
+ .jux-canvas{font-family:var(--font-sans,system-ui,sans-serif);background:hsl(var(--background,0 0% 100%));color:hsl(var(--foreground,222.2 84% 4.9%));border:1px solid hsl(var(--border,214.3 31.8% 91.4%));border-radius:var(--radius,8px);overflow:hidden}
584
+ .jux-canvas-header{padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid hsl(var(--border,214.3 31.8% 91.4%));background:hsl(var(--card,0 0% 100%))}
585
+ .jux-canvas-center{min-width:0}
586
+ .jux-canvas-area{flex:1;position:relative;overflow:auto;background:hsl(var(--muted,210 40% 96.1%));background-image:radial-gradient(circle,hsl(var(--border,214.3 31.8% 91.4%)) 1px,transparent 1px);background-size:20px 20px;min-height:200px;transition:background .15s}
587
+ .jux-canvas-area--dragover{background-color:hsl(var(--accent,210 40% 93%))}
588
+ .jux-canvas-area[data-empty]{display:flex;align-items:center;justify-content:center}
589
+ .jux-canvas-empty{color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));font-size:13px;text-align:center;padding:40px}
590
+ .jux-canvas-item{position:absolute;display:flex;align-items:center;gap:6px;padding:6px 10px;background:hsl(var(--card,0 0% 100%));border:1px solid hsl(var(--border,214.3 31.8% 91.4%));border-radius:var(--radius,6px);cursor:pointer;font-size:12px;box-shadow:0 1px 3px rgba(0,0,0,.06);transition:box-shadow .15s,border-color .15s;user-select:none;white-space:nowrap}
591
+ .jux-canvas-item:hover{box-shadow:0 2px 6px rgba(0,0,0,.1)}
592
+ .jux-canvas-item--selected{border-color:hsl(var(--primary,222.2 47.4% 11.2%));box-shadow:0 0 0 2px hsl(var(--ring,222.2 84% 4.9%)/0.15)}
593
+ .jux-canvas-item-badge{width:18px;height:18px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:10px;flex-shrink:0}
594
+ .jux-canvas-item-badge--shapes{background:hsl(217 91% 93%);color:hsl(217 91% 40%)}
595
+ .jux-canvas-item-badge--widgets{background:hsl(142 71% 90%);color:hsl(142 71% 35%)}
596
+ .jux-canvas-item-badge--components{background:hsl(25 95% 91%);color:hsl(25 95% 40%)}
597
+ .jux-canvas-item-label{font-weight:500}
598
+ .jux-canvas-item-remove{margin-left:auto;width:16px;height:16px;display:flex;align-items:center;justify-content:center;font-size:14px;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));cursor:pointer;border-radius:3px}
599
+ .jux-canvas-item-remove:hover{background:hsl(var(--destructive,0 84.2% 60.2%));color:white}
600
+ .jux-canvas-inventory,.jux-canvas-properties{border-right:1px solid hsl(var(--border,214.3 31.8% 91.4%));background:hsl(var(--card,0 0% 100%));overflow-y:auto;flex-shrink:0}
601
+ .jux-canvas-properties{border-right:none;border-left:1px solid hsl(var(--border,214.3 31.8% 91.4%))}
602
+ .jux-canvas-panel-title{padding:10px 12px;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));border-bottom:1px solid hsl(var(--border,214.3 31.8% 91.4%))}
603
+ .jux-canvas-inv-section{padding:4px 0}
604
+ .jux-canvas-inv-label{padding:6px 12px 2px;font-size:11px;font-weight:600;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));text-transform:uppercase;letter-spacing:.04em}
605
+ .jux-canvas-inv-item{padding:5px 12px 5px 20px;font-size:12px;cursor:pointer;transition:background .12s;user-select:none;white-space:nowrap}
606
+ .jux-canvas-inv-item:hover{background:hsl(var(--accent,210 40% 93%))}
607
+ .jux-canvas-inv-item--dragging{opacity:.5}
608
+ .jux-canvas-props-content{padding:0}
609
+ .jux-canvas-props-empty{color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));font-size:12px;padding:20px;text-align:center}
610
+ .jux-canvas-tab-bar{display:flex;border-bottom:1px solid hsl(var(--border,214.3 31.8% 91.4%))}
611
+ .jux-canvas-tab{flex:1;padding:7px 0;text-align:center;font-size:11px;font-weight:500;cursor:pointer;border:none;background:none;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));transition:color .12s,border-color .12s;border-bottom:2px solid transparent}
612
+ .jux-canvas-tab:hover{color:hsl(var(--foreground,222.2 84% 4.9%))}
613
+ .jux-canvas-tab--active{color:hsl(var(--foreground,222.2 84% 4.9%));border-bottom-color:hsl(var(--primary,222.2 47.4% 11.2%))}
614
+ .jux-canvas-tab-content{padding:10px}
615
+ .jux-canvas-prop-header{margin-bottom:10px;font-size:12px;display:flex;align-items:center;gap:8px}
616
+ .jux-canvas-prop-id{font-size:10px;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));font-family:monospace;background:hsl(var(--muted,210 40% 96.1%));padding:1px 5px;border-radius:3px}
617
+ .jux-canvas-prop-row{display:flex;align-items:center;gap:6px;margin-bottom:6px}
618
+ .jux-canvas-prop-label{font-size:11px;font-weight:500;width:60px;flex-shrink:0;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%))}
619
+ .jux-canvas-prop-input{flex:1;padding:4px 6px;font-size:11px;border:1px solid hsl(var(--border,214.3 31.8% 91.4%));border-radius:4px;background:hsl(var(--background,0 0% 100%));color:hsl(var(--foreground,222.2 84% 4.9%));font-family:inherit}
620
+ .jux-canvas-prop-input:focus{outline:none;border-color:hsl(var(--ring,222.2 84% 4.9%))}
621
+ .jux-canvas-code-label{font-size:11px;font-weight:600;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%));margin-bottom:4px}
622
+ .jux-canvas-code-editor{width:100%;min-height:80px;padding:6px;font-size:11px;font-family:'Fira Code',monospace;border:1px solid hsl(var(--border,214.3 31.8% 91.4%));border-radius:4px;background:hsl(var(--muted,210 40% 96.1%));color:hsl(var(--foreground,222.2 84% 4.9%));resize:vertical;box-sizing:border-box}
623
+ .jux-canvas-code-preview{font-size:10px;font-family:'Fira Code',monospace;background:hsl(var(--muted,210 40% 96.1%));padding:8px;border-radius:4px;overflow-x:auto;white-space:pre;margin:0;color:hsl(var(--foreground,222.2 84% 4.9%));border:1px solid hsl(var(--border,214.3 31.8% 91.4%))}
624
+ .jux-canvas-state-row{padding:4px 0;font-size:11px;display:flex;flex-direction:column;gap:1px;border-bottom:1px solid hsl(var(--border,214.3 31.8% 91.4%)/0.5)}
625
+ .jux-canvas-state-row code{font-family:'Fira Code',monospace;font-size:10px;color:hsl(var(--primary,222.2 47.4% 11.2%))}
626
+ .jux-canvas-state-desc{font-size:10px;color:hsl(var(--muted-foreground,215.4 16.3% 46.9%))}
627
+ `;
628
+ document.head.appendChild(s);
629
+ }
630
+ }
631
+ // ═══════════════════════════════════════════════════════════
632
+ // FACTORY
633
+ // ═══════════════════════════════════════════════════════════
634
+ export function canvas(id, options = {}) {
635
+ const cv = new Canvas(id, options);
636
+ pageState.__register(cv);
637
+ return cv;
638
+ }
639
+ export { Canvas };
640
+ export default canvas;
641
+ //# sourceMappingURL=canvas.js.map