@zentto/studio 0.5.2 → 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.d.ts +15 -0
- package/dist/designer/zs-app-wizard.d.ts.map +1 -1
- package/dist/designer/zs-app-wizard.js +749 -295
- package/dist/designer/zs-app-wizard.js.map +1 -1
- package/dist/designer/zs-page-designer.d.ts +27 -0
- package/dist/designer/zs-page-designer.d.ts.map +1 -1
- package/dist/designer/zs-page-designer.js +1396 -945
- package/dist/designer/zs-page-designer.js.map +1 -1
- package/dist/fields/zs-field-datagrid.d.ts +42 -0
- package/dist/fields/zs-field-datagrid.d.ts.map +1 -0
- package/dist/fields/zs-field-datagrid.js +206 -0
- package/dist/fields/zs-field-datagrid.js.map +1 -0
- package/dist/fields/zs-field-report.d.ts +36 -0
- package/dist/fields/zs-field-report.d.ts.map +1 -0
- package/dist/fields/zs-field-report.js +168 -0
- package/dist/fields/zs-field-report.js.map +1 -0
- package/dist/zentto-studio-renderer.d.ts +2 -0
- package/dist/zentto-studio-renderer.d.ts.map +1 -1
- package/dist/zentto-studio-renderer.js +313 -298
- package/dist/zentto-studio-renderer.js.map +1 -1
- package/package.json +74 -59
|
@@ -78,6 +78,16 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
78
78
|
this.undoStack = [];
|
|
79
79
|
this.redoStack = [];
|
|
80
80
|
this.saveTimer = null;
|
|
81
|
+
// InteractJS
|
|
82
|
+
this.interactLoaded = false;
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
this.interact = null;
|
|
85
|
+
this.dragClone = null;
|
|
86
|
+
this.dragSourceType = null;
|
|
87
|
+
this.dragSourceFieldId = null;
|
|
88
|
+
this.dragSourceSectionIndex = -1;
|
|
89
|
+
this.dragInsertIndex = -1;
|
|
90
|
+
this.dragTargetSectionIndex = -1;
|
|
81
91
|
// ─── API Panel (Data Sources) ──────────────────────
|
|
82
92
|
this.showTemplateMenu = false;
|
|
83
93
|
this.apiSources = [];
|
|
@@ -91,461 +101,835 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
91
101
|
this.apiCompany = '';
|
|
92
102
|
this.apiBranch = '';
|
|
93
103
|
}
|
|
94
|
-
static { this.styles = css `
|
|
95
|
-
:host {
|
|
96
|
-
display: block; height: 100%;
|
|
97
|
-
--zrd-bg: ${unsafeCSS(BG)};
|
|
98
|
-
--zrd-panel-bg: ${unsafeCSS(PANEL_BG)};
|
|
99
|
-
--zrd-border: ${unsafeCSS(BORDER)};
|
|
100
|
-
--zrd-accent: ${unsafeCSS(ACCENT)};
|
|
101
|
-
--zrd-accent-light: ${unsafeCSS(ACCENT_LIGHT)};
|
|
102
|
-
--zrd-text: ${unsafeCSS(TEXT)};
|
|
103
|
-
--zrd-text-muted: ${unsafeCSS(TEXT_MUTED)};
|
|
104
|
-
--zrd-danger: ${unsafeCSS(DANGER)};
|
|
105
|
-
font-family: 'Segoe UI', Roboto, Arial, sans-serif;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* ─── Layout ──────────────────────────────── */
|
|
109
|
-
.designer {
|
|
110
|
-
display: grid;
|
|
111
|
-
grid-template-rows: auto 1fr;
|
|
112
|
-
grid-template-columns: 200px 4px 1fr 4px 240px;
|
|
113
|
-
height: 100%; background: var(--zrd-bg);
|
|
114
|
-
overflow: hidden;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* ─── Toolbar ─────────────────────────────── */
|
|
118
|
-
.toolbar {
|
|
119
|
-
grid-column: 1 / -1;
|
|
120
|
-
display: flex; align-items: center; gap: 8px;
|
|
121
|
-
padding: 6px 12px; min-height: 36px;
|
|
122
|
-
background: var(--zrd-panel-bg);
|
|
123
|
-
border-bottom: 1px solid var(--zrd-border);
|
|
124
|
-
flex-wrap: wrap;
|
|
125
|
-
}
|
|
126
|
-
.toolbar-sep { width: 1px; height: 20px; background: var(--zrd-border); margin: 0 2px; }
|
|
127
|
-
.tb-btn {
|
|
128
|
-
background: none; border: 1px solid var(--zrd-border);
|
|
129
|
-
border-radius: 4px; padding: 4px 10px;
|
|
130
|
-
cursor: pointer; font-size: 12px; color: var(--zrd-text);
|
|
131
|
-
font-family: inherit; transition: background 0.15s;
|
|
132
|
-
white-space: nowrap; display: flex; align-items: center; gap: 4px;
|
|
133
|
-
}
|
|
134
|
-
.tb-btn:hover { background: var(--zrd-accent-light); }
|
|
135
|
-
.tb-btn:disabled { opacity: 0.4; cursor: default; }
|
|
136
|
-
.tb-btn--active { background: var(--zrd-accent); color: white; border-color: var(--zrd-accent); }
|
|
137
|
-
.tb-btn--danger:hover { background: #ffebee; color: var(--zrd-danger); border-color: var(--zrd-danger); }
|
|
138
|
-
.report-name {
|
|
139
|
-
font-weight: 600; font-size: 14px; cursor: pointer;
|
|
140
|
-
padding: 2px 6px; border-radius: 3px; border: 1px solid transparent;
|
|
141
|
-
color: var(--zrd-text);
|
|
142
|
-
}
|
|
143
|
-
.report-name:hover { border-color: var(--zrd-border); background: var(--zrd-accent-light); }
|
|
144
|
-
.report-name-input {
|
|
145
|
-
font-weight: 600; font-size: 14px; border: 1px solid var(--zrd-accent);
|
|
146
|
-
border-radius: 3px; padding: 2px 6px; outline: none;
|
|
147
|
-
background: white; color: var(--zrd-text); font-family: inherit;
|
|
148
|
-
}
|
|
149
|
-
.tb-spacer { flex: 1; }
|
|
150
|
-
.zoom-controls {
|
|
151
|
-
display: flex; align-items: center; gap: 4px;
|
|
152
|
-
padding-left: 8px; border-left: 1px solid var(--zrd-border);
|
|
153
|
-
}
|
|
154
|
-
.zoom-btn { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 3px; font-size: 14px; font-weight: bold; }
|
|
155
|
-
.zoom-label { font-size: 11px; min-width: 36px; text-align: center; color: var(--zrd-text-muted); cursor: pointer; }
|
|
156
|
-
|
|
157
|
-
/* ─── Resize Handle ───────────────────────── */
|
|
158
|
-
.panel-resize {
|
|
159
|
-
width: 4px; cursor: col-resize; background: transparent;
|
|
160
|
-
transition: background 0.15s; flex-shrink: 0;
|
|
161
|
-
}
|
|
162
|
-
.panel-resize:hover { background: var(--zrd-accent); }
|
|
163
|
-
|
|
164
|
-
/* ─── Left Panel (Toolbox) ────────────────── */
|
|
165
|
-
.left-panel {
|
|
166
|
-
background: var(--zrd-panel-bg);
|
|
167
|
-
border-right: 1px solid var(--zrd-border);
|
|
168
|
-
display: flex; flex-direction: column; overflow: hidden;
|
|
169
|
-
}
|
|
170
|
-
.panel-tabs {
|
|
171
|
-
display: flex; border-bottom: 1px solid var(--zrd-border);
|
|
172
|
-
}
|
|
173
|
-
.panel-tab {
|
|
174
|
-
flex: 1; padding: 8px 4px; text-align: center; cursor: pointer;
|
|
175
|
-
font-size: 11px; border-bottom: 2px solid transparent;
|
|
176
|
-
color: var(--zrd-text-muted); transition: all 0.15s;
|
|
177
|
-
background: none; border-top: none; border-left: none; border-right: none;
|
|
178
|
-
font-family: inherit;
|
|
179
|
-
}
|
|
180
|
-
.panel-tab:hover { color: var(--zrd-text); }
|
|
181
|
-
.panel-tab--active { border-bottom-color: var(--zrd-accent); color: var(--zrd-accent); font-weight: 600; }
|
|
182
|
-
.panel-content { padding: 4px; overflow-y: auto; flex: 1; }
|
|
183
|
-
.panel-content::-webkit-scrollbar { width: 6px; }
|
|
184
|
-
.panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
185
|
-
|
|
186
|
-
/* Toolbox grid (3 columns like report-designer) */
|
|
187
|
-
.toolbox-section {
|
|
188
|
-
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
189
|
-
letter-spacing: 0.8px; color: #aaa;
|
|
190
|
-
padding: 10px 8px 5px; margin-top: 2px;
|
|
191
|
-
}
|
|
192
|
-
.toolbox-section:first-child { margin-top: 0; padding-top: 6px; }
|
|
193
|
-
.toolbox-grid {
|
|
194
|
-
display: grid; grid-template-columns: repeat(3, 1fr);
|
|
195
|
-
gap: 3px; padding: 0 4px;
|
|
196
|
-
}
|
|
197
|
-
.toolbox-item {
|
|
198
|
-
display: flex; flex-direction: column; align-items: center;
|
|
199
|
-
gap: 3px; padding: 7px 2px 5px;
|
|
200
|
-
border: 1px solid transparent; border-radius: 5px;
|
|
201
|
-
cursor: grab; user-select: none; text-align: center;
|
|
202
|
-
transition: all 0.15s; background: transparent;
|
|
203
|
-
}
|
|
204
|
-
.toolbox-item:hover {
|
|
205
|
-
background: var(--zrd-accent-light); border-color: #c5dcf0;
|
|
206
|
-
box-shadow: 0 1px 4px rgba(25,118,210,0.1);
|
|
207
|
-
}
|
|
208
|
-
.toolbox-item:active { cursor: grabbing; opacity: 0.6; transform: scale(0.95); }
|
|
209
|
-
.toolbox-icon {
|
|
210
|
-
width: 28px; height: 28px; display: flex; align-items: center;
|
|
211
|
-
justify-content: center; border-radius: 6px;
|
|
212
|
-
font-size: 15px; flex-shrink: 0;
|
|
213
|
-
background: #f0f4f8; color: #1976d2;
|
|
214
|
-
border: 1px solid #e3e8ee;
|
|
215
|
-
transition: all 0.15s;
|
|
216
|
-
}
|
|
217
|
-
.toolbox-item:hover .toolbox-icon {
|
|
218
|
-
background: #1976d2; color: white; border-color: #1976d2;
|
|
219
|
-
box-shadow: 0 2px 6px rgba(25,118,210,0.3);
|
|
220
|
-
}
|
|
221
|
-
.toolbox-label {
|
|
222
|
-
font-size: 9px; font-weight: 500; line-height: 1.1;
|
|
223
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
224
|
-
max-width: 100%; color: #777;
|
|
225
|
-
}
|
|
226
|
-
.toolbox-item:hover .toolbox-label { color: #1976d2; }
|
|
227
|
-
|
|
228
|
-
/* ─── Canvas ──────────────────────────────── */
|
|
229
|
-
.canvas-area {
|
|
230
|
-
overflow: auto; padding: 30px; position: relative;
|
|
231
|
-
background: #d0d0d0; display: flex; justify-content: center;
|
|
232
|
-
}
|
|
233
|
-
.canvas {
|
|
234
|
-
background: white; position: relative; margin: 0 auto; border-radius: 2px;
|
|
235
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.08);
|
|
236
|
-
min-width: 600px; min-height: 400px; padding: 24px;
|
|
237
|
-
transform-origin: top center;
|
|
238
|
-
}
|
|
239
|
-
.canvas-section { margin-bottom: 20px; }
|
|
240
|
-
.canvas-section-header {
|
|
241
|
-
font-size: 13px; font-weight: 600; color: var(--zrd-text);
|
|
242
|
-
padding: 6px 8px; margin-bottom: 8px;
|
|
243
|
-
background: #f0f0f0; border-radius: 4px; border-left: 3px solid var(--zrd-accent);
|
|
244
|
-
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
245
|
-
}
|
|
246
|
-
.canvas-section-header:hover { background: var(--zrd-accent-light); }
|
|
247
|
-
.canvas-grid {
|
|
248
|
-
display: grid; gap: 8px; padding: 4px;
|
|
249
|
-
background: repeating-linear-gradient(0deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px),
|
|
250
|
-
repeating-linear-gradient(90deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/* Field on canvas */
|
|
254
|
-
.canvas-field {
|
|
255
|
-
position: relative; border: 1px solid transparent;
|
|
256
|
-
border-radius: 4px; cursor: pointer; user-select: none;
|
|
257
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
258
|
-
padding: 4px;
|
|
259
|
-
}
|
|
260
|
-
.canvas-field:hover { border-color: rgba(25,118,210,0.5); }
|
|
261
|
-
.canvas-field--selected {
|
|
262
|
-
border-color: var(--zrd-accent);
|
|
263
|
-
box-shadow: 0 0 0 1px var(--zrd-accent);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/* Type badge (above field) */
|
|
267
|
-
.field-type-badge {
|
|
268
|
-
position: absolute; top: -14px; left: 0;
|
|
269
|
-
font-size: 9px; border-radius: 3px 3px 0 0;
|
|
270
|
-
padding: 1px 6px; line-height: 13px;
|
|
271
|
-
pointer-events: none; z-index: 6; display: none;
|
|
272
|
-
white-space: nowrap;
|
|
273
|
-
}
|
|
274
|
-
.canvas-field:hover .field-type-badge,
|
|
275
|
-
.canvas-field--selected .field-type-badge { display: block; }
|
|
276
|
-
|
|
277
|
-
/* Resize handles (8-point like report-designer) */
|
|
278
|
-
.rh { position: absolute; width: 6px; height: 6px; background: var(--zrd-accent); border: 1px solid white; z-index: 5; display: none; border-radius: 1px; }
|
|
279
|
-
.rh:hover { background: #0d47a1; }
|
|
280
|
-
.canvas-field--selected .rh { display: block; }
|
|
281
|
-
.rh-nw { top: -3px; left: -3px; cursor: nw-resize; }
|
|
282
|
-
.rh-n { top: -3px; left: calc(50% - 3px); cursor: n-resize; }
|
|
283
|
-
.rh-ne { top: -3px; right: -3px; cursor: ne-resize; }
|
|
284
|
-
.rh-e { top: calc(50% - 3px); right: -3px; cursor: e-resize; }
|
|
285
|
-
.rh-se { bottom: -3px; right: -3px; cursor: se-resize; }
|
|
286
|
-
.rh-s { bottom: -3px; left: calc(50% - 3px); cursor: s-resize; }
|
|
287
|
-
.rh-sw { bottom: -3px; left: -3px; cursor: sw-resize; }
|
|
288
|
-
.rh-w { top: calc(50% - 3px); left: -3px; cursor: w-resize; }
|
|
289
|
-
|
|
290
|
-
/* Action buttons on field */
|
|
291
|
-
.field-actions {
|
|
292
|
-
position: absolute; top: -14px; right: 4px;
|
|
293
|
-
display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; z-index: 7;
|
|
294
|
-
}
|
|
295
|
-
.canvas-field:hover .field-actions { opacity: 1; }
|
|
296
|
-
.fa-btn {
|
|
297
|
-
width: 18px; height: 14px; border-radius: 3px 3px 0 0;
|
|
298
|
-
border: none; cursor: pointer; font-size: 9px;
|
|
299
|
-
display: flex; align-items: center; justify-content: center;
|
|
300
|
-
}
|
|
301
|
-
.fa-btn--move { background: var(--zrd-accent); color: white; }
|
|
302
|
-
.fa-btn--delete { background: var(--zrd-danger); color: white; }
|
|
303
|
-
.fa-btn--copy { background: #7c3aed; color: white; }
|
|
304
|
-
|
|
305
|
-
/* Field preview content */
|
|
306
|
-
.field-preview {
|
|
307
|
-
pointer-events: none;
|
|
308
|
-
}
|
|
309
|
-
.field-preview-label {
|
|
310
|
-
font-size: 11px; font-weight: 500; color: var(--zrd-text-muted);
|
|
311
|
-
margin-bottom: 3px;
|
|
312
|
-
}
|
|
313
|
-
.field-preview-input {
|
|
314
|
-
height: 30px; border: 1px solid #e0e0e0; border-radius: 4px;
|
|
315
|
-
background: #fafafa; display: flex; align-items: center;
|
|
316
|
-
padding: 0 8px; font-size: 12px; color: #999;
|
|
317
|
-
}
|
|
318
|
-
.field-preview-input--textarea { height: 60px; align-items: flex-start; padding-top: 6px; }
|
|
319
|
-
.field-preview-input--switch { height: auto; border: none; background: none; padding: 0; }
|
|
320
|
-
.field-preview-input--separator { height: 1px; border: none; background: #ddd; padding: 0; }
|
|
321
|
-
.field-preview-input--heading { height: auto; border: none; background: none; padding: 0; font-size: 16px; font-weight: 600; color: var(--zrd-text); }
|
|
322
|
-
.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); }
|
|
323
|
-
.field-preview-input--report { height: 120px; border: 2px dashed #e65100; background: #fff3e0; justify-content: center; font-weight: 500; color: #e65100; }
|
|
324
|
-
.field-preview-input--chart { height: 120px; border: 2px dashed #6a1b9a; background: #f3e5f5; justify-content: center; font-weight: 500; color: #6a1b9a; }
|
|
325
|
-
|
|
326
|
-
/* Drop zone */
|
|
327
|
-
.drop-zone {
|
|
328
|
-
border: 2px dashed var(--zrd-border); border-radius: 6px;
|
|
329
|
-
padding: 16px; text-align: center; margin: 4px 0;
|
|
330
|
-
color: var(--zrd-text-muted); font-size: 12px; transition: all 0.15s;
|
|
331
|
-
}
|
|
332
|
-
.drop-zone--active { border-color: var(--zrd-accent); background: var(--zrd-accent-light); color: var(--zrd-accent); }
|
|
333
|
-
|
|
334
|
-
/* ─── Right Panel (Properties — Figma-quality) ──── */
|
|
335
|
-
.right-panel {
|
|
336
|
-
background: var(--zrd-panel-bg);
|
|
337
|
-
border-left: 1px solid var(--zrd-border);
|
|
338
|
-
overflow-y: auto; font-size: 11px;
|
|
339
|
-
}
|
|
340
|
-
.right-panel::-webkit-scrollbar { width: 5px; }
|
|
341
|
-
.right-panel::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
|
|
342
|
-
.right-panel::-webkit-scrollbar-thumb:hover { background: #bbb; }
|
|
343
|
-
|
|
344
|
-
/* ─ Prop Section ─ */
|
|
345
|
-
.prop-section { border-bottom: 1px solid #eee; padding: 8px 10px 10px; }
|
|
346
|
-
.prop-section:last-child { border-bottom: none; }
|
|
347
|
-
.prop-section-header {
|
|
348
|
-
display: flex; align-items: center; gap: 6px;
|
|
349
|
-
cursor: pointer; user-select: none; margin-bottom: 6px; padding: 2px 0;
|
|
350
|
-
}
|
|
351
|
-
.prop-section-header h4 {
|
|
352
|
-
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
353
|
-
letter-spacing: 0.6px; margin: 0; flex: 1;
|
|
354
|
-
}
|
|
355
|
-
.prop-section-header[data-section="general"] h4 { color: #1976d2; }
|
|
356
|
-
.prop-section-header[data-section="layout"] h4 { color: #7c3aed; }
|
|
357
|
-
.prop-section-header[data-section="behavior"] h4 { color: #0d9488; }
|
|
358
|
-
.prop-section-header[data-section="rules"] h4 { color: #ea580c; }
|
|
359
|
-
.prop-section-header[data-section="style"] h4 { color: #c2185b; }
|
|
360
|
-
.prop-section-header[data-section="form"] h4 { color: #1976d2; }
|
|
361
|
-
.collapse-icon {
|
|
362
|
-
font-size: 8px; color: #bbb; transition: transform 0.15s;
|
|
363
|
-
width: 14px; height: 14px; display: flex; align-items: center;
|
|
364
|
-
justify-content: center; border-radius: 3px;
|
|
365
|
-
}
|
|
366
|
-
.collapse-icon:hover { background: #f0f0f0; color: #666; }
|
|
367
|
-
.collapse-icon--collapsed { transform: rotate(-90deg); }
|
|
368
|
-
|
|
369
|
-
/* ─ Prop Rows ─ */
|
|
370
|
-
.prop-row {
|
|
371
|
-
display: grid; grid-template-columns: 62px 1fr;
|
|
372
|
-
align-items: center; gap: 4px; min-height: 26px; margin-bottom: 1px;
|
|
373
|
-
}
|
|
374
|
-
.prop-row-full { grid-template-columns: 1fr; margin-bottom: 3px; }
|
|
375
|
-
.prop-label {
|
|
376
|
-
font-size: 11px; color: #999; font-weight: 400;
|
|
377
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
378
|
-
padding-left: 1px; line-height: 1;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/* ─ Figma-style input ─ */
|
|
382
|
-
.prop-input {
|
|
383
|
-
width: 100%; border: 1px solid transparent; border-radius: 4px;
|
|
384
|
-
padding: 5px 7px; font-size: 11px; background: #f5f5f5;
|
|
385
|
-
color: var(--zrd-text); outline: none; min-width: 0;
|
|
386
|
-
font-family: inherit; box-sizing: border-box;
|
|
387
|
-
transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
|
|
388
|
-
}
|
|
389
|
-
.prop-input:hover { background: #efefef; border-color: #ddd; }
|
|
390
|
-
.prop-input:focus { border-color: #1976d2; background: white; box-shadow: 0 0 0 2px rgba(25,118,210,0.08); }
|
|
391
|
-
select.prop-input {
|
|
392
|
-
padding: 4px 20px 4px 7px; cursor: pointer; appearance: none;
|
|
393
|
-
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");
|
|
394
|
-
background-repeat: no-repeat; background-position: right 6px center;
|
|
395
|
-
}
|
|
396
|
-
textarea.prop-input {
|
|
397
|
-
min-height: 44px; resize: vertical; font-family: 'SF Mono','Consolas','Monaco',monospace;
|
|
398
|
-
font-size: 10px; line-height: 1.5; padding: 6px 7px;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/* ─ Numeric stepper (Figma-style) ─ */
|
|
402
|
-
.prop-stepper {
|
|
403
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
404
|
-
border: 1px solid transparent; border-radius: 4px; overflow: hidden;
|
|
405
|
-
transition: border-color 0.12s;
|
|
406
|
-
}
|
|
407
|
-
.prop-stepper:hover { border-color: #ddd; }
|
|
408
|
-
.prop-stepper:focus-within { border-color: #1976d2; background: white; }
|
|
409
|
-
.prop-stepper input {
|
|
410
|
-
border: none; background: transparent; width: 100%;
|
|
411
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
412
|
-
padding: 4px 2px 4px 7px; min-width: 0; font-family: inherit;
|
|
413
|
-
-moz-appearance: textfield;
|
|
414
|
-
}
|
|
415
|
-
.prop-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
416
|
-
.prop-stepper-btns {
|
|
417
|
-
display: flex; flex-direction: column; border-left: 1px solid #eee;
|
|
418
|
-
}
|
|
419
|
-
.prop-stepper-btn {
|
|
420
|
-
border: none; background: none; cursor: pointer; padding: 0;
|
|
421
|
-
width: 18px; height: 12px; display: flex; align-items: center;
|
|
422
|
-
justify-content: center; font-size: 7px; color: #999;
|
|
423
|
-
transition: all 0.1s;
|
|
424
|
-
}
|
|
425
|
-
.prop-stepper-btn:hover { background: #e8e8e8; color: #333; }
|
|
426
|
-
|
|
427
|
-
/* ─ Position grid (4-cell Figma layout) ─ */
|
|
428
|
-
.prop-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
|
429
|
-
.prop-pos-cell {
|
|
430
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
431
|
-
border: 1px solid transparent; border-radius: 4px; padding: 0 6px;
|
|
432
|
-
height: 26px; gap: 3px; transition: border-color 0.12s;
|
|
433
|
-
}
|
|
434
|
-
.prop-pos-cell:hover { border-color: #ddd; }
|
|
435
|
-
.prop-pos-cell:focus-within { border-color: #1976d2; background: white; }
|
|
436
|
-
.prop-pos-label {
|
|
437
|
-
font-size: 9px; font-weight: 700; width: 10px; text-align: center;
|
|
438
|
-
flex-shrink: 0; user-select: none;
|
|
439
|
-
}
|
|
440
|
-
.prop-pos-label--x { color: #ef4444; }
|
|
441
|
-
.prop-pos-label--y { color: #3b82f6; }
|
|
442
|
-
.prop-pos-label--w { color: #8b5cf6; }
|
|
443
|
-
.prop-pos-label--h { color: #10b981; }
|
|
444
|
-
.prop-pos-cell input {
|
|
445
|
-
border: none; background: transparent; width: 100%;
|
|
446
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
447
|
-
padding: 0; min-width: 0; font-family: inherit;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/* ─ Toggle switches (Figma compact) ─ */
|
|
451
|
-
.prop-toggle {
|
|
452
|
-
display: flex; align-items: center; gap: 8px;
|
|
453
|
-
min-height: 24px; padding: 1px 0;
|
|
454
|
-
}
|
|
455
|
-
.prop-switch {
|
|
456
|
-
position: relative; width: 28px; height: 16px; border-radius: 8px;
|
|
457
|
-
background: #d4d4d4; cursor: pointer; transition: background 0.2s;
|
|
458
|
-
flex-shrink: 0; border: none; padding: 0;
|
|
459
|
-
}
|
|
460
|
-
.prop-switch--active { background: #1976d2; }
|
|
461
|
-
.prop-switch::after {
|
|
462
|
-
content: ''; position: absolute; top: 2px; left: 2px;
|
|
463
|
-
width: 12px; height: 12px; border-radius: 50%;
|
|
464
|
-
background: white; box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
|
465
|
-
transition: transform 0.2s;
|
|
466
|
-
}
|
|
467
|
-
.prop-switch--active::after { transform: translateX(12px); }
|
|
468
|
-
.prop-toggle-label { font-size: 11px; color: #888; }
|
|
469
|
-
.prop-toggle:hover .prop-toggle-label { color: #555; }
|
|
470
|
-
|
|
471
|
-
/* ─ Segmented control (alignment, format) ─ */
|
|
472
|
-
.prop-segmented {
|
|
473
|
-
display: flex; border: 1px solid #e0e0e0; border-radius: 5px;
|
|
474
|
-
overflow: hidden; background: #f5f5f5;
|
|
475
|
-
}
|
|
476
|
-
.prop-seg-btn {
|
|
477
|
-
flex: 1; padding: 4px 0; background: transparent; border: none;
|
|
478
|
-
border-right: 1px solid #e0e0e0; cursor: pointer;
|
|
479
|
-
font-size: 11px; color: #999; transition: all 0.12s; text-align: center;
|
|
480
|
-
font-family: inherit;
|
|
481
|
-
}
|
|
482
|
-
.prop-seg-btn:last-child { border-right: none; }
|
|
483
|
-
.prop-seg-btn:hover { background: #eee; color: #555; }
|
|
484
|
-
.prop-seg-btn--active { background: #1976d2; color: white; }
|
|
485
|
-
|
|
486
|
-
/* ─ Color picker (swatch + hex inline) ─ */
|
|
487
|
-
.prop-color-row { display: flex; align-items: center; gap: 6px; }
|
|
488
|
-
.prop-color-swatch {
|
|
489
|
-
width: 22px; height: 22px; border-radius: 5px;
|
|
490
|
-
border: 1px solid #ddd; cursor: pointer; flex-shrink: 0;
|
|
491
|
-
position: relative; overflow: hidden;
|
|
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
|
+
`; }
|
|
592
|
+
// ─── Lifecycle ────────────────────────────────────
|
|
593
|
+
updated(changed) {
|
|
594
|
+
if (changed.has('schema') && this.schema && this.undoStack.length === 0) {
|
|
595
|
+
this.undoStack = [JSON.stringify(this.schema)];
|
|
596
|
+
}
|
|
597
|
+
// Initialize InteractJS after render when in design mode
|
|
598
|
+
if (this.viewMode === 'design' && this.schema) {
|
|
599
|
+
this.initInteract();
|
|
600
|
+
}
|
|
492
601
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
602
|
+
disconnectedCallback() {
|
|
603
|
+
super.disconnectedCallback();
|
|
604
|
+
this.cleanupInteract();
|
|
605
|
+
}
|
|
606
|
+
// ─── InteractJS Integration ───────────────────────
|
|
607
|
+
async initInteract() {
|
|
608
|
+
if (!this.interactLoaded) {
|
|
609
|
+
try {
|
|
610
|
+
// @ts-ignore — optional peer dependency
|
|
611
|
+
const mod = await import('interactjs');
|
|
612
|
+
this.interact = mod.default;
|
|
613
|
+
this.interactLoaded = true;
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
console.warn('[zs-page-designer] InteractJS not available, falling back to native drag');
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Schedule setup after DOM updates
|
|
621
|
+
requestAnimationFrame(() => this.setupInteractions());
|
|
496
622
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
623
|
+
cleanupInteract() {
|
|
624
|
+
if (!this.interact)
|
|
625
|
+
return;
|
|
626
|
+
const root = this.shadowRoot;
|
|
627
|
+
if (!root)
|
|
628
|
+
return;
|
|
629
|
+
// Clean all interact instances within shadow DOM
|
|
630
|
+
root.querySelectorAll('.toolbox-item, .canvas-field, .drop-zone, .section-drop-zone, .canvas-grid').forEach(el => {
|
|
631
|
+
try {
|
|
632
|
+
this.interact.unset(el);
|
|
633
|
+
}
|
|
634
|
+
catch { /* already cleaned */ }
|
|
635
|
+
});
|
|
636
|
+
if (this.dragClone) {
|
|
637
|
+
this.dragClone.remove();
|
|
638
|
+
this.dragClone = null;
|
|
639
|
+
}
|
|
503
640
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
641
|
+
setupInteractions() {
|
|
642
|
+
if (!this.interact || !this.shadowRoot)
|
|
643
|
+
return;
|
|
644
|
+
const interact = this.interact;
|
|
645
|
+
const root = this.shadowRoot;
|
|
646
|
+
// ── Toolbox items: draggable to canvas ──
|
|
647
|
+
root.querySelectorAll('.toolbox-item').forEach(el => {
|
|
648
|
+
// Avoid re-initializing
|
|
649
|
+
if (el.__interactSetup)
|
|
650
|
+
return;
|
|
651
|
+
el.__interactSetup = true;
|
|
652
|
+
interact(el).draggable({
|
|
653
|
+
inertia: false,
|
|
654
|
+
autoScroll: true,
|
|
655
|
+
listeners: {
|
|
656
|
+
start: (event) => {
|
|
657
|
+
const type = event.target.getAttribute('data-field-type');
|
|
658
|
+
if (!type)
|
|
659
|
+
return;
|
|
660
|
+
this.dragSourceType = type;
|
|
661
|
+
this.dragSourceFieldId = null;
|
|
662
|
+
this.dragSourceSectionIndex = -1;
|
|
663
|
+
event.target.classList.add('dragging');
|
|
664
|
+
// Create clone
|
|
665
|
+
this.createDragClone(event.clientX, event.clientY, event.target.querySelector('.toolbox-label')?.textContent ?? type);
|
|
666
|
+
},
|
|
667
|
+
move: (event) => {
|
|
668
|
+
if (this.dragClone) {
|
|
669
|
+
this.dragClone.style.left = `${event.clientX + 12}px`;
|
|
670
|
+
this.dragClone.style.top = `${event.clientY + 12}px`;
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
end: (event) => {
|
|
674
|
+
event.target.classList.remove('dragging');
|
|
675
|
+
this.removeDragClone();
|
|
676
|
+
this.dragSourceType = null;
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
// ── Canvas fields: draggable for reorder + cross-section ──
|
|
682
|
+
root.querySelectorAll('.canvas-field').forEach(el => {
|
|
683
|
+
if (el.__interactSetup)
|
|
684
|
+
return;
|
|
685
|
+
el.__interactSetup = true;
|
|
686
|
+
interact(el).draggable({
|
|
687
|
+
inertia: false,
|
|
688
|
+
autoScroll: true,
|
|
689
|
+
listeners: {
|
|
690
|
+
start: (event) => {
|
|
691
|
+
const fieldId = event.target.getAttribute('data-field-id');
|
|
692
|
+
const si = parseInt(event.target.getAttribute('data-section-index') ?? '-1');
|
|
693
|
+
if (!fieldId)
|
|
694
|
+
return;
|
|
695
|
+
this.dragSourceFieldId = fieldId;
|
|
696
|
+
this.dragSourceType = null;
|
|
697
|
+
this.dragSourceSectionIndex = si;
|
|
698
|
+
event.target.classList.add('dragging');
|
|
699
|
+
const label = event.target.querySelector('.field-preview-label')?.textContent ?? fieldId;
|
|
700
|
+
this.createDragClone(event.clientX, event.clientY, label);
|
|
701
|
+
},
|
|
702
|
+
move: (event) => {
|
|
703
|
+
if (this.dragClone) {
|
|
704
|
+
this.dragClone.style.left = `${event.clientX + 12}px`;
|
|
705
|
+
this.dragClone.style.top = `${event.clientY + 12}px`;
|
|
706
|
+
}
|
|
707
|
+
// Calculate insert position
|
|
708
|
+
this.updateInsertIndicator(event);
|
|
709
|
+
},
|
|
710
|
+
end: (event) => {
|
|
711
|
+
event.target.classList.remove('dragging');
|
|
712
|
+
this.removeDragClone();
|
|
713
|
+
this.removeInsertIndicator();
|
|
714
|
+
// Perform reorder if we have a valid target
|
|
715
|
+
if (this.dragSourceFieldId && this.dragTargetSectionIndex >= 0 && this.dragInsertIndex >= 0) {
|
|
716
|
+
this.performFieldMove(this.dragSourceFieldId, this.dragSourceSectionIndex, this.dragTargetSectionIndex, this.dragInsertIndex);
|
|
717
|
+
}
|
|
718
|
+
this.dragSourceFieldId = null;
|
|
719
|
+
this.dragSourceSectionIndex = -1;
|
|
720
|
+
this.dragInsertIndex = -1;
|
|
721
|
+
this.dragTargetSectionIndex = -1;
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
// ── Drop zones: accept fields from toolbox ──
|
|
727
|
+
root.querySelectorAll('.drop-zone, .section-drop-zone').forEach(el => {
|
|
728
|
+
if (el.__interactSetup)
|
|
729
|
+
return;
|
|
730
|
+
el.__interactSetup = true;
|
|
731
|
+
interact(el).dropzone({
|
|
732
|
+
accept: '.toolbox-item, .canvas-field',
|
|
733
|
+
overlap: 0.25,
|
|
734
|
+
ondragenter: (event) => {
|
|
735
|
+
event.target.classList.add('drop-active', 'drop-target');
|
|
736
|
+
},
|
|
737
|
+
ondragleave: (event) => {
|
|
738
|
+
event.target.classList.remove('drop-active', 'drop-target');
|
|
739
|
+
},
|
|
740
|
+
ondrop: (event) => {
|
|
741
|
+
event.target.classList.remove('drop-active', 'drop-target');
|
|
742
|
+
const si = parseInt(event.target.getAttribute('data-section-index') ?? '0');
|
|
743
|
+
if (this.dragSourceType) {
|
|
744
|
+
// Drop from toolbox
|
|
745
|
+
this.addField(this.dragSourceType, si);
|
|
746
|
+
}
|
|
747
|
+
else if (this.dragSourceFieldId) {
|
|
748
|
+
// Drop from canvas (cross-section move)
|
|
749
|
+
const insertIdx = this.schema?.sections[si]?.fields.length ?? 0;
|
|
750
|
+
this.performFieldMove(this.dragSourceFieldId, this.dragSourceSectionIndex, si, insertIdx);
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
// ── Canvas grids: drop zones for reorder within section ──
|
|
756
|
+
root.querySelectorAll('.canvas-grid').forEach(el => {
|
|
757
|
+
if (el.__interactSetup)
|
|
758
|
+
return;
|
|
759
|
+
el.__interactSetup = true;
|
|
760
|
+
interact(el).dropzone({
|
|
761
|
+
accept: '.toolbox-item, .canvas-field',
|
|
762
|
+
overlap: 0.1,
|
|
763
|
+
ondragenter: (event) => {
|
|
764
|
+
event.target.classList.add('drop-active');
|
|
765
|
+
},
|
|
766
|
+
ondragleave: (event) => {
|
|
767
|
+
event.target.classList.remove('drop-active');
|
|
768
|
+
},
|
|
769
|
+
ondrop: (event) => {
|
|
770
|
+
event.target.classList.remove('drop-active');
|
|
771
|
+
const si = parseInt(event.target.getAttribute('data-section-index') ?? '0');
|
|
772
|
+
if (this.dragSourceType) {
|
|
773
|
+
const insertIdx = this.dragInsertIndex >= 0 ? this.dragInsertIndex : (this.schema?.sections[si]?.fields.length ?? 0);
|
|
774
|
+
this.addFieldAtIndex(this.dragSourceType, si, insertIdx);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
// ── Resize handles on selected field ──
|
|
780
|
+
root.querySelectorAll('.canvas-field--selected').forEach(fieldEl => {
|
|
781
|
+
if (fieldEl.__interactResize)
|
|
782
|
+
return;
|
|
783
|
+
fieldEl.__interactResize = true;
|
|
784
|
+
const maxCols = this.schema?.layout.columns ?? 2;
|
|
785
|
+
interact(fieldEl).resizable({
|
|
786
|
+
edges: { left: '.rh-w, .rh-nw, .rh-sw', right: '.rh-e, .rh-ne, .rh-se', top: false, bottom: false },
|
|
787
|
+
listeners: {
|
|
788
|
+
start: (event) => {
|
|
789
|
+
event.target.classList.add('resize-active');
|
|
790
|
+
},
|
|
791
|
+
move: (event) => {
|
|
792
|
+
// Calculate colSpan based on width change
|
|
793
|
+
const grid = event.target.closest('.canvas-grid');
|
|
794
|
+
if (!grid)
|
|
795
|
+
return;
|
|
796
|
+
const gridWidth = grid.getBoundingClientRect().width;
|
|
797
|
+
const colWidth = gridWidth / maxCols;
|
|
798
|
+
const newColSpan = Math.max(1, Math.min(maxCols, Math.round(event.rect.width / colWidth)));
|
|
799
|
+
const fieldId = event.target.getAttribute('data-field-id');
|
|
800
|
+
if (fieldId && this.schema) {
|
|
801
|
+
for (const s of this.schema.sections) {
|
|
802
|
+
const f = s.fields.find(f => f.id === fieldId);
|
|
803
|
+
if (f && f.colSpan !== newColSpan) {
|
|
804
|
+
f.colSpan = newColSpan;
|
|
805
|
+
this.requestUpdate();
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
end: (event) => {
|
|
812
|
+
event.target.classList.remove('resize-active');
|
|
813
|
+
this.commitChange();
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
modifiers: interact.modifiers ? [
|
|
817
|
+
interact.modifiers.restrictSize({
|
|
818
|
+
min: { width: 80, height: 30 },
|
|
819
|
+
}),
|
|
820
|
+
] : [],
|
|
821
|
+
});
|
|
822
|
+
});
|
|
512
823
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
824
|
+
createDragClone(x, y, label) {
|
|
825
|
+
this.removeDragClone();
|
|
826
|
+
const clone = document.createElement('div');
|
|
827
|
+
clone.className = 'drag-clone';
|
|
828
|
+
clone.textContent = label;
|
|
829
|
+
clone.style.left = `${x + 12}px`;
|
|
830
|
+
clone.style.top = `${y + 12}px`;
|
|
831
|
+
document.body.appendChild(clone);
|
|
832
|
+
this.dragClone = clone;
|
|
833
|
+
}
|
|
834
|
+
removeDragClone() {
|
|
835
|
+
if (this.dragClone) {
|
|
836
|
+
this.dragClone.remove();
|
|
837
|
+
this.dragClone = null;
|
|
838
|
+
}
|
|
520
839
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
840
|
+
updateInsertIndicator(event) {
|
|
841
|
+
if (!this.shadowRoot || !this.schema)
|
|
842
|
+
return;
|
|
843
|
+
const root = this.shadowRoot;
|
|
844
|
+
// Find which canvas-grid we're over
|
|
845
|
+
const grids = root.querySelectorAll('.canvas-grid');
|
|
846
|
+
for (let si = 0; si < grids.length; si++) {
|
|
847
|
+
const grid = grids[si];
|
|
848
|
+
const rect = grid.getBoundingClientRect();
|
|
849
|
+
if (event.clientX >= rect.left && event.clientX <= rect.right &&
|
|
850
|
+
event.clientY >= rect.top && event.clientY <= rect.bottom) {
|
|
851
|
+
this.dragTargetSectionIndex = si;
|
|
852
|
+
// Find insert position among fields
|
|
853
|
+
const fields = grid.querySelectorAll('.canvas-field');
|
|
854
|
+
let insertIdx = fields.length;
|
|
855
|
+
for (let fi = 0; fi < fields.length; fi++) {
|
|
856
|
+
const fieldRect = fields[fi].getBoundingClientRect();
|
|
857
|
+
const midY = fieldRect.top + fieldRect.height / 2;
|
|
858
|
+
if (event.clientY < midY) {
|
|
859
|
+
insertIdx = fi;
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
this.dragInsertIndex = insertIdx;
|
|
864
|
+
this.showInsertLine(grid, fields, insertIdx);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
this.removeInsertIndicator();
|
|
869
|
+
}
|
|
870
|
+
showInsertLine(grid, fields, index) {
|
|
871
|
+
this.removeInsertIndicator();
|
|
872
|
+
const line = document.createElement('div');
|
|
873
|
+
line.className = 'drag-insert-line';
|
|
874
|
+
line.setAttribute('data-insert-line', 'true');
|
|
875
|
+
if (fields.length === 0 || index >= fields.length) {
|
|
876
|
+
// Append at end
|
|
877
|
+
grid.appendChild(line);
|
|
878
|
+
line.style.position = 'relative';
|
|
879
|
+
line.style.marginTop = '4px';
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
// Insert before the field at index
|
|
883
|
+
const targetField = fields[index];
|
|
884
|
+
targetField.style.position = 'relative';
|
|
885
|
+
grid.insertBefore(line, targetField);
|
|
886
|
+
line.style.position = 'relative';
|
|
887
|
+
line.style.marginBottom = '4px';
|
|
888
|
+
}
|
|
530
889
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
890
|
+
removeInsertIndicator() {
|
|
891
|
+
if (!this.shadowRoot)
|
|
892
|
+
return;
|
|
893
|
+
this.shadowRoot.querySelectorAll('[data-insert-line]').forEach(el => el.remove());
|
|
534
894
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
895
|
+
performFieldMove(fieldId, fromSi, toSi, toIndex) {
|
|
896
|
+
if (!this.schema || fromSi < 0)
|
|
897
|
+
return;
|
|
898
|
+
const fromSection = this.schema.sections[fromSi];
|
|
899
|
+
const toSection = this.schema.sections[toSi];
|
|
900
|
+
if (!fromSection || !toSection)
|
|
901
|
+
return;
|
|
902
|
+
const fromFi = fromSection.fields.findIndex(f => f.id === fieldId);
|
|
903
|
+
if (fromFi < 0)
|
|
904
|
+
return;
|
|
905
|
+
const [field] = fromSection.fields.splice(fromFi, 1);
|
|
906
|
+
// Adjust target index if moving within same section and removing shifted indices
|
|
907
|
+
let adjustedIndex = toIndex;
|
|
908
|
+
if (fromSi === toSi && fromFi < toIndex) {
|
|
909
|
+
adjustedIndex = Math.max(0, toIndex - 1);
|
|
910
|
+
}
|
|
911
|
+
adjustedIndex = Math.min(adjustedIndex, toSection.fields.length);
|
|
912
|
+
toSection.fields.splice(adjustedIndex, 0, field);
|
|
913
|
+
this.commitChange();
|
|
542
914
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
this.undoStack = [JSON.stringify(this.schema)];
|
|
915
|
+
addFieldAtIndex(type, sectionIndex, insertIndex) {
|
|
916
|
+
if (!this.schema) {
|
|
917
|
+
this.addField(type, sectionIndex);
|
|
918
|
+
return;
|
|
548
919
|
}
|
|
920
|
+
if (sectionIndex >= this.schema.sections.length)
|
|
921
|
+
sectionIndex = 0;
|
|
922
|
+
const id = `${type}_${Date.now()}`;
|
|
923
|
+
const meta = getAllFields().find(f => f.type === type);
|
|
924
|
+
const newField = {
|
|
925
|
+
id, type, field: id,
|
|
926
|
+
label: meta?.label ?? type,
|
|
927
|
+
props: meta?.defaultProps ? { ...meta.defaultProps } : undefined,
|
|
928
|
+
};
|
|
929
|
+
const idx = Math.min(insertIndex, this.schema.sections[sectionIndex].fields.length);
|
|
930
|
+
this.schema.sections[sectionIndex].fields.splice(idx, 0, newField);
|
|
931
|
+
this.selectedFieldId = id;
|
|
932
|
+
this.commitChange();
|
|
549
933
|
}
|
|
550
934
|
// ─── Undo/Redo ────────────────────────────────────
|
|
551
935
|
pushUndo() {
|
|
@@ -631,6 +1015,84 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
631
1015
|
this.selectedFieldId = id;
|
|
632
1016
|
this.commitChange();
|
|
633
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
|
+
}
|
|
634
1096
|
removeField(si, fi) {
|
|
635
1097
|
if (!this.schema)
|
|
636
1098
|
return;
|
|
@@ -663,77 +1125,77 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
663
1125
|
}
|
|
664
1126
|
// ─── Render ───────────────────────────────────────
|
|
665
1127
|
render() {
|
|
666
|
-
return html `
|
|
667
|
-
<div class="designer">
|
|
668
|
-
${this.renderToolbar()}
|
|
669
|
-
${this.renderLeftPanel()}
|
|
670
|
-
<div class="panel-resize"></div>
|
|
671
|
-
${this.renderCanvas()}
|
|
672
|
-
<div class="panel-resize"></div>
|
|
673
|
-
${this.renderRightPanel()}
|
|
674
|
-
</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>
|
|
675
1137
|
`;
|
|
676
1138
|
}
|
|
677
1139
|
// ─── Toolbar ──────────────────────────────────────
|
|
678
1140
|
renderToolbar() {
|
|
679
|
-
return html `
|
|
680
|
-
<div class="toolbar">
|
|
1141
|
+
return html `
|
|
1142
|
+
<div class="toolbar">
|
|
681
1143
|
${this.editingTitle
|
|
682
|
-
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
1144
|
+
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
683
1145
|
@blur="${(e) => { if (this.schema)
|
|
684
|
-
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
1146
|
+
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
685
1147
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
686
|
-
e.target.blur(); }}"
|
|
1148
|
+
e.target.blur(); }}"
|
|
687
1149
|
/>`
|
|
688
|
-
: html `<span class="report-name" @click="${() => { this.editingTitle = true; }}">${this.schema?.title || 'Sin titulo'}</span>`}
|
|
689
|
-
|
|
690
|
-
<div class="toolbar-sep"></div>
|
|
691
|
-
|
|
692
|
-
<button class="tb-btn" ?disabled="${this.undoStack.length <= 1}" @click="${this.undo}" title="Deshacer (Ctrl+Z)">↩ Deshacer</button>
|
|
693
|
-
<button class="tb-btn" ?disabled="${this.redoStack.length === 0}" @click="${this.redo}" title="Rehacer (Ctrl+Y)">↪ Rehacer</button>
|
|
694
|
-
|
|
695
|
-
<div class="toolbar-sep"></div>
|
|
696
|
-
|
|
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
|
+
|
|
697
1159
|
<button class="tb-btn" @click="${() => {
|
|
698
1160
|
if (!this.schema)
|
|
699
1161
|
return;
|
|
700
1162
|
this.schema.sections.push({ id: `section_${Date.now()}`, title: 'Nueva Seccion', fields: [] });
|
|
701
1163
|
this.commitChange();
|
|
702
|
-
}}">+ Seccion</button>
|
|
703
|
-
|
|
704
|
-
<div style="position:relative;display:inline-block;">
|
|
705
|
-
<button class="tb-btn" @click="${() => { this.showTemplateMenu = !this.showTemplateMenu; }}">📋 Plantillas ▾</button>
|
|
706
|
-
${this.showTemplateMenu ? this.renderTemplateMenu() : nothing}
|
|
707
|
-
</div>
|
|
708
|
-
|
|
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
|
+
|
|
709
1171
|
<button class="tb-btn tb-btn--danger" @click="${() => {
|
|
710
1172
|
this.schema = { id: 'new-form', version: '1.0', title: 'Nuevo Formulario', layout: { type: 'grid', columns: 2 }, sections: [{ id: 'main', title: 'Datos', fields: [] }] };
|
|
711
1173
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
712
1174
|
this.redoStack = [];
|
|
713
1175
|
this.selectedFieldId = null;
|
|
714
1176
|
this.commitChange();
|
|
715
|
-
}}">🗑 Nuevo</button>
|
|
716
|
-
|
|
717
|
-
<div class="toolbar-sep"></div>
|
|
718
|
-
|
|
719
|
-
<button class="tb-btn ${this.viewMode === 'design' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'design'; }}">✏️ Diseño</button>
|
|
720
|
-
<button class="tb-btn ${this.viewMode === 'preview' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'preview'; }}">👁 Preview</button>
|
|
721
|
-
<button class="tb-btn ${this.viewMode === 'json' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'json'; }}"></> JSON</button>
|
|
722
|
-
|
|
723
|
-
<span class="tb-spacer"></span>
|
|
724
|
-
|
|
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
|
+
|
|
725
1187
|
<button class="tb-btn" @click="${() => {
|
|
726
1188
|
if (this.schema) {
|
|
727
1189
|
navigator.clipboard.writeText(JSON.stringify(this.schema, null, 2));
|
|
728
1190
|
}
|
|
729
|
-
}}">📋 Copiar</button>
|
|
730
|
-
|
|
731
|
-
<div class="zoom-controls">
|
|
732
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.max(0.5, this.zoom - 0.1); }}">−</button>
|
|
733
|
-
<span class="zoom-label" @click="${() => { this.zoom = 1; }}">${Math.round(this.zoom * 100)}%</span>
|
|
734
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.min(2, this.zoom + 0.1); }}">+</button>
|
|
735
|
-
</div>
|
|
736
|
-
</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>
|
|
737
1199
|
`;
|
|
738
1200
|
}
|
|
739
1201
|
// ─── Left Panel ───────────────────────────────────
|
|
@@ -745,104 +1207,103 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
745
1207
|
{ key: 'media', label: 'Media' },
|
|
746
1208
|
{ key: 'layout', label: 'Layout' },
|
|
747
1209
|
];
|
|
748
|
-
return html `
|
|
749
|
-
<div class="left-panel">
|
|
750
|
-
<div class="panel-tabs">
|
|
751
|
-
<button class="panel-tab ${this.leftTab === 'fields' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'fields'; }}">Campos</button>
|
|
752
|
-
<button class="panel-tab ${this.leftTab === 'sections' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'sections'; }}">Secciones</button>
|
|
753
|
-
<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>
|
|
754
|
-
</div>
|
|
755
|
-
<div class="panel-content">
|
|
756
|
-
${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 `
|
|
757
1219
|
${categories.map(cat => {
|
|
758
1220
|
const items = getFieldsByCategory(cat.key);
|
|
759
1221
|
if (items.length === 0)
|
|
760
1222
|
return nothing;
|
|
761
|
-
return html `
|
|
762
|
-
<div class="toolbox-section">${cat.label}</div>
|
|
763
|
-
<div class="toolbox-grid">
|
|
764
|
-
${items.map(f => html `
|
|
765
|
-
<div class="toolbox-item"
|
|
766
|
-
draggable="true"
|
|
767
|
-
|
|
768
|
-
@
|
|
769
|
-
@
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
<span class="toolbox-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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>
|
|
777
1240
|
`;
|
|
778
|
-
})}
|
|
779
|
-
` : this.leftTab === 'sections' ? html `
|
|
780
|
-
${this.schema?.sections.map((s, i) => html `
|
|
781
|
-
<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;"
|
|
782
|
-
@click="${() => { }}"
|
|
783
|
-
>
|
|
784
|
-
<span style="background:#e3f2fd;color:#1976d2;font-weight:700;font-size:10px;padding:2px 6px;border-radius:4px;">§${i + 1}</span>
|
|
785
|
-
<span style="flex:1;font-weight:500;">${s.title ?? 'Sin titulo'}</span>
|
|
786
|
-
<span style="color:#aaa;font-size:10px;">${s.fields.length}</span>
|
|
787
|
-
</div>
|
|
788
|
-
`) ?? nothing}
|
|
789
|
-
<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;"
|
|
790
1253
|
@click="${() => { if (this.schema) {
|
|
791
1254
|
this.schema.sections.push({ id: 'section_' + Date.now(), title: 'Nueva Seccion', fields: [] });
|
|
792
1255
|
this.commitChange();
|
|
793
|
-
} }}"
|
|
794
|
-
>+ Agregar Seccion</button>
|
|
795
|
-
` : this.renderApiPanel()}
|
|
796
|
-
</div>
|
|
797
|
-
</div>
|
|
1256
|
+
} }}"
|
|
1257
|
+
>+ Agregar Seccion</button>
|
|
1258
|
+
` : this.renderApiPanel()}
|
|
1259
|
+
</div>
|
|
1260
|
+
</div>
|
|
798
1261
|
`;
|
|
799
1262
|
}
|
|
800
1263
|
// ─── Canvas ───────────────────────────────────────
|
|
801
1264
|
renderCanvas() {
|
|
802
|
-
return html `
|
|
803
|
-
<div class="canvas-area">
|
|
1265
|
+
return html `
|
|
1266
|
+
<div class="canvas-area">
|
|
804
1267
|
${this.viewMode === 'json'
|
|
805
1268
|
? html `<div class="json-panel" style="width:100%;max-width:800px;">${JSON.stringify(this.schema, null, 2)}</div>`
|
|
806
1269
|
: this.viewMode === 'preview'
|
|
807
1270
|
? html `<div class="canvas" style="transform:scale(${this.zoom});"><zentto-studio-renderer .schema="${this.schema}" .data="${this.data}"></zentto-studio-renderer></div>`
|
|
808
|
-
: this.renderDesignCanvas()}
|
|
809
|
-
</div>
|
|
1271
|
+
: this.renderDesignCanvas()}
|
|
1272
|
+
</div>
|
|
810
1273
|
`;
|
|
811
1274
|
}
|
|
812
1275
|
renderDesignCanvas() {
|
|
813
1276
|
if (!this.schema) {
|
|
814
|
-
return html `<div class="drop-zone
|
|
815
|
-
@dragover="${(e) => e.preventDefault()}"
|
|
816
|
-
@drop="${(e) =>
|
|
817
|
-
|
|
818
|
-
this.dragType = null;
|
|
819
|
-
} }}"
|
|
820
|
-
>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>`;
|
|
821
1281
|
}
|
|
822
1282
|
const cols = this.schema.layout.columns ?? 1;
|
|
823
|
-
return html `
|
|
824
|
-
<div class="canvas" style="transform:scale(${this.zoom});" @click="${() => { this.selectedFieldId = null; }}">
|
|
825
|
-
${this.schema.sections.map((section, si) => html `
|
|
826
|
-
<div class="canvas-section">
|
|
827
|
-
<div class="canvas-section-header" @click="${(e) => e.stopPropagation()}">
|
|
828
|
-
<span style="font-size:10px;color:var(--zrd-accent);">§${si + 1}</span>
|
|
829
|
-
${section.title ?? 'Seccion'}
|
|
830
|
-
<span style="flex:1;"></span>
|
|
831
|
-
<span style="font-size:10px;color:var(--zrd-text-muted);">${section.fields.length} campos</span>
|
|
832
|
-
</div>
|
|
833
|
-
<div class="
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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>
|
|
846
1307
|
`;
|
|
847
1308
|
}
|
|
848
1309
|
renderCanvasField(field, si, fi, maxCols) {
|
|
@@ -851,37 +1312,39 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
851
1312
|
const fullWidth = ['separator', 'heading', 'html', 'datagrid', 'report', 'chart'].includes(field.type);
|
|
852
1313
|
const gridCol = fullWidth ? '1 / -1' : span > 1 ? `span ${span}` : '';
|
|
853
1314
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
854
|
-
return html `
|
|
855
|
-
<div class="canvas-field ${isSelected ? 'canvas-field--selected' : ''}"
|
|
856
|
-
style="${gridCol ? `grid-column:${gridCol};` : ''}"
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
<button class="fa-btn fa-btn--
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
<div class="rh rh-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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>
|
|
885
1348
|
`;
|
|
886
1349
|
}
|
|
887
1350
|
getPreviewClass(type) {
|
|
@@ -934,227 +1397,215 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
934
1397
|
renderRightPanel() {
|
|
935
1398
|
const field = this.selectedField;
|
|
936
1399
|
if (!field) {
|
|
937
|
-
return html `
|
|
938
|
-
<div class="right-panel">
|
|
939
|
-
<div class="props-empty">
|
|
940
|
-
<div class="props-empty-icon">⬚</div>
|
|
941
|
-
<div class="props-empty-text">Selecciona un campo<br/>para editar propiedades</div>
|
|
942
|
-
</div>
|
|
943
|
-
${this.schema ? this.renderFormProperties() : ''}
|
|
944
|
-
</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>
|
|
945
1408
|
`;
|
|
946
1409
|
}
|
|
947
1410
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
948
|
-
return html `
|
|
949
|
-
<div class="right-panel">
|
|
950
|
-
<!-- Header with type badge -->
|
|
951
|
-
<div style="padding:8px 10px;border-bottom:1px solid #eee;">
|
|
952
|
-
<span class="props-type-badge" style="${typeColor}">${field.type.toUpperCase()}</span>
|
|
953
|
-
<div class="props-field-id">#${field.id}</div>
|
|
954
|
-
</div>
|
|
955
|
-
|
|
956
|
-
<!-- General -->
|
|
957
|
-
<div class="prop-section">
|
|
958
|
-
<div class="prop-section-header" data-section="general" @click="${() => this.toggleSection('general')}">
|
|
959
|
-
<span class="collapse-icon ${this.collapsedSections.has('general') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
960
|
-
<h4>General</h4>
|
|
961
|
-
</div>
|
|
962
|
-
${!this.collapsedSections.has('general') ? html `
|
|
963
|
-
<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>
|
|
964
|
-
<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>
|
|
965
|
-
<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>
|
|
966
|
-
<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>
|
|
967
|
-
` : ''}
|
|
968
|
-
</div>
|
|
969
|
-
|
|
970
|
-
<!-- Layout (Figma position grid) -->
|
|
971
|
-
<div class="prop-section">
|
|
972
|
-
<div class="prop-section-header" data-section="layout" @click="${() => this.toggleSection('layout')}">
|
|
973
|
-
<span class="collapse-icon ${this.collapsedSections.has('layout') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
974
|
-
<h4>Layout</h4>
|
|
975
|
-
</div>
|
|
976
|
-
${!this.collapsedSections.has('layout') ? html `
|
|
977
|
-
<div class="prop-pos-grid">
|
|
978
|
-
<div class="prop-pos-cell">
|
|
979
|
-
<span class="prop-pos-label prop-pos-label--w">W</span>
|
|
980
|
-
<input type="number" min="1" max="6" .value="${String(field.colSpan ?? 1)}" @change="${(e) => { field.colSpan = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
981
|
-
</div>
|
|
982
|
-
<div class="prop-pos-cell">
|
|
983
|
-
<span class="prop-pos-label prop-pos-label--h">T</span>
|
|
984
|
-
<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;">
|
|
985
|
-
${getAllFields().map(f => html `<option value="${f.type}" ?selected="${f.type === field.type}">${f.label}</option>`)}
|
|
986
|
-
</select>
|
|
987
|
-
</div>
|
|
988
|
-
</div>
|
|
989
|
-
<div class="prop-divider"></div>
|
|
990
|
-
<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>
|
|
991
|
-
` : ''}
|
|
992
|
-
</div>
|
|
993
|
-
|
|
994
|
-
<!-- Style -->
|
|
995
|
-
<div class="prop-section">
|
|
996
|
-
<div class="prop-section-header" data-section="style" @click="${() => this.toggleSection('style')}">
|
|
997
|
-
<span class="collapse-icon ${this.collapsedSections.has('style') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
998
|
-
<h4>Estilo</h4>
|
|
999
|
-
</div>
|
|
1000
|
-
${!this.collapsedSections.has('style') ? html `
|
|
1001
|
-
<div class="prop-row">
|
|
1002
|
-
<span class="prop-label">Ancho</span>
|
|
1003
|
-
<div class="prop-segmented">
|
|
1004
|
-
${['auto', '100%', '50%'].map(w => html `
|
|
1005
|
-
<button class="prop-seg-btn ${(field.width ?? 'auto') === w ? 'prop-seg-btn--active' : ''}"
|
|
1006
|
-
@click="${() => { field.width = w === 'auto' ? undefined : w; this.commitChange(); }}"
|
|
1007
|
-
>${w}</button>
|
|
1008
|
-
`)}
|
|
1009
|
-
</div>
|
|
1010
|
-
</div>
|
|
1011
|
-
` : ''}
|
|
1012
|
-
</div>
|
|
1013
|
-
|
|
1014
|
-
<!-- Behavior (toggles) -->
|
|
1015
|
-
<div class="prop-section">
|
|
1016
|
-
<div class="prop-section-header" data-section="behavior" @click="${() => this.toggleSection('behavior')}">
|
|
1017
|
-
<span class="collapse-icon ${this.collapsedSections.has('behavior') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1018
|
-
<h4>Comportamiento</h4>
|
|
1019
|
-
</div>
|
|
1020
|
-
${!this.collapsedSections.has('behavior') ? html `
|
|
1021
|
-
${this.renderToggle('Requerido', field.required ?? false, (v) => { field.required = v; this.commitChange(); })}
|
|
1022
|
-
${this.renderToggle('Solo lectura', field.readOnly ?? false, (v) => { field.readOnly = v; this.commitChange(); })}
|
|
1023
|
-
${this.renderToggle('Deshabilitado', field.disabled ?? false, (v) => { field.disabled = v; this.commitChange(); })}
|
|
1024
|
-
${this.renderToggle('Oculto', field.hidden ?? false, (v) => { field.hidden = v; this.commitChange(); })}
|
|
1025
|
-
` : ''}
|
|
1026
|
-
</div>
|
|
1027
|
-
|
|
1028
|
-
<!-- Rules (expressions) -->
|
|
1029
|
-
<div class="prop-section">
|
|
1030
|
-
<div class="prop-section-header" data-section="rules" @click="${() => this.toggleSection('rules')}">
|
|
1031
|
-
<span class="collapse-icon ${this.collapsedSections.has('rules') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1032
|
-
<h4>Reglas</h4>
|
|
1033
|
-
</div>
|
|
1034
|
-
${!this.collapsedSections.has('rules') ? html `
|
|
1035
|
-
<div class="prop-info">💡 Usa {campo} para referencias y expresiones</div>
|
|
1036
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Condicion de visibilidad</span></div>
|
|
1037
|
-
<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>
|
|
1038
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Valor computado</span></div>
|
|
1039
|
-
<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>
|
|
1040
|
-
<div class="prop-divider"></div>
|
|
1041
|
-
<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>
|
|
1042
|
-
` : ''}
|
|
1043
|
-
</div>
|
|
1044
|
-
|
|
1045
|
-
<!-- Data Source (API connection like report-designer) -->
|
|
1046
|
-
<div class="prop-section">
|
|
1047
|
-
<div class="prop-section-header" data-section="datasource" @click="${() => this.toggleSection('datasource')}">
|
|
1048
|
-
<span class="collapse-icon ${this.collapsedSections.has('datasource') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1049
|
-
<h4>Origen de Datos</h4>
|
|
1050
|
-
</div>
|
|
1051
|
-
${!this.collapsedSections.has('datasource') ? html `
|
|
1052
|
-
<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>
|
|
1053
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)
|
|
1054
|
-
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>
|
|
1055
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)
|
|
1056
|
-
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>
|
|
1057
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)
|
|
1058
|
-
field.props = {}; field.props.displayField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1059
|
-
${field.type === 'datagrid' || field.type === 'select' || field.type === 'lookup' ? html `
|
|
1060
|
-
<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>
|
|
1061
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)
|
|
1062
|
-
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1063
|
-
` : ''}
|
|
1064
|
-
` : ''}
|
|
1065
|
-
</div>
|
|
1066
|
-
</div>
|
|
1525
|
+
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1526
|
+
` : ''}
|
|
1527
|
+
` : ''}
|
|
1528
|
+
</div>
|
|
1529
|
+
</div>
|
|
1067
1530
|
`;
|
|
1068
1531
|
}
|
|
1069
1532
|
renderFormProperties() {
|
|
1070
1533
|
if (!this.schema)
|
|
1071
1534
|
return nothing;
|
|
1072
|
-
return html `
|
|
1073
|
-
<div class="prop-section">
|
|
1074
|
-
<div class="prop-section-header" data-section="form">
|
|
1075
|
-
<span class="collapse-icon">▾</span>
|
|
1076
|
-
<h4>Formulario</h4>
|
|
1077
|
-
</div>
|
|
1078
|
-
<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>
|
|
1079
|
-
<div class="prop-divider"></div>
|
|
1080
|
-
<div class="prop-pos-grid">
|
|
1081
|
-
<div class="prop-pos-cell">
|
|
1082
|
-
<span class="prop-pos-label prop-pos-label--w">C</span>
|
|
1083
|
-
<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(); }}" />
|
|
1084
|
-
</div>
|
|
1085
|
-
<div class="prop-pos-cell">
|
|
1086
|
-
<span class="prop-pos-label prop-pos-label--y">G</span>
|
|
1087
|
-
<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(); }}" />
|
|
1088
|
-
</div>
|
|
1089
|
-
</div>
|
|
1090
|
-
<div style="font-size:9px;color:#bbb;margin-top:4px;display:flex;gap:12px;">
|
|
1091
|
-
<span>C = Columnas</span><span>G = Gap (px)</span>
|
|
1092
|
-
</div>
|
|
1093
|
-
</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>
|
|
1094
1557
|
`;
|
|
1095
1558
|
}
|
|
1096
1559
|
renderApiPanel() {
|
|
1097
|
-
return html `
|
|
1098
|
-
<div style="padding:4px;">
|
|
1099
|
-
<!-- Auth Section -->
|
|
1100
|
-
${this.renderApiAuth()}
|
|
1101
|
-
|
|
1102
|
-
<!-- Quick connect (only when logged in or no auth needed) -->
|
|
1103
|
-
<div style="padding:4px 8px;margin-top:4px;">
|
|
1104
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Conectar Endpoint</div>
|
|
1105
|
-
<div style="display:flex;gap:4px;margin-bottom:6px;">
|
|
1106
|
-
<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;">
|
|
1107
|
-
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
|
|
1108
|
-
</select>
|
|
1109
|
-
<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" />
|
|
1110
|
-
</div>
|
|
1111
|
-
<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;"
|
|
1112
|
-
@click="${this.fetchApiFields}"
|
|
1113
|
-
>${this.apiLoading ? 'Cargando...' : '🔌 Probar Conexion'}</button>
|
|
1114
|
-
</div>
|
|
1115
|
-
|
|
1116
|
-
<!-- Fetched sources -->
|
|
1117
|
-
${this.apiSources.length > 0 ? html `
|
|
1118
|
-
<div style="margin-top:8px;">
|
|
1119
|
-
${this.apiSources.map(src => html `
|
|
1120
|
-
<div style="margin:4px;border:1px solid #eee;border-radius:6px;overflow:hidden;">
|
|
1121
|
-
<div style="padding:6px 8px;background:#f8f9fa;display:flex;align-items:center;gap:6px;border-bottom:1px solid #eee;">
|
|
1122
|
-
<span style="background:#ea580c;color:white;font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;">${src.method}</span>
|
|
1123
|
-
<span style="font-size:10px;font-weight:600;color:#333;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${src.name}</span>
|
|
1124
|
-
<span style="font-size:9px;color:#aaa;">${src.fields.length} campos</span>
|
|
1125
|
-
</div>
|
|
1126
|
-
<div style="padding:2px 4px;max-height:150px;overflow-y:auto;">
|
|
1127
|
-
${src.fields.map(field => html `
|
|
1128
|
-
<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;font-size:11px;cursor:grab;border-radius:3px;transition:background 0.1s;"
|
|
1129
|
-
draggable="true"
|
|
1130
|
-
@dragstart="${(e) => { this.dragType = 'text'; e.dataTransfer?.setData('text/plain', 'text'); e.dataTransfer?.setData('field-name', field); e.dataTransfer?.setData('ds-id', src.id); }}"
|
|
1131
|
-
@dblclick="${() =>
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
<span style="color:#555;">${field}</span>
|
|
1147
|
-
</div>
|
|
1148
|
-
`)}
|
|
1149
|
-
</div>
|
|
1150
|
-
</div>
|
|
1151
|
-
`)}
|
|
1152
|
-
</div>
|
|
1153
|
-
` : ''}
|
|
1154
|
-
|
|
1155
|
-
<!-- Zentto API shortcuts -->
|
|
1156
|
-
<div style="margin-top:12px;padding:4px 8px;">
|
|
1157
|
-
<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>
|
|
1158
1609
|
${[
|
|
1159
1610
|
{ label: 'Clientes', endpoint: '/v1/clientes', icon: '👥' },
|
|
1160
1611
|
{ label: 'Articulos', endpoint: '/v1/articulos', icon: '📦' },
|
|
@@ -1164,34 +1615,34 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1164
1615
|
{ label: 'Proveedores', endpoint: '/v1/proveedores', icon: '🏭' },
|
|
1165
1616
|
{ label: 'Bancos', endpoint: '/v1/bancos', icon: '🏦' },
|
|
1166
1617
|
{ label: 'Paises', endpoint: '/v1/config/countries', icon: '🌍' },
|
|
1167
|
-
].map(api => html `
|
|
1168
|
-
<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;"
|
|
1169
1620
|
@click="${() => {
|
|
1170
1621
|
const urlInput = this.shadowRoot?.querySelector('#api-url');
|
|
1171
1622
|
if (urlInput)
|
|
1172
1623
|
urlInput.value = api.endpoint;
|
|
1173
|
-
}}"
|
|
1174
|
-
@mouseenter="${(e) => { e.target.style.background = '#fff7ed'; e.target.style.borderColor = '#fed7aa'; }}"
|
|
1175
|
-
@mouseleave="${(e) => { e.target.style.background = ''; e.target.style.borderColor = 'transparent'; }}"
|
|
1176
|
-
>
|
|
1177
|
-
<span style="font-size:14px;">${api.icon}</span>
|
|
1178
|
-
<span style="flex:1;color:#555;">${api.label}</span>
|
|
1179
|
-
<span style="font-size:9px;color:#bbb;font-family:'SF Mono','Consolas',monospace;">${api.endpoint}</span>
|
|
1180
|
-
</div>
|
|
1181
|
-
`)}
|
|
1182
|
-
</div>
|
|
1183
|
-
|
|
1184
|
-
<!-- Manual data source -->
|
|
1185
|
-
<div style="margin-top:8px;padding:4px 8px;">
|
|
1186
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Datos Estaticos</div>
|
|
1187
|
-
<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;"
|
|
1188
1639
|
@click="${() => {
|
|
1189
1640
|
const src = { id: 'static_' + Date.now(), name: 'Datos Manuales', endpoint: '', method: 'STATIC', fields: ['campo1', 'campo2', 'campo3'] };
|
|
1190
1641
|
this.apiSources = [...this.apiSources, src];
|
|
1191
|
-
}}"
|
|
1192
|
-
>+ Agregar Datos Estaticos</button>
|
|
1193
|
-
</div>
|
|
1194
|
-
</div>
|
|
1642
|
+
}}"
|
|
1643
|
+
>+ Agregar Datos Estaticos</button>
|
|
1644
|
+
</div>
|
|
1645
|
+
</div>
|
|
1195
1646
|
`;
|
|
1196
1647
|
}
|
|
1197
1648
|
// ─── Template Menu ─────────────────────────────────
|
|
@@ -1302,13 +1753,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1302
1753
|
], actions: [{ id: 'send', type: 'submit', label: 'Enviar Encuesta', variant: 'primary' }] },
|
|
1303
1754
|
},
|
|
1304
1755
|
];
|
|
1305
|
-
return html `
|
|
1306
|
-
<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;">
|
|
1307
|
-
<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;">
|
|
1308
|
-
Cargar Plantilla
|
|
1309
|
-
</div>
|
|
1310
|
-
${templates.map(t => html `
|
|
1311
|
-
<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;"
|
|
1312
1763
|
@click="${() => {
|
|
1313
1764
|
this.schema = structuredClone(t.schema);
|
|
1314
1765
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
@@ -1316,116 +1767,116 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1316
1767
|
this.selectedFieldId = null;
|
|
1317
1768
|
this.showTemplateMenu = false;
|
|
1318
1769
|
this.commitChange();
|
|
1319
|
-
}}"
|
|
1320
|
-
@mouseenter="${(e) => { e.currentTarget.style.background = '#f0f7ff'; }}"
|
|
1321
|
-
@mouseleave="${(e) => { e.currentTarget.style.background = ''; }}"
|
|
1322
|
-
>
|
|
1323
|
-
<span style="font-size:22px;margin-top:1px;">${t.icon}</span>
|
|
1324
|
-
<div>
|
|
1325
|
-
<div style="font-size:13px;font-weight:600;color:#333;">${t.title}</div>
|
|
1326
|
-
<div style="font-size:11px;color:#999;margin-top:1px;">${t.desc}</div>
|
|
1327
|
-
</div>
|
|
1328
|
-
</div>
|
|
1329
|
-
`)}
|
|
1330
|
-
<div style="padding:8px 14px;border-top:1px solid #eee;">
|
|
1331
|
-
<div style="font-size:10px;color:#bbb;text-align:center;">Clic en una plantilla para cargarla</div>
|
|
1332
|
-
</div>
|
|
1333
|
-
</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>
|
|
1334
1785
|
`;
|
|
1335
1786
|
}
|
|
1336
1787
|
renderApiAuth() {
|
|
1337
1788
|
if (this.apiLoggedIn) {
|
|
1338
|
-
return html `
|
|
1339
|
-
<div style="margin:4px;padding:10px;background:linear-gradient(135deg,#e8f5e9,#f1f8e9);border:1px solid #c8e6c9;border-radius:8px;">
|
|
1340
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
1341
|
-
<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>
|
|
1342
|
-
<div style="flex:1;">
|
|
1343
|
-
<div style="font-size:11px;font-weight:600;color:#2e7d32;">Conectado</div>
|
|
1344
|
-
<div style="font-size:10px;color:#66bb6a;">${this.apiUser}${this.apiCompany ? ` — ${this.apiCompany}` : ''}</div>
|
|
1345
|
-
</div>
|
|
1346
|
-
<button style="border:none;background:none;cursor:pointer;font-size:14px;color:#999;padding:2px;" title="Cerrar sesion"
|
|
1347
|
-
@click="${() => { this.apiLoggedIn = false; this.apiToken = ''; this.apiUser = ''; this.apiCompany = ''; this.apiBranch = ''; }}"
|
|
1348
|
-
>✕</button>
|
|
1349
|
-
</div>
|
|
1350
|
-
<div style="font-size:9px;color:#81c784;font-family:'SF Mono','Consolas',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${this.apiBaseUrl}">
|
|
1351
|
-
🔗 ${this.apiBaseUrl}
|
|
1352
|
-
</div>
|
|
1353
|
-
</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>
|
|
1354
1805
|
`;
|
|
1355
1806
|
}
|
|
1356
|
-
return html `
|
|
1357
|
-
<div style="margin:4px;padding:10px;background:#fafafa;border:1px solid #eee;border-radius:8px;">
|
|
1358
|
-
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;">
|
|
1359
|
-
<span style="font-size:14px;">🔐</span>
|
|
1360
|
-
<span style="font-size:11px;font-weight:600;color:#333;">Iniciar Sesion</span>
|
|
1361
|
-
</div>
|
|
1362
|
-
|
|
1363
|
-
${this.apiLoginError ? html `
|
|
1364
|
-
<div style="padding:6px 8px;background:#fde8e8;border:1px solid #f5c6cb;border-radius:5px;margin-bottom:8px;font-size:10px;color:#c62828;">
|
|
1365
|
-
⚠ ${this.apiLoginError}
|
|
1366
|
-
</div>
|
|
1367
|
-
` : ''}
|
|
1368
|
-
|
|
1369
|
-
<div style="margin-bottom:6px;">
|
|
1370
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">URL Base</label>
|
|
1371
|
-
<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;"
|
|
1372
|
-
.value="${this.apiBaseUrl}" placeholder="Vacio = proxy local (recomendado)"
|
|
1373
|
-
@input="${(e) => { this.apiBaseUrl = e.target.value; }}"
|
|
1374
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1375
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1376
|
-
/>
|
|
1377
|
-
</div>
|
|
1378
|
-
|
|
1379
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:6px;">
|
|
1380
|
-
<div>
|
|
1381
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Usuario</label>
|
|
1382
|
-
<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;"
|
|
1383
|
-
placeholder="SUP"
|
|
1384
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1385
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1386
|
-
/>
|
|
1387
|
-
</div>
|
|
1388
|
-
<div>
|
|
1389
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Clave</label>
|
|
1390
|
-
<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;"
|
|
1391
|
-
placeholder="••••"
|
|
1392
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1393
|
-
@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'; }}"
|
|
1394
1845
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
1395
|
-
this.doApiLogin(); }}"
|
|
1396
|
-
/>
|
|
1397
|
-
</div>
|
|
1398
|
-
</div>
|
|
1399
|
-
|
|
1400
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:8px;">
|
|
1401
|
-
<div>
|
|
1402
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Empresa (cod)</label>
|
|
1403
|
-
<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;"
|
|
1404
|
-
placeholder="01" value="01"
|
|
1405
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1406
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1407
|
-
/>
|
|
1408
|
-
</div>
|
|
1409
|
-
<div>
|
|
1410
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Sucursal</label>
|
|
1411
|
-
<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;"
|
|
1412
|
-
placeholder="01" value="01"
|
|
1413
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1414
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1415
|
-
/>
|
|
1416
|
-
</div>
|
|
1417
|
-
</div>
|
|
1418
|
-
|
|
1419
|
-
<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;' : ''}"
|
|
1420
|
-
@click="${this.doApiLogin}"
|
|
1421
|
-
@mouseenter="${(e) => { e.target.style.background = '#1565c0'; }}"
|
|
1422
|
-
@mouseleave="${(e) => { e.target.style.background = '#1976d2'; }}"
|
|
1423
|
-
>${this.apiLoginLoading ? '⏳ Conectando...' : '🔑 Iniciar Sesion'}</button>
|
|
1424
|
-
|
|
1425
|
-
<div style="margin-top:6px;font-size:9px;color:#bbb;text-align:center;">
|
|
1426
|
-
Usa las mismas credenciales de Zentto ERP
|
|
1427
|
-
</div>
|
|
1428
|
-
</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>
|
|
1429
1880
|
`;
|
|
1430
1881
|
}
|
|
1431
1882
|
async doApiLogin() {
|
|
@@ -1555,13 +2006,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1555
2006
|
return Object.keys(sample).filter(k => !k.startsWith('_'));
|
|
1556
2007
|
}
|
|
1557
2008
|
renderToggle(label, value, onChange) {
|
|
1558
|
-
return html `
|
|
1559
|
-
<div class="prop-toggle">
|
|
1560
|
-
<button class="prop-switch ${value ? 'prop-switch--active' : ''}"
|
|
1561
|
-
@click="${() => onChange(!value)}"
|
|
1562
|
-
></button>
|
|
1563
|
-
<span class="prop-toggle-label">${label}</span>
|
|
1564
|
-
</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>
|
|
1565
2016
|
`;
|
|
1566
2017
|
}
|
|
1567
2018
|
toggleSection(id) {
|