ngx-dynamic-entity 1.1.0 → 1.3.0

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 (58) hide show
  1. package/README.md +1 -1
  2. package/esm2022/lib/field-types/array-field.component.mjs +13 -5
  3. package/esm2022/lib/field-types/boolean-field.component.mjs +4 -3
  4. package/esm2022/lib/field-types/checkbox-field.component.mjs +4 -3
  5. package/esm2022/lib/field-types/currency-field.component.mjs +4 -3
  6. package/esm2022/lib/field-types/date-field.component.mjs +4 -3
  7. package/esm2022/lib/field-types/date-time-field.component.mjs +138 -0
  8. package/esm2022/lib/field-types/dropdown-field.component.mjs +9 -7
  9. package/esm2022/lib/field-types/email-field.component.mjs +4 -3
  10. package/esm2022/lib/field-types/entity-ref-field.component.mjs +21 -7
  11. package/esm2022/lib/field-types/field-component.contract.mjs +2 -0
  12. package/esm2022/lib/field-types/file-field.component.mjs +4 -3
  13. package/esm2022/lib/field-types/group-field.component.mjs +5 -5
  14. package/esm2022/lib/field-types/image-field.component.mjs +4 -3
  15. package/esm2022/lib/field-types/month-year-field.component.mjs +4 -3
  16. package/esm2022/lib/field-types/multi-select-field.component.mjs +4 -3
  17. package/esm2022/lib/field-types/number-field.component.mjs +7 -11
  18. package/esm2022/lib/field-types/password-field.component.mjs +4 -3
  19. package/esm2022/lib/field-types/radio-field.component.mjs +4 -3
  20. package/esm2022/lib/field-types/text-field.component.mjs +25 -18
  21. package/esm2022/lib/field-types/textarea-field.component.mjs +4 -3
  22. package/esm2022/lib/field-types/time-field.component.mjs +96 -0
  23. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +55 -7
  24. package/esm2022/lib/form/dynamic-form.component.mjs +140 -15
  25. package/esm2022/lib/form/dynamic-record-form.component.mjs +54 -17
  26. package/esm2022/lib/providers/provide-field-types.mjs +5 -2
  27. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +7 -2
  28. package/esm2022/lib/services/cascade-data.service.mjs +2 -2
  29. package/esm2022/lib/services/entity-ref-registry.service.mjs +2 -2
  30. package/esm2022/lib/services/lookup-registry.service.mjs +2 -2
  31. package/esm2022/lib/services/validation-messages.service.mjs +56 -0
  32. package/esm2022/lib/services/validator-registry.service.mjs +24 -2
  33. package/esm2022/lib/tokens/injection-tokens.mjs +19 -6
  34. package/esm2022/public-api.mjs +9 -2
  35. package/fesm2022/ngx-dynamic-entity.mjs +663 -98
  36. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  37. package/lib/field-types/array-field.component.d.ts +7 -1
  38. package/lib/field-types/date-time-field.component.d.ts +30 -0
  39. package/lib/field-types/dropdown-field.component.d.ts +1 -0
  40. package/lib/field-types/entity-ref-field.component.d.ts +11 -1
  41. package/lib/field-types/field-component.contract.d.ts +49 -0
  42. package/lib/field-types/group-field.component.d.ts +1 -1
  43. package/lib/field-types/number-field.component.d.ts +1 -0
  44. package/lib/field-types/text-field.component.d.ts +10 -2
  45. package/lib/field-types/time-field.component.d.ts +27 -0
  46. package/lib/form/dynamic-field/dynamic-field.component.d.ts +39 -4
  47. package/lib/form/dynamic-form.component.d.ts +64 -2
  48. package/lib/form/dynamic-record-form.component.d.ts +19 -0
  49. package/lib/providers/provide-ngx-dynamic-entity.d.ts +17 -1
  50. package/lib/services/cascade-data.service.d.ts +1 -1
  51. package/lib/services/entity-ref-registry.service.d.ts +1 -1
  52. package/lib/services/lookup-registry.service.d.ts +1 -1
  53. package/lib/services/validation-messages.service.d.ts +43 -0
  54. package/lib/services/validator-registry.service.d.ts +13 -1
  55. package/lib/tokens/injection-tokens.d.ts +18 -5
  56. package/package.json +2 -2
  57. package/public-api.d.ts +4 -0
  58. package/styles.css +579 -0
