@praxisui/crud 9.0.0-beta.6 → 9.0.0-beta.60
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/README.md +1 -1
- package/ai/component-registry.json +10741 -0
- package/fesm2022/praxisui-crud.mjs +543 -70
- package/package.json +12 -8
- package/types/praxisui-crud.d.ts +38 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
2
|
+
import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Inject, ViewEncapsulation, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { HttpClient } from '@angular/common/http';
|
|
5
5
|
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
@@ -10,6 +10,7 @@ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionServi
|
|
|
10
10
|
import { SettingsPanelService } from '@praxisui/settings-panel';
|
|
11
11
|
import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
|
|
12
12
|
import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
|
|
13
|
+
import { take, filter } from 'rxjs/operators';
|
|
13
14
|
import * as i1 from '@angular/material/dialog';
|
|
14
15
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
15
16
|
export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
|
|
@@ -34,7 +35,6 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
|
34
35
|
import * as i10 from '@angular/material/tabs';
|
|
35
36
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
36
37
|
import { PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
37
|
-
import { filter, take } from 'rxjs/operators';
|
|
38
38
|
|
|
39
39
|
class DialogService {
|
|
40
40
|
matDialog;
|
|
@@ -68,6 +68,27 @@ function getCrudDrawerAdapterToken() {
|
|
|
68
68
|
}
|
|
69
69
|
const CRUD_DRAWER_ADAPTER = getCrudDrawerAdapterToken();
|
|
70
70
|
|
|
71
|
+
function isCrudDebugEnabled$1() {
|
|
72
|
+
try {
|
|
73
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (typeof localStorage !== 'undefined') {
|
|
77
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function debugCrudLauncher(message, ...data) {
|
|
84
|
+
if (!isCrudDebugEnabled$1()) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
console.debug(message, ...data);
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
}
|
|
71
92
|
class CrudLauncherService {
|
|
72
93
|
router = inject(Router);
|
|
73
94
|
dialog = inject(DialogService);
|
|
@@ -85,8 +106,9 @@ class CrudLauncherService {
|
|
|
85
106
|
async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
|
|
86
107
|
// Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
|
|
87
108
|
const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
|
|
109
|
+
merged.action = this.normalizeActionForLaunch(merged.action);
|
|
88
110
|
const mode = this.resolveOpenMode(merged.action, merged.metadata);
|
|
89
|
-
|
|
111
|
+
debugCrudLauncher('[CRUD:Launcher] mode=', mode, 'action=', action);
|
|
90
112
|
if (mode === 'route') {
|
|
91
113
|
if (!merged.action.route) {
|
|
92
114
|
throw new Error(`Route not provided for action ${merged.action.action}`);
|
|
@@ -95,22 +117,6 @@ class CrudLauncherService {
|
|
|
95
117
|
await this.router.navigateByUrl(url);
|
|
96
118
|
return { mode };
|
|
97
119
|
}
|
|
98
|
-
if (mode === 'drawer' && this.drawerAdapter) {
|
|
99
|
-
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
100
|
-
const inputs = this.mapInputs(actionForLaunch, row, merged.metadata, runtime);
|
|
101
|
-
const idField = merged.metadata.resource?.idField ?? 'id';
|
|
102
|
-
if (row && inputs[idField] === undefined && row[idField] !== undefined) {
|
|
103
|
-
inputs[idField] = row[idField];
|
|
104
|
-
}
|
|
105
|
-
await Promise.resolve(this.drawerAdapter.open({
|
|
106
|
-
action: actionForLaunch,
|
|
107
|
-
metadata: merged.metadata,
|
|
108
|
-
inputs,
|
|
109
|
-
onClose: drawerCallbacks?.onClose,
|
|
110
|
-
onResult: drawerCallbacks?.onResult,
|
|
111
|
-
}));
|
|
112
|
-
return { mode };
|
|
113
|
-
}
|
|
114
120
|
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
115
121
|
if (!actionForLaunch.formId) {
|
|
116
122
|
throw new Error(`formId not provided for action ${actionForLaunch.action}`);
|
|
@@ -123,18 +129,26 @@ class CrudLauncherService {
|
|
|
123
129
|
row[idField] !== undefined) {
|
|
124
130
|
inputs[idField] = row[idField];
|
|
125
131
|
}
|
|
132
|
+
if (mode === 'drawer' && this.drawerAdapter) {
|
|
133
|
+
await Promise.resolve(this.drawerAdapter.open({
|
|
134
|
+
action: actionForLaunch,
|
|
135
|
+
metadata: merged.metadata,
|
|
136
|
+
inputs,
|
|
137
|
+
onClose: drawerCallbacks?.onClose,
|
|
138
|
+
onResult: drawerCallbacks?.onResult,
|
|
139
|
+
}));
|
|
140
|
+
return { mode };
|
|
141
|
+
}
|
|
126
142
|
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
127
|
-
|
|
143
|
+
debugCrudLauncher('[CRUD:Launcher] opening dialog with:', {
|
|
128
144
|
action: merged.action.action,
|
|
129
145
|
formId: actionForLaunch.formId,
|
|
130
146
|
inputs,
|
|
131
147
|
modalCfg,
|
|
132
148
|
resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
|
|
133
149
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
panelClasses.push(modalCfg.panelClass);
|
|
137
|
-
}
|
|
150
|
+
const drawerMode = mode === 'drawer';
|
|
151
|
+
const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
|
|
138
152
|
// Backdrop style presets
|
|
139
153
|
const backdropClasses = [];
|
|
140
154
|
const style = modalCfg.backdropStyle;
|
|
@@ -147,20 +161,44 @@ class CrudLauncherService {
|
|
|
147
161
|
else if (style === 'transparent') {
|
|
148
162
|
backdropClasses.push('pfx-transparent-backdrop');
|
|
149
163
|
}
|
|
150
|
-
|
|
151
|
-
backdropClasses.push(modalCfg.backdropClass);
|
|
152
|
-
}
|
|
164
|
+
const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
|
|
153
165
|
const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
|
|
154
166
|
...modalCfg,
|
|
155
167
|
panelClass: panelClasses,
|
|
156
|
-
backdropClass:
|
|
168
|
+
backdropClass: mergedBackdropClasses,
|
|
157
169
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
158
170
|
restoreFocus: modalCfg.restoreFocus ?? true,
|
|
159
|
-
minWidth: '360px',
|
|
160
|
-
|
|
171
|
+
minWidth: drawerMode ? (modalCfg.minWidth ?? '0') : '360px',
|
|
172
|
+
width: drawerMode ? (modalCfg.width ?? 'min(560px, 100vw)') : modalCfg.width,
|
|
173
|
+
height: drawerMode ? (modalCfg.height ?? '100dvh') : modalCfg.height,
|
|
174
|
+
maxWidth: drawerMode ? (modalCfg.maxWidth ?? '100vw') : '95vw',
|
|
175
|
+
maxHeight: drawerMode ? (modalCfg.maxHeight ?? '100dvh') : modalCfg.maxHeight,
|
|
176
|
+
position: drawerMode ? (modalCfg.position ?? { right: '0', top: '0' }) : modalCfg.position,
|
|
161
177
|
ariaLabelledBy: 'crudDialogTitle',
|
|
162
|
-
data: {
|
|
178
|
+
data: {
|
|
179
|
+
action: actionForLaunch,
|
|
180
|
+
row,
|
|
181
|
+
metadata: merged.metadata,
|
|
182
|
+
inputs,
|
|
183
|
+
presentation: drawerMode ? 'drawer' : 'modal',
|
|
184
|
+
},
|
|
163
185
|
});
|
|
186
|
+
if (drawerMode) {
|
|
187
|
+
ref
|
|
188
|
+
.afterClosed()
|
|
189
|
+
.pipe(take(1))
|
|
190
|
+
.subscribe((closedValue) => {
|
|
191
|
+
const result = toCrudDrawerResult(closedValue);
|
|
192
|
+
drawerCallbacks?.onClose?.();
|
|
193
|
+
if (result?.type) {
|
|
194
|
+
drawerCallbacks?.onResult?.(result);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
drawerCallbacks?.onResult?.({ type: 'close' });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return { mode, ref };
|
|
201
|
+
}
|
|
164
202
|
return { mode, ref };
|
|
165
203
|
}
|
|
166
204
|
resolveOpenMode(action, metadata) {
|
|
@@ -248,6 +286,9 @@ class CrudLauncherService {
|
|
|
248
286
|
if (resolved.apiUrlEntry != null) {
|
|
249
287
|
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
250
288
|
}
|
|
289
|
+
if (resolved.layoutPolicy != null) {
|
|
290
|
+
inputs['layoutPolicy'] = resolved.layoutPolicy;
|
|
291
|
+
}
|
|
251
292
|
if (action.form?.initialValue != null) {
|
|
252
293
|
inputs['initialValue'] = action.form.initialValue;
|
|
253
294
|
}
|
|
@@ -257,6 +298,7 @@ class CrudLauncherService {
|
|
|
257
298
|
return inputs;
|
|
258
299
|
}
|
|
259
300
|
resolveActionForLaunch(action, metadata) {
|
|
301
|
+
action = this.normalizeActionForLaunch(action);
|
|
260
302
|
if (action.formId) {
|
|
261
303
|
return action;
|
|
262
304
|
}
|
|
@@ -264,29 +306,61 @@ class CrudLauncherService {
|
|
|
264
306
|
return action;
|
|
265
307
|
}
|
|
266
308
|
const formId = this.buildInferredFormId(action, metadata);
|
|
267
|
-
return formId
|
|
309
|
+
return formId
|
|
310
|
+
? {
|
|
311
|
+
...action,
|
|
312
|
+
formId,
|
|
313
|
+
form: {
|
|
314
|
+
...(action.form || {}),
|
|
315
|
+
layoutPolicy: action.form?.layoutPolicy ?? this.defaultInferredLayoutPolicy(action),
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
: action;
|
|
319
|
+
}
|
|
320
|
+
resolveSubmitUrlTemplate(submitUrl, row, metadata) {
|
|
321
|
+
const template = String(submitUrl || '').trim();
|
|
322
|
+
if (!template || !template.includes('{')) {
|
|
323
|
+
return template;
|
|
324
|
+
}
|
|
325
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
326
|
+
const rowId = row?.[idField];
|
|
327
|
+
if (rowId == null) {
|
|
328
|
+
return template;
|
|
329
|
+
}
|
|
330
|
+
const encodedId = encodeURIComponent(String(rowId));
|
|
331
|
+
const idFieldPattern = new RegExp(`\\{${escapeRegExp(idField)}\\}`, 'g');
|
|
332
|
+
return template
|
|
333
|
+
.replace(idFieldPattern, encodedId)
|
|
334
|
+
.replace(/\{id\}/g, encodedId)
|
|
335
|
+
.replace(/\{resourceId\}/g, encodedId);
|
|
268
336
|
}
|
|
269
337
|
resolveActionFormContract(action, row, metadata, runtime) {
|
|
270
338
|
if (this.isExplicitCrudAction(action)) {
|
|
271
339
|
return {
|
|
272
340
|
schemaUrl: action.form?.schemaUrl ?? null,
|
|
273
|
-
submitUrl: action.form?.submitUrl
|
|
341
|
+
submitUrl: action.form?.submitUrl
|
|
342
|
+
? this.resolveSubmitUrlTemplate(action.form.submitUrl, row, metadata)
|
|
343
|
+
: null,
|
|
274
344
|
submitMethod: action.form?.submitMethod ?? null,
|
|
275
345
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
276
346
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
347
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
277
348
|
};
|
|
278
349
|
}
|
|
279
350
|
if (!this.isCanonicalCrudAction(action.action)) {
|
|
280
351
|
return {
|
|
281
352
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
282
353
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
354
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
283
355
|
};
|
|
284
356
|
}
|
|
285
357
|
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
358
|
+
const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
|
|
286
359
|
if (!resourcePath) {
|
|
287
360
|
return {
|
|
288
361
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
289
362
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
363
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
290
364
|
};
|
|
291
365
|
}
|
|
292
366
|
const idField = String(metadata.resource?.idField ?? 'id');
|
|
@@ -294,6 +368,7 @@ class CrudLauncherService {
|
|
|
294
368
|
const resolved = this.operationResolver.resolve({
|
|
295
369
|
operation: action.action,
|
|
296
370
|
resourcePath,
|
|
371
|
+
schemaResourcePath,
|
|
297
372
|
resourceId: resourceId ?? null,
|
|
298
373
|
capabilities: runtime?.capabilities ?? null,
|
|
299
374
|
links: runtime?.links ?? null,
|
|
@@ -301,14 +376,15 @@ class CrudLauncherService {
|
|
|
301
376
|
endpointKey: action.form?.apiEndpointKey ??
|
|
302
377
|
metadata.resource?.endpointKey ??
|
|
303
378
|
undefined,
|
|
304
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
379
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
305
380
|
});
|
|
306
381
|
return {
|
|
307
382
|
schemaUrl: resolved?.schemaUrl ?? null,
|
|
308
383
|
submitUrl: resolved?.submitUrl ?? null,
|
|
309
384
|
submitMethod: resolved?.submitMethod ?? null,
|
|
310
385
|
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
311
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
386
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
387
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
312
388
|
};
|
|
313
389
|
}
|
|
314
390
|
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
@@ -327,9 +403,20 @@ class CrudLauncherService {
|
|
|
327
403
|
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
328
404
|
}
|
|
329
405
|
isCanonicalCrudAction(actionName) {
|
|
330
|
-
const normalized =
|
|
406
|
+
const normalized = this.normalizeCrudOperationName(actionName);
|
|
331
407
|
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
332
408
|
}
|
|
409
|
+
normalizeActionForLaunch(action) {
|
|
410
|
+
const normalized = this.normalizeCrudOperationName(action.action);
|
|
411
|
+
return normalized === action.action ? action : { ...action, action: normalized };
|
|
412
|
+
}
|
|
413
|
+
normalizeCrudOperationName(actionName) {
|
|
414
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
415
|
+
if (['add', 'novo', 'new', 'incluir', 'inserir'].includes(normalized)) {
|
|
416
|
+
return 'create';
|
|
417
|
+
}
|
|
418
|
+
return normalized;
|
|
419
|
+
}
|
|
333
420
|
isExplicitCrudAction(action) {
|
|
334
421
|
if (action.mode === 'explicit') {
|
|
335
422
|
return true;
|
|
@@ -338,6 +425,19 @@ class CrudLauncherService {
|
|
|
338
425
|
String(action.form?.submitUrl || '').trim() ||
|
|
339
426
|
String(action.form?.submitMethod || '').trim());
|
|
340
427
|
}
|
|
428
|
+
defaultInferredLayoutPolicy(action) {
|
|
429
|
+
const operation = this.normalizeCrudOperationName(action.action);
|
|
430
|
+
if (operation !== 'create' && operation !== 'edit') {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
source: 'schema',
|
|
435
|
+
intent: 'command',
|
|
436
|
+
preset: 'groupedCommand',
|
|
437
|
+
persistence: 'transient',
|
|
438
|
+
schemaType: 'request',
|
|
439
|
+
};
|
|
440
|
+
}
|
|
341
441
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
342
442
|
try {
|
|
343
443
|
if (!componentKeyId)
|
|
@@ -392,6 +492,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
392
492
|
type: Injectable,
|
|
393
493
|
args: [{ providedIn: 'root' }]
|
|
394
494
|
}] });
|
|
495
|
+
function mergeClassList(base, extra) {
|
|
496
|
+
if (!extra) {
|
|
497
|
+
return base;
|
|
498
|
+
}
|
|
499
|
+
if (typeof extra === 'string') {
|
|
500
|
+
return [...base, extra];
|
|
501
|
+
}
|
|
502
|
+
if (Array.isArray(extra)) {
|
|
503
|
+
return [...base, ...extra];
|
|
504
|
+
}
|
|
505
|
+
return [
|
|
506
|
+
...base,
|
|
507
|
+
...Object.keys(extra).filter((className) => !!extra[className]),
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
function escapeRegExp(value) {
|
|
511
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
512
|
+
}
|
|
513
|
+
function toCrudDrawerResult(value) {
|
|
514
|
+
if (!value || typeof value !== 'object') {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const result = value;
|
|
518
|
+
return result.type === 'save' || result.type === 'delete' || result.type === 'close'
|
|
519
|
+
? result
|
|
520
|
+
: undefined;
|
|
521
|
+
}
|
|
395
522
|
|
|
396
523
|
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
397
524
|
const DOCUMENT_VERSION = 1;
|
|
@@ -510,10 +637,15 @@ function normalizeCrudMetadata(metadata) {
|
|
|
510
637
|
if (base.resource && typeof base.resource === 'object') {
|
|
511
638
|
normalized.resource = stripUndefinedShallow({
|
|
512
639
|
path: trimString(base.resource.path) || undefined,
|
|
640
|
+
schemaPath: trimString(base.resource.schemaPath) || undefined,
|
|
513
641
|
idField: typeof base.resource.idField === 'string'
|
|
514
642
|
? trimString(base.resource.idField) || undefined
|
|
515
643
|
: base.resource.idField,
|
|
644
|
+
title: trimString(base.resource.title) || undefined,
|
|
645
|
+
label: trimString(base.resource.label) || undefined,
|
|
646
|
+
formTitle: trimString(base.resource.formTitle) || undefined,
|
|
516
647
|
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
648
|
+
apiUrlEntry: base.resource.apiUrlEntry || undefined,
|
|
517
649
|
});
|
|
518
650
|
}
|
|
519
651
|
if (base.defaults && typeof base.defaults === 'object') {
|
|
@@ -564,6 +696,7 @@ function normalizeCrudAction(action) {
|
|
|
564
696
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
697
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
698
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
699
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
700
|
}
|
|
568
701
|
: undefined,
|
|
569
702
|
});
|
|
@@ -3498,6 +3631,7 @@ class PraxisCrudComponent {
|
|
|
3498
3631
|
resourceDiscoveryInstance;
|
|
3499
3632
|
actionOpenAdapterInstance;
|
|
3500
3633
|
surfaceOpenAdapterInstance;
|
|
3634
|
+
materializedRefreshSequence = 0;
|
|
3501
3635
|
global = (() => {
|
|
3502
3636
|
try {
|
|
3503
3637
|
return inject(GlobalConfigService);
|
|
@@ -3527,6 +3661,7 @@ class PraxisCrudComponent {
|
|
|
3527
3661
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3662
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3663
|
currentAuthoringDocument;
|
|
3664
|
+
selectedRow = null;
|
|
3530
3665
|
getResourceDiscovery() {
|
|
3531
3666
|
const assigned = this.resourceDiscovery;
|
|
3532
3667
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,15 +3688,32 @@ class PraxisCrudComponent {
|
|
|
3553
3688
|
catch { }
|
|
3554
3689
|
}
|
|
3555
3690
|
onTableRowClick(event) {
|
|
3691
|
+
const row = this.extractRowFromEvent(event);
|
|
3692
|
+
if (row) {
|
|
3693
|
+
this.selectedRow = row;
|
|
3694
|
+
}
|
|
3556
3695
|
this.rowClick.emit(event);
|
|
3557
3696
|
}
|
|
3558
3697
|
onTableSelectionChange(event) {
|
|
3698
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3699
|
this.selectionChange.emit(event);
|
|
3560
3700
|
}
|
|
3701
|
+
async onBulkAction(event) {
|
|
3702
|
+
const action = String(event?.action || '').trim();
|
|
3703
|
+
if (!action) {
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3707
|
+
if (row) {
|
|
3708
|
+
this.selectedRow = row;
|
|
3709
|
+
}
|
|
3710
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3711
|
+
}
|
|
3561
3712
|
ngOnChanges(changes) {
|
|
3562
3713
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3714
|
return;
|
|
3564
3715
|
}
|
|
3716
|
+
this.materializedRefreshSequence += 1;
|
|
3565
3717
|
try {
|
|
3566
3718
|
const parsed = typeof this.metadata === 'string'
|
|
3567
3719
|
? JSON.parse(this.metadata)
|
|
@@ -3595,33 +3747,41 @@ class PraxisCrudComponent {
|
|
|
3595
3747
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3748
|
try {
|
|
3597
3749
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3750
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3751
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3752
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3753
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3754
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3755
|
+
}
|
|
3599
3756
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3757
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3758
|
if (ctxAction) {
|
|
3602
3759
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3760
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3761
|
openMode: ctxAction.openMode,
|
|
3605
3762
|
formId: ctxAction.formId,
|
|
3606
3763
|
route: ctxAction.route,
|
|
3607
3764
|
};
|
|
3608
3765
|
}
|
|
3609
3766
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3767
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3768
|
}
|
|
3612
3769
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3770
|
+
const effectiveAction = {
|
|
3771
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3772
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3773
|
+
};
|
|
3614
3774
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3775
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3776
|
if (openedByDiscovery) {
|
|
3617
3777
|
return;
|
|
3618
3778
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3779
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3780
|
if (handledByWorkflowAction) {
|
|
3621
3781
|
return;
|
|
3622
3782
|
}
|
|
3623
3783
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3784
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3785
|
if (handledByDelete) {
|
|
3626
3786
|
return;
|
|
3627
3787
|
}
|
|
@@ -3632,7 +3792,7 @@ class PraxisCrudComponent {
|
|
|
3632
3792
|
drawerCloseEmitted = true;
|
|
3633
3793
|
this.afterClose.emit();
|
|
3634
3794
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3795
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3796
|
onClose: () => emitDrawerClose(),
|
|
3637
3797
|
onResult: (result) => {
|
|
3638
3798
|
emitDrawerClose();
|
|
@@ -3653,9 +3813,12 @@ class PraxisCrudComponent {
|
|
|
3653
3813
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3814
|
? this.collectionCapabilities
|
|
3655
3815
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3816
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3657
3817
|
});
|
|
3658
3818
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3819
|
+
if (mode === 'drawer') {
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3659
3822
|
if (!ref) {
|
|
3660
3823
|
return;
|
|
3661
3824
|
}
|
|
@@ -3842,8 +4005,77 @@ class PraxisCrudComponent {
|
|
|
3842
4005
|
return true;
|
|
3843
4006
|
}
|
|
3844
4007
|
refreshTable() {
|
|
4008
|
+
if (this.tryRefreshMaterializedLocalData()) {
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
3845
4011
|
this.table.refetch();
|
|
3846
4012
|
}
|
|
4013
|
+
tryRefreshMaterializedLocalData() {
|
|
4014
|
+
const readUrl = this.resolveMaterializedReadUrl();
|
|
4015
|
+
if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
|
|
4016
|
+
return false;
|
|
4017
|
+
}
|
|
4018
|
+
const refreshSequence = ++this.materializedRefreshSequence;
|
|
4019
|
+
void firstValueFrom(this.getResourceDiscovery().fetchJson(readUrl, {
|
|
4020
|
+
endpointKey: this.resolvedMetadata.resource?.endpointKey,
|
|
4021
|
+
apiUrlEntry: this.resolvedMetadata.resource?.apiUrlEntry,
|
|
4022
|
+
}))
|
|
4023
|
+
.then((response) => {
|
|
4024
|
+
if (!this.isCurrentMaterializedRefresh(refreshSequence, readUrl)) {
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
const data = this.extractCollectionData(this.unwrapRestData(response));
|
|
4028
|
+
if (!data) {
|
|
4029
|
+
this.error.emit({
|
|
4030
|
+
code: 'CRUD_MATERIALIZED_REFRESH_INVALID_COLLECTION',
|
|
4031
|
+
readUrl,
|
|
4032
|
+
});
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
this.resolvedMetadata = {
|
|
4036
|
+
...this.resolvedMetadata,
|
|
4037
|
+
data,
|
|
4038
|
+
};
|
|
4039
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4040
|
+
this.cdr.markForCheck();
|
|
4041
|
+
})
|
|
4042
|
+
.catch((error) => this.error.emit(error));
|
|
4043
|
+
return true;
|
|
4044
|
+
}
|
|
4045
|
+
isCurrentMaterializedRefresh(refreshSequence, readUrl) {
|
|
4046
|
+
return (refreshSequence === this.materializedRefreshSequence &&
|
|
4047
|
+
this.resolveMaterializedReadUrl() === readUrl &&
|
|
4048
|
+
this.resolveResourcePath(this.resolvedMetadata).length === 0);
|
|
4049
|
+
}
|
|
4050
|
+
resolveMaterializedReadUrl() {
|
|
4051
|
+
const materialization = this.context?.['materialization'];
|
|
4052
|
+
return String(materialization?.['readUrl'] || '').trim();
|
|
4053
|
+
}
|
|
4054
|
+
unwrapRestData(value) {
|
|
4055
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
|
|
4056
|
+
return value.data;
|
|
4057
|
+
}
|
|
4058
|
+
return value;
|
|
4059
|
+
}
|
|
4060
|
+
extractCollectionData(data) {
|
|
4061
|
+
if (Array.isArray(data)) {
|
|
4062
|
+
return data;
|
|
4063
|
+
}
|
|
4064
|
+
if (!data || typeof data !== 'object') {
|
|
4065
|
+
return null;
|
|
4066
|
+
}
|
|
4067
|
+
const record = data;
|
|
4068
|
+
if (Array.isArray(record['content'])) {
|
|
4069
|
+
return record['content'];
|
|
4070
|
+
}
|
|
4071
|
+
if (Array.isArray(record['items'])) {
|
|
4072
|
+
return record['items'];
|
|
4073
|
+
}
|
|
4074
|
+
if (Array.isArray(record['data'])) {
|
|
4075
|
+
return record['data'];
|
|
4076
|
+
}
|
|
4077
|
+
return null;
|
|
4078
|
+
}
|
|
3847
4079
|
emitTableRuntimeConfigSnapshot() {
|
|
3848
4080
|
const snapshot = this.getCurrentTableConfigSnapshot();
|
|
3849
4081
|
if (!snapshot)
|
|
@@ -3901,11 +4133,47 @@ class PraxisCrudComponent {
|
|
|
3901
4133
|
}
|
|
3902
4134
|
}
|
|
3903
4135
|
resolveQueryContext(meta) {
|
|
4136
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4137
|
+
return null;
|
|
4138
|
+
}
|
|
3904
4139
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4140
|
}
|
|
3906
4141
|
resolveFilterCriteria(meta) {
|
|
3907
4142
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4143
|
}
|
|
4144
|
+
extractRowFromEvent(event) {
|
|
4145
|
+
if (!this.isRecord(event)) {
|
|
4146
|
+
return null;
|
|
4147
|
+
}
|
|
4148
|
+
const row = event['row'];
|
|
4149
|
+
return this.isRecord(row) ? row : null;
|
|
4150
|
+
}
|
|
4151
|
+
extractSelectedRowFromEvent(event) {
|
|
4152
|
+
if (!this.isRecord(event)) {
|
|
4153
|
+
return null;
|
|
4154
|
+
}
|
|
4155
|
+
const selection = event['selection'];
|
|
4156
|
+
if (Array.isArray(selection)) {
|
|
4157
|
+
const first = selection[0];
|
|
4158
|
+
return this.isRecord(first) ? first : null;
|
|
4159
|
+
}
|
|
4160
|
+
const row = event['row'];
|
|
4161
|
+
if (this.isRecord(row)) {
|
|
4162
|
+
return row;
|
|
4163
|
+
}
|
|
4164
|
+
return null;
|
|
4165
|
+
}
|
|
4166
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4167
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4168
|
+
return this.isRecord(first) ? first : null;
|
|
4169
|
+
}
|
|
4170
|
+
resolveSelectedRowForAction(action) {
|
|
4171
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4172
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4173
|
+
return undefined;
|
|
4174
|
+
}
|
|
4175
|
+
return this.selectedRow ?? undefined;
|
|
4176
|
+
}
|
|
3909
4177
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4178
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4179
|
if (!this.surfaceService) {
|
|
@@ -3999,7 +4267,7 @@ class PraxisCrudComponent {
|
|
|
3999
4267
|
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent?.actionConfig);
|
|
4000
4268
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4269
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4270
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4271
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4272
|
return false;
|
|
4005
4273
|
}
|
|
@@ -4222,12 +4490,19 @@ class PraxisCrudComponent {
|
|
|
4222
4490
|
}
|
|
4223
4491
|
return cfg.toolbar;
|
|
4224
4492
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4493
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4494
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4495
|
if (addAction) {
|
|
4228
4496
|
if (hasToolbarAdd) {
|
|
4229
4497
|
const toolbar = ensureToolbar();
|
|
4230
4498
|
toolbar.visible = true;
|
|
4499
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4500
|
+
? {
|
|
4501
|
+
...action,
|
|
4502
|
+
action: 'create',
|
|
4503
|
+
}
|
|
4504
|
+
: action);
|
|
4505
|
+
changed = true;
|
|
4231
4506
|
}
|
|
4232
4507
|
else {
|
|
4233
4508
|
const toolbar = ensureToolbar();
|
|
@@ -4362,6 +4637,28 @@ class PraxisCrudComponent {
|
|
|
4362
4637
|
}
|
|
4363
4638
|
return false;
|
|
4364
4639
|
}
|
|
4640
|
+
normalizeCrudActionName(action) {
|
|
4641
|
+
const raw = String(action || '').trim();
|
|
4642
|
+
const normalized = raw.toLowerCase();
|
|
4643
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4644
|
+
}
|
|
4645
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4646
|
+
if (!action)
|
|
4647
|
+
return false;
|
|
4648
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4649
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4650
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4651
|
+
return true;
|
|
4652
|
+
if (explicitId)
|
|
4653
|
+
return false;
|
|
4654
|
+
const icon = normalize(action.icon);
|
|
4655
|
+
const label = normalize(action.label);
|
|
4656
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4657
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4658
|
+
}
|
|
4659
|
+
isCreateActionAliasName(actionName) {
|
|
4660
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4661
|
+
}
|
|
4365
4662
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4663
|
if (!meta)
|
|
4367
4664
|
return undefined;
|
|
@@ -4642,6 +4939,7 @@ class PraxisCrudComponent {
|
|
|
4642
4939
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
4940
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
4941
|
(toolbarAction)="onAction($event.action)"
|
|
4942
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
4943
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
4944
|
(reset)="onResetPreferences()"
|
|
4647
4945
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4657,7 +4955,7 @@ class PraxisCrudComponent {
|
|
|
4657
4955
|
/>
|
|
4658
4956
|
}
|
|
4659
4957
|
}
|
|
4660
|
-
`, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "dense"], outputs: ["rowClick", "widgetEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
|
|
4958
|
+
`, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "dense"], outputs: ["rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone", "variant", "alignment", "density", "iconContainer"] }] });
|
|
4661
4959
|
}
|
|
4662
4960
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
4961
|
type: Component,
|
|
@@ -4679,6 +4977,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4679
4977
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
4978
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
4979
|
(toolbarAction)="onAction($event.action)"
|
|
4980
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
4981
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
4982
|
(reset)="onResetPreferences()"
|
|
4684
4983
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4734,6 +5033,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
5033
|
args: [PraxisTable]
|
|
4735
5034
|
}] } });
|
|
4736
5035
|
|
|
5036
|
+
function isCrudDebugEnabled() {
|
|
5037
|
+
try {
|
|
5038
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
5039
|
+
return true;
|
|
5040
|
+
}
|
|
5041
|
+
if (typeof localStorage !== 'undefined') {
|
|
5042
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
catch { }
|
|
5046
|
+
return false;
|
|
5047
|
+
}
|
|
5048
|
+
function debugCrudHost(message, data) {
|
|
5049
|
+
if (!isCrudDebugEnabled()) {
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
try {
|
|
5053
|
+
console.debug(message, data);
|
|
5054
|
+
}
|
|
5055
|
+
catch { }
|
|
5056
|
+
}
|
|
4737
5057
|
class DynamicFormDialogHostComponent {
|
|
4738
5058
|
dialogRef;
|
|
4739
5059
|
data;
|
|
@@ -4742,6 +5062,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
5062
|
configStorage;
|
|
4743
5063
|
formComp;
|
|
4744
5064
|
modal = {};
|
|
5065
|
+
presentation = 'modal';
|
|
4745
5066
|
maximized = false;
|
|
4746
5067
|
initialSize = {};
|
|
4747
5068
|
rememberState = false;
|
|
@@ -4756,7 +5077,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4756
5077
|
submitMethod;
|
|
4757
5078
|
apiEndpointKey;
|
|
4758
5079
|
apiUrlEntry;
|
|
5080
|
+
layoutPolicy;
|
|
5081
|
+
formConfig = {};
|
|
4759
5082
|
formActions;
|
|
5083
|
+
formConfigPersistenceStrategy = 'input-first';
|
|
4760
5084
|
mode = 'create';
|
|
4761
5085
|
backConfig;
|
|
4762
5086
|
idField = 'id';
|
|
@@ -4778,6 +5102,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5102
|
this.crud = crud;
|
|
4779
5103
|
this.configStorage = configStorage;
|
|
4780
5104
|
this.dialogRef.disableClose = true;
|
|
5105
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5106
|
// i18n
|
|
4782
5107
|
this.texts = {
|
|
4783
5108
|
...this.texts,
|
|
@@ -4812,12 +5137,14 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5137
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5138
|
const act = this.data.action?.action;
|
|
4814
5139
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5140
|
+
this.formConfig = this.resolveFormConfig();
|
|
5141
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5142
|
this.formActions = this.resolveFormActions();
|
|
4816
5143
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5144
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5145
|
this.backDefaults = defaults;
|
|
4819
5146
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5147
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5148
|
action: this.data?.action,
|
|
4822
5149
|
resourcePath: this.resourcePath,
|
|
4823
5150
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5184,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5184
|
'apiEndpointKey',
|
|
4858
5185
|
'apiUrlEntry',
|
|
4859
5186
|
'initialValue',
|
|
5187
|
+
'layoutPolicy',
|
|
4860
5188
|
]);
|
|
4861
5189
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5190
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5197,50 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5197
|
}
|
|
4870
5198
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5199
|
}
|
|
5200
|
+
resolveFormConfig() {
|
|
5201
|
+
const config = this.data.metadata?.form;
|
|
5202
|
+
return config && typeof config === 'object' ? config : {};
|
|
5203
|
+
}
|
|
5204
|
+
get dialogTitle() {
|
|
5205
|
+
const actionLabel = this.resolveActionDialogTitle();
|
|
5206
|
+
return actionLabel ||
|
|
5207
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5208
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5209
|
+
this.texts.title;
|
|
5210
|
+
}
|
|
5211
|
+
resolveActionDialogTitle() {
|
|
5212
|
+
const actionLabel = stringOrUndefined(this.data.action?.label);
|
|
5213
|
+
if (!actionLabel) {
|
|
5214
|
+
return undefined;
|
|
5215
|
+
}
|
|
5216
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5217
|
+
const table = this.data.metadata?.table ?? {};
|
|
5218
|
+
const businessTitle = stringOrUndefined(resource.formTitle ??
|
|
5219
|
+
resource.title ??
|
|
5220
|
+
resource.label ??
|
|
5221
|
+
table.title ??
|
|
5222
|
+
table.label);
|
|
5223
|
+
const routeFallbackTitle = deriveModeTitle(this.mode, titleFromResourcePath(this.resourcePath));
|
|
5224
|
+
return businessTitle && routeFallbackTitle && sameTitle(actionLabel, routeFallbackTitle)
|
|
5225
|
+
? undefined
|
|
5226
|
+
: actionLabel;
|
|
5227
|
+
}
|
|
5228
|
+
resolveResourceTitle() {
|
|
5229
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5230
|
+
const table = this.data.metadata?.table ?? {};
|
|
5231
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5232
|
+
resource.title ??
|
|
5233
|
+
resource.label ??
|
|
5234
|
+
table.title ??
|
|
5235
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5236
|
+
}
|
|
4872
5237
|
resolveFormActions() {
|
|
4873
5238
|
if (this.mode === 'view') {
|
|
4874
5239
|
return undefined;
|
|
4875
5240
|
}
|
|
5241
|
+
if (this.formConfig?.actions) {
|
|
5242
|
+
return undefined;
|
|
5243
|
+
}
|
|
4876
5244
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5245
|
if (!submitLabel) {
|
|
4878
5246
|
return undefined;
|
|
@@ -4890,6 +5258,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5258
|
},
|
|
4891
5259
|
};
|
|
4892
5260
|
}
|
|
5261
|
+
resolveLayoutPolicy() {
|
|
5262
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5263
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5264
|
+
this.data.action?.layoutPolicy;
|
|
5265
|
+
if (explicit && typeof explicit === 'object') {
|
|
5266
|
+
return explicit;
|
|
5267
|
+
}
|
|
5268
|
+
return null;
|
|
5269
|
+
}
|
|
4893
5270
|
resolveSubmitLabel() {
|
|
4894
5271
|
const action = this.data.action ?? {};
|
|
4895
5272
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,10 +5339,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5339
|
ref
|
|
4963
5340
|
.afterClosed()
|
|
4964
5341
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5342
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5343
|
}
|
|
4967
5344
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5345
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5346
|
}
|
|
4970
5347
|
}
|
|
4971
5348
|
toggleMaximize(initial = false) {
|
|
@@ -5008,7 +5385,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5385
|
width: saved?.width ?? this.modal.width,
|
|
5009
5386
|
height: saved?.height ?? this.modal.height,
|
|
5010
5387
|
};
|
|
5011
|
-
|
|
5388
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5389
|
initialSize: this.initialSize,
|
|
5013
5390
|
startMaximized: this.modal.startMaximized,
|
|
5014
5391
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5032,10 +5409,10 @@ class DynamicFormDialogHostComponent {
|
|
|
5032
5409
|
}
|
|
5033
5410
|
}
|
|
5034
5411
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2$1.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
|
|
5035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"" }, classAttribute: "praxis-dialog" }, providers: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
5412
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"", "attr.data-presentation": "presentation", "class.praxis-drawer": "presentation === \"drawer\"" }, classAttribute: "praxis-dialog" }, providers: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
5036
5413
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5414
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5415
|
+
{{ dialogTitle }}
|
|
5039
5416
|
</h2>
|
|
5040
5417
|
<span class="spacer"></span>
|
|
5041
5418
|
@if (modal.canMaximize) {
|
|
@@ -5069,11 +5446,14 @@ class DynamicFormDialogHostComponent {
|
|
|
5069
5446
|
[resourceId]="resourceId"
|
|
5070
5447
|
[initialValue]="initialValue"
|
|
5071
5448
|
[mode]="mode"
|
|
5449
|
+
[config]="formConfig"
|
|
5072
5450
|
[schemaUrl]="schemaUrl"
|
|
5073
5451
|
[submitUrl]="submitUrl"
|
|
5074
5452
|
[submitMethod]="submitMethod"
|
|
5075
5453
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5454
|
[apiUrlEntry]="apiUrlEntry"
|
|
5455
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5456
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5457
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5458
|
[backConfig]="backConfig"
|
|
5079
5459
|
[actions]="formActions"
|
|
@@ -5081,7 +5461,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5461
|
(formCancel)="onCancel()"
|
|
5082
5462
|
></praxis-dynamic-form>
|
|
5083
5463
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5464
|
+
`, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 64px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
5085
5465
|
}
|
|
5086
5466
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5467
|
type: Component,
|
|
@@ -5091,13 +5471,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5091
5471
|
MatIconModule,
|
|
5092
5472
|
PraxisIconDirective,
|
|
5093
5473
|
PraxisDynamicForm
|
|
5094
|
-
], providers: [GenericCrudService], host: {
|
|
5474
|
+
], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
|
|
5095
5475
|
class: 'praxis-dialog',
|
|
5096
5476
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5477
|
+
'[attr.data-presentation]': 'presentation',
|
|
5478
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5479
|
}, template: `
|
|
5098
5480
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5481
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5482
|
+
{{ dialogTitle }}
|
|
5101
5483
|
</h2>
|
|
5102
5484
|
<span class="spacer"></span>
|
|
5103
5485
|
@if (modal.canMaximize) {
|
|
@@ -5131,11 +5513,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5131
5513
|
[resourceId]="resourceId"
|
|
5132
5514
|
[initialValue]="initialValue"
|
|
5133
5515
|
[mode]="mode"
|
|
5516
|
+
[config]="formConfig"
|
|
5134
5517
|
[schemaUrl]="schemaUrl"
|
|
5135
5518
|
[submitUrl]="submitUrl"
|
|
5136
5519
|
[submitMethod]="submitMethod"
|
|
5137
5520
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5521
|
[apiUrlEntry]="apiUrlEntry"
|
|
5522
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5523
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5524
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5525
|
[backConfig]="backConfig"
|
|
5141
5526
|
[actions]="formActions"
|
|
@@ -5143,7 +5528,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5528
|
(formCancel)="onCancel()"
|
|
5144
5529
|
></praxis-dynamic-form>
|
|
5145
5530
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5531
|
+
`, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 64px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{border-radius:0!important}\n"] }]
|
|
5147
5532
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5533
|
type: Inject,
|
|
5149
5534
|
args: [MatDialogRef]
|
|
@@ -5170,6 +5555,17 @@ function stringOrUndefined(value) {
|
|
|
5170
5555
|
const text = String(value ?? '').trim();
|
|
5171
5556
|
return text || undefined;
|
|
5172
5557
|
}
|
|
5558
|
+
function sameTitle(left, right) {
|
|
5559
|
+
return normalizeTitle(left) === normalizeTitle(right);
|
|
5560
|
+
}
|
|
5561
|
+
function normalizeTitle(value) {
|
|
5562
|
+
return value
|
|
5563
|
+
.normalize('NFD')
|
|
5564
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5565
|
+
.replace(/\s+/g, ' ')
|
|
5566
|
+
.trim()
|
|
5567
|
+
.toLocaleLowerCase('pt-BR');
|
|
5568
|
+
}
|
|
5173
5569
|
function deriveCreateSubmitLabel(actionLabel) {
|
|
5174
5570
|
if (!actionLabel) {
|
|
5175
5571
|
return 'Criar';
|
|
@@ -5180,6 +5576,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5576
|
}
|
|
5181
5577
|
return actionLabel;
|
|
5182
5578
|
}
|
|
5579
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5580
|
+
if (!resourceTitle) {
|
|
5581
|
+
return undefined;
|
|
5582
|
+
}
|
|
5583
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5584
|
+
if (mode === 'create') {
|
|
5585
|
+
return `Adicionar ${singular}`;
|
|
5586
|
+
}
|
|
5587
|
+
if (mode === 'edit') {
|
|
5588
|
+
return `Editar ${singular}`;
|
|
5589
|
+
}
|
|
5590
|
+
return `Visualizar ${singular}`;
|
|
5591
|
+
}
|
|
5592
|
+
function titleFromResourcePath(resourcePath) {
|
|
5593
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5594
|
+
if (!lastSegment) {
|
|
5595
|
+
return undefined;
|
|
5596
|
+
}
|
|
5597
|
+
return lastSegment
|
|
5598
|
+
.replace(/[-_]+/g, ' ')
|
|
5599
|
+
.replace(/\s+/g, ' ')
|
|
5600
|
+
.trim()
|
|
5601
|
+
.toLocaleLowerCase('pt-BR');
|
|
5602
|
+
}
|
|
5603
|
+
function singularizePtBrResourceTitle(title) {
|
|
5604
|
+
const normalized = title.trim();
|
|
5605
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5606
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5607
|
+
return 'código de frequência';
|
|
5608
|
+
}
|
|
5609
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5610
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5611
|
+
return [singularFirst, ...rest].join(' ');
|
|
5612
|
+
}
|
|
5613
|
+
function singularizePtBrWord(word) {
|
|
5614
|
+
if (/ões$/i.test(word)) {
|
|
5615
|
+
return word.replace(/ões$/i, 'ão');
|
|
5616
|
+
}
|
|
5617
|
+
if (/ais$/i.test(word)) {
|
|
5618
|
+
return word.replace(/ais$/i, 'al');
|
|
5619
|
+
}
|
|
5620
|
+
if (/eis$/i.test(word)) {
|
|
5621
|
+
return word.replace(/eis$/i, 'el');
|
|
5622
|
+
}
|
|
5623
|
+
if (/res$/i.test(word)) {
|
|
5624
|
+
return word.replace(/es$/i, '');
|
|
5625
|
+
}
|
|
5626
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5627
|
+
return word.replace(/s$/i, '');
|
|
5628
|
+
}
|
|
5629
|
+
return word;
|
|
5630
|
+
}
|
|
5631
|
+
function lowerFirstLetter(value) {
|
|
5632
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5633
|
+
}
|
|
5183
5634
|
|
|
5184
5635
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5636
|
__proto__: null,
|
|
@@ -5325,11 +5776,31 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
5776
|
type: 'PraxisDataQueryContext | null',
|
|
5326
5777
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
5778
|
},
|
|
5779
|
+
{
|
|
5780
|
+
name: 'metadata.resource.schemaPath',
|
|
5781
|
+
type: 'string | null',
|
|
5782
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
5783
|
+
},
|
|
5784
|
+
{
|
|
5785
|
+
name: 'metadata.resource.title',
|
|
5786
|
+
type: 'string | null',
|
|
5787
|
+
description: 'Titulo documental do recurso usado pelo shell CRUD para derivar titulos contextuais de criacao, edicao e visualizacao quando a action nao informa label.',
|
|
5788
|
+
},
|
|
5789
|
+
{
|
|
5790
|
+
name: 'metadata.resource.formTitle',
|
|
5791
|
+
type: 'string | null',
|
|
5792
|
+
description: 'Titulo documental especifico para formularios do recurso, usado antes de metadata.resource.title no shell modal/drawer.',
|
|
5793
|
+
},
|
|
5328
5794
|
{
|
|
5329
5795
|
name: 'metadata.filterCriteria',
|
|
5330
5796
|
type: 'Record<string, unknown> | null',
|
|
5331
5797
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
5798
|
},
|
|
5799
|
+
{
|
|
5800
|
+
name: 'metadata.form',
|
|
5801
|
+
type: 'FormConfig',
|
|
5802
|
+
description: 'Configuração do formulário encaminhada para o PraxisDynamicForm aberto por ações modal/drawer; layout, fieldMetadata, actions e helpPresentation permanecem propriedade do Dynamic Form.',
|
|
5803
|
+
},
|
|
5333
5804
|
{
|
|
5334
5805
|
name: 'metadata.actions[].form',
|
|
5335
5806
|
type: 'CrudActionFormContract',
|
|
@@ -5699,6 +6170,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6170
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6171
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6172
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6173
|
+
{ path: 'actions[].form.layoutPolicy', category: 'actions', valueKind: 'object', description: 'Politica schema-driven opt-in encaminhada ao Dynamic Form para materializar detalhe ou command form sem FormConfig.sections local; deve acompanhar schemaUrl compativel, especialmente schemaType=request para comandos.' },
|
|
5702
6174
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6175
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6176
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6208,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6208
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6209
|
apiEndpointKey: { type: 'string' },
|
|
5738
6210
|
initialValue: { type: 'object' },
|
|
6211
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6212
|
},
|
|
5740
6213
|
},
|
|
5741
6214
|
params: {
|
|
@@ -5854,14 +6327,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5854
6327
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6328
|
],
|
|
5856
6329
|
editableTargets: [
|
|
5857
|
-
{ kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, id field, endpoint key and query context owned by CRUD orchestration.' },
|
|
6330
|
+
{ kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, optional schema path template, id field, endpoint key and query context owned by CRUD orchestration.' },
|
|
5858
6331
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6332
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6333
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6334
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6335
|
{ kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', description: 'Delete action enablement, confirmation, endpoint and capability policy.' },
|
|
5863
|
-
{ kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent.' },
|
|
5864
|
-
{ kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue.' },
|
|
6336
|
+
{ kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent, including contextual shell titles derived from action label, form title or resource metadata.' },
|
|
6337
|
+
{ kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue. Shared FormConfig remains owned by Dynamic Form and is forwarded through CrudMetadata.form when present.' },
|
|
5865
6338
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6339
|
{ kind: 'domainGovernanceContext', resolver: 'domain-catalog-context-by-resource-key', description: 'Read-only semantic/governance context resolved from Domain Catalog and referenced from CRUD queryContext.meta.' },
|
|
5867
6340
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6350,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6350
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6351
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6352
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6353
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6354
|
inputSchema: resourceBindSchema,
|
|
5882
6355
|
failureModes: ['resource-not-found', 'schema-url-not-canonical', 'capabilities-unavailable', 'id-field-missing'],
|
|
5883
|
-
description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use.',
|
|
6356
|
+
description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use. Use CrudMetadata.resource.schemaPath when schema discovery must use a templated path while resource.path targets a concrete nested collection.',
|
|
5884
6357
|
} }],
|
|
5885
6358
|
validators: ['resource-exists-in-api-metadata', 'resource-path-canonical', 'resource-key-stable', 'id-field-known', 'resource-capabilities-resolvable'],
|
|
5886
|
-
affectedPaths: ['resource.path', 'resource.idField', 'resource.endpointKey', 'queryContext', 'filterCriteria'],
|
|
6359
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6360
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6361
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6362
|
},
|
|
@@ -6017,14 +6490,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6490
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6491
|
inputSchema: dialogHostSchema,
|
|
6019
6492
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6493
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6494
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6495
|
identityKeys: ['crudId'],
|
|
6023
6496
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6497
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
|
|
6498
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6499
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6500
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6501
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6502
|
submissionImpact: 'config-only',
|
|
6030
6503
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6573,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6573
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6574
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6575
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6576
|
+
{ validatorId: 'drawer-runtime-available', level: 'error', code: 'CRUD_DRAWER_RUNTIME_AVAILABLE', description: 'Drawer open mode uses the default dialog-backed drawer runtime with CRUD-owned chrome, responsive right-panel sizing, Escape/backdrop handling and semantic close/save results; a host drawer adapter is optional only for shell-specific presentation.' },
|
|
6104
6577
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6578
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6579
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|