millas 0.2.12-beta → 0.2.12-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/package.json +3 -16
  2. package/src/admin/ActivityLog.js +153 -52
  3. package/src/admin/Admin.js +400 -167
  4. package/src/admin/AdminAuth.js +213 -98
  5. package/src/admin/FormGenerator.js +372 -0
  6. package/src/admin/HookRegistry.js +256 -0
  7. package/src/admin/QueryEngine.js +263 -0
  8. package/src/admin/ViewContext.js +309 -0
  9. package/src/admin/WidgetRegistry.js +406 -0
  10. package/src/admin/index.js +17 -0
  11. package/src/admin/resources/AdminResource.js +383 -97
  12. package/src/admin/static/admin.css +1341 -0
  13. package/src/admin/static/date-picker.css +157 -0
  14. package/src/admin/static/date-picker.js +316 -0
  15. package/src/admin/static/json-editor.css +649 -0
  16. package/src/admin/static/json-editor.js +1429 -0
  17. package/src/admin/static/ui.js +1044 -0
  18. package/src/admin/views/layouts/base.njk +65 -1013
  19. package/src/admin/views/pages/detail.njk +40 -16
  20. package/src/admin/views/pages/form.njk +47 -599
  21. package/src/admin/views/pages/list.njk +145 -62
  22. package/src/admin/views/partials/form-field.njk +53 -0
  23. package/src/admin/views/partials/form-footer.njk +28 -0
  24. package/src/admin/views/partials/form-readonly.njk +114 -0
  25. package/src/admin/views/partials/form-scripts.njk +476 -0
  26. package/src/admin/views/partials/form-widget.njk +296 -0
  27. package/src/admin/views/partials/json-dialog.njk +80 -0
  28. package/src/admin/views/partials/json-editor.njk +37 -0
  29. package/src/admin.zip +0 -0
  30. package/src/auth/Auth.js +31 -10
  31. package/src/auth/AuthController.js +3 -1
  32. package/src/auth/AuthUser.js +119 -0
  33. package/src/cli.js +4 -2
  34. package/src/commands/createsuperuser.js +254 -0
  35. package/src/commands/lang.js +589 -0
  36. package/src/commands/migrate.js +154 -81
  37. package/src/commands/serve.js +82 -110
  38. package/src/container/AppInitializer.js +215 -0
  39. package/src/container/Application.js +278 -253
  40. package/src/container/HttpServer.js +156 -0
  41. package/src/container/MillasApp.js +29 -279
  42. package/src/container/MillasConfig.js +192 -0
  43. package/src/core/admin.js +5 -0
  44. package/src/core/auth.js +9 -0
  45. package/src/core/db.js +9 -0
  46. package/src/core/foundation.js +59 -0
  47. package/src/core/http.js +11 -0
  48. package/src/core/lang.js +1 -0
  49. package/src/core/mail.js +6 -0
  50. package/src/core/queue.js +7 -0
  51. package/src/core/validation.js +29 -0
  52. package/src/facades/Admin.js +1 -1
  53. package/src/facades/Auth.js +22 -39
  54. package/src/facades/Cache.js +21 -10
  55. package/src/facades/Database.js +1 -1
  56. package/src/facades/Events.js +18 -17
  57. package/src/facades/Facade.js +197 -0
  58. package/src/facades/Http.js +42 -45
  59. package/src/facades/Log.js +25 -49
  60. package/src/facades/Mail.js +27 -32
  61. package/src/facades/Queue.js +22 -15
  62. package/src/facades/Storage.js +18 -10
  63. package/src/facades/Url.js +53 -0
  64. package/src/http/HttpClient.js +673 -0
  65. package/src/http/ResponseDispatcher.js +18 -111
  66. package/src/http/UrlGenerator.js +375 -0
  67. package/src/http/WelcomePage.js +273 -0
  68. package/src/http/adapters/ExpressAdapter.js +315 -0
  69. package/src/http/adapters/HttpAdapter.js +168 -0
  70. package/src/http/adapters/index.js +9 -0
  71. package/src/i18n/I18nServiceProvider.js +91 -0
  72. package/src/i18n/Translator.js +635 -0
  73. package/src/i18n/defaults.js +122 -0
  74. package/src/i18n/index.js +164 -0
  75. package/src/i18n/locales/en.js +55 -0
  76. package/src/i18n/locales/sw.js +48 -0
  77. package/src/index.js +5 -144
  78. package/src/logger/formatters/PrettyFormatter.js +103 -57
  79. package/src/logger/internal.js +2 -2
  80. package/src/logger/patchConsole.js +91 -81
  81. package/src/middleware/MiddlewareRegistry.js +62 -82
  82. package/src/migrations/system/0001_users.js +21 -0
  83. package/src/migrations/system/0002_admin_log.js +25 -0
  84. package/src/migrations/system/0003_sessions.js +23 -0
  85. package/src/orm/fields/index.js +210 -188
  86. package/src/orm/migration/DefaultValueParser.js +325 -0
  87. package/src/orm/migration/InteractiveResolver.js +191 -0
  88. package/src/orm/migration/Makemigrations.js +312 -0
  89. package/src/orm/migration/MigrationGraph.js +227 -0
  90. package/src/orm/migration/MigrationRunner.js +202 -108
  91. package/src/orm/migration/MigrationWriter.js +463 -0
  92. package/src/orm/migration/ModelInspector.js +412 -344
  93. package/src/orm/migration/ModelScanner.js +225 -0
  94. package/src/orm/migration/ProjectState.js +213 -0
  95. package/src/orm/migration/RenameDetector.js +175 -0
  96. package/src/orm/migration/SchemaBuilder.js +8 -81
  97. package/src/orm/migration/operations/base.js +57 -0
  98. package/src/orm/migration/operations/column.js +191 -0
  99. package/src/orm/migration/operations/fields.js +252 -0
  100. package/src/orm/migration/operations/index.js +55 -0
  101. package/src/orm/migration/operations/models.js +152 -0
  102. package/src/orm/migration/operations/registry.js +131 -0
  103. package/src/orm/migration/operations/special.js +51 -0
  104. package/src/orm/migration/utils.js +208 -0
  105. package/src/orm/model/Model.js +81 -13
  106. package/src/providers/AdminServiceProvider.js +66 -9
  107. package/src/providers/AuthServiceProvider.js +46 -7
  108. package/src/providers/CacheStorageServiceProvider.js +5 -3
  109. package/src/providers/DatabaseServiceProvider.js +3 -2
  110. package/src/providers/EventServiceProvider.js +2 -1
  111. package/src/providers/LogServiceProvider.js +7 -3
  112. package/src/providers/MailServiceProvider.js +4 -3
  113. package/src/providers/QueueServiceProvider.js +4 -3
  114. package/src/router/Router.js +119 -152
  115. package/src/scaffold/templates.js +83 -26
  116. package/src/facades/Validation.js +0 -69