package/styles.css ADDED
@@ -0,0 +1,579 @@
1
+ /**
2
+ * ngx-dynamic-entity — optional base stylesheet.
3
+ *
4
+ * The package ships no styles by default: the components emit stable BEM-style hooks and
5
+ * nothing else, so they drop into any design system without fighting it. That is the right
6
+ * default for a library, but it meant a consumer's first render was unstyled browser HTML
7
+ * with no starting point.
8
+ *
9
+ * This file is that starting point. Import it and the forms are legible; override the custom
10
+ * properties below and they match your product. It is deliberately plain — no shadows, no
11
+ * brand colour, no opinion about type — because it exists to be built on, not shipped as-is.
12
+ *
13
+ * // angular.json → styles, or a global stylesheet
14
+ * @import 'ngx-dynamic-entity/styles.css';
15
+ *
16
+ * Everything is driven by the custom properties in the :root block, which are scoped under
17
+ * .ngx-form / .ngx-record-editor so importing this cannot affect the rest of your app.
18
+ * Colours use `color-mix` sparingly and always with a plain fallback declared first.
19
+ */
20
+
21
+ .ngx-form,
22
+ .ngx-record-editor {
23
+ /* Palette */
24
+ --ngx-color-text: #16202a;
25
+ --ngx-color-muted: #65788a;
26
+ --ngx-color-border: #cfd8e0;
27
+ --ngx-color-surface: #ffffff;
28
+ --ngx-color-surface-alt: #f5f8fa;
29
+ --ngx-color-accent: #0b5d8a;
30
+ --ngx-color-error: #9e2a2b;
31
+ --ngx-color-warning: #8a5a12;
32
+ --ngx-color-success: #1c6b52;
33
+ --ngx-color-disabled-bg: #eef1f4;
34
+
35
+ /* Metrics */
36
+ --ngx-radius: 4px;
37
+ --ngx-gap: 16px;
38
+ --ngx-field-gap: 6px;
39
+ --ngx-control-height: 38px;
40
+ --ngx-control-padding: 8px 10px;
41
+
42
+ /* Type */
43
+ --ngx-font: inherit;
44
+ --ngx-font-size: 14px;
45
+ --ngx-label-size: 13px;
46
+ --ngx-hint-size: 12px;
47
+
48
+ color: var(--ngx-color-text);
49
+ font-family: var(--ngx-font);
50
+ font-size: var(--ngx-font-size);
51
+ }
52
+
53
+ /* ─── Layout ──────────────────────────────────────────────────────────────── */
54
+
55
+ .ngx-form__panel {
56
+ display: grid;
57
+ grid-template-columns: repeat(12, minmax(0, 1fr));
58
+ gap: var(--ngx-gap);
59
+ align-items: start;
60
+ }
61
+
62
+ /* colSpan is applied inline by the renderer; this is the fallback for fields without one. */
63
+ .ngx-form__field-slot {
64
+ grid-column: span 12;
65
+ min-width: 0; /* lets long values shrink instead of forcing the grid wider */
66
+ position: relative;
67
+ }
68
+
69
+ .ngx-form__tabpanel:focus-visible {
70
+ outline: 2px solid var(--ngx-color-accent);
71
+ outline-offset: 2px;
72
+ }
73
+
74
+ .ngx-form__module-panel {
75
+ padding: var(--ngx-gap) 0;
76
+ }
77
+
78
+ /* ─── Tabs ────────────────────────────────────────────────────────────────── */
79
+
80
+ .ngx-form__tabs {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ gap: 4px;
84
+ border-bottom: 1px solid var(--ngx-color-border);
85
+ margin-bottom: var(--ngx-gap);
86
+ }
87
+
88
+ .ngx-form__tab {
89
+ appearance: none;
90
+ background: none;
91
+ border: 0;
92
+ border-bottom: 2px solid transparent;
93
+ padding: 8px 12px;
94
+ font: inherit;
95
+ color: var(--ngx-color-muted);
96
+ cursor: pointer;
97
+ }
98
+
99
+ .ngx-form__tab:hover {
100
+ color: var(--ngx-color-text);
101
+ }
102
+
103
+ .ngx-form__tab--active {
104
+ color: var(--ngx-color-accent);
105
+ border-bottom-color: var(--ngx-color-accent);
106
+ }
107
+
108
+ .ngx-form__tabs--sub {
109
+ border-bottom: 0;
110
+ }
111
+
112
+ .ngx-form__tab--sub {
113
+ font-size: var(--ngx-hint-size);
114
+ padding: 4px 10px;
115
+ }
116
+
117
+ /* ─── Fields ──────────────────────────────────────────────────────────────── */
118
+
119
+ .ngx-field {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: var(--ngx-field-gap);
123
+ }
124
+
125
+ .ngx-field__label {
126
+ font-size: var(--ngx-label-size);
127
+ color: var(--ngx-color-muted);
128
+ }
129
+
130
+ .ngx-field__req {
131
+ color: var(--ngx-color-error);
132
+ margin-left: 2px;
133
+ }
134
+
135
+ .ngx-field__input,
136
+ .ngx-field__file-input {
137
+ width: 100%;
138
+ min-height: var(--ngx-control-height);
139
+ padding: var(--ngx-control-padding);
140
+ font: inherit;
141
+ color: inherit;
142
+ background: var(--ngx-color-surface);
143
+ border: 1px solid var(--ngx-color-border);
144
+ border-radius: var(--ngx-radius);
145
+ box-sizing: border-box;
146
+ }
147
+
148
+ textarea.ngx-field__input {
149
+ min-height: calc(var(--ngx-control-height) * 2);
150
+ resize: vertical;
151
+ }
152
+
153
+ .ngx-field__input:focus-visible {
154
+ outline: 2px solid var(--ngx-color-accent);
155
+ outline-offset: 1px;
156
+ border-color: var(--ngx-color-accent);
157
+ }
158
+
159
+ .ngx-field__input:disabled,
160
+ .ngx-field__input[disabled] {
161
+ background: var(--ngx-color-disabled-bg);
162
+ color: var(--ngx-color-muted);
163
+ cursor: not-allowed;
164
+ }
165
+
166
+ .ngx-field--invalid .ngx-field__input {
167
+ border-color: var(--ngx-color-error);
168
+ }
169
+
170
+ .ngx-field__error {
171
+ font-size: var(--ngx-hint-size);
172
+ color: var(--ngx-color-error);
173
+ }
174
+
175
+ /* A referenced field whose source definition has changed since it was linked. */
176
+ .ngx-field__drift {
177
+ font-size: var(--ngx-hint-size);
178
+ color: var(--ngx-color-warning);
179
+ }
180
+
181
+ .ngx-field__hint {
182
+ font-size: var(--ngx-hint-size);
183
+ color: var(--ngx-color-muted);
184
+ }
185
+
186
+ /* Read-only and masked renderings are text, not controls. */
187
+ .ngx-field__value {
188
+ min-height: var(--ngx-control-height);
189
+ display: flex;
190
+ align-items: center;
191
+ padding: var(--ngx-control-padding);
192
+ background: var(--ngx-color-surface-alt);
193
+ border: 1px solid transparent;
194
+ border-radius: var(--ngx-radius);
195
+ word-break: break-word;
196
+ }
197
+
198
+ .ngx-field__value--masked {
199
+ color: var(--ngx-color-muted);
200
+ letter-spacing: 0.12em;
201
+ }
202
+
203
+ /* ─── Composite controls ──────────────────────────────────────────────────── */
204
+
205
+ .ngx-field__currency-wrap,
206
+ .ngx-field__password-wrap,
207
+ .ngx-field__month-year-wrap,
208
+ .ngx-field__file-wrap {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 6px;
212
+ }
213
+
214
+ .ngx-field__currency-symbol {
215
+ color: var(--ngx-color-muted);
216
+ }
217
+
218
+ .ngx-field__eye-btn,
219
+ .ngx-field__upload-btn,
220
+ .ngx-field__remove-btn,
221
+ .ngx-field__array-add-btn,
222
+ .ngx-field__array-remove-btn,
223
+ .ngx-form__lock {
224
+ appearance: none;
225
+ font: inherit;
226
+ cursor: pointer;
227
+ background: var(--ngx-color-surface);
228
+ color: inherit;
229
+ border: 1px solid var(--ngx-color-border);
230
+ border-radius: var(--ngx-radius);
231
+ padding: 6px 10px;
232
+ }
233
+
234
+ .ngx-field__eye-btn:focus-visible,
235
+ .ngx-field__upload-btn:focus-visible,
236
+ .ngx-field__remove-btn:focus-visible,
237
+ .ngx-field__array-add-btn:focus-visible,
238
+ .ngx-field__array-remove-btn:focus-visible,
239
+ .ngx-form__lock:focus-visible,
240
+ .ngx-form__tab:focus-visible,
241
+ .ngx-form__submit:focus-visible,
242
+ .ngx-form__reset:focus-visible {
243
+ outline: 2px solid var(--ngx-color-accent);
244
+ outline-offset: 2px;
245
+ }
246
+
247
+ .ngx-form__lock {
248
+ position: absolute;
249
+ top: 0;
250
+ right: 0;
251
+ padding: 2px 6px;
252
+ line-height: 1;
253
+ }
254
+
255
+ /* Radio */
256
+ .ngx-field__radio-group {
257
+ display: flex;
258
+ flex-wrap: wrap;
259
+ gap: 12px;
260
+ }
261
+
262
+ .ngx-field__radio-option {
263
+ display: inline-flex;
264
+ align-items: center;
265
+ gap: 6px;
266
+ }
267
+
268
+ .ngx-field__radio-label {
269
+ cursor: pointer;
270
+ }
271
+
272
+ /* Boolean switch */
273
+ .ngx-field__toggle-wrap,
274
+ .ngx-field__toggle-label {
275
+ display: inline-flex;
276
+ align-items: center;
277
+ gap: 8px;
278
+ cursor: pointer;
279
+ }
280
+
281
+ .ngx-field__toggle-track {
282
+ position: relative;
283
+ width: 38px;
284
+ height: 20px;
285
+ border-radius: 999px;
286
+ background: var(--ngx-color-border);
287
+ transition: background 0.15s ease;
288
+ flex: none;
289
+ }
290
+
291
+ .ngx-field__toggle-thumb {
292
+ position: absolute;
293
+ top: 2px;
294
+ left: 2px;
295
+ width: 16px;
296
+ height: 16px;
297
+ border-radius: 50%;
298
+ background: var(--ngx-color-surface);
299
+ transition: transform 0.15s ease;
300
+ }
301
+
302
+ .ngx-field__toggle-input:checked + .ngx-field__toggle-track {
303
+ background: var(--ngx-color-accent);
304
+ }
305
+
306
+ .ngx-field__toggle-input:checked + .ngx-field__toggle-track .ngx-field__toggle-thumb {
307
+ transform: translateX(18px);
308
+ }
309
+
310
+ .ngx-field__toggle-input:focus-visible + .ngx-field__toggle-track {
311
+ outline: 2px solid var(--ngx-color-accent);
312
+ outline-offset: 2px;
313
+ }
314
+
315
+ @media (prefers-reduced-motion: reduce) {
316
+ .ngx-field__toggle-track,
317
+ .ngx-field__toggle-thumb {
318
+ transition: none;
319
+ }
320
+ }
321
+
322
+ /* Group and array containers */
323
+ .ngx-field__fieldset {
324
+ border: 1px solid var(--ngx-color-border);
325
+ border-radius: var(--ngx-radius);
326
+ padding: 12px;
327
+ margin: 0;
328
+ min-width: 0;
329
+ }
330
+
331
+ .ngx-field__legend {
332
+ padding: 0 6px;
333
+ font-size: var(--ngx-label-size);
334
+ color: var(--ngx-color-muted);
335
+ }
336
+
337
+ .ngx-field__group-children,
338
+ .ngx-field__array-item-fields {
339
+ display: grid;
340
+ grid-template-columns: repeat(12, minmax(0, 1fr));
341
+ gap: var(--ngx-gap);
342
+ }
343
+
344
+ .ngx-field__array-list {
345
+ display: flex;
346
+ flex-direction: column;
347
+ gap: 10px;
348
+ }
349
+
350
+ .ngx-field__array-item {
351
+ border: 1px solid var(--ngx-color-border);
352
+ border-radius: var(--ngx-radius);
353
+ padding: 10px;
354
+ }
355
+
356
+ .ngx-field__array-header,
357
+ .ngx-field__array-item-header {
358
+ display: flex;
359
+ align-items: center;
360
+ justify-content: space-between;
361
+ gap: 8px;
362
+ margin-bottom: 8px;
363
+ }
364
+
365
+ .ngx-field__array-item-title {
366
+ font-size: var(--ngx-label-size);
367
+ color: var(--ngx-color-muted);
368
+ }
369
+
370
+ .ngx-field__array-empty,
371
+ .ngx-field__image-placeholder,
372
+ .ngx-record-editor__row-empty {
373
+ color: var(--ngx-color-muted);
374
+ font-size: var(--ngx-hint-size);
375
+ }
376
+
377
+ /* Image and file */
378
+ .ngx-field__image-wrap,
379
+ .ngx-field__image-actions {
380
+ display: flex;
381
+ align-items: center;
382
+ gap: 10px;
383
+ flex-wrap: wrap;
384
+ }
385
+
386
+ .ngx-field__image-preview {
387
+ max-width: 140px;
388
+ max-height: 140px;
389
+ border-radius: var(--ngx-radius);
390
+ border: 1px solid var(--ngx-color-border);
391
+ }
392
+
393
+ .ngx-field__file-link,
394
+ .ngx-field__email-link {
395
+ color: var(--ngx-color-accent);
396
+ }
397
+
398
+ .ngx-field__file-name,
399
+ .ngx-field__file-selected {
400
+ font-size: var(--ngx-hint-size);
401
+ color: var(--ngx-color-muted);
402
+ word-break: break-all;
403
+ }
404
+
405
+ /* ─── Banners and actions ─────────────────────────────────────────────────── */
406
+
407
+ .ngx-form__error-banner,
408
+ .ngx-form__warning-banner,
409
+ .ngx-form__info-banner,
410
+ .ngx-form__denied,
411
+ .ngx-record-editor__banner {
412
+ padding: 10px 12px;
413
+ border-radius: var(--ngx-radius);
414
+ border: 1px solid var(--ngx-color-border);
415
+ background: var(--ngx-color-surface-alt);
416
+ margin-bottom: 10px;
417
+ }
418
+
419
+ .ngx-form__error-banner,
420
+ .ngx-record-editor__banner--error {
421
+ color: var(--ngx-color-error);
422
+ border-color: currentColor;
423
+ }
424
+
425
+ .ngx-form__warning-banner,
426
+ .ngx-record-editor__banner--warning {
427
+ color: var(--ngx-color-warning);
428
+ border-color: currentColor;
429
+ }
430
+
431
+ .ngx-form__info-banner,
432
+ .ngx-record-editor__banner--info {
433
+ color: var(--ngx-color-accent);
434
+ border-color: currentColor;
435
+ }
436
+
437
+ .ngx-form__actions {
438
+ display: flex;
439
+ gap: 10px;
440
+ margin-top: var(--ngx-gap);
441
+ }
442
+
443
+ .ngx-form__submit,
444
+ .ngx-form__reset {
445
+ appearance: none;
446
+ font: inherit;
447
+ cursor: pointer;
448
+ min-height: var(--ngx-control-height);
449
+ padding: 0 18px;
450
+ border-radius: var(--ngx-radius);
451
+ border: 1px solid var(--ngx-color-border);
452
+ background: var(--ngx-color-surface);
453
+ color: inherit;
454
+ }
455
+
456
+ .ngx-form__submit {
457
+ background: var(--ngx-color-accent);
458
+ border-color: var(--ngx-color-accent);
459
+ color: #fff;
460
+ }
461
+
462
+ .ngx-form__submit:disabled,
463
+ .ngx-form__reset:disabled {
464
+ opacity: 0.55;
465
+ cursor: not-allowed;
466
+ }
467
+
468
+ /* ─── Record editor ───────────────────────────────────────────────────────── */
469
+
470
+ .ngx-record-editor__header {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 12px;
474
+ margin-bottom: var(--ngx-gap);
475
+ }
476
+
477
+ .ngx-record-editor__avatar {
478
+ width: 40px;
479
+ height: 40px;
480
+ border-radius: 50%;
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: center;
484
+ background: var(--ngx-color-surface-alt);
485
+ border: 1px solid var(--ngx-color-border);
486
+ flex: none;
487
+ }
488
+
489
+ .ngx-record-editor__title {
490
+ margin: 0;
491
+ font-size: 18px;
492
+ }
493
+
494
+ .ngx-record-editor__subtitle,
495
+ .ngx-record-editor__section-hint,
496
+ .ngx-record-editor__summary-label {
497
+ color: var(--ngx-color-muted);
498
+ font-size: var(--ngx-hint-size);
499
+ }
500
+
501
+ .ngx-record-editor__summary-panel,
502
+ .ngx-record-editor__drawer {
503
+ border: 1px solid var(--ngx-color-border);
504
+ border-radius: var(--ngx-radius);
505
+ padding: 12px;
506
+ background: var(--ngx-color-surface-alt);
507
+ margin-bottom: var(--ngx-gap);
508
+ }
509
+
510
+ .ngx-record-editor__summary-item {
511
+ display: flex;
512
+ flex-direction: column;
513
+ gap: 2px;
514
+ padding: 4px 0;
515
+ }
516
+
517
+ .ngx-record-editor__section-bar {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 10px;
521
+ flex-wrap: wrap;
522
+ margin-bottom: 10px;
523
+ }
524
+
525
+ .ngx-record-editor__rows {
526
+ display: flex;
527
+ flex-direction: column;
528
+ gap: 6px;
529
+ }
530
+
531
+ .ngx-record-editor__row {
532
+ display: flex;
533
+ align-items: center;
534
+ justify-content: space-between;
535
+ gap: 10px;
536
+ padding: 8px 10px;
537
+ border: 1px solid var(--ngx-color-border);
538
+ border-radius: var(--ngx-radius);
539
+ }
540
+
541
+ .ngx-record-editor__row-text {
542
+ min-width: 0;
543
+ word-break: break-word;
544
+ }
545
+
546
+ .ngx-record-editor__banner-dismiss,
547
+ .ngx-record-editor__row-btn,
548
+ .ngx-record-editor__header-toggle {
549
+ appearance: none;
550
+ font: inherit;
551
+ cursor: pointer;
552
+ background: none;
553
+ border: 1px solid var(--ngx-color-border);
554
+ border-radius: var(--ngx-radius);
555
+ padding: 4px 8px;
556
+ color: inherit;
557
+ }
558
+
559
+ .ngx-record-editor__banner-dismiss:focus-visible,
560
+ .ngx-record-editor__row-btn:focus-visible,
561
+ .ngx-record-editor__header-toggle:focus-visible {
562
+ outline: 2px solid var(--ngx-color-accent);
563
+ outline-offset: 2px;
564
+ }
565
+
566
+ /* ─── Narrow screens ──────────────────────────────────────────────────────── */
567
+
568
+ @media (max-width: 640px) {
569
+ /* colSpan is set inline per field; below this width a 12-column grid is unreadable. */
570
+ .ngx-form__panel,
571
+ .ngx-field__group-children,
572
+ .ngx-field__array-item-fields {
573
+ grid-template-columns: minmax(0, 1fr);
574
+ }
575
+
576
+ .ngx-form__field-slot {
577
+ grid-column: 1 / -1 !important;
578
+ }
579
+ }