@zentto/studio 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/designer/zs-app-wizard.js +562 -562
- package/dist/designer/zs-page-designer.d.ts +8 -0
- package/dist/designer/zs-page-designer.d.ts.map +1 -1
- package/dist/designer/zs-page-designer.js +1053 -991
- package/dist/designer/zs-page-designer.js.map +1 -1
- package/dist/fields/zs-field-datagrid.js +58 -58
- package/dist/fields/zs-field-report.js +52 -52
- package/dist/zentto-studio-renderer.js +307 -307
- package/package.json +74 -70
|
@@ -101,493 +101,493 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
101
101
|
this.apiCompany = '';
|
|
102
102
|
this.apiBranch = '';
|
|
103
103
|
}
|
|
104
|
-
static { this.styles = css `
|
|
105
|
-
:host {
|
|
106
|
-
display: block; height: 100%;
|
|
107
|
-
--zrd-bg: ${unsafeCSS(BG)};
|
|
108
|
-
--zrd-panel-bg: ${unsafeCSS(PANEL_BG)};
|
|
109
|
-
--zrd-border: ${unsafeCSS(BORDER)};
|
|
110
|
-
--zrd-accent: ${unsafeCSS(ACCENT)};
|
|
111
|
-
--zrd-accent-light: ${unsafeCSS(ACCENT_LIGHT)};
|
|
112
|
-
--zrd-text: ${unsafeCSS(TEXT)};
|
|
113
|
-
--zrd-text-muted: ${unsafeCSS(TEXT_MUTED)};
|
|
114
|
-
--zrd-danger: ${unsafeCSS(DANGER)};
|
|
115
|
-
font-family: 'Segoe UI', Roboto, Arial, sans-serif;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* ─── Layout ──────────────────────────────── */
|
|
119
|
-
.designer {
|
|
120
|
-
display: grid;
|
|
121
|
-
grid-template-rows: auto 1fr;
|
|
122
|
-
grid-template-columns: 200px 4px 1fr 4px 240px;
|
|
123
|
-
height: 100%; background: var(--zrd-bg);
|
|
124
|
-
overflow: hidden;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* ─── Toolbar ─────────────────────────────── */
|
|
128
|
-
.toolbar {
|
|
129
|
-
grid-column: 1 / -1;
|
|
130
|
-
display: flex; align-items: center; gap: 8px;
|
|
131
|
-
padding: 6px 12px; min-height: 36px;
|
|
132
|
-
background: var(--zrd-panel-bg);
|
|
133
|
-
border-bottom: 1px solid var(--zrd-border);
|
|
134
|
-
flex-wrap: wrap;
|
|
135
|
-
}
|
|
136
|
-
.toolbar-sep { width: 1px; height: 20px; background: var(--zrd-border); margin: 0 2px; }
|
|
137
|
-
.tb-btn {
|
|
138
|
-
background: none; border: 1px solid var(--zrd-border);
|
|
139
|
-
border-radius: 4px; padding: 4px 10px;
|
|
140
|
-
cursor: pointer; font-size: 12px; color: var(--zrd-text);
|
|
141
|
-
font-family: inherit; transition: background 0.15s;
|
|
142
|
-
white-space: nowrap; display: flex; align-items: center; gap: 4px;
|
|
143
|
-
}
|
|
144
|
-
.tb-btn:hover { background: var(--zrd-accent-light); }
|
|
145
|
-
.tb-btn:disabled { opacity: 0.4; cursor: default; }
|
|
146
|
-
.tb-btn--active { background: var(--zrd-accent); color: white; border-color: var(--zrd-accent); }
|
|
147
|
-
.tb-btn--danger:hover { background: #ffebee; color: var(--zrd-danger); border-color: var(--zrd-danger); }
|
|
148
|
-
.report-name {
|
|
149
|
-
font-weight: 600; font-size: 14px; cursor: pointer;
|
|
150
|
-
padding: 2px 6px; border-radius: 3px; border: 1px solid transparent;
|
|
151
|
-
color: var(--zrd-text);
|
|
152
|
-
}
|
|
153
|
-
.report-name:hover { border-color: var(--zrd-border); background: var(--zrd-accent-light); }
|
|
154
|
-
.report-name-input {
|
|
155
|
-
font-weight: 600; font-size: 14px; border: 1px solid var(--zrd-accent);
|
|
156
|
-
border-radius: 3px; padding: 2px 6px; outline: none;
|
|
157
|
-
background: white; color: var(--zrd-text); font-family: inherit;
|
|
158
|
-
}
|
|
159
|
-
.tb-spacer { flex: 1; }
|
|
160
|
-
.zoom-controls {
|
|
161
|
-
display: flex; align-items: center; gap: 4px;
|
|
162
|
-
padding-left: 8px; border-left: 1px solid var(--zrd-border);
|
|
163
|
-
}
|
|
164
|
-
.zoom-btn { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 3px; font-size: 14px; font-weight: bold; }
|
|
165
|
-
.zoom-label { font-size: 11px; min-width: 36px; text-align: center; color: var(--zrd-text-muted); cursor: pointer; }
|
|
166
|
-
|
|
167
|
-
/* ─── Resize Handle ───────────────────────── */
|
|
168
|
-
.panel-resize {
|
|
169
|
-
width: 4px; cursor: col-resize; background: transparent;
|
|
170
|
-
transition: background 0.15s; flex-shrink: 0;
|
|
171
|
-
}
|
|
172
|
-
.panel-resize:hover { background: var(--zrd-accent); }
|
|
173
|
-
|
|
174
|
-
/* ─── Left Panel (Toolbox) ────────────────── */
|
|
175
|
-
.left-panel {
|
|
176
|
-
background: var(--zrd-panel-bg);
|
|
177
|
-
border-right: 1px solid var(--zrd-border);
|
|
178
|
-
display: flex; flex-direction: column; overflow: hidden;
|
|
179
|
-
}
|
|
180
|
-
.panel-tabs {
|
|
181
|
-
display: flex; border-bottom: 1px solid var(--zrd-border);
|
|
182
|
-
}
|
|
183
|
-
.panel-tab {
|
|
184
|
-
flex: 1; padding: 8px 4px; text-align: center; cursor: pointer;
|
|
185
|
-
font-size: 11px; border-bottom: 2px solid transparent;
|
|
186
|
-
color: var(--zrd-text-muted); transition: all 0.15s;
|
|
187
|
-
background: none; border-top: none; border-left: none; border-right: none;
|
|
188
|
-
font-family: inherit;
|
|
189
|
-
}
|
|
190
|
-
.panel-tab:hover { color: var(--zrd-text); }
|
|
191
|
-
.panel-tab--active { border-bottom-color: var(--zrd-accent); color: var(--zrd-accent); font-weight: 600; }
|
|
192
|
-
.panel-content { padding: 4px; overflow-y: auto; flex: 1; }
|
|
193
|
-
.panel-content::-webkit-scrollbar { width: 6px; }
|
|
194
|
-
.panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
195
|
-
|
|
196
|
-
/* Toolbox grid (3 columns like report-designer) */
|
|
197
|
-
.toolbox-section {
|
|
198
|
-
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
199
|
-
letter-spacing: 0.8px; color: #aaa;
|
|
200
|
-
padding: 10px 8px 5px; margin-top: 2px;
|
|
201
|
-
}
|
|
202
|
-
.toolbox-section:first-child { margin-top: 0; padding-top: 6px; }
|
|
203
|
-
.toolbox-grid {
|
|
204
|
-
display: grid; grid-template-columns: repeat(3, 1fr);
|
|
205
|
-
gap: 3px; padding: 0 4px;
|
|
206
|
-
}
|
|
207
|
-
.toolbox-item {
|
|
208
|
-
display: flex; flex-direction: column; align-items: center;
|
|
209
|
-
gap: 3px; padding: 7px 2px 5px;
|
|
210
|
-
border: 1px solid transparent; border-radius: 5px;
|
|
211
|
-
cursor: grab; user-select: none; text-align: center;
|
|
212
|
-
transition: all 0.15s; background: transparent;
|
|
213
|
-
}
|
|
214
|
-
.toolbox-item:hover {
|
|
215
|
-
background: var(--zrd-accent-light); border-color: #c5dcf0;
|
|
216
|
-
box-shadow: 0 1px 4px rgba(25,118,210,0.1);
|
|
217
|
-
}
|
|
218
|
-
.toolbox-item:active { cursor: grabbing; opacity: 0.6; transform: scale(0.95); }
|
|
219
|
-
.toolbox-icon {
|
|
220
|
-
width: 28px; height: 28px; display: flex; align-items: center;
|
|
221
|
-
justify-content: center; border-radius: 6px;
|
|
222
|
-
font-size: 15px; flex-shrink: 0;
|
|
223
|
-
background: #f0f4f8; color: #1976d2;
|
|
224
|
-
border: 1px solid #e3e8ee;
|
|
225
|
-
transition: all 0.15s;
|
|
226
|
-
}
|
|
227
|
-
.toolbox-item:hover .toolbox-icon {
|
|
228
|
-
background: #1976d2; color: white; border-color: #1976d2;
|
|
229
|
-
box-shadow: 0 2px 6px rgba(25,118,210,0.3);
|
|
230
|
-
}
|
|
231
|
-
.toolbox-label {
|
|
232
|
-
font-size: 9px; font-weight: 500; line-height: 1.1;
|
|
233
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
234
|
-
max-width: 100%; color: #777;
|
|
235
|
-
}
|
|
236
|
-
.toolbox-item:hover .toolbox-label { color: #1976d2; }
|
|
237
|
-
|
|
238
|
-
/* ─── Canvas ──────────────────────────────── */
|
|
239
|
-
.canvas-area {
|
|
240
|
-
overflow: auto; padding: 30px; position: relative;
|
|
241
|
-
background: #d0d0d0; display: flex; justify-content: center;
|
|
242
|
-
}
|
|
243
|
-
.canvas {
|
|
244
|
-
background: white; position: relative; margin: 0 auto; border-radius: 2px;
|
|
245
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.08);
|
|
246
|
-
min-width: 600px; min-height: 400px; padding: 24px;
|
|
247
|
-
transform-origin: top center;
|
|
248
|
-
}
|
|
249
|
-
.canvas-section { margin-bottom: 20px; }
|
|
250
|
-
.canvas-section-header {
|
|
251
|
-
font-size: 13px; font-weight: 600; color: var(--zrd-text);
|
|
252
|
-
padding: 6px 8px; margin-bottom: 8px;
|
|
253
|
-
background: #f0f0f0; border-radius: 4px; border-left: 3px solid var(--zrd-accent);
|
|
254
|
-
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
255
|
-
}
|
|
256
|
-
.canvas-section-header:hover { background: var(--zrd-accent-light); }
|
|
257
|
-
.canvas-grid {
|
|
258
|
-
display: grid; gap: 8px; padding: 4px;
|
|
259
|
-
background: repeating-linear-gradient(0deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px),
|
|
260
|
-
repeating-linear-gradient(90deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/* Field on canvas */
|
|
264
|
-
.canvas-field {
|
|
265
|
-
position: relative; border: 1px solid transparent;
|
|
266
|
-
border-radius: 4px; cursor: pointer; user-select: none;
|
|
267
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
268
|
-
padding: 4px;
|
|
269
|
-
}
|
|
270
|
-
.canvas-field:hover { border-color: rgba(25,118,210,0.5); }
|
|
271
|
-
.canvas-field--selected {
|
|
272
|
-
border-color: var(--zrd-accent);
|
|
273
|
-
box-shadow: 0 0 0 1px var(--zrd-accent);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/* Type badge (above field) */
|
|
277
|
-
.field-type-badge {
|
|
278
|
-
position: absolute; top: -14px; left: 0;
|
|
279
|
-
font-size: 9px; border-radius: 3px 3px 0 0;
|
|
280
|
-
padding: 1px 6px; line-height: 13px;
|
|
281
|
-
pointer-events: none; z-index: 6; display: none;
|
|
282
|
-
white-space: nowrap;
|
|
283
|
-
}
|
|
284
|
-
.canvas-field:hover .field-type-badge,
|
|
285
|
-
.canvas-field--selected .field-type-badge { display: block; }
|
|
286
|
-
|
|
287
|
-
/* Resize handles (8-point like report-designer) */
|
|
288
|
-
.rh { position: absolute; width: 6px; height: 6px; background: var(--zrd-accent); border: 1px solid white; z-index: 5; display: none; border-radius: 1px; }
|
|
289
|
-
.rh:hover { background: #0d47a1; }
|
|
290
|
-
.canvas-field--selected .rh { display: block; }
|
|
291
|
-
.rh-nw { top: -3px; left: -3px; cursor: nw-resize; }
|
|
292
|
-
.rh-n { top: -3px; left: calc(50% - 3px); cursor: n-resize; }
|
|
293
|
-
.rh-ne { top: -3px; right: -3px; cursor: ne-resize; }
|
|
294
|
-
.rh-e { top: calc(50% - 3px); right: -3px; cursor: e-resize; }
|
|
295
|
-
.rh-se { bottom: -3px; right: -3px; cursor: se-resize; }
|
|
296
|
-
.rh-s { bottom: -3px; left: calc(50% - 3px); cursor: s-resize; }
|
|
297
|
-
.rh-sw { bottom: -3px; left: -3px; cursor: sw-resize; }
|
|
298
|
-
.rh-w { top: calc(50% - 3px); left: -3px; cursor: w-resize; }
|
|
299
|
-
|
|
300
|
-
/* Action buttons on field */
|
|
301
|
-
.field-actions {
|
|
302
|
-
position: absolute; top: -14px; right: 4px;
|
|
303
|
-
display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; z-index: 7;
|
|
304
|
-
}
|
|
305
|
-
.canvas-field:hover .field-actions { opacity: 1; }
|
|
306
|
-
.fa-btn {
|
|
307
|
-
width: 18px; height: 14px; border-radius: 3px 3px 0 0;
|
|
308
|
-
border: none; cursor: pointer; font-size: 9px;
|
|
309
|
-
display: flex; align-items: center; justify-content: center;
|
|
310
|
-
}
|
|
311
|
-
.fa-btn--move { background: var(--zrd-accent); color: white; }
|
|
312
|
-
.fa-btn--delete { background: var(--zrd-danger); color: white; }
|
|
313
|
-
.fa-btn--copy { background: #7c3aed; color: white; }
|
|
314
|
-
|
|
315
|
-
/* Field preview content */
|
|
316
|
-
.field-preview {
|
|
317
|
-
pointer-events: none;
|
|
318
|
-
}
|
|
319
|
-
.field-preview-label {
|
|
320
|
-
font-size: 11px; font-weight: 500; color: var(--zrd-text-muted);
|
|
321
|
-
margin-bottom: 3px;
|
|
322
|
-
}
|
|
323
|
-
.field-preview-input {
|
|
324
|
-
height: 30px; border: 1px solid #e0e0e0; border-radius: 4px;
|
|
325
|
-
background: #fafafa; display: flex; align-items: center;
|
|
326
|
-
padding: 0 8px; font-size: 12px; color: #999;
|
|
327
|
-
}
|
|
328
|
-
.field-preview-input--textarea { height: 60px; align-items: flex-start; padding-top: 6px; }
|
|
329
|
-
.field-preview-input--switch { height: auto; border: none; background: none; padding: 0; }
|
|
330
|
-
.field-preview-input--separator { height: 1px; border: none; background: #ddd; padding: 0; }
|
|
331
|
-
.field-preview-input--heading { height: auto; border: none; background: none; padding: 0; font-size: 16px; font-weight: 600; color: var(--zrd-text); }
|
|
332
|
-
.field-preview-input--datagrid { height: 120px; border: 2px dashed var(--zrd-accent); background: var(--zrd-accent-light); justify-content: center; font-weight: 500; color: var(--zrd-accent); }
|
|
333
|
-
.field-preview-input--report { height: 120px; border: 2px dashed #e65100; background: #fff3e0; justify-content: center; font-weight: 500; color: #e65100; }
|
|
334
|
-
.field-preview-input--chart { height: 120px; border: 2px dashed #6a1b9a; background: #f3e5f5; justify-content: center; font-weight: 500; color: #6a1b9a; }
|
|
335
|
-
|
|
336
|
-
/* Drop zone */
|
|
337
|
-
.drop-zone {
|
|
338
|
-
border: 2px dashed var(--zrd-border); border-radius: 6px;
|
|
339
|
-
padding: 16px; text-align: center; margin: 4px 0;
|
|
340
|
-
color: var(--zrd-text-muted); font-size: 12px; transition: all 0.15s;
|
|
341
|
-
}
|
|
342
|
-
.drop-zone--active { border-color: var(--zrd-accent); background: var(--zrd-accent-light); color: var(--zrd-accent); }
|
|
343
|
-
|
|
344
|
-
/* ─── Right Panel (Properties — Figma-quality) ──── */
|
|
345
|
-
.right-panel {
|
|
346
|
-
background: var(--zrd-panel-bg);
|
|
347
|
-
border-left: 1px solid var(--zrd-border);
|
|
348
|
-
overflow-y: auto; font-size: 11px;
|
|
349
|
-
}
|
|
350
|
-
.right-panel::-webkit-scrollbar { width: 5px; }
|
|
351
|
-
.right-panel::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
|
|
352
|
-
.right-panel::-webkit-scrollbar-thumb:hover { background: #bbb; }
|
|
353
|
-
|
|
354
|
-
/* ─ Prop Section ─ */
|
|
355
|
-
.prop-section { border-bottom: 1px solid #eee; padding: 8px 10px 10px; }
|
|
356
|
-
.prop-section:last-child { border-bottom: none; }
|
|
357
|
-
.prop-section-header {
|
|
358
|
-
display: flex; align-items: center; gap: 6px;
|
|
359
|
-
cursor: pointer; user-select: none; margin-bottom: 6px; padding: 2px 0;
|
|
360
|
-
}
|
|
361
|
-
.prop-section-header h4 {
|
|
362
|
-
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
363
|
-
letter-spacing: 0.6px; margin: 0; flex: 1;
|
|
364
|
-
}
|
|
365
|
-
.prop-section-header[data-section="general"] h4 { color: #1976d2; }
|
|
366
|
-
.prop-section-header[data-section="layout"] h4 { color: #7c3aed; }
|
|
367
|
-
.prop-section-header[data-section="behavior"] h4 { color: #0d9488; }
|
|
368
|
-
.prop-section-header[data-section="rules"] h4 { color: #ea580c; }
|
|
369
|
-
.prop-section-header[data-section="style"] h4 { color: #c2185b; }
|
|
370
|
-
.prop-section-header[data-section="form"] h4 { color: #1976d2; }
|
|
371
|
-
.collapse-icon {
|
|
372
|
-
font-size: 8px; color: #bbb; transition: transform 0.15s;
|
|
373
|
-
width: 14px; height: 14px; display: flex; align-items: center;
|
|
374
|
-
justify-content: center; border-radius: 3px;
|
|
375
|
-
}
|
|
376
|
-
.collapse-icon:hover { background: #f0f0f0; color: #666; }
|
|
377
|
-
.collapse-icon--collapsed { transform: rotate(-90deg); }
|
|
378
|
-
|
|
379
|
-
/* ─ Prop Rows ─ */
|
|
380
|
-
.prop-row {
|
|
381
|
-
display: grid; grid-template-columns: 62px 1fr;
|
|
382
|
-
align-items: center; gap: 4px; min-height: 26px; margin-bottom: 1px;
|
|
383
|
-
}
|
|
384
|
-
.prop-row-full { grid-template-columns: 1fr; margin-bottom: 3px; }
|
|
385
|
-
.prop-label {
|
|
386
|
-
font-size: 11px; color: #999; font-weight: 400;
|
|
387
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
388
|
-
padding-left: 1px; line-height: 1;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/* ─ Figma-style input ─ */
|
|
392
|
-
.prop-input {
|
|
393
|
-
width: 100%; border: 1px solid transparent; border-radius: 4px;
|
|
394
|
-
padding: 5px 7px; font-size: 11px; background: #f5f5f5;
|
|
395
|
-
color: var(--zrd-text); outline: none; min-width: 0;
|
|
396
|
-
font-family: inherit; box-sizing: border-box;
|
|
397
|
-
transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
|
|
398
|
-
}
|
|
399
|
-
.prop-input:hover { background: #efefef; border-color: #ddd; }
|
|
400
|
-
.prop-input:focus { border-color: #1976d2; background: white; box-shadow: 0 0 0 2px rgba(25,118,210,0.08); }
|
|
401
|
-
select.prop-input {
|
|
402
|
-
padding: 4px 20px 4px 7px; cursor: pointer; appearance: none;
|
|
403
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
|
|
404
|
-
background-repeat: no-repeat; background-position: right 6px center;
|
|
405
|
-
}
|
|
406
|
-
textarea.prop-input {
|
|
407
|
-
min-height: 44px; resize: vertical; font-family: 'SF Mono','Consolas','Monaco',monospace;
|
|
408
|
-
font-size: 10px; line-height: 1.5; padding: 6px 7px;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/* ─ Numeric stepper (Figma-style) ─ */
|
|
412
|
-
.prop-stepper {
|
|
413
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
414
|
-
border: 1px solid transparent; border-radius: 4px; overflow: hidden;
|
|
415
|
-
transition: border-color 0.12s;
|
|
416
|
-
}
|
|
417
|
-
.prop-stepper:hover { border-color: #ddd; }
|
|
418
|
-
.prop-stepper:focus-within { border-color: #1976d2; background: white; }
|
|
419
|
-
.prop-stepper input {
|
|
420
|
-
border: none; background: transparent; width: 100%;
|
|
421
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
422
|
-
padding: 4px 2px 4px 7px; min-width: 0; font-family: inherit;
|
|
423
|
-
-moz-appearance: textfield;
|
|
424
|
-
}
|
|
425
|
-
.prop-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
426
|
-
.prop-stepper-btns {
|
|
427
|
-
display: flex; flex-direction: column; border-left: 1px solid #eee;
|
|
428
|
-
}
|
|
429
|
-
.prop-stepper-btn {
|
|
430
|
-
border: none; background: none; cursor: pointer; padding: 0;
|
|
431
|
-
width: 18px; height: 12px; display: flex; align-items: center;
|
|
432
|
-
justify-content: center; font-size: 7px; color: #999;
|
|
433
|
-
transition: all 0.1s;
|
|
434
|
-
}
|
|
435
|
-
.prop-stepper-btn:hover { background: #e8e8e8; color: #333; }
|
|
436
|
-
|
|
437
|
-
/* ─ Position grid (4-cell Figma layout) ─ */
|
|
438
|
-
.prop-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
|
439
|
-
.prop-pos-cell {
|
|
440
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
441
|
-
border: 1px solid transparent; border-radius: 4px; padding: 0 6px;
|
|
442
|
-
height: 26px; gap: 3px; transition: border-color 0.12s;
|
|
443
|
-
}
|
|
444
|
-
.prop-pos-cell:hover { border-color: #ddd; }
|
|
445
|
-
.prop-pos-cell:focus-within { border-color: #1976d2; background: white; }
|
|
446
|
-
.prop-pos-label {
|
|
447
|
-
font-size: 9px; font-weight: 700; width: 10px; text-align: center;
|
|
448
|
-
flex-shrink: 0; user-select: none;
|
|
449
|
-
}
|
|
450
|
-
.prop-pos-label--x { color: #ef4444; }
|
|
451
|
-
.prop-pos-label--y { color: #3b82f6; }
|
|
452
|
-
.prop-pos-label--w { color: #8b5cf6; }
|
|
453
|
-
.prop-pos-label--h { color: #10b981; }
|
|
454
|
-
.prop-pos-cell input {
|
|
455
|
-
border: none; background: transparent; width: 100%;
|
|
456
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
457
|
-
padding: 0; min-width: 0; font-family: inherit;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/* ─ Toggle switches (Figma compact) ─ */
|
|
461
|
-
.prop-toggle {
|
|
462
|
-
display: flex; align-items: center; gap: 8px;
|
|
463
|
-
min-height: 24px; padding: 1px 0;
|
|
464
|
-
}
|
|
465
|
-
.prop-switch {
|
|
466
|
-
position: relative; width: 28px; height: 16px; border-radius: 8px;
|
|
467
|
-
background: #d4d4d4; cursor: pointer; transition: background 0.2s;
|
|
468
|
-
flex-shrink: 0; border: none; padding: 0;
|
|
469
|
-
}
|
|
470
|
-
.prop-switch--active { background: #1976d2; }
|
|
471
|
-
.prop-switch::after {
|
|
472
|
-
content: ''; position: absolute; top: 2px; left: 2px;
|
|
473
|
-
width: 12px; height: 12px; border-radius: 50%;
|
|
474
|
-
background: white; box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
|
475
|
-
transition: transform 0.2s;
|
|
476
|
-
}
|
|
477
|
-
.prop-switch--active::after { transform: translateX(12px); }
|
|
478
|
-
.prop-toggle-label { font-size: 11px; color: #888; }
|
|
479
|
-
.prop-toggle:hover .prop-toggle-label { color: #555; }
|
|
480
|
-
|
|
481
|
-
/* ─ Segmented control (alignment, format) ─ */
|
|
482
|
-
.prop-segmented {
|
|
483
|
-
display: flex; border: 1px solid #e0e0e0; border-radius: 5px;
|
|
484
|
-
overflow: hidden; background: #f5f5f5;
|
|
485
|
-
}
|
|
486
|
-
.prop-seg-btn {
|
|
487
|
-
flex: 1; padding: 4px 0; background: transparent; border: none;
|
|
488
|
-
border-right: 1px solid #e0e0e0; cursor: pointer;
|
|
489
|
-
font-size: 11px; color: #999; transition: all 0.12s; text-align: center;
|
|
490
|
-
font-family: inherit;
|
|
491
|
-
}
|
|
492
|
-
.prop-seg-btn:last-child { border-right: none; }
|
|
493
|
-
.prop-seg-btn:hover { background: #eee; color: #555; }
|
|
494
|
-
.prop-seg-btn--active { background: #1976d2; color: white; }
|
|
495
|
-
|
|
496
|
-
/* ─ Color picker (swatch + hex inline) ─ */
|
|
497
|
-
.prop-color-row { display: flex; align-items: center; gap: 6px; }
|
|
498
|
-
.prop-color-swatch {
|
|
499
|
-
width: 22px; height: 22px; border-radius: 5px;
|
|
500
|
-
border: 1px solid #ddd; cursor: pointer; flex-shrink: 0;
|
|
501
|
-
position: relative; overflow: hidden;
|
|
502
|
-
}
|
|
503
|
-
.prop-color-swatch input[type="color"] {
|
|
504
|
-
position: absolute; inset: -4px; width: calc(100% + 8px);
|
|
505
|
-
height: calc(100% + 8px); cursor: pointer; border: none; padding: 0;
|
|
506
|
-
}
|
|
507
|
-
.prop-color-hex {
|
|
508
|
-
border: 1px solid transparent; border-radius: 4px;
|
|
509
|
-
padding: 4px 6px; font-size: 10px;
|
|
510
|
-
font-family: 'SF Mono','Consolas',monospace;
|
|
511
|
-
background: #f5f5f5; color: var(--zrd-text); width: 70px; outline: none;
|
|
512
|
-
box-sizing: border-box;
|
|
513
|
-
}
|
|
514
|
-
.prop-color-hex:hover { border-color: #ddd; }
|
|
515
|
-
.prop-color-hex:focus { border-color: #1976d2; background: white; }
|
|
516
|
-
|
|
517
|
-
/* ─ Info badge ─ */
|
|
518
|
-
.prop-info {
|
|
519
|
-
display: flex; align-items: center; gap: 4px;
|
|
520
|
-
padding: 5px 8px; border-radius: 4px; background: #f0f7ff;
|
|
521
|
-
font-size: 10px; color: #1976d2; margin-bottom: 4px;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/* ─ Divider line ─ */
|
|
525
|
-
.prop-divider { height: 1px; background: #f0f0f0; margin: 4px 0; }
|
|
526
|
-
|
|
527
|
-
/* ─ Empty state ─ */
|
|
528
|
-
.props-empty {
|
|
529
|
-
text-align: center; padding: 32px 16px; color: #ccc;
|
|
530
|
-
}
|
|
531
|
-
.props-empty-icon { font-size: 28px; margin-bottom: 6px; opacity: 0.4; }
|
|
532
|
-
.props-empty-text { font-size: 11px; line-height: 1.5; }
|
|
533
|
-
|
|
534
|
-
/* ─ Type badge ─ */
|
|
535
|
-
.props-type-badge {
|
|
536
|
-
display: inline-flex; align-items: center; gap: 4px;
|
|
537
|
-
padding: 3px 8px; border-radius: 4px;
|
|
538
|
-
font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
|
|
539
|
-
text-transform: uppercase;
|
|
540
|
-
}
|
|
541
|
-
.props-field-id {
|
|
542
|
-
font-size: 10px; color: #bbb; font-family: 'SF Mono','Consolas',monospace;
|
|
543
|
-
margin-top: 2px;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/* ─ JSON view ─ */
|
|
547
|
-
.json-panel {
|
|
548
|
-
font-family: 'SF Mono','Consolas','Monaco',monospace; font-size: 11px;
|
|
549
|
-
line-height: 1.5; background: #1e1e2d; color: #a2a3b7;
|
|
550
|
-
padding: 16px; border-radius: 4px; overflow: auto;
|
|
551
|
-
white-space: pre; tab-size: 2;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/* ─── InteractJS Drag & Drop ─────────────────── */
|
|
555
|
-
.dragging { opacity: 0.4; cursor: grabbing !important; }
|
|
556
|
-
.drag-clone {
|
|
557
|
-
position: fixed; pointer-events: none; z-index: 9999;
|
|
558
|
-
opacity: 0.85; border: 2px solid var(--zrd-accent);
|
|
559
|
-
border-radius: 6px; background: white;
|
|
560
|
-
box-shadow: 0 8px 24px rgba(25,118,210,0.25);
|
|
561
|
-
padding: 8px 12px; font-size: 12px; font-weight: 500;
|
|
562
|
-
color: var(--zrd-accent); max-width: 200px;
|
|
563
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
564
|
-
}
|
|
565
|
-
.drop-active { border: 2px dashed var(--zrd-accent) !important; background: rgba(25,118,210,0.04) !important; }
|
|
566
|
-
.drop-target { background: rgba(25,118,210,0.1) !important; border-color: var(--zrd-accent) !important; }
|
|
567
|
-
.resize-active { outline: 2px dashed var(--zrd-accent); outline-offset: 2px; }
|
|
568
|
-
.drag-insert-line {
|
|
569
|
-
position: absolute; left: 4px; right: 4px; height: 2px;
|
|
570
|
-
background: var(--zrd-accent); border-radius: 1px; z-index: 10;
|
|
571
|
-
pointer-events: none;
|
|
572
|
-
}
|
|
573
|
-
.drag-insert-line::before, .drag-insert-line::after {
|
|
574
|
-
content: ''; position: absolute; top: -3px;
|
|
575
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
576
|
-
background: var(--zrd-accent);
|
|
577
|
-
}
|
|
578
|
-
.drag-insert-line::before { left: -4px; }
|
|
579
|
-
.drag-insert-line::after { right: -4px; }
|
|
580
|
-
.canvas-field.can-drop { box-shadow: 0 0 0 2px rgba(25,118,210,0.3); }
|
|
581
|
-
.section-drop-zone {
|
|
582
|
-
min-height: 8px; transition: all 0.15s; border-radius: 4px;
|
|
583
|
-
margin: 2px 4px;
|
|
584
|
-
}
|
|
585
|
-
.section-drop-zone.drop-active {
|
|
586
|
-
min-height: 32px; border: 2px dashed var(--zrd-accent);
|
|
587
|
-
background: rgba(25,118,210,0.06);
|
|
588
|
-
display: flex; align-items: center; justify-content: center;
|
|
589
|
-
font-size: 11px; color: var(--zrd-accent);
|
|
590
|
-
}
|
|
104
|
+
static { this.styles = css `
|
|
105
|
+
:host {
|
|
106
|
+
display: block; height: 100%;
|
|
107
|
+
--zrd-bg: ${unsafeCSS(BG)};
|
|
108
|
+
--zrd-panel-bg: ${unsafeCSS(PANEL_BG)};
|
|
109
|
+
--zrd-border: ${unsafeCSS(BORDER)};
|
|
110
|
+
--zrd-accent: ${unsafeCSS(ACCENT)};
|
|
111
|
+
--zrd-accent-light: ${unsafeCSS(ACCENT_LIGHT)};
|
|
112
|
+
--zrd-text: ${unsafeCSS(TEXT)};
|
|
113
|
+
--zrd-text-muted: ${unsafeCSS(TEXT_MUTED)};
|
|
114
|
+
--zrd-danger: ${unsafeCSS(DANGER)};
|
|
115
|
+
font-family: 'Segoe UI', Roboto, Arial, sans-serif;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ─── Layout ──────────────────────────────── */
|
|
119
|
+
.designer {
|
|
120
|
+
display: grid;
|
|
121
|
+
grid-template-rows: auto 1fr;
|
|
122
|
+
grid-template-columns: 200px 4px 1fr 4px 240px;
|
|
123
|
+
height: 100%; background: var(--zrd-bg);
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ─── Toolbar ─────────────────────────────── */
|
|
128
|
+
.toolbar {
|
|
129
|
+
grid-column: 1 / -1;
|
|
130
|
+
display: flex; align-items: center; gap: 8px;
|
|
131
|
+
padding: 6px 12px; min-height: 36px;
|
|
132
|
+
background: var(--zrd-panel-bg);
|
|
133
|
+
border-bottom: 1px solid var(--zrd-border);
|
|
134
|
+
flex-wrap: wrap;
|
|
135
|
+
}
|
|
136
|
+
.toolbar-sep { width: 1px; height: 20px; background: var(--zrd-border); margin: 0 2px; }
|
|
137
|
+
.tb-btn {
|
|
138
|
+
background: none; border: 1px solid var(--zrd-border);
|
|
139
|
+
border-radius: 4px; padding: 4px 10px;
|
|
140
|
+
cursor: pointer; font-size: 12px; color: var(--zrd-text);
|
|
141
|
+
font-family: inherit; transition: background 0.15s;
|
|
142
|
+
white-space: nowrap; display: flex; align-items: center; gap: 4px;
|
|
143
|
+
}
|
|
144
|
+
.tb-btn:hover { background: var(--zrd-accent-light); }
|
|
145
|
+
.tb-btn:disabled { opacity: 0.4; cursor: default; }
|
|
146
|
+
.tb-btn--active { background: var(--zrd-accent); color: white; border-color: var(--zrd-accent); }
|
|
147
|
+
.tb-btn--danger:hover { background: #ffebee; color: var(--zrd-danger); border-color: var(--zrd-danger); }
|
|
148
|
+
.report-name {
|
|
149
|
+
font-weight: 600; font-size: 14px; cursor: pointer;
|
|
150
|
+
padding: 2px 6px; border-radius: 3px; border: 1px solid transparent;
|
|
151
|
+
color: var(--zrd-text);
|
|
152
|
+
}
|
|
153
|
+
.report-name:hover { border-color: var(--zrd-border); background: var(--zrd-accent-light); }
|
|
154
|
+
.report-name-input {
|
|
155
|
+
font-weight: 600; font-size: 14px; border: 1px solid var(--zrd-accent);
|
|
156
|
+
border-radius: 3px; padding: 2px 6px; outline: none;
|
|
157
|
+
background: white; color: var(--zrd-text); font-family: inherit;
|
|
158
|
+
}
|
|
159
|
+
.tb-spacer { flex: 1; }
|
|
160
|
+
.zoom-controls {
|
|
161
|
+
display: flex; align-items: center; gap: 4px;
|
|
162
|
+
padding-left: 8px; border-left: 1px solid var(--zrd-border);
|
|
163
|
+
}
|
|
164
|
+
.zoom-btn { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 3px; font-size: 14px; font-weight: bold; }
|
|
165
|
+
.zoom-label { font-size: 11px; min-width: 36px; text-align: center; color: var(--zrd-text-muted); cursor: pointer; }
|
|
166
|
+
|
|
167
|
+
/* ─── Resize Handle ───────────────────────── */
|
|
168
|
+
.panel-resize {
|
|
169
|
+
width: 4px; cursor: col-resize; background: transparent;
|
|
170
|
+
transition: background 0.15s; flex-shrink: 0;
|
|
171
|
+
}
|
|
172
|
+
.panel-resize:hover { background: var(--zrd-accent); }
|
|
173
|
+
|
|
174
|
+
/* ─── Left Panel (Toolbox) ────────────────── */
|
|
175
|
+
.left-panel {
|
|
176
|
+
background: var(--zrd-panel-bg);
|
|
177
|
+
border-right: 1px solid var(--zrd-border);
|
|
178
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
179
|
+
}
|
|
180
|
+
.panel-tabs {
|
|
181
|
+
display: flex; border-bottom: 1px solid var(--zrd-border);
|
|
182
|
+
}
|
|
183
|
+
.panel-tab {
|
|
184
|
+
flex: 1; padding: 8px 4px; text-align: center; cursor: pointer;
|
|
185
|
+
font-size: 11px; border-bottom: 2px solid transparent;
|
|
186
|
+
color: var(--zrd-text-muted); transition: all 0.15s;
|
|
187
|
+
background: none; border-top: none; border-left: none; border-right: none;
|
|
188
|
+
font-family: inherit;
|
|
189
|
+
}
|
|
190
|
+
.panel-tab:hover { color: var(--zrd-text); }
|
|
191
|
+
.panel-tab--active { border-bottom-color: var(--zrd-accent); color: var(--zrd-accent); font-weight: 600; }
|
|
192
|
+
.panel-content { padding: 4px; overflow-y: auto; flex: 1; }
|
|
193
|
+
.panel-content::-webkit-scrollbar { width: 6px; }
|
|
194
|
+
.panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
195
|
+
|
|
196
|
+
/* Toolbox grid (3 columns like report-designer) */
|
|
197
|
+
.toolbox-section {
|
|
198
|
+
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
199
|
+
letter-spacing: 0.8px; color: #aaa;
|
|
200
|
+
padding: 10px 8px 5px; margin-top: 2px;
|
|
201
|
+
}
|
|
202
|
+
.toolbox-section:first-child { margin-top: 0; padding-top: 6px; }
|
|
203
|
+
.toolbox-grid {
|
|
204
|
+
display: grid; grid-template-columns: repeat(3, 1fr);
|
|
205
|
+
gap: 3px; padding: 0 4px;
|
|
206
|
+
}
|
|
207
|
+
.toolbox-item {
|
|
208
|
+
display: flex; flex-direction: column; align-items: center;
|
|
209
|
+
gap: 3px; padding: 7px 2px 5px;
|
|
210
|
+
border: 1px solid transparent; border-radius: 5px;
|
|
211
|
+
cursor: grab; user-select: none; text-align: center;
|
|
212
|
+
transition: all 0.15s; background: transparent;
|
|
213
|
+
}
|
|
214
|
+
.toolbox-item:hover {
|
|
215
|
+
background: var(--zrd-accent-light); border-color: #c5dcf0;
|
|
216
|
+
box-shadow: 0 1px 4px rgba(25,118,210,0.1);
|
|
217
|
+
}
|
|
218
|
+
.toolbox-item:active { cursor: grabbing; opacity: 0.6; transform: scale(0.95); }
|
|
219
|
+
.toolbox-icon {
|
|
220
|
+
width: 28px; height: 28px; display: flex; align-items: center;
|
|
221
|
+
justify-content: center; border-radius: 6px;
|
|
222
|
+
font-size: 15px; flex-shrink: 0;
|
|
223
|
+
background: #f0f4f8; color: #1976d2;
|
|
224
|
+
border: 1px solid #e3e8ee;
|
|
225
|
+
transition: all 0.15s;
|
|
226
|
+
}
|
|
227
|
+
.toolbox-item:hover .toolbox-icon {
|
|
228
|
+
background: #1976d2; color: white; border-color: #1976d2;
|
|
229
|
+
box-shadow: 0 2px 6px rgba(25,118,210,0.3);
|
|
230
|
+
}
|
|
231
|
+
.toolbox-label {
|
|
232
|
+
font-size: 9px; font-weight: 500; line-height: 1.1;
|
|
233
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
234
|
+
max-width: 100%; color: #777;
|
|
235
|
+
}
|
|
236
|
+
.toolbox-item:hover .toolbox-label { color: #1976d2; }
|
|
237
|
+
|
|
238
|
+
/* ─── Canvas ──────────────────────────────── */
|
|
239
|
+
.canvas-area {
|
|
240
|
+
overflow: auto; padding: 30px; position: relative;
|
|
241
|
+
background: #d0d0d0; display: flex; justify-content: center;
|
|
242
|
+
}
|
|
243
|
+
.canvas {
|
|
244
|
+
background: white; position: relative; margin: 0 auto; border-radius: 2px;
|
|
245
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.08);
|
|
246
|
+
min-width: 600px; min-height: 400px; padding: 24px;
|
|
247
|
+
transform-origin: top center;
|
|
248
|
+
}
|
|
249
|
+
.canvas-section { margin-bottom: 20px; }
|
|
250
|
+
.canvas-section-header {
|
|
251
|
+
font-size: 13px; font-weight: 600; color: var(--zrd-text);
|
|
252
|
+
padding: 6px 8px; margin-bottom: 8px;
|
|
253
|
+
background: #f0f0f0; border-radius: 4px; border-left: 3px solid var(--zrd-accent);
|
|
254
|
+
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
.canvas-section-header:hover { background: var(--zrd-accent-light); }
|
|
257
|
+
.canvas-grid {
|
|
258
|
+
display: grid; gap: 8px; padding: 4px;
|
|
259
|
+
background: repeating-linear-gradient(0deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px),
|
|
260
|
+
repeating-linear-gradient(90deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Field on canvas */
|
|
264
|
+
.canvas-field {
|
|
265
|
+
position: relative; border: 1px solid transparent;
|
|
266
|
+
border-radius: 4px; cursor: pointer; user-select: none;
|
|
267
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
268
|
+
padding: 4px;
|
|
269
|
+
}
|
|
270
|
+
.canvas-field:hover { border-color: rgba(25,118,210,0.5); }
|
|
271
|
+
.canvas-field--selected {
|
|
272
|
+
border-color: var(--zrd-accent);
|
|
273
|
+
box-shadow: 0 0 0 1px var(--zrd-accent);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Type badge (above field) */
|
|
277
|
+
.field-type-badge {
|
|
278
|
+
position: absolute; top: -14px; left: 0;
|
|
279
|
+
font-size: 9px; border-radius: 3px 3px 0 0;
|
|
280
|
+
padding: 1px 6px; line-height: 13px;
|
|
281
|
+
pointer-events: none; z-index: 6; display: none;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
}
|
|
284
|
+
.canvas-field:hover .field-type-badge,
|
|
285
|
+
.canvas-field--selected .field-type-badge { display: block; }
|
|
286
|
+
|
|
287
|
+
/* Resize handles (8-point like report-designer) */
|
|
288
|
+
.rh { position: absolute; width: 6px; height: 6px; background: var(--zrd-accent); border: 1px solid white; z-index: 5; display: none; border-radius: 1px; }
|
|
289
|
+
.rh:hover { background: #0d47a1; }
|
|
290
|
+
.canvas-field--selected .rh { display: block; }
|
|
291
|
+
.rh-nw { top: -3px; left: -3px; cursor: nw-resize; }
|
|
292
|
+
.rh-n { top: -3px; left: calc(50% - 3px); cursor: n-resize; }
|
|
293
|
+
.rh-ne { top: -3px; right: -3px; cursor: ne-resize; }
|
|
294
|
+
.rh-e { top: calc(50% - 3px); right: -3px; cursor: e-resize; }
|
|
295
|
+
.rh-se { bottom: -3px; right: -3px; cursor: se-resize; }
|
|
296
|
+
.rh-s { bottom: -3px; left: calc(50% - 3px); cursor: s-resize; }
|
|
297
|
+
.rh-sw { bottom: -3px; left: -3px; cursor: sw-resize; }
|
|
298
|
+
.rh-w { top: calc(50% - 3px); left: -3px; cursor: w-resize; }
|
|
299
|
+
|
|
300
|
+
/* Action buttons on field */
|
|
301
|
+
.field-actions {
|
|
302
|
+
position: absolute; top: -14px; right: 4px;
|
|
303
|
+
display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; z-index: 7;
|
|
304
|
+
}
|
|
305
|
+
.canvas-field:hover .field-actions { opacity: 1; }
|
|
306
|
+
.fa-btn {
|
|
307
|
+
width: 18px; height: 14px; border-radius: 3px 3px 0 0;
|
|
308
|
+
border: none; cursor: pointer; font-size: 9px;
|
|
309
|
+
display: flex; align-items: center; justify-content: center;
|
|
310
|
+
}
|
|
311
|
+
.fa-btn--move { background: var(--zrd-accent); color: white; }
|
|
312
|
+
.fa-btn--delete { background: var(--zrd-danger); color: white; }
|
|
313
|
+
.fa-btn--copy { background: #7c3aed; color: white; }
|
|
314
|
+
|
|
315
|
+
/* Field preview content */
|
|
316
|
+
.field-preview {
|
|
317
|
+
pointer-events: none;
|
|
318
|
+
}
|
|
319
|
+
.field-preview-label {
|
|
320
|
+
font-size: 11px; font-weight: 500; color: var(--zrd-text-muted);
|
|
321
|
+
margin-bottom: 3px;
|
|
322
|
+
}
|
|
323
|
+
.field-preview-input {
|
|
324
|
+
height: 30px; border: 1px solid #e0e0e0; border-radius: 4px;
|
|
325
|
+
background: #fafafa; display: flex; align-items: center;
|
|
326
|
+
padding: 0 8px; font-size: 12px; color: #999;
|
|
327
|
+
}
|
|
328
|
+
.field-preview-input--textarea { height: 60px; align-items: flex-start; padding-top: 6px; }
|
|
329
|
+
.field-preview-input--switch { height: auto; border: none; background: none; padding: 0; }
|
|
330
|
+
.field-preview-input--separator { height: 1px; border: none; background: #ddd; padding: 0; }
|
|
331
|
+
.field-preview-input--heading { height: auto; border: none; background: none; padding: 0; font-size: 16px; font-weight: 600; color: var(--zrd-text); }
|
|
332
|
+
.field-preview-input--datagrid { height: 120px; border: 2px dashed var(--zrd-accent); background: var(--zrd-accent-light); justify-content: center; font-weight: 500; color: var(--zrd-accent); }
|
|
333
|
+
.field-preview-input--report { height: 120px; border: 2px dashed #e65100; background: #fff3e0; justify-content: center; font-weight: 500; color: #e65100; }
|
|
334
|
+
.field-preview-input--chart { height: 120px; border: 2px dashed #6a1b9a; background: #f3e5f5; justify-content: center; font-weight: 500; color: #6a1b9a; }
|
|
335
|
+
|
|
336
|
+
/* Drop zone */
|
|
337
|
+
.drop-zone {
|
|
338
|
+
border: 2px dashed var(--zrd-border); border-radius: 6px;
|
|
339
|
+
padding: 16px; text-align: center; margin: 4px 0;
|
|
340
|
+
color: var(--zrd-text-muted); font-size: 12px; transition: all 0.15s;
|
|
341
|
+
}
|
|
342
|
+
.drop-zone--active { border-color: var(--zrd-accent); background: var(--zrd-accent-light); color: var(--zrd-accent); }
|
|
343
|
+
|
|
344
|
+
/* ─── Right Panel (Properties — Figma-quality) ──── */
|
|
345
|
+
.right-panel {
|
|
346
|
+
background: var(--zrd-panel-bg);
|
|
347
|
+
border-left: 1px solid var(--zrd-border);
|
|
348
|
+
overflow-y: auto; font-size: 11px;
|
|
349
|
+
}
|
|
350
|
+
.right-panel::-webkit-scrollbar { width: 5px; }
|
|
351
|
+
.right-panel::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
|
|
352
|
+
.right-panel::-webkit-scrollbar-thumb:hover { background: #bbb; }
|
|
353
|
+
|
|
354
|
+
/* ─ Prop Section ─ */
|
|
355
|
+
.prop-section { border-bottom: 1px solid #eee; padding: 8px 10px 10px; }
|
|
356
|
+
.prop-section:last-child { border-bottom: none; }
|
|
357
|
+
.prop-section-header {
|
|
358
|
+
display: flex; align-items: center; gap: 6px;
|
|
359
|
+
cursor: pointer; user-select: none; margin-bottom: 6px; padding: 2px 0;
|
|
360
|
+
}
|
|
361
|
+
.prop-section-header h4 {
|
|
362
|
+
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
363
|
+
letter-spacing: 0.6px; margin: 0; flex: 1;
|
|
364
|
+
}
|
|
365
|
+
.prop-section-header[data-section="general"] h4 { color: #1976d2; }
|
|
366
|
+
.prop-section-header[data-section="layout"] h4 { color: #7c3aed; }
|
|
367
|
+
.prop-section-header[data-section="behavior"] h4 { color: #0d9488; }
|
|
368
|
+
.prop-section-header[data-section="rules"] h4 { color: #ea580c; }
|
|
369
|
+
.prop-section-header[data-section="style"] h4 { color: #c2185b; }
|
|
370
|
+
.prop-section-header[data-section="form"] h4 { color: #1976d2; }
|
|
371
|
+
.collapse-icon {
|
|
372
|
+
font-size: 8px; color: #bbb; transition: transform 0.15s;
|
|
373
|
+
width: 14px; height: 14px; display: flex; align-items: center;
|
|
374
|
+
justify-content: center; border-radius: 3px;
|
|
375
|
+
}
|
|
376
|
+
.collapse-icon:hover { background: #f0f0f0; color: #666; }
|
|
377
|
+
.collapse-icon--collapsed { transform: rotate(-90deg); }
|
|
378
|
+
|
|
379
|
+
/* ─ Prop Rows ─ */
|
|
380
|
+
.prop-row {
|
|
381
|
+
display: grid; grid-template-columns: 62px 1fr;
|
|
382
|
+
align-items: center; gap: 4px; min-height: 26px; margin-bottom: 1px;
|
|
383
|
+
}
|
|
384
|
+
.prop-row-full { grid-template-columns: 1fr; margin-bottom: 3px; }
|
|
385
|
+
.prop-label {
|
|
386
|
+
font-size: 11px; color: #999; font-weight: 400;
|
|
387
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
388
|
+
padding-left: 1px; line-height: 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* ─ Figma-style input ─ */
|
|
392
|
+
.prop-input {
|
|
393
|
+
width: 100%; border: 1px solid transparent; border-radius: 4px;
|
|
394
|
+
padding: 5px 7px; font-size: 11px; background: #f5f5f5;
|
|
395
|
+
color: var(--zrd-text); outline: none; min-width: 0;
|
|
396
|
+
font-family: inherit; box-sizing: border-box;
|
|
397
|
+
transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
|
|
398
|
+
}
|
|
399
|
+
.prop-input:hover { background: #efefef; border-color: #ddd; }
|
|
400
|
+
.prop-input:focus { border-color: #1976d2; background: white; box-shadow: 0 0 0 2px rgba(25,118,210,0.08); }
|
|
401
|
+
select.prop-input {
|
|
402
|
+
padding: 4px 20px 4px 7px; cursor: pointer; appearance: none;
|
|
403
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
|
|
404
|
+
background-repeat: no-repeat; background-position: right 6px center;
|
|
405
|
+
}
|
|
406
|
+
textarea.prop-input {
|
|
407
|
+
min-height: 44px; resize: vertical; font-family: 'SF Mono','Consolas','Monaco',monospace;
|
|
408
|
+
font-size: 10px; line-height: 1.5; padding: 6px 7px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* ─ Numeric stepper (Figma-style) ─ */
|
|
412
|
+
.prop-stepper {
|
|
413
|
+
display: flex; align-items: center; background: #f5f5f5;
|
|
414
|
+
border: 1px solid transparent; border-radius: 4px; overflow: hidden;
|
|
415
|
+
transition: border-color 0.12s;
|
|
416
|
+
}
|
|
417
|
+
.prop-stepper:hover { border-color: #ddd; }
|
|
418
|
+
.prop-stepper:focus-within { border-color: #1976d2; background: white; }
|
|
419
|
+
.prop-stepper input {
|
|
420
|
+
border: none; background: transparent; width: 100%;
|
|
421
|
+
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
422
|
+
padding: 4px 2px 4px 7px; min-width: 0; font-family: inherit;
|
|
423
|
+
-moz-appearance: textfield;
|
|
424
|
+
}
|
|
425
|
+
.prop-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
426
|
+
.prop-stepper-btns {
|
|
427
|
+
display: flex; flex-direction: column; border-left: 1px solid #eee;
|
|
428
|
+
}
|
|
429
|
+
.prop-stepper-btn {
|
|
430
|
+
border: none; background: none; cursor: pointer; padding: 0;
|
|
431
|
+
width: 18px; height: 12px; display: flex; align-items: center;
|
|
432
|
+
justify-content: center; font-size: 7px; color: #999;
|
|
433
|
+
transition: all 0.1s;
|
|
434
|
+
}
|
|
435
|
+
.prop-stepper-btn:hover { background: #e8e8e8; color: #333; }
|
|
436
|
+
|
|
437
|
+
/* ─ Position grid (4-cell Figma layout) ─ */
|
|
438
|
+
.prop-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
|
439
|
+
.prop-pos-cell {
|
|
440
|
+
display: flex; align-items: center; background: #f5f5f5;
|
|
441
|
+
border: 1px solid transparent; border-radius: 4px; padding: 0 6px;
|
|
442
|
+
height: 26px; gap: 3px; transition: border-color 0.12s;
|
|
443
|
+
}
|
|
444
|
+
.prop-pos-cell:hover { border-color: #ddd; }
|
|
445
|
+
.prop-pos-cell:focus-within { border-color: #1976d2; background: white; }
|
|
446
|
+
.prop-pos-label {
|
|
447
|
+
font-size: 9px; font-weight: 700; width: 10px; text-align: center;
|
|
448
|
+
flex-shrink: 0; user-select: none;
|
|
449
|
+
}
|
|
450
|
+
.prop-pos-label--x { color: #ef4444; }
|
|
451
|
+
.prop-pos-label--y { color: #3b82f6; }
|
|
452
|
+
.prop-pos-label--w { color: #8b5cf6; }
|
|
453
|
+
.prop-pos-label--h { color: #10b981; }
|
|
454
|
+
.prop-pos-cell input {
|
|
455
|
+
border: none; background: transparent; width: 100%;
|
|
456
|
+
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
457
|
+
padding: 0; min-width: 0; font-family: inherit;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ─ Toggle switches (Figma compact) ─ */
|
|
461
|
+
.prop-toggle {
|
|
462
|
+
display: flex; align-items: center; gap: 8px;
|
|
463
|
+
min-height: 24px; padding: 1px 0;
|
|
464
|
+
}
|
|
465
|
+
.prop-switch {
|
|
466
|
+
position: relative; width: 28px; height: 16px; border-radius: 8px;
|
|
467
|
+
background: #d4d4d4; cursor: pointer; transition: background 0.2s;
|
|
468
|
+
flex-shrink: 0; border: none; padding: 0;
|
|
469
|
+
}
|
|
470
|
+
.prop-switch--active { background: #1976d2; }
|
|
471
|
+
.prop-switch::after {
|
|
472
|
+
content: ''; position: absolute; top: 2px; left: 2px;
|
|
473
|
+
width: 12px; height: 12px; border-radius: 50%;
|
|
474
|
+
background: white; box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
|
475
|
+
transition: transform 0.2s;
|
|
476
|
+
}
|
|
477
|
+
.prop-switch--active::after { transform: translateX(12px); }
|
|
478
|
+
.prop-toggle-label { font-size: 11px; color: #888; }
|
|
479
|
+
.prop-toggle:hover .prop-toggle-label { color: #555; }
|
|
480
|
+
|
|
481
|
+
/* ─ Segmented control (alignment, format) ─ */
|
|
482
|
+
.prop-segmented {
|
|
483
|
+
display: flex; border: 1px solid #e0e0e0; border-radius: 5px;
|
|
484
|
+
overflow: hidden; background: #f5f5f5;
|
|
485
|
+
}
|
|
486
|
+
.prop-seg-btn {
|
|
487
|
+
flex: 1; padding: 4px 0; background: transparent; border: none;
|
|
488
|
+
border-right: 1px solid #e0e0e0; cursor: pointer;
|
|
489
|
+
font-size: 11px; color: #999; transition: all 0.12s; text-align: center;
|
|
490
|
+
font-family: inherit;
|
|
491
|
+
}
|
|
492
|
+
.prop-seg-btn:last-child { border-right: none; }
|
|
493
|
+
.prop-seg-btn:hover { background: #eee; color: #555; }
|
|
494
|
+
.prop-seg-btn--active { background: #1976d2; color: white; }
|
|
495
|
+
|
|
496
|
+
/* ─ Color picker (swatch + hex inline) ─ */
|
|
497
|
+
.prop-color-row { display: flex; align-items: center; gap: 6px; }
|
|
498
|
+
.prop-color-swatch {
|
|
499
|
+
width: 22px; height: 22px; border-radius: 5px;
|
|
500
|
+
border: 1px solid #ddd; cursor: pointer; flex-shrink: 0;
|
|
501
|
+
position: relative; overflow: hidden;
|
|
502
|
+
}
|
|
503
|
+
.prop-color-swatch input[type="color"] {
|
|
504
|
+
position: absolute; inset: -4px; width: calc(100% + 8px);
|
|
505
|
+
height: calc(100% + 8px); cursor: pointer; border: none; padding: 0;
|
|
506
|
+
}
|
|
507
|
+
.prop-color-hex {
|
|
508
|
+
border: 1px solid transparent; border-radius: 4px;
|
|
509
|
+
padding: 4px 6px; font-size: 10px;
|
|
510
|
+
font-family: 'SF Mono','Consolas',monospace;
|
|
511
|
+
background: #f5f5f5; color: var(--zrd-text); width: 70px; outline: none;
|
|
512
|
+
box-sizing: border-box;
|
|
513
|
+
}
|
|
514
|
+
.prop-color-hex:hover { border-color: #ddd; }
|
|
515
|
+
.prop-color-hex:focus { border-color: #1976d2; background: white; }
|
|
516
|
+
|
|
517
|
+
/* ─ Info badge ─ */
|
|
518
|
+
.prop-info {
|
|
519
|
+
display: flex; align-items: center; gap: 4px;
|
|
520
|
+
padding: 5px 8px; border-radius: 4px; background: #f0f7ff;
|
|
521
|
+
font-size: 10px; color: #1976d2; margin-bottom: 4px;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ─ Divider line ─ */
|
|
525
|
+
.prop-divider { height: 1px; background: #f0f0f0; margin: 4px 0; }
|
|
526
|
+
|
|
527
|
+
/* ─ Empty state ─ */
|
|
528
|
+
.props-empty {
|
|
529
|
+
text-align: center; padding: 32px 16px; color: #ccc;
|
|
530
|
+
}
|
|
531
|
+
.props-empty-icon { font-size: 28px; margin-bottom: 6px; opacity: 0.4; }
|
|
532
|
+
.props-empty-text { font-size: 11px; line-height: 1.5; }
|
|
533
|
+
|
|
534
|
+
/* ─ Type badge ─ */
|
|
535
|
+
.props-type-badge {
|
|
536
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
537
|
+
padding: 3px 8px; border-radius: 4px;
|
|
538
|
+
font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
|
|
539
|
+
text-transform: uppercase;
|
|
540
|
+
}
|
|
541
|
+
.props-field-id {
|
|
542
|
+
font-size: 10px; color: #bbb; font-family: 'SF Mono','Consolas',monospace;
|
|
543
|
+
margin-top: 2px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* ─ JSON view ─ */
|
|
547
|
+
.json-panel {
|
|
548
|
+
font-family: 'SF Mono','Consolas','Monaco',monospace; font-size: 11px;
|
|
549
|
+
line-height: 1.5; background: #1e1e2d; color: #a2a3b7;
|
|
550
|
+
padding: 16px; border-radius: 4px; overflow: auto;
|
|
551
|
+
white-space: pre; tab-size: 2;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* ─── InteractJS Drag & Drop ─────────────────── */
|
|
555
|
+
.dragging { opacity: 0.4; cursor: grabbing !important; }
|
|
556
|
+
.drag-clone {
|
|
557
|
+
position: fixed; pointer-events: none; z-index: 9999;
|
|
558
|
+
opacity: 0.85; border: 2px solid var(--zrd-accent);
|
|
559
|
+
border-radius: 6px; background: white;
|
|
560
|
+
box-shadow: 0 8px 24px rgba(25,118,210,0.25);
|
|
561
|
+
padding: 8px 12px; font-size: 12px; font-weight: 500;
|
|
562
|
+
color: var(--zrd-accent); max-width: 200px;
|
|
563
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
564
|
+
}
|
|
565
|
+
.drop-active { border: 2px dashed var(--zrd-accent) !important; background: rgba(25,118,210,0.04) !important; }
|
|
566
|
+
.drop-target { background: rgba(25,118,210,0.1) !important; border-color: var(--zrd-accent) !important; }
|
|
567
|
+
.resize-active { outline: 2px dashed var(--zrd-accent); outline-offset: 2px; }
|
|
568
|
+
.drag-insert-line {
|
|
569
|
+
position: absolute; left: 4px; right: 4px; height: 2px;
|
|
570
|
+
background: var(--zrd-accent); border-radius: 1px; z-index: 10;
|
|
571
|
+
pointer-events: none;
|
|
572
|
+
}
|
|
573
|
+
.drag-insert-line::before, .drag-insert-line::after {
|
|
574
|
+
content: ''; position: absolute; top: -3px;
|
|
575
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
576
|
+
background: var(--zrd-accent);
|
|
577
|
+
}
|
|
578
|
+
.drag-insert-line::before { left: -4px; }
|
|
579
|
+
.drag-insert-line::after { right: -4px; }
|
|
580
|
+
.canvas-field.can-drop { box-shadow: 0 0 0 2px rgba(25,118,210,0.3); }
|
|
581
|
+
.section-drop-zone {
|
|
582
|
+
min-height: 8px; transition: all 0.15s; border-radius: 4px;
|
|
583
|
+
margin: 2px 4px;
|
|
584
|
+
}
|
|
585
|
+
.section-drop-zone.drop-active {
|
|
586
|
+
min-height: 32px; border: 2px dashed var(--zrd-accent);
|
|
587
|
+
background: rgba(25,118,210,0.06);
|
|
588
|
+
display: flex; align-items: center; justify-content: center;
|
|
589
|
+
font-size: 11px; color: var(--zrd-accent);
|
|
590
|
+
}
|
|
591
591
|
`; }
|
|
592
592
|
// ─── Lifecycle ────────────────────────────────────
|
|
593
593
|
updated(changed) {
|
|
@@ -1015,6 +1015,84 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1015
1015
|
this.selectedFieldId = id;
|
|
1016
1016
|
this.commitChange();
|
|
1017
1017
|
}
|
|
1018
|
+
/** Handle drop from toolbox OR API fields — unified */
|
|
1019
|
+
handleCanvasDrop(e, sectionIndex) {
|
|
1020
|
+
e.preventDefault();
|
|
1021
|
+
const fieldName = e.dataTransfer?.getData('field-name');
|
|
1022
|
+
const dsId = e.dataTransfer?.getData('ds-id');
|
|
1023
|
+
if (fieldName && dsId) {
|
|
1024
|
+
// Drop from API tab — create field with smart type + binding
|
|
1025
|
+
this.addApiField(fieldName, dsId, sectionIndex);
|
|
1026
|
+
}
|
|
1027
|
+
else if (this.dragType) {
|
|
1028
|
+
// Drop from toolbox
|
|
1029
|
+
this.addField(this.dragType, sectionIndex);
|
|
1030
|
+
}
|
|
1031
|
+
this.dragType = null;
|
|
1032
|
+
}
|
|
1033
|
+
/** Add a field from an API data source with smart type detection */
|
|
1034
|
+
addApiField(fieldName, dsId, sectionIndex = 0) {
|
|
1035
|
+
if (!this.schema) {
|
|
1036
|
+
this.schema = {
|
|
1037
|
+
id: 'new-form', version: '1.0', title: 'Nuevo Formulario',
|
|
1038
|
+
layout: { type: 'grid', columns: 2 },
|
|
1039
|
+
sections: [{ id: 'main', title: 'Datos', fields: [] }],
|
|
1040
|
+
};
|
|
1041
|
+
this.undoStack = [JSON.stringify(this.schema)];
|
|
1042
|
+
}
|
|
1043
|
+
if (sectionIndex >= this.schema.sections.length)
|
|
1044
|
+
sectionIndex = 0;
|
|
1045
|
+
const type = this.inferFieldType(fieldName);
|
|
1046
|
+
const id = `${fieldName.toLowerCase().replace(/[^a-z0-9]/g, '_')}_${Date.now()}`;
|
|
1047
|
+
const label = this.humanizeFieldName(fieldName);
|
|
1048
|
+
const meta = getAllFields().find(f => f.type === type);
|
|
1049
|
+
this.schema.sections[sectionIndex].fields.push({
|
|
1050
|
+
id,
|
|
1051
|
+
type,
|
|
1052
|
+
field: fieldName,
|
|
1053
|
+
label,
|
|
1054
|
+
props: { ...meta?.defaultProps, dataSourceId: dsId },
|
|
1055
|
+
});
|
|
1056
|
+
this.selectedFieldId = id;
|
|
1057
|
+
this.commitChange();
|
|
1058
|
+
}
|
|
1059
|
+
/** Infer field type from column/field name */
|
|
1060
|
+
inferFieldType(name) {
|
|
1061
|
+
const lower = name.toLowerCase();
|
|
1062
|
+
if (lower.includes('email') || lower.includes('correo'))
|
|
1063
|
+
return 'email';
|
|
1064
|
+
if (lower.includes('phone') || lower.includes('telefono') || lower.includes('celular'))
|
|
1065
|
+
return 'phone';
|
|
1066
|
+
if (lower.includes('fecha') || lower.includes('date') || lower.includes('nacimiento') || lower.includes('ingreso') || lower.includes('vencimiento'))
|
|
1067
|
+
return 'date';
|
|
1068
|
+
if (lower.includes('precio') || lower.includes('price') || lower.includes('costo') || lower.includes('monto') || lower.includes('total') || lower.includes('saldo') || lower.includes('salario') || lower.includes('limite'))
|
|
1069
|
+
return 'currency';
|
|
1070
|
+
if (lower.includes('cantidad') || lower.includes('qty') || lower.includes('stock') || lower.includes('edad'))
|
|
1071
|
+
return 'number';
|
|
1072
|
+
if (lower.includes('activ') || lower.includes('active') || lower.includes('enabled') || lower.includes('visible'))
|
|
1073
|
+
return 'switch';
|
|
1074
|
+
if (lower.includes('descripcion') || lower.includes('description') || lower.includes('notas') || lower.includes('notes') || lower.includes('observ'))
|
|
1075
|
+
return 'textarea';
|
|
1076
|
+
if (lower.includes('password') || lower.includes('clave'))
|
|
1077
|
+
return 'password';
|
|
1078
|
+
if (lower.includes('url') || lower.includes('website') || lower.includes('link'))
|
|
1079
|
+
return 'url';
|
|
1080
|
+
if (lower.includes('imagen') || lower.includes('image') || lower.includes('foto') || lower.includes('avatar'))
|
|
1081
|
+
return 'image';
|
|
1082
|
+
if (lower.includes('pais') || lower.includes('country') || lower.includes('estado') || lower.includes('tipo') || lower.includes('status') || lower.includes('genero') || lower.includes('categoria'))
|
|
1083
|
+
return 'select';
|
|
1084
|
+
if (lower.includes('direccion') || lower.includes('address'))
|
|
1085
|
+
return 'address';
|
|
1086
|
+
return 'text';
|
|
1087
|
+
}
|
|
1088
|
+
/** Convert FIELD_NAME to "Field Name" */
|
|
1089
|
+
humanizeFieldName(name) {
|
|
1090
|
+
return name
|
|
1091
|
+
.replace(/([A-Z])/g, ' $1')
|
|
1092
|
+
.replace(/[_-]/g, ' ')
|
|
1093
|
+
.replace(/\b\w/g, c => c.toUpperCase())
|
|
1094
|
+
.trim();
|
|
1095
|
+
}
|
|
1018
1096
|
removeField(si, fi) {
|
|
1019
1097
|
if (!this.schema)
|
|
1020
1098
|
return;
|
|
@@ -1047,77 +1125,77 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1047
1125
|
}
|
|
1048
1126
|
// ─── Render ───────────────────────────────────────
|
|
1049
1127
|
render() {
|
|
1050
|
-
return html `
|
|
1051
|
-
<div class="designer">
|
|
1052
|
-
${this.renderToolbar()}
|
|
1053
|
-
${this.renderLeftPanel()}
|
|
1054
|
-
<div class="panel-resize"></div>
|
|
1055
|
-
${this.renderCanvas()}
|
|
1056
|
-
<div class="panel-resize"></div>
|
|
1057
|
-
${this.renderRightPanel()}
|
|
1058
|
-
</div>
|
|
1128
|
+
return html `
|
|
1129
|
+
<div class="designer">
|
|
1130
|
+
${this.renderToolbar()}
|
|
1131
|
+
${this.renderLeftPanel()}
|
|
1132
|
+
<div class="panel-resize"></div>
|
|
1133
|
+
${this.renderCanvas()}
|
|
1134
|
+
<div class="panel-resize"></div>
|
|
1135
|
+
${this.renderRightPanel()}
|
|
1136
|
+
</div>
|
|
1059
1137
|
`;
|
|
1060
1138
|
}
|
|
1061
1139
|
// ─── Toolbar ──────────────────────────────────────
|
|
1062
1140
|
renderToolbar() {
|
|
1063
|
-
return html `
|
|
1064
|
-
<div class="toolbar">
|
|
1141
|
+
return html `
|
|
1142
|
+
<div class="toolbar">
|
|
1065
1143
|
${this.editingTitle
|
|
1066
|
-
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
1144
|
+
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
1067
1145
|
@blur="${(e) => { if (this.schema)
|
|
1068
|
-
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
1146
|
+
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
1069
1147
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
1070
|
-
e.target.blur(); }}"
|
|
1148
|
+
e.target.blur(); }}"
|
|
1071
1149
|
/>`
|
|
1072
|
-
: html `<span class="report-name" @click="${() => { this.editingTitle = true; }}">${this.schema?.title || 'Sin titulo'}</span>`}
|
|
1073
|
-
|
|
1074
|
-
<div class="toolbar-sep"></div>
|
|
1075
|
-
|
|
1076
|
-
<button class="tb-btn" ?disabled="${this.undoStack.length <= 1}" @click="${this.undo}" title="Deshacer (Ctrl+Z)">↩ Deshacer</button>
|
|
1077
|
-
<button class="tb-btn" ?disabled="${this.redoStack.length === 0}" @click="${this.redo}" title="Rehacer (Ctrl+Y)">↪ Rehacer</button>
|
|
1078
|
-
|
|
1079
|
-
<div class="toolbar-sep"></div>
|
|
1080
|
-
|
|
1150
|
+
: html `<span class="report-name" @click="${() => { this.editingTitle = true; }}">${this.schema?.title || 'Sin titulo'}</span>`}
|
|
1151
|
+
|
|
1152
|
+
<div class="toolbar-sep"></div>
|
|
1153
|
+
|
|
1154
|
+
<button class="tb-btn" ?disabled="${this.undoStack.length <= 1}" @click="${this.undo}" title="Deshacer (Ctrl+Z)">↩ Deshacer</button>
|
|
1155
|
+
<button class="tb-btn" ?disabled="${this.redoStack.length === 0}" @click="${this.redo}" title="Rehacer (Ctrl+Y)">↪ Rehacer</button>
|
|
1156
|
+
|
|
1157
|
+
<div class="toolbar-sep"></div>
|
|
1158
|
+
|
|
1081
1159
|
<button class="tb-btn" @click="${() => {
|
|
1082
1160
|
if (!this.schema)
|
|
1083
1161
|
return;
|
|
1084
1162
|
this.schema.sections.push({ id: `section_${Date.now()}`, title: 'Nueva Seccion', fields: [] });
|
|
1085
1163
|
this.commitChange();
|
|
1086
|
-
}}">+ Seccion</button>
|
|
1087
|
-
|
|
1088
|
-
<div style="position:relative;display:inline-block;">
|
|
1089
|
-
<button class="tb-btn" @click="${() => { this.showTemplateMenu = !this.showTemplateMenu; }}">📋 Plantillas ▾</button>
|
|
1090
|
-
${this.showTemplateMenu ? this.renderTemplateMenu() : nothing}
|
|
1091
|
-
</div>
|
|
1092
|
-
|
|
1164
|
+
}}">+ Seccion</button>
|
|
1165
|
+
|
|
1166
|
+
<div style="position:relative;display:inline-block;">
|
|
1167
|
+
<button class="tb-btn" @click="${() => { this.showTemplateMenu = !this.showTemplateMenu; }}">📋 Plantillas ▾</button>
|
|
1168
|
+
${this.showTemplateMenu ? this.renderTemplateMenu() : nothing}
|
|
1169
|
+
</div>
|
|
1170
|
+
|
|
1093
1171
|
<button class="tb-btn tb-btn--danger" @click="${() => {
|
|
1094
1172
|
this.schema = { id: 'new-form', version: '1.0', title: 'Nuevo Formulario', layout: { type: 'grid', columns: 2 }, sections: [{ id: 'main', title: 'Datos', fields: [] }] };
|
|
1095
1173
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
1096
1174
|
this.redoStack = [];
|
|
1097
1175
|
this.selectedFieldId = null;
|
|
1098
1176
|
this.commitChange();
|
|
1099
|
-
}}">🗑 Nuevo</button>
|
|
1100
|
-
|
|
1101
|
-
<div class="toolbar-sep"></div>
|
|
1102
|
-
|
|
1103
|
-
<button class="tb-btn ${this.viewMode === 'design' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'design'; }}">✏️ Diseño</button>
|
|
1104
|
-
<button class="tb-btn ${this.viewMode === 'preview' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'preview'; }}">👁 Preview</button>
|
|
1105
|
-
<button class="tb-btn ${this.viewMode === 'json' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'json'; }}"></> JSON</button>
|
|
1106
|
-
|
|
1107
|
-
<span class="tb-spacer"></span>
|
|
1108
|
-
|
|
1177
|
+
}}">🗑 Nuevo</button>
|
|
1178
|
+
|
|
1179
|
+
<div class="toolbar-sep"></div>
|
|
1180
|
+
|
|
1181
|
+
<button class="tb-btn ${this.viewMode === 'design' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'design'; }}">✏️ Diseño</button>
|
|
1182
|
+
<button class="tb-btn ${this.viewMode === 'preview' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'preview'; }}">👁 Preview</button>
|
|
1183
|
+
<button class="tb-btn ${this.viewMode === 'json' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'json'; }}"></> JSON</button>
|
|
1184
|
+
|
|
1185
|
+
<span class="tb-spacer"></span>
|
|
1186
|
+
|
|
1109
1187
|
<button class="tb-btn" @click="${() => {
|
|
1110
1188
|
if (this.schema) {
|
|
1111
1189
|
navigator.clipboard.writeText(JSON.stringify(this.schema, null, 2));
|
|
1112
1190
|
}
|
|
1113
|
-
}}">📋 Copiar</button>
|
|
1114
|
-
|
|
1115
|
-
<div class="zoom-controls">
|
|
1116
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.max(0.5, this.zoom - 0.1); }}">−</button>
|
|
1117
|
-
<span class="zoom-label" @click="${() => { this.zoom = 1; }}">${Math.round(this.zoom * 100)}%</span>
|
|
1118
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.min(2, this.zoom + 0.1); }}">+</button>
|
|
1119
|
-
</div>
|
|
1120
|
-
</div>
|
|
1191
|
+
}}">📋 Copiar</button>
|
|
1192
|
+
|
|
1193
|
+
<div class="zoom-controls">
|
|
1194
|
+
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.max(0.5, this.zoom - 0.1); }}">−</button>
|
|
1195
|
+
<span class="zoom-label" @click="${() => { this.zoom = 1; }}">${Math.round(this.zoom * 100)}%</span>
|
|
1196
|
+
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.min(2, this.zoom + 0.1); }}">+</button>
|
|
1197
|
+
</div>
|
|
1198
|
+
</div>
|
|
1121
1199
|
`;
|
|
1122
1200
|
}
|
|
1123
1201
|
// ─── Left Panel ───────────────────────────────────
|
|
@@ -1129,107 +1207,103 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1129
1207
|
{ key: 'media', label: 'Media' },
|
|
1130
1208
|
{ key: 'layout', label: 'Layout' },
|
|
1131
1209
|
];
|
|
1132
|
-
return html `
|
|
1133
|
-
<div class="left-panel">
|
|
1134
|
-
<div class="panel-tabs">
|
|
1135
|
-
<button class="panel-tab ${this.leftTab === 'fields' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'fields'; }}">Campos</button>
|
|
1136
|
-
<button class="panel-tab ${this.leftTab === 'sections' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'sections'; }}">Secciones</button>
|
|
1137
|
-
<button class="panel-tab ${this.leftTab === 'api' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'api'; }}" style="${this.leftTab === 'api' ? 'color:#ea580c;border-bottom-color:#ea580c;' : ''}">API</button>
|
|
1138
|
-
</div>
|
|
1139
|
-
<div class="panel-content">
|
|
1140
|
-
${this.leftTab === 'fields' ? html `
|
|
1210
|
+
return html `
|
|
1211
|
+
<div class="left-panel">
|
|
1212
|
+
<div class="panel-tabs">
|
|
1213
|
+
<button class="panel-tab ${this.leftTab === 'fields' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'fields'; }}">Campos</button>
|
|
1214
|
+
<button class="panel-tab ${this.leftTab === 'sections' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'sections'; }}">Secciones</button>
|
|
1215
|
+
<button class="panel-tab ${this.leftTab === 'api' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'api'; }}" style="${this.leftTab === 'api' ? 'color:#ea580c;border-bottom-color:#ea580c;' : ''}">API</button>
|
|
1216
|
+
</div>
|
|
1217
|
+
<div class="panel-content">
|
|
1218
|
+
${this.leftTab === 'fields' ? html `
|
|
1141
1219
|
${categories.map(cat => {
|
|
1142
1220
|
const items = getFieldsByCategory(cat.key);
|
|
1143
1221
|
if (items.length === 0)
|
|
1144
1222
|
return nothing;
|
|
1145
|
-
return html `
|
|
1146
|
-
<div class="toolbox-section">${cat.label}</div>
|
|
1147
|
-
<div class="toolbox-grid">
|
|
1148
|
-
${items.map(f => html `
|
|
1149
|
-
<div class="toolbox-item"
|
|
1150
|
-
draggable="true"
|
|
1151
|
-
data-field-type="${f.type}"
|
|
1152
|
-
@dragstart="${(e) => { this.dragType = f.type; e.dataTransfer?.setData('text/plain', f.type); }}"
|
|
1153
|
-
@dragend="${() => { this.dragType = null; }}"
|
|
1154
|
-
@dblclick="${() => this.addField(f.type)}"
|
|
1155
|
-
title="${f.label}"
|
|
1156
|
-
>
|
|
1157
|
-
<span class="toolbox-icon">${unsafeHTML(resolveIcon(f.icon, this.provider))}</span>
|
|
1158
|
-
<span class="toolbox-label">${f.label}</span>
|
|
1159
|
-
</div>
|
|
1160
|
-
`)}
|
|
1161
|
-
</div>
|
|
1223
|
+
return html `
|
|
1224
|
+
<div class="toolbox-section">${cat.label}</div>
|
|
1225
|
+
<div class="toolbox-grid">
|
|
1226
|
+
${items.map(f => html `
|
|
1227
|
+
<div class="toolbox-item"
|
|
1228
|
+
draggable="true"
|
|
1229
|
+
data-field-type="${f.type}"
|
|
1230
|
+
@dragstart="${(e) => { this.dragType = f.type; e.dataTransfer?.setData('text/plain', f.type); }}"
|
|
1231
|
+
@dragend="${() => { this.dragType = null; }}"
|
|
1232
|
+
@dblclick="${() => this.addField(f.type)}"
|
|
1233
|
+
title="${f.label}"
|
|
1234
|
+
>
|
|
1235
|
+
<span class="toolbox-icon">${unsafeHTML(resolveIcon(f.icon, this.provider))}</span>
|
|
1236
|
+
<span class="toolbox-label">${f.label}</span>
|
|
1237
|
+
</div>
|
|
1238
|
+
`)}
|
|
1239
|
+
</div>
|
|
1162
1240
|
`;
|
|
1163
|
-
})}
|
|
1164
|
-
` : this.leftTab === 'sections' ? html `
|
|
1165
|
-
${this.schema?.sections.map((s, i) => html `
|
|
1166
|
-
<div style="padding:8px 10px;margin:2px 4px;background:${i % 2 === 0 ? '#f8f9fa' : 'white'};border-radius:6px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:8px;border:1px solid #eee;transition:all 0.15s;"
|
|
1167
|
-
@click="${() => { }}"
|
|
1168
|
-
>
|
|
1169
|
-
<span style="background:#e3f2fd;color:#1976d2;font-weight:700;font-size:10px;padding:2px 6px;border-radius:4px;">§${i + 1}</span>
|
|
1170
|
-
<span style="flex:1;font-weight:500;">${s.title ?? 'Sin titulo'}</span>
|
|
1171
|
-
<span style="color:#aaa;font-size:10px;">${s.fields.length}</span>
|
|
1172
|
-
</div>
|
|
1173
|
-
`) ?? nothing}
|
|
1174
|
-
<button style="margin:8px 4px;padding:8px;width:calc(100% - 8px);border:1px dashed #ccc;border-radius:6px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;transition:all 0.15s;"
|
|
1241
|
+
})}
|
|
1242
|
+
` : this.leftTab === 'sections' ? html `
|
|
1243
|
+
${this.schema?.sections.map((s, i) => html `
|
|
1244
|
+
<div style="padding:8px 10px;margin:2px 4px;background:${i % 2 === 0 ? '#f8f9fa' : 'white'};border-radius:6px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:8px;border:1px solid #eee;transition:all 0.15s;"
|
|
1245
|
+
@click="${() => { }}"
|
|
1246
|
+
>
|
|
1247
|
+
<span style="background:#e3f2fd;color:#1976d2;font-weight:700;font-size:10px;padding:2px 6px;border-radius:4px;">§${i + 1}</span>
|
|
1248
|
+
<span style="flex:1;font-weight:500;">${s.title ?? 'Sin titulo'}</span>
|
|
1249
|
+
<span style="color:#aaa;font-size:10px;">${s.fields.length}</span>
|
|
1250
|
+
</div>
|
|
1251
|
+
`) ?? nothing}
|
|
1252
|
+
<button style="margin:8px 4px;padding:8px;width:calc(100% - 8px);border:1px dashed #ccc;border-radius:6px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;transition:all 0.15s;"
|
|
1175
1253
|
@click="${() => { if (this.schema) {
|
|
1176
1254
|
this.schema.sections.push({ id: 'section_' + Date.now(), title: 'Nueva Seccion', fields: [] });
|
|
1177
1255
|
this.commitChange();
|
|
1178
|
-
} }}"
|
|
1179
|
-
>+ Agregar Seccion</button>
|
|
1180
|
-
` : this.renderApiPanel()}
|
|
1181
|
-
</div>
|
|
1182
|
-
</div>
|
|
1256
|
+
} }}"
|
|
1257
|
+
>+ Agregar Seccion</button>
|
|
1258
|
+
` : this.renderApiPanel()}
|
|
1259
|
+
</div>
|
|
1260
|
+
</div>
|
|
1183
1261
|
`;
|
|
1184
1262
|
}
|
|
1185
1263
|
// ─── Canvas ───────────────────────────────────────
|
|
1186
1264
|
renderCanvas() {
|
|
1187
|
-
return html `
|
|
1188
|
-
<div class="canvas-area">
|
|
1265
|
+
return html `
|
|
1266
|
+
<div class="canvas-area">
|
|
1189
1267
|
${this.viewMode === 'json'
|
|
1190
1268
|
? html `<div class="json-panel" style="width:100%;max-width:800px;">${JSON.stringify(this.schema, null, 2)}</div>`
|
|
1191
1269
|
: this.viewMode === 'preview'
|
|
1192
1270
|
? html `<div class="canvas" style="transform:scale(${this.zoom});"><zentto-studio-renderer .schema="${this.schema}" .data="${this.data}"></zentto-studio-renderer></div>`
|
|
1193
|
-
: this.renderDesignCanvas()}
|
|
1194
|
-
</div>
|
|
1271
|
+
: this.renderDesignCanvas()}
|
|
1272
|
+
</div>
|
|
1195
1273
|
`;
|
|
1196
1274
|
}
|
|
1197
1275
|
renderDesignCanvas() {
|
|
1198
1276
|
if (!this.schema) {
|
|
1199
|
-
return html `<div class="drop-zone
|
|
1200
|
-
@dragover="${(e) => e.preventDefault()}"
|
|
1201
|
-
@drop="${(e) =>
|
|
1202
|
-
|
|
1203
|
-
this.dragType = null;
|
|
1204
|
-
} }}"
|
|
1205
|
-
>Arrastra un campo aqui para empezar</div>`;
|
|
1277
|
+
return html `<div class="drop-zone drop-zone--active" data-section-index="0" style="width:500px;height:200px;display:flex;align-items:center;justify-content:center;"
|
|
1278
|
+
@dragover="${(e) => e.preventDefault()}"
|
|
1279
|
+
@drop="${(e) => this.handleCanvasDrop(e, 0)}"
|
|
1280
|
+
>Arrastra campos del toolbox o de la API aqui</div>`;
|
|
1206
1281
|
}
|
|
1207
1282
|
const cols = this.schema.layout.columns ?? 1;
|
|
1208
|
-
return html `
|
|
1209
|
-
<div class="canvas" style="transform:scale(${this.zoom});" @click="${() => { this.selectedFieldId = null; }}">
|
|
1210
|
-
${this.schema.sections.map((section, si) => html `
|
|
1211
|
-
<div class="canvas-section">
|
|
1212
|
-
<div class="canvas-section-header" @click="${(e) => e.stopPropagation()}">
|
|
1213
|
-
<span style="font-size:10px;color:var(--zrd-accent);">§${si + 1}</span>
|
|
1214
|
-
${section.title ?? 'Seccion'}
|
|
1215
|
-
<span style="flex:1;"></span>
|
|
1216
|
-
<span style="font-size:10px;color:var(--zrd-text-muted);">${section.fields.length} campos</span>
|
|
1217
|
-
</div>
|
|
1218
|
-
<div class="section-drop-zone" data-section-index="${si}" data-position="top"></div>
|
|
1219
|
-
<div class="canvas-grid" data-section-index="${si}" style="grid-template-columns:repeat(${section.columns ?? cols}, 1fr);">
|
|
1220
|
-
${section.fields.map((field, fi) => this.renderCanvasField(field, si, fi, section.columns ?? cols))}
|
|
1221
|
-
</div>
|
|
1222
|
-
<div class="drop-zone ${this.dragType ? 'drop-zone--active' : ''}"
|
|
1223
|
-
data-section-index="${si}"
|
|
1224
|
-
@dragover="${(e) => e.preventDefault()}"
|
|
1225
|
-
@
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
</div>
|
|
1283
|
+
return html `
|
|
1284
|
+
<div class="canvas" style="transform:scale(${this.zoom});" @click="${() => { this.selectedFieldId = null; }}">
|
|
1285
|
+
${this.schema.sections.map((section, si) => html `
|
|
1286
|
+
<div class="canvas-section">
|
|
1287
|
+
<div class="canvas-section-header" @click="${(e) => e.stopPropagation()}">
|
|
1288
|
+
<span style="font-size:10px;color:var(--zrd-accent);">§${si + 1}</span>
|
|
1289
|
+
${section.title ?? 'Seccion'}
|
|
1290
|
+
<span style="flex:1;"></span>
|
|
1291
|
+
<span style="font-size:10px;color:var(--zrd-text-muted);">${section.fields.length} campos</span>
|
|
1292
|
+
</div>
|
|
1293
|
+
<div class="section-drop-zone" data-section-index="${si}" data-position="top"></div>
|
|
1294
|
+
<div class="canvas-grid" data-section-index="${si}" style="grid-template-columns:repeat(${section.columns ?? cols}, 1fr);">
|
|
1295
|
+
${section.fields.map((field, fi) => this.renderCanvasField(field, si, fi, section.columns ?? cols))}
|
|
1296
|
+
</div>
|
|
1297
|
+
<div class="drop-zone ${this.dragType ? 'drop-zone--active' : ''}"
|
|
1298
|
+
data-section-index="${si}"
|
|
1299
|
+
@dragover="${(e) => { e.preventDefault(); e.currentTarget.classList.add('drop-zone--active'); }}"
|
|
1300
|
+
@dragleave="${(e) => { if (!this.dragType)
|
|
1301
|
+
e.currentTarget.classList.remove('drop-zone--active'); }}"
|
|
1302
|
+
@drop="${(e) => this.handleCanvasDrop(e, si)}"
|
|
1303
|
+
>${this.dragType ? '↓ Soltar aqui' : '+ Arrastra campos o campos de API'}</div>
|
|
1304
|
+
</div>
|
|
1305
|
+
`)}
|
|
1306
|
+
</div>
|
|
1233
1307
|
`;
|
|
1234
1308
|
}
|
|
1235
1309
|
renderCanvasField(field, si, fi, maxCols) {
|
|
@@ -1238,39 +1312,39 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1238
1312
|
const fullWidth = ['separator', 'heading', 'html', 'datagrid', 'report', 'chart'].includes(field.type);
|
|
1239
1313
|
const gridCol = fullWidth ? '1 / -1' : span > 1 ? `span ${span}` : '';
|
|
1240
1314
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
1241
|
-
return html `
|
|
1242
|
-
<div class="canvas-field ${isSelected ? 'canvas-field--selected' : ''}"
|
|
1243
|
-
style="${gridCol ? `grid-column:${gridCol};` : ''}"
|
|
1244
|
-
data-field-id="${field.id}"
|
|
1245
|
-
data-section-index="${si}"
|
|
1246
|
-
@click="${(e) => { e.stopPropagation(); this.selectedFieldId = field.id; }}"
|
|
1247
|
-
>
|
|
1248
|
-
<!-- Type badge -->
|
|
1249
|
-
<span class="field-type-badge" style="${typeColor}">${field.type}</span>
|
|
1250
|
-
|
|
1251
|
-
<!-- Action buttons -->
|
|
1252
|
-
<div class="field-actions">
|
|
1253
|
-
${fi > 0 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, -1); }}" title="Subir">↑</button>` : ''}
|
|
1254
|
-
${fi < (this.schema?.sections[si].fields.length ?? 0) - 1 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, 1); }}" title="Bajar">↓</button>` : ''}
|
|
1255
|
-
<button class="fa-btn fa-btn--copy" @click="${(e) => { e.stopPropagation(); this.duplicateField(si, fi); }}" title="Duplicar">⎘</button>
|
|
1256
|
-
<button class="fa-btn fa-btn--delete" @click="${(e) => { e.stopPropagation(); this.removeField(si, fi); }}" title="Eliminar">✕</button>
|
|
1257
|
-
</div>
|
|
1258
|
-
|
|
1259
|
-
<!-- Resize handles -->
|
|
1260
|
-
${isSelected ? html `
|
|
1261
|
-
<div class="rh rh-nw"></div><div class="rh rh-n"></div><div class="rh rh-ne"></div>
|
|
1262
|
-
<div class="rh rh-e"></div><div class="rh rh-se"></div><div class="rh rh-s"></div>
|
|
1263
|
-
<div class="rh rh-sw"></div><div class="rh rh-w"></div>
|
|
1264
|
-
` : ''}
|
|
1265
|
-
|
|
1266
|
-
<!-- Field preview -->
|
|
1267
|
-
<div class="field-preview">
|
|
1268
|
-
<div class="field-preview-label">${field.label ?? field.id}${field.required ? ' *' : ''}</div>
|
|
1269
|
-
<div class="field-preview-input ${this.getPreviewClass(field.type)}">
|
|
1270
|
-
${this.getPreviewContent(field)}
|
|
1271
|
-
</div>
|
|
1272
|
-
</div>
|
|
1273
|
-
</div>
|
|
1315
|
+
return html `
|
|
1316
|
+
<div class="canvas-field ${isSelected ? 'canvas-field--selected' : ''}"
|
|
1317
|
+
style="${gridCol ? `grid-column:${gridCol};` : ''}"
|
|
1318
|
+
data-field-id="${field.id}"
|
|
1319
|
+
data-section-index="${si}"
|
|
1320
|
+
@click="${(e) => { e.stopPropagation(); this.selectedFieldId = field.id; }}"
|
|
1321
|
+
>
|
|
1322
|
+
<!-- Type badge -->
|
|
1323
|
+
<span class="field-type-badge" style="${typeColor}">${field.type}</span>
|
|
1324
|
+
|
|
1325
|
+
<!-- Action buttons -->
|
|
1326
|
+
<div class="field-actions">
|
|
1327
|
+
${fi > 0 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, -1); }}" title="Subir">↑</button>` : ''}
|
|
1328
|
+
${fi < (this.schema?.sections[si].fields.length ?? 0) - 1 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, 1); }}" title="Bajar">↓</button>` : ''}
|
|
1329
|
+
<button class="fa-btn fa-btn--copy" @click="${(e) => { e.stopPropagation(); this.duplicateField(si, fi); }}" title="Duplicar">⎘</button>
|
|
1330
|
+
<button class="fa-btn fa-btn--delete" @click="${(e) => { e.stopPropagation(); this.removeField(si, fi); }}" title="Eliminar">✕</button>
|
|
1331
|
+
</div>
|
|
1332
|
+
|
|
1333
|
+
<!-- Resize handles -->
|
|
1334
|
+
${isSelected ? html `
|
|
1335
|
+
<div class="rh rh-nw"></div><div class="rh rh-n"></div><div class="rh rh-ne"></div>
|
|
1336
|
+
<div class="rh rh-e"></div><div class="rh rh-se"></div><div class="rh rh-s"></div>
|
|
1337
|
+
<div class="rh rh-sw"></div><div class="rh rh-w"></div>
|
|
1338
|
+
` : ''}
|
|
1339
|
+
|
|
1340
|
+
<!-- Field preview -->
|
|
1341
|
+
<div class="field-preview">
|
|
1342
|
+
<div class="field-preview-label">${field.label ?? field.id}${field.required ? ' *' : ''}</div>
|
|
1343
|
+
<div class="field-preview-input ${this.getPreviewClass(field.type)}">
|
|
1344
|
+
${this.getPreviewContent(field)}
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
1274
1348
|
`;
|
|
1275
1349
|
}
|
|
1276
1350
|
getPreviewClass(type) {
|
|
@@ -1323,227 +1397,215 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1323
1397
|
renderRightPanel() {
|
|
1324
1398
|
const field = this.selectedField;
|
|
1325
1399
|
if (!field) {
|
|
1326
|
-
return html `
|
|
1327
|
-
<div class="right-panel">
|
|
1328
|
-
<div class="props-empty">
|
|
1329
|
-
<div class="props-empty-icon">⬚</div>
|
|
1330
|
-
<div class="props-empty-text">Selecciona un campo<br/>para editar propiedades</div>
|
|
1331
|
-
</div>
|
|
1332
|
-
${this.schema ? this.renderFormProperties() : ''}
|
|
1333
|
-
</div>
|
|
1400
|
+
return html `
|
|
1401
|
+
<div class="right-panel">
|
|
1402
|
+
<div class="props-empty">
|
|
1403
|
+
<div class="props-empty-icon">⬚</div>
|
|
1404
|
+
<div class="props-empty-text">Selecciona un campo<br/>para editar propiedades</div>
|
|
1405
|
+
</div>
|
|
1406
|
+
${this.schema ? this.renderFormProperties() : ''}
|
|
1407
|
+
</div>
|
|
1334
1408
|
`;
|
|
1335
1409
|
}
|
|
1336
1410
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
1337
|
-
return html `
|
|
1338
|
-
<div class="right-panel">
|
|
1339
|
-
<!-- Header with type badge -->
|
|
1340
|
-
<div style="padding:8px 10px;border-bottom:1px solid #eee;">
|
|
1341
|
-
<span class="props-type-badge" style="${typeColor}">${field.type.toUpperCase()}</span>
|
|
1342
|
-
<div class="props-field-id">#${field.id}</div>
|
|
1343
|
-
</div>
|
|
1344
|
-
|
|
1345
|
-
<!-- General -->
|
|
1346
|
-
<div class="prop-section">
|
|
1347
|
-
<div class="prop-section-header" data-section="general" @click="${() => this.toggleSection('general')}">
|
|
1348
|
-
<span class="collapse-icon ${this.collapsedSections.has('general') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1349
|
-
<h4>General</h4>
|
|
1350
|
-
</div>
|
|
1351
|
-
${!this.collapsedSections.has('general') ? html `
|
|
1352
|
-
<div class="prop-row"><span class="prop-label">Label</span><input class="prop-input" .value="${field.label ?? ''}" @input="${(e) => { field.label = e.target.value; this.emitChange(); }}" /></div>
|
|
1353
|
-
<div class="prop-row"><span class="prop-label">Field</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.field}" @change="${(e) => { field.field = e.target.value; this.commitChange(); }}" /></div>
|
|
1354
|
-
<div class="prop-row"><span class="prop-label">Placeholder</span><input class="prop-input" .value="${field.placeholder ?? ''}" @input="${(e) => { field.placeholder = e.target.value; this.emitChange(); }}" /></div>
|
|
1355
|
-
<div class="prop-row"><span class="prop-label">Ayuda</span><input class="prop-input" .value="${field.helpText ?? ''}" @input="${(e) => { field.helpText = e.target.value; this.emitChange(); }}" /></div>
|
|
1356
|
-
` : ''}
|
|
1357
|
-
</div>
|
|
1358
|
-
|
|
1359
|
-
<!-- Layout (Figma position grid) -->
|
|
1360
|
-
<div class="prop-section">
|
|
1361
|
-
<div class="prop-section-header" data-section="layout" @click="${() => this.toggleSection('layout')}">
|
|
1362
|
-
<span class="collapse-icon ${this.collapsedSections.has('layout') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1363
|
-
<h4>Layout</h4>
|
|
1364
|
-
</div>
|
|
1365
|
-
${!this.collapsedSections.has('layout') ? html `
|
|
1366
|
-
<div class="prop-pos-grid">
|
|
1367
|
-
<div class="prop-pos-cell">
|
|
1368
|
-
<span class="prop-pos-label prop-pos-label--w">W</span>
|
|
1369
|
-
<input type="number" min="1" max="6" .value="${String(field.colSpan ?? 1)}" @change="${(e) => { field.colSpan = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1370
|
-
</div>
|
|
1371
|
-
<div class="prop-pos-cell">
|
|
1372
|
-
<span class="prop-pos-label prop-pos-label--h">T</span>
|
|
1373
|
-
<select .value="${field.type}" @change="${(e) => { field.type = e.target.value; this.commitChange(); }}" style="border:none;background:transparent;font-size:11px;width:100%;outline:none;color:var(--zrd-text);font-family:inherit;">
|
|
1374
|
-
${getAllFields().map(f => html `<option value="${f.type}" ?selected="${f.type === field.type}">${f.label}</option>`)}
|
|
1375
|
-
</select>
|
|
1376
|
-
</div>
|
|
1377
|
-
</div>
|
|
1378
|
-
<div class="prop-divider"></div>
|
|
1379
|
-
<div class="prop-row"><span class="prop-label">CSS Class</span><input class="prop-input" .value="${field.cssClass ?? ''}" placeholder="mi-clase" @input="${(e) => { field.cssClass = e.target.value; this.emitChange(); }}" /></div>
|
|
1380
|
-
` : ''}
|
|
1381
|
-
</div>
|
|
1382
|
-
|
|
1383
|
-
<!-- Style -->
|
|
1384
|
-
<div class="prop-section">
|
|
1385
|
-
<div class="prop-section-header" data-section="style" @click="${() => this.toggleSection('style')}">
|
|
1386
|
-
<span class="collapse-icon ${this.collapsedSections.has('style') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1387
|
-
<h4>Estilo</h4>
|
|
1388
|
-
</div>
|
|
1389
|
-
${!this.collapsedSections.has('style') ? html `
|
|
1390
|
-
<div class="prop-row">
|
|
1391
|
-
<span class="prop-label">Ancho</span>
|
|
1392
|
-
<div class="prop-segmented">
|
|
1393
|
-
${['auto', '100%', '50%'].map(w => html `
|
|
1394
|
-
<button class="prop-seg-btn ${(field.width ?? 'auto') === w ? 'prop-seg-btn--active' : ''}"
|
|
1395
|
-
@click="${() => { field.width = w === 'auto' ? undefined : w; this.commitChange(); }}"
|
|
1396
|
-
>${w}</button>
|
|
1397
|
-
`)}
|
|
1398
|
-
</div>
|
|
1399
|
-
</div>
|
|
1400
|
-
` : ''}
|
|
1401
|
-
</div>
|
|
1402
|
-
|
|
1403
|
-
<!-- Behavior (toggles) -->
|
|
1404
|
-
<div class="prop-section">
|
|
1405
|
-
<div class="prop-section-header" data-section="behavior" @click="${() => this.toggleSection('behavior')}">
|
|
1406
|
-
<span class="collapse-icon ${this.collapsedSections.has('behavior') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1407
|
-
<h4>Comportamiento</h4>
|
|
1408
|
-
</div>
|
|
1409
|
-
${!this.collapsedSections.has('behavior') ? html `
|
|
1410
|
-
${this.renderToggle('Requerido', field.required ?? false, (v) => { field.required = v; this.commitChange(); })}
|
|
1411
|
-
${this.renderToggle('Solo lectura', field.readOnly ?? false, (v) => { field.readOnly = v; this.commitChange(); })}
|
|
1412
|
-
${this.renderToggle('Deshabilitado', field.disabled ?? false, (v) => { field.disabled = v; this.commitChange(); })}
|
|
1413
|
-
${this.renderToggle('Oculto', field.hidden ?? false, (v) => { field.hidden = v; this.commitChange(); })}
|
|
1414
|
-
` : ''}
|
|
1415
|
-
</div>
|
|
1416
|
-
|
|
1417
|
-
<!-- Rules (expressions) -->
|
|
1418
|
-
<div class="prop-section">
|
|
1419
|
-
<div class="prop-section-header" data-section="rules" @click="${() => this.toggleSection('rules')}">
|
|
1420
|
-
<span class="collapse-icon ${this.collapsedSections.has('rules') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1421
|
-
<h4>Reglas</h4>
|
|
1422
|
-
</div>
|
|
1423
|
-
${!this.collapsedSections.has('rules') ? html `
|
|
1424
|
-
<div class="prop-info">💡 Usa {campo} para referencias y expresiones</div>
|
|
1425
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Condicion de visibilidad</span></div>
|
|
1426
|
-
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.visibilityRule ?? ''}" placeholder='{tipo} == "empresa" AND {activo} == true' @change="${(e) => { field.visibilityRule = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1427
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Valor computado</span></div>
|
|
1428
|
-
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.computedValue ?? ''}" placeholder='{precio} * {cantidad} * (1 + {iva}/100)' @change="${(e) => { field.computedValue = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1429
|
-
<div class="prop-divider"></div>
|
|
1430
|
-
<div class="prop-row"><span class="prop-label">Default</span><input class="prop-input" .value="${String(field.defaultValue ?? '')}" @change="${(e) => { field.defaultValue = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1431
|
-
` : ''}
|
|
1432
|
-
</div>
|
|
1433
|
-
|
|
1434
|
-
<!-- Data Source (API connection like report-designer) -->
|
|
1435
|
-
<div class="prop-section">
|
|
1436
|
-
<div class="prop-section-header" data-section="datasource" @click="${() => this.toggleSection('datasource')}">
|
|
1437
|
-
<span class="collapse-icon ${this.collapsedSections.has('datasource') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1438
|
-
<h4>Origen de Datos</h4>
|
|
1439
|
-
</div>
|
|
1440
|
-
${!this.collapsedSections.has('datasource') ? html `
|
|
1441
|
-
<div class="prop-info">🔌 Conecta campos a APIs y endpoints</div>
|
|
1411
|
+
return html `
|
|
1412
|
+
<div class="right-panel">
|
|
1413
|
+
<!-- Header with type badge -->
|
|
1414
|
+
<div style="padding:8px 10px;border-bottom:1px solid #eee;">
|
|
1415
|
+
<span class="props-type-badge" style="${typeColor}">${field.type.toUpperCase()}</span>
|
|
1416
|
+
<div class="props-field-id">#${field.id}</div>
|
|
1417
|
+
</div>
|
|
1418
|
+
|
|
1419
|
+
<!-- General -->
|
|
1420
|
+
<div class="prop-section">
|
|
1421
|
+
<div class="prop-section-header" data-section="general" @click="${() => this.toggleSection('general')}">
|
|
1422
|
+
<span class="collapse-icon ${this.collapsedSections.has('general') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1423
|
+
<h4>General</h4>
|
|
1424
|
+
</div>
|
|
1425
|
+
${!this.collapsedSections.has('general') ? html `
|
|
1426
|
+
<div class="prop-row"><span class="prop-label">Label</span><input class="prop-input" .value="${field.label ?? ''}" @input="${(e) => { field.label = e.target.value; this.emitChange(); }}" /></div>
|
|
1427
|
+
<div class="prop-row"><span class="prop-label">Field</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.field}" @change="${(e) => { field.field = e.target.value; this.commitChange(); }}" /></div>
|
|
1428
|
+
<div class="prop-row"><span class="prop-label">Placeholder</span><input class="prop-input" .value="${field.placeholder ?? ''}" @input="${(e) => { field.placeholder = e.target.value; this.emitChange(); }}" /></div>
|
|
1429
|
+
<div class="prop-row"><span class="prop-label">Ayuda</span><input class="prop-input" .value="${field.helpText ?? ''}" @input="${(e) => { field.helpText = e.target.value; this.emitChange(); }}" /></div>
|
|
1430
|
+
` : ''}
|
|
1431
|
+
</div>
|
|
1432
|
+
|
|
1433
|
+
<!-- Layout (Figma position grid) -->
|
|
1434
|
+
<div class="prop-section">
|
|
1435
|
+
<div class="prop-section-header" data-section="layout" @click="${() => this.toggleSection('layout')}">
|
|
1436
|
+
<span class="collapse-icon ${this.collapsedSections.has('layout') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1437
|
+
<h4>Layout</h4>
|
|
1438
|
+
</div>
|
|
1439
|
+
${!this.collapsedSections.has('layout') ? html `
|
|
1440
|
+
<div class="prop-pos-grid">
|
|
1441
|
+
<div class="prop-pos-cell">
|
|
1442
|
+
<span class="prop-pos-label prop-pos-label--w">W</span>
|
|
1443
|
+
<input type="number" min="1" max="6" .value="${String(field.colSpan ?? 1)}" @change="${(e) => { field.colSpan = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1444
|
+
</div>
|
|
1445
|
+
<div class="prop-pos-cell">
|
|
1446
|
+
<span class="prop-pos-label prop-pos-label--h">T</span>
|
|
1447
|
+
<select .value="${field.type}" @change="${(e) => { field.type = e.target.value; this.commitChange(); }}" style="border:none;background:transparent;font-size:11px;width:100%;outline:none;color:var(--zrd-text);font-family:inherit;">
|
|
1448
|
+
${getAllFields().map(f => html `<option value="${f.type}" ?selected="${f.type === field.type}">${f.label}</option>`)}
|
|
1449
|
+
</select>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
<div class="prop-divider"></div>
|
|
1453
|
+
<div class="prop-row"><span class="prop-label">CSS Class</span><input class="prop-input" .value="${field.cssClass ?? ''}" placeholder="mi-clase" @input="${(e) => { field.cssClass = e.target.value; this.emitChange(); }}" /></div>
|
|
1454
|
+
` : ''}
|
|
1455
|
+
</div>
|
|
1456
|
+
|
|
1457
|
+
<!-- Style -->
|
|
1458
|
+
<div class="prop-section">
|
|
1459
|
+
<div class="prop-section-header" data-section="style" @click="${() => this.toggleSection('style')}">
|
|
1460
|
+
<span class="collapse-icon ${this.collapsedSections.has('style') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1461
|
+
<h4>Estilo</h4>
|
|
1462
|
+
</div>
|
|
1463
|
+
${!this.collapsedSections.has('style') ? html `
|
|
1464
|
+
<div class="prop-row">
|
|
1465
|
+
<span class="prop-label">Ancho</span>
|
|
1466
|
+
<div class="prop-segmented">
|
|
1467
|
+
${['auto', '100%', '50%'].map(w => html `
|
|
1468
|
+
<button class="prop-seg-btn ${(field.width ?? 'auto') === w ? 'prop-seg-btn--active' : ''}"
|
|
1469
|
+
@click="${() => { field.width = w === 'auto' ? undefined : w; this.commitChange(); }}"
|
|
1470
|
+
>${w}</button>
|
|
1471
|
+
`)}
|
|
1472
|
+
</div>
|
|
1473
|
+
</div>
|
|
1474
|
+
` : ''}
|
|
1475
|
+
</div>
|
|
1476
|
+
|
|
1477
|
+
<!-- Behavior (toggles) -->
|
|
1478
|
+
<div class="prop-section">
|
|
1479
|
+
<div class="prop-section-header" data-section="behavior" @click="${() => this.toggleSection('behavior')}">
|
|
1480
|
+
<span class="collapse-icon ${this.collapsedSections.has('behavior') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1481
|
+
<h4>Comportamiento</h4>
|
|
1482
|
+
</div>
|
|
1483
|
+
${!this.collapsedSections.has('behavior') ? html `
|
|
1484
|
+
${this.renderToggle('Requerido', field.required ?? false, (v) => { field.required = v; this.commitChange(); })}
|
|
1485
|
+
${this.renderToggle('Solo lectura', field.readOnly ?? false, (v) => { field.readOnly = v; this.commitChange(); })}
|
|
1486
|
+
${this.renderToggle('Deshabilitado', field.disabled ?? false, (v) => { field.disabled = v; this.commitChange(); })}
|
|
1487
|
+
${this.renderToggle('Oculto', field.hidden ?? false, (v) => { field.hidden = v; this.commitChange(); })}
|
|
1488
|
+
` : ''}
|
|
1489
|
+
</div>
|
|
1490
|
+
|
|
1491
|
+
<!-- Rules (expressions) -->
|
|
1492
|
+
<div class="prop-section">
|
|
1493
|
+
<div class="prop-section-header" data-section="rules" @click="${() => this.toggleSection('rules')}">
|
|
1494
|
+
<span class="collapse-icon ${this.collapsedSections.has('rules') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1495
|
+
<h4>Reglas</h4>
|
|
1496
|
+
</div>
|
|
1497
|
+
${!this.collapsedSections.has('rules') ? html `
|
|
1498
|
+
<div class="prop-info">💡 Usa {campo} para referencias y expresiones</div>
|
|
1499
|
+
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Condicion de visibilidad</span></div>
|
|
1500
|
+
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.visibilityRule ?? ''}" placeholder='{tipo} == "empresa" AND {activo} == true' @change="${(e) => { field.visibilityRule = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1501
|
+
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Valor computado</span></div>
|
|
1502
|
+
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.computedValue ?? ''}" placeholder='{precio} * {cantidad} * (1 + {iva}/100)' @change="${(e) => { field.computedValue = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1503
|
+
<div class="prop-divider"></div>
|
|
1504
|
+
<div class="prop-row"><span class="prop-label">Default</span><input class="prop-input" .value="${String(field.defaultValue ?? '')}" @change="${(e) => { field.defaultValue = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1505
|
+
` : ''}
|
|
1506
|
+
</div>
|
|
1507
|
+
|
|
1508
|
+
<!-- Data Source (API connection like report-designer) -->
|
|
1509
|
+
<div class="prop-section">
|
|
1510
|
+
<div class="prop-section-header" data-section="datasource" @click="${() => this.toggleSection('datasource')}">
|
|
1511
|
+
<span class="collapse-icon ${this.collapsedSections.has('datasource') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1512
|
+
<h4>Origen de Datos</h4>
|
|
1513
|
+
</div>
|
|
1514
|
+
${!this.collapsedSections.has('datasource') ? html `
|
|
1515
|
+
<div class="prop-info">🔌 Conecta campos a APIs y endpoints</div>
|
|
1442
1516
|
<div class="prop-row"><span class="prop-label">Endpoint</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.props?.endpoint ?? ''}" placeholder="/v1/clientes" @change="${(e) => { if (!field.props)
|
|
1443
|
-
field.props = {}; field.props.endpoint = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1517
|
+
field.props = {}; field.props.endpoint = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1444
1518
|
<div class="prop-row"><span class="prop-label">Campo valor</span><input class="prop-input" .value="${field.props?.valueField ?? ''}" placeholder="id" @change="${(e) => { if (!field.props)
|
|
1445
|
-
field.props = {}; field.props.valueField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1519
|
+
field.props = {}; field.props.valueField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1446
1520
|
<div class="prop-row"><span class="prop-label">Campo label</span><input class="prop-input" .value="${field.props?.displayField ?? ''}" placeholder="nombre" @change="${(e) => { if (!field.props)
|
|
1447
|
-
field.props = {}; field.props.displayField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1448
|
-
${field.type === 'datagrid' || field.type === 'select' || field.type === 'lookup' ? html `
|
|
1449
|
-
<div class="prop-divider"></div>
|
|
1521
|
+
field.props = {}; field.props.displayField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1522
|
+
${field.type === 'datagrid' || field.type === 'select' || field.type === 'lookup' ? html `
|
|
1523
|
+
<div class="prop-divider"></div>
|
|
1450
1524
|
<div class="prop-row"><span class="prop-label">Data Source</span><input class="prop-input" .value="${field.props?.dataSourceId ?? ''}" placeholder="clientesList" @change="${(e) => { if (!field.props)
|
|
1451
|
-
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1452
|
-
` : ''}
|
|
1453
|
-
` : ''}
|
|
1454
|
-
</div>
|
|
1455
|
-
</div>
|
|
1525
|
+
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1526
|
+
` : ''}
|
|
1527
|
+
` : ''}
|
|
1528
|
+
</div>
|
|
1529
|
+
</div>
|
|
1456
1530
|
`;
|
|
1457
1531
|
}
|
|
1458
1532
|
renderFormProperties() {
|
|
1459
1533
|
if (!this.schema)
|
|
1460
1534
|
return nothing;
|
|
1461
|
-
return html `
|
|
1462
|
-
<div class="prop-section">
|
|
1463
|
-
<div class="prop-section-header" data-section="form">
|
|
1464
|
-
<span class="collapse-icon">▾</span>
|
|
1465
|
-
<h4>Formulario</h4>
|
|
1466
|
-
</div>
|
|
1467
|
-
<div class="prop-row"><span class="prop-label">Titulo</span><input class="prop-input" .value="${this.schema.title}" @input="${(e) => { this.schema.title = e.target.value; this.emitChange(); }}" /></div>
|
|
1468
|
-
<div class="prop-divider"></div>
|
|
1469
|
-
<div class="prop-pos-grid">
|
|
1470
|
-
<div class="prop-pos-cell">
|
|
1471
|
-
<span class="prop-pos-label prop-pos-label--w">C</span>
|
|
1472
|
-
<input type="number" min="1" max="6" .value="${String(this.schema.layout.columns ?? 1)}" @change="${(e) => { this.schema.layout.columns = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1473
|
-
</div>
|
|
1474
|
-
<div class="prop-pos-cell">
|
|
1475
|
-
<span class="prop-pos-label prop-pos-label--y">G</span>
|
|
1476
|
-
<input type="number" min="0" .value="${String(this.schema.layout.gap ?? 16)}" @change="${(e) => { this.schema.layout.gap = parseInt(e.target.value) || 16; this.commitChange(); }}" />
|
|
1477
|
-
</div>
|
|
1478
|
-
</div>
|
|
1479
|
-
<div style="font-size:9px;color:#bbb;margin-top:4px;display:flex;gap:12px;">
|
|
1480
|
-
<span>C = Columnas</span><span>G = Gap (px)</span>
|
|
1481
|
-
</div>
|
|
1482
|
-
</div>
|
|
1535
|
+
return html `
|
|
1536
|
+
<div class="prop-section">
|
|
1537
|
+
<div class="prop-section-header" data-section="form">
|
|
1538
|
+
<span class="collapse-icon">▾</span>
|
|
1539
|
+
<h4>Formulario</h4>
|
|
1540
|
+
</div>
|
|
1541
|
+
<div class="prop-row"><span class="prop-label">Titulo</span><input class="prop-input" .value="${this.schema.title}" @input="${(e) => { this.schema.title = e.target.value; this.emitChange(); }}" /></div>
|
|
1542
|
+
<div class="prop-divider"></div>
|
|
1543
|
+
<div class="prop-pos-grid">
|
|
1544
|
+
<div class="prop-pos-cell">
|
|
1545
|
+
<span class="prop-pos-label prop-pos-label--w">C</span>
|
|
1546
|
+
<input type="number" min="1" max="6" .value="${String(this.schema.layout.columns ?? 1)}" @change="${(e) => { this.schema.layout.columns = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1547
|
+
</div>
|
|
1548
|
+
<div class="prop-pos-cell">
|
|
1549
|
+
<span class="prop-pos-label prop-pos-label--y">G</span>
|
|
1550
|
+
<input type="number" min="0" .value="${String(this.schema.layout.gap ?? 16)}" @change="${(e) => { this.schema.layout.gap = parseInt(e.target.value) || 16; this.commitChange(); }}" />
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
<div style="font-size:9px;color:#bbb;margin-top:4px;display:flex;gap:12px;">
|
|
1554
|
+
<span>C = Columnas</span><span>G = Gap (px)</span>
|
|
1555
|
+
</div>
|
|
1556
|
+
</div>
|
|
1483
1557
|
`;
|
|
1484
1558
|
}
|
|
1485
1559
|
renderApiPanel() {
|
|
1486
|
-
return html `
|
|
1487
|
-
<div style="padding:4px;">
|
|
1488
|
-
<!-- Auth Section -->
|
|
1489
|
-
${this.renderApiAuth()}
|
|
1490
|
-
|
|
1491
|
-
<!-- Quick connect (only when logged in or no auth needed) -->
|
|
1492
|
-
<div style="padding:4px 8px;margin-top:4px;">
|
|
1493
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Conectar Endpoint</div>
|
|
1494
|
-
<div style="display:flex;gap:4px;margin-bottom:6px;">
|
|
1495
|
-
<select id="api-method" style="width:70px;padding:4px 6px;border:1px solid #ddd;border-radius:4px;font-size:10px;background:white;font-family:inherit;">
|
|
1496
|
-
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
|
|
1497
|
-
</select>
|
|
1498
|
-
<input id="api-url" style="flex:1;padding:4px 8px;border:1px solid #ddd;border-radius:4px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:#fafafa;" placeholder="/v1/clientes" />
|
|
1499
|
-
</div>
|
|
1500
|
-
<button style="width:100%;padding:6px;border:1px solid #ea580c;border-radius:5px;background:#fff7ed;color:#ea580c;cursor:pointer;font-size:11px;font-weight:600;font-family:inherit;transition:all 0.15s;"
|
|
1501
|
-
@click="${this.fetchApiFields}"
|
|
1502
|
-
>${this.apiLoading ? 'Cargando...' : '🔌 Probar Conexion'}</button>
|
|
1503
|
-
</div>
|
|
1504
|
-
|
|
1505
|
-
<!-- Fetched sources -->
|
|
1506
|
-
${this.apiSources.length > 0 ? html `
|
|
1507
|
-
<div style="margin-top:8px;">
|
|
1508
|
-
${this.apiSources.map(src => html `
|
|
1509
|
-
<div style="margin:4px;border:1px solid #eee;border-radius:6px;overflow:hidden;">
|
|
1510
|
-
<div style="padding:6px 8px;background:#f8f9fa;display:flex;align-items:center;gap:6px;border-bottom:1px solid #eee;">
|
|
1511
|
-
<span style="background:#ea580c;color:white;font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;">${src.method}</span>
|
|
1512
|
-
<span style="font-size:10px;font-weight:600;color:#333;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${src.name}</span>
|
|
1513
|
-
<span style="font-size:9px;color:#aaa;">${src.fields.length} campos</span>
|
|
1514
|
-
</div>
|
|
1515
|
-
<div style="padding:2px 4px;max-height:150px;overflow-y:auto;">
|
|
1516
|
-
${src.fields.map(field => html `
|
|
1517
|
-
<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;font-size:11px;cursor:grab;border-radius:3px;transition:background 0.1s;"
|
|
1518
|
-
draggable="true"
|
|
1519
|
-
@dragstart="${(e) => { this.dragType = 'text'; e.dataTransfer?.setData('text/plain', 'text'); e.dataTransfer?.setData('field-name', field); e.dataTransfer?.setData('ds-id', src.id); }}"
|
|
1520
|
-
@dblclick="${() =>
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
<span style="color:#555;">${field}</span>
|
|
1536
|
-
</div>
|
|
1537
|
-
`)}
|
|
1538
|
-
</div>
|
|
1539
|
-
</div>
|
|
1540
|
-
`)}
|
|
1541
|
-
</div>
|
|
1542
|
-
` : ''}
|
|
1543
|
-
|
|
1544
|
-
<!-- Zentto API shortcuts -->
|
|
1545
|
-
<div style="margin-top:12px;padding:4px 8px;">
|
|
1546
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:6px;">Accesos Rapidos Zentto</div>
|
|
1560
|
+
return html `
|
|
1561
|
+
<div style="padding:4px;">
|
|
1562
|
+
<!-- Auth Section -->
|
|
1563
|
+
${this.renderApiAuth()}
|
|
1564
|
+
|
|
1565
|
+
<!-- Quick connect (only when logged in or no auth needed) -->
|
|
1566
|
+
<div style="padding:4px 8px;margin-top:4px;">
|
|
1567
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Conectar Endpoint</div>
|
|
1568
|
+
<div style="display:flex;gap:4px;margin-bottom:6px;">
|
|
1569
|
+
<select id="api-method" style="width:70px;padding:4px 6px;border:1px solid #ddd;border-radius:4px;font-size:10px;background:white;font-family:inherit;">
|
|
1570
|
+
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
|
|
1571
|
+
</select>
|
|
1572
|
+
<input id="api-url" style="flex:1;padding:4px 8px;border:1px solid #ddd;border-radius:4px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:#fafafa;" placeholder="/v1/clientes" />
|
|
1573
|
+
</div>
|
|
1574
|
+
<button style="width:100%;padding:6px;border:1px solid #ea580c;border-radius:5px;background:#fff7ed;color:#ea580c;cursor:pointer;font-size:11px;font-weight:600;font-family:inherit;transition:all 0.15s;"
|
|
1575
|
+
@click="${this.fetchApiFields}"
|
|
1576
|
+
>${this.apiLoading ? 'Cargando...' : '🔌 Probar Conexion'}</button>
|
|
1577
|
+
</div>
|
|
1578
|
+
|
|
1579
|
+
<!-- Fetched sources -->
|
|
1580
|
+
${this.apiSources.length > 0 ? html `
|
|
1581
|
+
<div style="margin-top:8px;">
|
|
1582
|
+
${this.apiSources.map(src => html `
|
|
1583
|
+
<div style="margin:4px;border:1px solid #eee;border-radius:6px;overflow:hidden;">
|
|
1584
|
+
<div style="padding:6px 8px;background:#f8f9fa;display:flex;align-items:center;gap:6px;border-bottom:1px solid #eee;">
|
|
1585
|
+
<span style="background:#ea580c;color:white;font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;">${src.method}</span>
|
|
1586
|
+
<span style="font-size:10px;font-weight:600;color:#333;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${src.name}</span>
|
|
1587
|
+
<span style="font-size:9px;color:#aaa;">${src.fields.length} campos</span>
|
|
1588
|
+
</div>
|
|
1589
|
+
<div style="padding:2px 4px;max-height:150px;overflow-y:auto;">
|
|
1590
|
+
${src.fields.map(field => html `
|
|
1591
|
+
<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;font-size:11px;cursor:grab;border-radius:3px;transition:background 0.1s;"
|
|
1592
|
+
draggable="true"
|
|
1593
|
+
@dragstart="${(e) => { this.dragType = 'text'; e.dataTransfer?.setData('text/plain', 'text'); e.dataTransfer?.setData('field-name', field); e.dataTransfer?.setData('ds-id', src.id); }}"
|
|
1594
|
+
@dblclick="${() => this.addApiField(field, src.id)}"
|
|
1595
|
+
>
|
|
1596
|
+
<span style="color:#1976d2;font-size:10px;">⬡</span>
|
|
1597
|
+
<span style="color:#555;">${field}</span>
|
|
1598
|
+
</div>
|
|
1599
|
+
`)}
|
|
1600
|
+
</div>
|
|
1601
|
+
</div>
|
|
1602
|
+
`)}
|
|
1603
|
+
</div>
|
|
1604
|
+
` : ''}
|
|
1605
|
+
|
|
1606
|
+
<!-- Zentto API shortcuts -->
|
|
1607
|
+
<div style="margin-top:12px;padding:4px 8px;">
|
|
1608
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:6px;">Accesos Rapidos Zentto</div>
|
|
1547
1609
|
${[
|
|
1548
1610
|
{ label: 'Clientes', endpoint: '/v1/clientes', icon: '👥' },
|
|
1549
1611
|
{ label: 'Articulos', endpoint: '/v1/articulos', icon: '📦' },
|
|
@@ -1553,34 +1615,34 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1553
1615
|
{ label: 'Proveedores', endpoint: '/v1/proveedores', icon: '🏭' },
|
|
1554
1616
|
{ label: 'Bancos', endpoint: '/v1/bancos', icon: '🏦' },
|
|
1555
1617
|
{ label: 'Paises', endpoint: '/v1/config/countries', icon: '🌍' },
|
|
1556
|
-
].map(api => html `
|
|
1557
|
-
<div style="display:flex;align-items:center;gap:8px;padding:6px 8px;margin:2px 0;border-radius:5px;cursor:pointer;font-size:11px;transition:all 0.15s;border:1px solid transparent;"
|
|
1618
|
+
].map(api => html `
|
|
1619
|
+
<div style="display:flex;align-items:center;gap:8px;padding:6px 8px;margin:2px 0;border-radius:5px;cursor:pointer;font-size:11px;transition:all 0.15s;border:1px solid transparent;"
|
|
1558
1620
|
@click="${() => {
|
|
1559
1621
|
const urlInput = this.shadowRoot?.querySelector('#api-url');
|
|
1560
1622
|
if (urlInput)
|
|
1561
1623
|
urlInput.value = api.endpoint;
|
|
1562
|
-
}}"
|
|
1563
|
-
@mouseenter="${(e) => { e.target.style.background = '#fff7ed'; e.target.style.borderColor = '#fed7aa'; }}"
|
|
1564
|
-
@mouseleave="${(e) => { e.target.style.background = ''; e.target.style.borderColor = 'transparent'; }}"
|
|
1565
|
-
>
|
|
1566
|
-
<span style="font-size:14px;">${api.icon}</span>
|
|
1567
|
-
<span style="flex:1;color:#555;">${api.label}</span>
|
|
1568
|
-
<span style="font-size:9px;color:#bbb;font-family:'SF Mono','Consolas',monospace;">${api.endpoint}</span>
|
|
1569
|
-
</div>
|
|
1570
|
-
`)}
|
|
1571
|
-
</div>
|
|
1572
|
-
|
|
1573
|
-
<!-- Manual data source -->
|
|
1574
|
-
<div style="margin-top:8px;padding:4px 8px;">
|
|
1575
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Datos Estaticos</div>
|
|
1576
|
-
<button style="width:100%;padding:6px;border:1px dashed #ccc;border-radius:5px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;"
|
|
1624
|
+
}}"
|
|
1625
|
+
@mouseenter="${(e) => { e.target.style.background = '#fff7ed'; e.target.style.borderColor = '#fed7aa'; }}"
|
|
1626
|
+
@mouseleave="${(e) => { e.target.style.background = ''; e.target.style.borderColor = 'transparent'; }}"
|
|
1627
|
+
>
|
|
1628
|
+
<span style="font-size:14px;">${api.icon}</span>
|
|
1629
|
+
<span style="flex:1;color:#555;">${api.label}</span>
|
|
1630
|
+
<span style="font-size:9px;color:#bbb;font-family:'SF Mono','Consolas',monospace;">${api.endpoint}</span>
|
|
1631
|
+
</div>
|
|
1632
|
+
`)}
|
|
1633
|
+
</div>
|
|
1634
|
+
|
|
1635
|
+
<!-- Manual data source -->
|
|
1636
|
+
<div style="margin-top:8px;padding:4px 8px;">
|
|
1637
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Datos Estaticos</div>
|
|
1638
|
+
<button style="width:100%;padding:6px;border:1px dashed #ccc;border-radius:5px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;"
|
|
1577
1639
|
@click="${() => {
|
|
1578
1640
|
const src = { id: 'static_' + Date.now(), name: 'Datos Manuales', endpoint: '', method: 'STATIC', fields: ['campo1', 'campo2', 'campo3'] };
|
|
1579
1641
|
this.apiSources = [...this.apiSources, src];
|
|
1580
|
-
}}"
|
|
1581
|
-
>+ Agregar Datos Estaticos</button>
|
|
1582
|
-
</div>
|
|
1583
|
-
</div>
|
|
1642
|
+
}}"
|
|
1643
|
+
>+ Agregar Datos Estaticos</button>
|
|
1644
|
+
</div>
|
|
1645
|
+
</div>
|
|
1584
1646
|
`;
|
|
1585
1647
|
}
|
|
1586
1648
|
// ─── Template Menu ─────────────────────────────────
|
|
@@ -1691,13 +1753,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1691
1753
|
], actions: [{ id: 'send', type: 'submit', label: 'Enviar Encuesta', variant: 'primary' }] },
|
|
1692
1754
|
},
|
|
1693
1755
|
];
|
|
1694
|
-
return html `
|
|
1695
|
-
<div style="position:absolute;top:100%;left:0;z-index:100;background:white;border:1px solid #ddd;border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,0.15);width:320px;max-height:400px;overflow-y:auto;margin-top:4px;">
|
|
1696
|
-
<div style="padding:10px 12px;border-bottom:1px solid #eee;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:0.5px;">
|
|
1697
|
-
Cargar Plantilla
|
|
1698
|
-
</div>
|
|
1699
|
-
${templates.map(t => html `
|
|
1700
|
-
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 14px;cursor:pointer;transition:background 0.1s;border-bottom:1px solid #f5f5f5;"
|
|
1756
|
+
return html `
|
|
1757
|
+
<div style="position:absolute;top:100%;left:0;z-index:100;background:white;border:1px solid #ddd;border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,0.15);width:320px;max-height:400px;overflow-y:auto;margin-top:4px;">
|
|
1758
|
+
<div style="padding:10px 12px;border-bottom:1px solid #eee;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:0.5px;">
|
|
1759
|
+
Cargar Plantilla
|
|
1760
|
+
</div>
|
|
1761
|
+
${templates.map(t => html `
|
|
1762
|
+
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 14px;cursor:pointer;transition:background 0.1s;border-bottom:1px solid #f5f5f5;"
|
|
1701
1763
|
@click="${() => {
|
|
1702
1764
|
this.schema = structuredClone(t.schema);
|
|
1703
1765
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
@@ -1705,116 +1767,116 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1705
1767
|
this.selectedFieldId = null;
|
|
1706
1768
|
this.showTemplateMenu = false;
|
|
1707
1769
|
this.commitChange();
|
|
1708
|
-
}}"
|
|
1709
|
-
@mouseenter="${(e) => { e.currentTarget.style.background = '#f0f7ff'; }}"
|
|
1710
|
-
@mouseleave="${(e) => { e.currentTarget.style.background = ''; }}"
|
|
1711
|
-
>
|
|
1712
|
-
<span style="font-size:22px;margin-top:1px;">${t.icon}</span>
|
|
1713
|
-
<div>
|
|
1714
|
-
<div style="font-size:13px;font-weight:600;color:#333;">${t.title}</div>
|
|
1715
|
-
<div style="font-size:11px;color:#999;margin-top:1px;">${t.desc}</div>
|
|
1716
|
-
</div>
|
|
1717
|
-
</div>
|
|
1718
|
-
`)}
|
|
1719
|
-
<div style="padding:8px 14px;border-top:1px solid #eee;">
|
|
1720
|
-
<div style="font-size:10px;color:#bbb;text-align:center;">Clic en una plantilla para cargarla</div>
|
|
1721
|
-
</div>
|
|
1722
|
-
</div>
|
|
1770
|
+
}}"
|
|
1771
|
+
@mouseenter="${(e) => { e.currentTarget.style.background = '#f0f7ff'; }}"
|
|
1772
|
+
@mouseleave="${(e) => { e.currentTarget.style.background = ''; }}"
|
|
1773
|
+
>
|
|
1774
|
+
<span style="font-size:22px;margin-top:1px;">${t.icon}</span>
|
|
1775
|
+
<div>
|
|
1776
|
+
<div style="font-size:13px;font-weight:600;color:#333;">${t.title}</div>
|
|
1777
|
+
<div style="font-size:11px;color:#999;margin-top:1px;">${t.desc}</div>
|
|
1778
|
+
</div>
|
|
1779
|
+
</div>
|
|
1780
|
+
`)}
|
|
1781
|
+
<div style="padding:8px 14px;border-top:1px solid #eee;">
|
|
1782
|
+
<div style="font-size:10px;color:#bbb;text-align:center;">Clic en una plantilla para cargarla</div>
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1723
1785
|
`;
|
|
1724
1786
|
}
|
|
1725
1787
|
renderApiAuth() {
|
|
1726
1788
|
if (this.apiLoggedIn) {
|
|
1727
|
-
return html `
|
|
1728
|
-
<div style="margin:4px;padding:10px;background:linear-gradient(135deg,#e8f5e9,#f1f8e9);border:1px solid #c8e6c9;border-radius:8px;">
|
|
1729
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
1730
|
-
<span style="width:28px;height:28px;border-radius:50%;background:#27ae60;color:white;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;">✓</span>
|
|
1731
|
-
<div style="flex:1;">
|
|
1732
|
-
<div style="font-size:11px;font-weight:600;color:#2e7d32;">Conectado</div>
|
|
1733
|
-
<div style="font-size:10px;color:#66bb6a;">${this.apiUser}${this.apiCompany ? ` — ${this.apiCompany}` : ''}</div>
|
|
1734
|
-
</div>
|
|
1735
|
-
<button style="border:none;background:none;cursor:pointer;font-size:14px;color:#999;padding:2px;" title="Cerrar sesion"
|
|
1736
|
-
@click="${() => { this.apiLoggedIn = false; this.apiToken = ''; this.apiUser = ''; this.apiCompany = ''; this.apiBranch = ''; }}"
|
|
1737
|
-
>✕</button>
|
|
1738
|
-
</div>
|
|
1739
|
-
<div style="font-size:9px;color:#81c784;font-family:'SF Mono','Consolas',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${this.apiBaseUrl}">
|
|
1740
|
-
🔗 ${this.apiBaseUrl}
|
|
1741
|
-
</div>
|
|
1742
|
-
</div>
|
|
1789
|
+
return html `
|
|
1790
|
+
<div style="margin:4px;padding:10px;background:linear-gradient(135deg,#e8f5e9,#f1f8e9);border:1px solid #c8e6c9;border-radius:8px;">
|
|
1791
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
1792
|
+
<span style="width:28px;height:28px;border-radius:50%;background:#27ae60;color:white;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;">✓</span>
|
|
1793
|
+
<div style="flex:1;">
|
|
1794
|
+
<div style="font-size:11px;font-weight:600;color:#2e7d32;">Conectado</div>
|
|
1795
|
+
<div style="font-size:10px;color:#66bb6a;">${this.apiUser}${this.apiCompany ? ` — ${this.apiCompany}` : ''}</div>
|
|
1796
|
+
</div>
|
|
1797
|
+
<button style="border:none;background:none;cursor:pointer;font-size:14px;color:#999;padding:2px;" title="Cerrar sesion"
|
|
1798
|
+
@click="${() => { this.apiLoggedIn = false; this.apiToken = ''; this.apiUser = ''; this.apiCompany = ''; this.apiBranch = ''; }}"
|
|
1799
|
+
>✕</button>
|
|
1800
|
+
</div>
|
|
1801
|
+
<div style="font-size:9px;color:#81c784;font-family:'SF Mono','Consolas',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${this.apiBaseUrl}">
|
|
1802
|
+
🔗 ${this.apiBaseUrl}
|
|
1803
|
+
</div>
|
|
1804
|
+
</div>
|
|
1743
1805
|
`;
|
|
1744
1806
|
}
|
|
1745
|
-
return html `
|
|
1746
|
-
<div style="margin:4px;padding:10px;background:#fafafa;border:1px solid #eee;border-radius:8px;">
|
|
1747
|
-
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;">
|
|
1748
|
-
<span style="font-size:14px;">🔐</span>
|
|
1749
|
-
<span style="font-size:11px;font-weight:600;color:#333;">Iniciar Sesion</span>
|
|
1750
|
-
</div>
|
|
1751
|
-
|
|
1752
|
-
${this.apiLoginError ? html `
|
|
1753
|
-
<div style="padding:6px 8px;background:#fde8e8;border:1px solid #f5c6cb;border-radius:5px;margin-bottom:8px;font-size:10px;color:#c62828;">
|
|
1754
|
-
⚠ ${this.apiLoginError}
|
|
1755
|
-
</div>
|
|
1756
|
-
` : ''}
|
|
1757
|
-
|
|
1758
|
-
<div style="margin-bottom:6px;">
|
|
1759
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">URL Base</label>
|
|
1760
|
-
<input id="api-base-url" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:white;box-sizing:border-box;outline:none;transition:border-color 0.15s;"
|
|
1761
|
-
.value="${this.apiBaseUrl}" placeholder="Vacio = proxy local (recomendado)"
|
|
1762
|
-
@input="${(e) => { this.apiBaseUrl = e.target.value; }}"
|
|
1763
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1764
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1765
|
-
/>
|
|
1766
|
-
</div>
|
|
1767
|
-
|
|
1768
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:6px;">
|
|
1769
|
-
<div>
|
|
1770
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Usuario</label>
|
|
1771
|
-
<input id="api-login-user" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1772
|
-
placeholder="SUP"
|
|
1773
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1774
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1775
|
-
/>
|
|
1776
|
-
</div>
|
|
1777
|
-
<div>
|
|
1778
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Clave</label>
|
|
1779
|
-
<input id="api-login-pass" type="password" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1780
|
-
placeholder="••••"
|
|
1781
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1782
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1807
|
+
return html `
|
|
1808
|
+
<div style="margin:4px;padding:10px;background:#fafafa;border:1px solid #eee;border-radius:8px;">
|
|
1809
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;">
|
|
1810
|
+
<span style="font-size:14px;">🔐</span>
|
|
1811
|
+
<span style="font-size:11px;font-weight:600;color:#333;">Iniciar Sesion</span>
|
|
1812
|
+
</div>
|
|
1813
|
+
|
|
1814
|
+
${this.apiLoginError ? html `
|
|
1815
|
+
<div style="padding:6px 8px;background:#fde8e8;border:1px solid #f5c6cb;border-radius:5px;margin-bottom:8px;font-size:10px;color:#c62828;">
|
|
1816
|
+
⚠ ${this.apiLoginError}
|
|
1817
|
+
</div>
|
|
1818
|
+
` : ''}
|
|
1819
|
+
|
|
1820
|
+
<div style="margin-bottom:6px;">
|
|
1821
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">URL Base</label>
|
|
1822
|
+
<input id="api-base-url" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:white;box-sizing:border-box;outline:none;transition:border-color 0.15s;"
|
|
1823
|
+
.value="${this.apiBaseUrl}" placeholder="Vacio = proxy local (recomendado)"
|
|
1824
|
+
@input="${(e) => { this.apiBaseUrl = e.target.value; }}"
|
|
1825
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1826
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1827
|
+
/>
|
|
1828
|
+
</div>
|
|
1829
|
+
|
|
1830
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:6px;">
|
|
1831
|
+
<div>
|
|
1832
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Usuario</label>
|
|
1833
|
+
<input id="api-login-user" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1834
|
+
placeholder="SUP"
|
|
1835
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1836
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1837
|
+
/>
|
|
1838
|
+
</div>
|
|
1839
|
+
<div>
|
|
1840
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Clave</label>
|
|
1841
|
+
<input id="api-login-pass" type="password" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1842
|
+
placeholder="••••"
|
|
1843
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1844
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1783
1845
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
1784
|
-
this.doApiLogin(); }}"
|
|
1785
|
-
/>
|
|
1786
|
-
</div>
|
|
1787
|
-
</div>
|
|
1788
|
-
|
|
1789
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:8px;">
|
|
1790
|
-
<div>
|
|
1791
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Empresa (cod)</label>
|
|
1792
|
-
<input id="api-login-company" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1793
|
-
placeholder="01" value="01"
|
|
1794
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1795
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1796
|
-
/>
|
|
1797
|
-
</div>
|
|
1798
|
-
<div>
|
|
1799
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Sucursal</label>
|
|
1800
|
-
<input id="api-login-branch" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1801
|
-
placeholder="01" value="01"
|
|
1802
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1803
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1804
|
-
/>
|
|
1805
|
-
</div>
|
|
1806
|
-
</div>
|
|
1807
|
-
|
|
1808
|
-
<button style="width:100%;padding:8px;border:none;border-radius:6px;background:#1976d2;color:white;cursor:pointer;font-size:12px;font-weight:600;font-family:inherit;transition:all 0.15s;${this.apiLoginLoading ? 'opacity:0.7;pointer-events:none;' : ''}"
|
|
1809
|
-
@click="${this.doApiLogin}"
|
|
1810
|
-
@mouseenter="${(e) => { e.target.style.background = '#1565c0'; }}"
|
|
1811
|
-
@mouseleave="${(e) => { e.target.style.background = '#1976d2'; }}"
|
|
1812
|
-
>${this.apiLoginLoading ? '⏳ Conectando...' : '🔑 Iniciar Sesion'}</button>
|
|
1813
|
-
|
|
1814
|
-
<div style="margin-top:6px;font-size:9px;color:#bbb;text-align:center;">
|
|
1815
|
-
Usa las mismas credenciales de Zentto ERP
|
|
1816
|
-
</div>
|
|
1817
|
-
</div>
|
|
1846
|
+
this.doApiLogin(); }}"
|
|
1847
|
+
/>
|
|
1848
|
+
</div>
|
|
1849
|
+
</div>
|
|
1850
|
+
|
|
1851
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:8px;">
|
|
1852
|
+
<div>
|
|
1853
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Empresa (cod)</label>
|
|
1854
|
+
<input id="api-login-company" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1855
|
+
placeholder="01" value="01"
|
|
1856
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1857
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1858
|
+
/>
|
|
1859
|
+
</div>
|
|
1860
|
+
<div>
|
|
1861
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Sucursal</label>
|
|
1862
|
+
<input id="api-login-branch" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1863
|
+
placeholder="01" value="01"
|
|
1864
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1865
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1866
|
+
/>
|
|
1867
|
+
</div>
|
|
1868
|
+
</div>
|
|
1869
|
+
|
|
1870
|
+
<button style="width:100%;padding:8px;border:none;border-radius:6px;background:#1976d2;color:white;cursor:pointer;font-size:12px;font-weight:600;font-family:inherit;transition:all 0.15s;${this.apiLoginLoading ? 'opacity:0.7;pointer-events:none;' : ''}"
|
|
1871
|
+
@click="${this.doApiLogin}"
|
|
1872
|
+
@mouseenter="${(e) => { e.target.style.background = '#1565c0'; }}"
|
|
1873
|
+
@mouseleave="${(e) => { e.target.style.background = '#1976d2'; }}"
|
|
1874
|
+
>${this.apiLoginLoading ? '⏳ Conectando...' : '🔑 Iniciar Sesion'}</button>
|
|
1875
|
+
|
|
1876
|
+
<div style="margin-top:6px;font-size:9px;color:#bbb;text-align:center;">
|
|
1877
|
+
Usa las mismas credenciales de Zentto ERP
|
|
1878
|
+
</div>
|
|
1879
|
+
</div>
|
|
1818
1880
|
`;
|
|
1819
1881
|
}
|
|
1820
1882
|
async doApiLogin() {
|
|
@@ -1944,13 +2006,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1944
2006
|
return Object.keys(sample).filter(k => !k.startsWith('_'));
|
|
1945
2007
|
}
|
|
1946
2008
|
renderToggle(label, value, onChange) {
|
|
1947
|
-
return html `
|
|
1948
|
-
<div class="prop-toggle">
|
|
1949
|
-
<button class="prop-switch ${value ? 'prop-switch--active' : ''}"
|
|
1950
|
-
@click="${() => onChange(!value)}"
|
|
1951
|
-
></button>
|
|
1952
|
-
<span class="prop-toggle-label">${label}</span>
|
|
1953
|
-
</div>
|
|
2009
|
+
return html `
|
|
2010
|
+
<div class="prop-toggle">
|
|
2011
|
+
<button class="prop-switch ${value ? 'prop-switch--active' : ''}"
|
|
2012
|
+
@click="${() => onChange(!value)}"
|
|
2013
|
+
></button>
|
|
2014
|
+
<span class="prop-toggle-label">${label}</span>
|
|
2015
|
+
</div>
|
|
1954
2016
|
`;
|
|
1955
2017
|
}
|
|
1956
2018
|
toggleSection(id) {
|