@@ -0,0 +1,649 @@
1
+
2
+ /* ═══════════════════════════════════════════════════════════════
3
+ JSON EDITOR
4
+ ═══════════════════════════════════════════════════════════════ */
5
+
6
+ /* ── Inline trigger (form field) ── */
7
+ .je-trigger {
8
+ display: flex;
9
+ align-items: flex-start;
10
+ justify-content: space-between;
11
+ gap: 10px;
12
+ border: 1px solid var(--border);
13
+ border-radius: var(--radius-sm);
14
+ padding: 8px 10px;
15
+ background: var(--surface);
16
+ cursor: pointer;
17
+ min-height: 38px;
18
+ transition: border-color .12s, box-shadow .12s;
19
+ }
20
+ .je-trigger:hover { border-color: #c4c9d4; }
21
+ .je-trigger:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
22
+ .je-trigger.error { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-bg); }
23
+
24
+ .je-preview-wrap {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ gap: 5px;
28
+ flex: 1;
29
+ min-width: 0;
30
+ align-items: center;
31
+ }
32
+ .je-preview-placeholder { font-size: 12.5px; color: var(--text-xmuted); font-style: italic; }
33
+ .je-preview-error { color: var(--danger); }
34
+
35
+ .je-chip {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 2px;
39
+ background: var(--surface2);
40
+ border: 1px solid var(--border);
41
+ border-radius: 4px;
42
+ padding: 1px 7px;
43
+ font-size: 12px;
44
+ max-width: 180px;
45
+ overflow: hidden;
46
+ }
47
+ .je-chip-key { color: var(--text-soft); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
48
+ .je-chip-sep { color: var(--text-xmuted); flex-shrink: 0; }
49
+ .je-chip-val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'DM Mono', monospace; font-size: 11px; }
50
+ .je-chip-more { font-size: 11.5px; color: var(--text-muted); padding: 1px 5px; }
51
+
52
+ .je-open-btn {
53
+ flex-shrink: 0;
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: 5px;
57
+ font-size: 11.5px;
58
+ font-weight: 500;
59
+ color: var(--text-muted);
60
+ background: none;
61
+ border: 1px solid var(--border);
62
+ border-radius: 4px;
63
+ padding: 3px 8px;
64
+ cursor: pointer;
65
+ transition: all .1s;
66
+ white-space: nowrap;
67
+ }
68
+ .je-open-btn:hover { background: var(--surface2); color: var(--primary); border-color: var(--primary); }
69
+
70
+ /* ── Dialog content ── */
71
+ .je-modal .modal-body { padding: 0; }
72
+
73
+ .je-toolbar {
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: space-between;
77
+ padding: 12px 18px;
78
+ border-bottom: 1px solid var(--border-soft);
79
+ gap: 10px;
80
+ flex-shrink: 0;
81
+ }
82
+ .je-toolbar-title {
83
+ font-size: 13px;
84
+ font-weight: 600;
85
+ color: var(--text);
86
+ text-transform: capitalize;
87
+ }
88
+ .je-toolbar-right {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 8px;
92
+ }
93
+
94
+ /* View tabs */
95
+ .je-view-tabs {
96
+ display: flex;
97
+ border: 1px solid var(--border);
98
+ border-radius: var(--radius-sm);
99
+ overflow: hidden;
100
+ }
101
+ .je-view-tab {
102
+ padding: 4px 12px;
103
+ font-size: 12px;
104
+ font-weight: 500;
105
+ background: none;
106
+ border: none;
107
+ cursor: pointer;
108
+ color: var(--text-muted);
109
+ font-family: inherit;
110
+ transition: background .1s, color .1s;
111
+ }
112
+ .je-view-tab:not(:last-child) { border-right: 1px solid var(--border); }
113
+ .je-view-tab:hover { background: var(--surface2); color: var(--text); }
114
+ .je-view-tab.active { background: var(--primary-soft); color: var(--primary); }
115
+
116
+ .je-btn-icon {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 4px;
120
+ background: none;
121
+ border: 1px solid var(--border);
122
+ border-radius: var(--radius-sm);
123
+ padding: 4px 8px;
124
+ font-size: 11.5px;
125
+ color: var(--text-muted);
126
+ cursor: pointer;
127
+ transition: all .1s;
128
+ font-family: inherit;
129
+ }
130
+ .je-btn-icon:hover { background: var(--surface2); color: var(--text); }
131
+
132
+ /* ── Pretty view (tree) ── */
133
+ .je-pretty-view {
134
+ padding: 10px 12px;
135
+ min-height: 200px;
136
+ max-height: 450px;
137
+ overflow-y: auto;
138
+ }
139
+
140
+ .je-row {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 4px;
144
+ padding: 3px 4px;
145
+ border-radius: 4px;
146
+ min-height: 32px;
147
+ }
148
+ .je-row:hover { background: var(--surface2); }
149
+
150
+ .je-indent { display: inline-block; flex-shrink: 0; }
151
+
152
+ .je-toggle {
153
+ width: 16px;
154
+ height: 16px;
155
+ flex-shrink: 0;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ color: var(--text-xmuted);
160
+ border-radius: 3px;
161
+ }
162
+ .je-toggle-active { cursor: pointer; color: var(--text-muted); }
163
+ .je-toggle-active:hover { background: var(--surface3); color: var(--text); }
164
+
165
+ .je-key-input {
166
+ width: 120px;
167
+ flex-shrink: 0;
168
+ border: 1px solid transparent;
169
+ border-radius: 3px;
170
+ padding: 3px 6px;
171
+ font-size: 12.5px;
172
+ font-family: 'DM Mono', monospace;
173
+ color: var(--text);
174
+ font-weight: 500;
175
+ background: none;
176
+ outline: none;
177
+ transition: border-color .1s, background .1s;
178
+ }
179
+ .je-key-input:hover { border-color: var(--border); background: var(--surface); }
180
+ .je-key-input:focus { border-color: var(--primary); background: var(--surface); box-shadow: 0 0 0 2px var(--primary-soft); }
181
+
182
+ .je-colon {
183
+ color: var(--text-xmuted);
184
+ font-size: 13px;
185
+ flex-shrink: 0;
186
+ width: 8px;
187
+ text-align: center;
188
+ }
189
+ .je-colon::before { content: ':'; }
190
+
191
+ .je-type-select {
192
+ border: 1px solid transparent;
193
+ border-radius: 3px;
194
+ padding: 2px 4px;
195
+ font-size: 11px;
196
+ font-weight: 600;
197
+ font-family: inherit;
198
+ background: none;
199
+ cursor: pointer;
200
+ outline: none;
201
+ flex-shrink: 0;
202
+ transition: border-color .1s;
203
+ }
204
+ .je-type-select:hover { border-color: var(--border); background: var(--surface); }
205
+ .je-type-select:focus { border-color: var(--primary); }
206
+
207
+ .je-value-cell { flex: 1; min-width: 0; display: flex; align-items: center; }
208
+
209
+ .je-val-input {
210
+ flex: 1;
211
+ min-width: 0;
212
+ border: 1px solid transparent;
213
+ border-radius: 3px;
214
+ padding: 3px 6px;
215
+ font-size: 12.5px;
216
+ font-family: 'DM Mono', monospace;
217
+ color: var(--success, #16a34a);
218
+ background: none;
219
+ outline: none;
220
+ transition: border-color .1s, background .1s;
221
+ }
222
+ .je-val-input:hover { border-color: var(--border); background: var(--surface); }
223
+ .je-val-input:focus { border-color: var(--primary); background: var(--surface); box-shadow: 0 0 0 2px var(--primary-soft); }
224
+
225
+ .je-val-select {
226
+ border: 1px solid transparent;
227
+ border-radius: 3px;
228
+ padding: 3px 6px;
229
+ font-size: 12.5px;
230
+ font-family: 'DM Mono', monospace;
231
+ color: var(--warning, #d97706);
232
+ background: none;
233
+ cursor: pointer;
234
+ outline: none;
235
+ }
236
+ .je-val-select:hover { border-color: var(--border); background: var(--surface); }
237
+
238
+ .je-null-label {
239
+ font-family: 'DM Mono', monospace;
240
+ font-size: 12.5px;
241
+ color: var(--text-xmuted);
242
+ font-style: italic;
243
+ padding: 3px 6px;
244
+ }
245
+
246
+ .je-nested-badge {
247
+ display: inline-flex;
248
+ align-items: center;
249
+ gap: 4px;
250
+ font-size: 11.5px;
251
+ font-weight: 500;
252
+ background: var(--primary-soft);
253
+ color: var(--primary);
254
+ border: 1px solid var(--primary-dim);
255
+ border-radius: 99px;
256
+ padding: 1px 8px;
257
+ cursor: pointer;
258
+ transition: background .1s;
259
+ }
260
+ .je-nested-badge:hover { background: var(--primary-dim); }
261
+
262
+ .je-del-btn {
263
+ flex-shrink: 0;
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ width: 22px;
268
+ height: 22px;
269
+ border: none;
270
+ border-radius: 3px;
271
+ background: none;
272
+ cursor: pointer;
273
+ color: var(--text-xmuted);
274
+ opacity: 0;
275
+ transition: opacity .1s, background .1s, color .1s;
276
+ }
277
+ .je-row:hover .je-del-btn { opacity: 1; }
278
+ .je-del-btn:hover { background: var(--danger-bg); color: var(--danger); }
279
+
280
+ .je-children {
281
+ border-left: 2px solid var(--border);
282
+ margin-left: 20px;
283
+ padding-left: 4px;
284
+ }
285
+
286
+ .je-more-chip {
287
+ display: inline-flex;
288
+ align-items: center;
289
+ gap: 4px;
290
+ font-size: 12px;
291
+ color: var(--text-muted);
292
+ background: var(--surface2);
293
+ border: 1px dashed var(--border);
294
+ border-radius: 4px;
295
+ padding: 4px 10px;
296
+ cursor: pointer;
297
+ margin: 4px 4px;
298
+ transition: background .1s, color .1s;
299
+ }
300
+ .je-more-chip:hover { background: var(--surface3); color: var(--text); }
301
+
302
+ .je-add-row-btn {
303
+ display: inline-flex;
304
+ align-items: center;
305
+ gap: 5px;
306
+ font-size: 12px;
307
+ color: var(--text-muted);
308
+ background: none;
309
+ border: none;
310
+ cursor: pointer;
311
+ padding: 4px 6px;
312
+ border-radius: 4px;
313
+ font-family: inherit;
314
+ transition: background .1s, color .1s;
315
+ margin-top: 2px;
316
+ }
317
+ .je-add-row-btn:hover { background: var(--surface2); color: var(--primary); }
318
+
319
+ /* ── Raw view ── */
320
+ .je-raw-view { padding: 12px; }
321
+
322
+ .je-raw-ta {
323
+ width: 100%;
324
+ min-height: 300px;
325
+ max-height: 420px;
326
+ resize: vertical;
327
+ border: 1px solid var(--border);
328
+ border-radius: var(--radius-sm);
329
+ padding: 12px;
330
+ font-family: 'DM Mono', monospace;
331
+ font-size: 12.5px;
332
+ line-height: 1.6;
333
+ color: var(--text);
334
+ background: var(--surface2);
335
+ outline: none;
336
+ tab-size: 2;
337
+ }
338
+ .je-raw-ta:focus { border-color: var(--primary); background: var(--surface); box-shadow: 0 0 0 3px var(--primary-soft); }
339
+
340
+ .je-raw-hint { font-size: 11.5px; color: var(--success); margin-top: 6px; min-height: 18px; }
341
+ .je-raw-hint-error { color: var(--danger); }
342
+
343
+ /* ── Footer ── */
344
+ .je-footer {
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: space-between;
348
+ padding: 12px 18px;
349
+ border-top: 1px solid var(--border-soft);
350
+ background: var(--surface);
351
+ flex-shrink: 0;
352
+ }
353
+ .je-count { font-size: 12px; color: var(--text-muted); }
354
+
355
+
356
+ /* ── Root type toggle (toolbar) ── */
357
+ .je-root-type-wrap {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 6px;
361
+ }
362
+ .je-root-type-label {
363
+ font-size: 11px;
364
+ font-weight: 600;
365
+ text-transform: uppercase;
366
+ letter-spacing: .5px;
367
+ color: var(--text-muted);
368
+ }
369
+ .je-root-type-btn {
370
+ display: inline-flex;
371
+ align-items: center;
372
+ padding: 3px 10px;
373
+ font-size: 11.5px;
374
+ font-weight: 500;
375
+ font-family: 'DM Mono', monospace;
376
+ background: none;
377
+ border: 1px solid var(--border);
378
+ border-radius: var(--radius-sm);
379
+ cursor: pointer;
380
+ color: var(--text-muted);
381
+ transition: all .12s;
382
+ }
383
+ .je-root-type-btn:hover {
384
+ background: var(--surface2);
385
+ color: var(--text);
386
+ }
387
+ .je-root-type-btn.active {
388
+ background: var(--primary-soft);
389
+ border-color: var(--primary-dim);
390
+ color: var(--primary);
391
+ font-weight: 600;
392
+ }
393
+
394
+ /* ── Array index badge (replaces key input for array items) ── */
395
+ .je-index-badge {
396
+ display: inline-flex;
397
+ align-items: center;
398
+ justify-content: center;
399
+ min-width: 24px;
400
+ padding: 1px 6px;
401
+ background: var(--primary-soft);
402
+ border: 1px solid var(--primary-dim);
403
+ border-radius: 4px;
404
+ font-size: 11px;
405
+ font-family: 'DM Mono', monospace;
406
+ font-weight: 600;
407
+ color: var(--primary);
408
+ flex-shrink: 0;
409
+ user-select: none;
410
+ }
411
+
412
+ /* ═══════════════════════════════════════════════════════════════
413
+ Phase 3 — Indent guides
414
+ ═══════════════════════════════════════════════════════════════ */
415
+
416
+ /* Vertical guide line on each nesting level */
417
+ .je-children {
418
+ border-left: 2px solid var(--border);
419
+ margin-left: 10px;
420
+ padding-left: 6px;
421
+ position: relative;
422
+ }
423
+
424
+ /* Subtle connector tick on each row at depth > 0 */
425
+ .je-children > .je-row::before {
426
+ content: '';
427
+ position: absolute;
428
+ left: -8px;
429
+ top: 50%;
430
+ width: 6px;
431
+ height: 1px;
432
+ background: var(--border);
433
+ pointer-events: none;
434
+ }
435
+
436
+ /* ═══════════════════════════════════════════════════════════════
437
+ Phase 4 — Type-change warning banner
438
+ ═══════════════════════════════════════════════════════════════ */
439
+
440
+ .je-type-warn {
441
+ display: flex;
442
+ align-items: center;
443
+ gap: 10px;
444
+ flex-wrap: wrap;
445
+ padding: 7px 12px;
446
+ margin: 2px 0 2px 28px; /* indent to align with row content */
447
+ background: var(--warning-bg, #fffbeb);
448
+ border: 1px solid var(--warning-border, #fed7aa);
449
+ border-radius: var(--radius-sm);
450
+ font-size: 12px;
451
+ color: var(--warning, #d97706);
452
+ animation: je-warn-in .15s ease;
453
+ }
454
+
455
+ @keyframes je-warn-in {
456
+ from { opacity: 0; transform: translateY(-4px); }
457
+ to { opacity: 1; transform: translateY(0); }
458
+ }
459
+
460
+ .je-type-warn-msg {
461
+ display: flex;
462
+ align-items: center;
463
+ gap: 5px;
464
+ flex: 1;
465
+ min-width: 0;
466
+ }
467
+ .je-type-warn-msg strong { color: var(--text); }
468
+
469
+ .je-type-warn-actions {
470
+ display: flex;
471
+ gap: 6px;
472
+ flex-shrink: 0;
473
+ }
474
+
475
+ .je-type-warn-btn {
476
+ padding: 3px 9px;
477
+ font-size: 11.5px;
478
+ font-weight: 500;
479
+ font-family: inherit;
480
+ border-radius: var(--radius-sm);
481
+ cursor: pointer;
482
+ border: 1px solid transparent;
483
+ transition: all .1s;
484
+ white-space: nowrap;
485
+ }
486
+
487
+ .je-type-warn-drop {
488
+ background: var(--danger-bg, #fef2f2);
489
+ color: var(--danger, #dc2626);
490
+ border-color: var(--danger-border, #fecaca);
491
+ }
492
+ .je-type-warn-drop:hover {
493
+ background: var(--danger, #dc2626);
494
+ color: #fff;
495
+ }
496
+
497
+ .je-type-warn-ser {
498
+ background: var(--primary-soft);
499
+ color: var(--primary);
500
+ border-color: var(--primary-dim);
501
+ }
502
+ .je-type-warn-ser:hover {
503
+ background: var(--primary);
504
+ color: #fff;
505
+ }
506
+
507
+ .je-type-warn-cancel {
508
+ background: none;
509
+ color: var(--text-muted);
510
+ border-color: var(--border);
511
+ }
512
+ .je-type-warn-cancel:hover {
513
+ background: var(--surface2);
514
+ color: var(--text);
515
+ }
516
+
517
+ /* ═══════════════════════════════════════════════════════════════
518
+ Phase 5 — Drag to Reorder
519
+ ═══════════════════════════════════════════════════════════════ */
520
+
521
+ /* Drag handle */
522
+ .je-drag-handle {
523
+ display: inline-flex;
524
+ align-items: center;
525
+ justify-content: center;
526
+ width: 16px;
527
+ height: 22px;
528
+ flex-shrink: 0;
529
+ cursor: grab;
530
+ color: var(--text-xmuted);
531
+ opacity: 0;
532
+ transition: opacity .1s, color .1s;
533
+ margin-right: 2px;
534
+ }
535
+ .je-row:hover .je-drag-handle { opacity: 1; }
536
+ .je-drag-handle:hover { color: var(--text-muted); }
537
+ .je-drag-handle:active { cursor: grabbing; }
538
+
539
+ /* Row while being dragged */
540
+ .je-row.je-dragging {
541
+ opacity: .35;
542
+ pointer-events: none;
543
+ }
544
+
545
+ /* Drop indicator line */
546
+ .je-drop-indicator {
547
+ height: 2px;
548
+ background: var(--primary);
549
+ border-radius: 2px;
550
+ margin: 1px 4px;
551
+ pointer-events: none;
552
+ position: relative;
553
+ }
554
+ /* Dot on the left end of the indicator */
555
+ .je-drop-indicator::before {
556
+ content: '';
557
+ position: absolute;
558
+ left: -3px;
559
+ top: -3px;
560
+ width: 8px;
561
+ height: 8px;
562
+ border-radius: 50%;
563
+ background: var(--primary);
564
+ }
565
+
566
+ /* ═══════════════════════════════════════════════════════════════
567
+ Phase 6 — Undo / Redo toolbar buttons
568
+ ═══════════════════════════════════════════════════════════════ */
569
+
570
+ .je-undo-wrap {
571
+ display: flex;
572
+ gap: 2px;
573
+ }
574
+
575
+ /* Disabled state for undo/redo buttons */
576
+ .je-btn-icon:disabled {
577
+ opacity: .3;
578
+ cursor: not-allowed;
579
+ pointer-events: none;
580
+ }
581
+ /* ═══════════════════════════════════════════════════════════════
582
+ Phase 7 — Polish
583
+ ═══════════════════════════════════════════════════════════════ */
584
+
585
+ /* ── Keyboard focus ring on rows ── */
586
+ .je-row:focus {
587
+ outline: none;
588
+ background: var(--primary-soft);
589
+ border-radius: 4px;
590
+ }
591
+ .je-row:focus-visible {
592
+ box-shadow: inset 0 0 0 2px var(--primary-dim);
593
+ }
594
+
595
+ /* ── Empty state ── */
596
+ .je-empty-state {
597
+ display: flex;
598
+ flex-direction: column;
599
+ align-items: center;
600
+ justify-content: center;
601
+ gap: 10px;
602
+ padding: 40px 20px;
603
+ color: var(--text-muted);
604
+ font-size: 13px;
605
+ text-align: center;
606
+ }
607
+ .je-empty-state p {
608
+ margin: 0;
609
+ color: var(--text-muted);
610
+ }
611
+ .je-empty-add-btn {
612
+ padding: 5px 14px;
613
+ font-size: 12.5px;
614
+ font-weight: 500;
615
+ font-family: inherit;
616
+ background: var(--primary-soft);
617
+ border: 1px solid var(--primary-dim);
618
+ border-radius: var(--radius-sm);
619
+ color: var(--primary);
620
+ cursor: pointer;
621
+ transition: background .1s;
622
+ }
623
+ .je-empty-add-btn:hover {
624
+ background: var(--primary-dim);
625
+ }
626
+
627
+ /* ── Render error ── */
628
+ .je-render-error {
629
+ display: flex;
630
+ align-items: center;
631
+ gap: 8px;
632
+ padding: 16px;
633
+ color: var(--danger);
634
+ font-size: 13px;
635
+ background: var(--danger-bg);
636
+ border: 1px solid var(--danger-border);
637
+ border-radius: var(--radius-sm);
638
+ margin: 12px;
639
+ }
640
+
641
+ /* ── Duplicate key highlight ── */
642
+ .je-key-input.je-dup-key {
643
+ border-color: var(--danger) !important;
644
+ background: var(--danger-bg);
645
+ color: var(--danger);
646
+ }
647
+ .je-key-input.je-dup-key:focus {
648
+ box-shadow: 0 0 0 2px var(--danger-bg);
649
+ }