@praxisui/crud 9.0.0-beta.7 → 9.0.0-beta.71
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 +26 -9
- package/ai/component-registry.json +10741 -0
- package/fesm2022/praxisui-crud.mjs +569 -73
- package/package.json +12 -8
- package/types/praxisui-crud.d.ts +39 -1
|
@@ -1,15 +1,16 @@
|
|
|
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';
|
|
6
6
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
7
7
|
import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
|
|
8
8
|
import * as i2$1 from '@praxisui/core';
|
|
9
|
-
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, GLOBAL_SURFACE_SERVICE, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
|
|
9
|
+
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
|
|
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,35 @@ function getCrudDrawerAdapterToken() {
|
|
|
68
68
|
}
|
|
69
69
|
const CRUD_DRAWER_ADAPTER = getCrudDrawerAdapterToken();
|
|
70
70
|
|
|
71
|
+
const DEFAULT_CRUD_DRAWER_MODAL_CONFIG = {
|
|
72
|
+
minWidth: 'min(100vw, 520px)',
|
|
73
|
+
width: 'min(760px, 58vw)',
|
|
74
|
+
height: '100dvh',
|
|
75
|
+
maxWidth: '100vw',
|
|
76
|
+
maxHeight: '100dvh',
|
|
77
|
+
position: { right: '0', top: '0' },
|
|
78
|
+
};
|
|
79
|
+
function isCrudDebugEnabled$1() {
|
|
80
|
+
try {
|
|
81
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (typeof localStorage !== 'undefined') {
|
|
85
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function debugCrudLauncher(message, ...data) {
|
|
92
|
+
if (!isCrudDebugEnabled$1()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
console.debug(message, ...data);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
}
|
|
71
100
|
class CrudLauncherService {
|
|
72
101
|
router = inject(Router);
|
|
73
102
|
dialog = inject(DialogService);
|
|
@@ -85,8 +114,9 @@ class CrudLauncherService {
|
|
|
85
114
|
async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
|
|
86
115
|
// Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
|
|
87
116
|
const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
|
|
117
|
+
merged.action = this.normalizeActionForLaunch(merged.action);
|
|
88
118
|
const mode = this.resolveOpenMode(merged.action, merged.metadata);
|
|
89
|
-
|
|
119
|
+
debugCrudLauncher('[CRUD:Launcher] mode=', mode, 'action=', action);
|
|
90
120
|
if (mode === 'route') {
|
|
91
121
|
if (!merged.action.route) {
|
|
92
122
|
throw new Error(`Route not provided for action ${merged.action.action}`);
|
|
@@ -95,22 +125,6 @@ class CrudLauncherService {
|
|
|
95
125
|
await this.router.navigateByUrl(url);
|
|
96
126
|
return { mode };
|
|
97
127
|
}
|
|
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
128
|
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
115
129
|
if (!actionForLaunch.formId) {
|
|
116
130
|
throw new Error(`formId not provided for action ${actionForLaunch.action}`);
|
|
@@ -123,18 +137,26 @@ class CrudLauncherService {
|
|
|
123
137
|
row[idField] !== undefined) {
|
|
124
138
|
inputs[idField] = row[idField];
|
|
125
139
|
}
|
|
140
|
+
if (mode === 'drawer' && this.drawerAdapter) {
|
|
141
|
+
await Promise.resolve(this.drawerAdapter.open({
|
|
142
|
+
action: actionForLaunch,
|
|
143
|
+
metadata: merged.metadata,
|
|
144
|
+
inputs,
|
|
145
|
+
onClose: drawerCallbacks?.onClose,
|
|
146
|
+
onResult: drawerCallbacks?.onResult,
|
|
147
|
+
}));
|
|
148
|
+
return { mode };
|
|
149
|
+
}
|
|
126
150
|
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
127
|
-
|
|
151
|
+
debugCrudLauncher('[CRUD:Launcher] opening dialog with:', {
|
|
128
152
|
action: merged.action.action,
|
|
129
153
|
formId: actionForLaunch.formId,
|
|
130
154
|
inputs,
|
|
131
155
|
modalCfg,
|
|
132
156
|
resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
|
|
133
157
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
panelClasses.push(modalCfg.panelClass);
|
|
137
|
-
}
|
|
158
|
+
const drawerMode = mode === 'drawer';
|
|
159
|
+
const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
|
|
138
160
|
// Backdrop style presets
|
|
139
161
|
const backdropClasses = [];
|
|
140
162
|
const style = modalCfg.backdropStyle;
|
|
@@ -147,20 +169,56 @@ class CrudLauncherService {
|
|
|
147
169
|
else if (style === 'transparent') {
|
|
148
170
|
backdropClasses.push('pfx-transparent-backdrop');
|
|
149
171
|
}
|
|
150
|
-
|
|
151
|
-
backdropClasses.push(modalCfg.backdropClass);
|
|
152
|
-
}
|
|
172
|
+
const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
|
|
153
173
|
const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
|
|
154
174
|
...modalCfg,
|
|
155
175
|
panelClass: panelClasses,
|
|
156
|
-
backdropClass:
|
|
176
|
+
backdropClass: mergedBackdropClasses,
|
|
157
177
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
158
178
|
restoreFocus: modalCfg.restoreFocus ?? true,
|
|
159
|
-
minWidth:
|
|
160
|
-
|
|
179
|
+
minWidth: drawerMode
|
|
180
|
+
? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
|
|
181
|
+
: '360px',
|
|
182
|
+
width: drawerMode
|
|
183
|
+
? (modalCfg.width ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.width)
|
|
184
|
+
: modalCfg.width,
|
|
185
|
+
height: drawerMode
|
|
186
|
+
? (modalCfg.height ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.height)
|
|
187
|
+
: modalCfg.height,
|
|
188
|
+
maxWidth: drawerMode
|
|
189
|
+
? (modalCfg.maxWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxWidth)
|
|
190
|
+
: '95vw',
|
|
191
|
+
maxHeight: drawerMode
|
|
192
|
+
? (modalCfg.maxHeight ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxHeight)
|
|
193
|
+
: modalCfg.maxHeight,
|
|
194
|
+
position: drawerMode
|
|
195
|
+
? (modalCfg.position ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.position)
|
|
196
|
+
: modalCfg.position,
|
|
161
197
|
ariaLabelledBy: 'crudDialogTitle',
|
|
162
|
-
data: {
|
|
198
|
+
data: {
|
|
199
|
+
action: actionForLaunch,
|
|
200
|
+
row,
|
|
201
|
+
metadata: merged.metadata,
|
|
202
|
+
inputs,
|
|
203
|
+
presentation: drawerMode ? 'drawer' : 'modal',
|
|
204
|
+
},
|
|
163
205
|
});
|
|
206
|
+
if (drawerMode) {
|
|
207
|
+
ref
|
|
208
|
+
.afterClosed()
|
|
209
|
+
.pipe(take(1))
|
|
210
|
+
.subscribe((closedValue) => {
|
|
211
|
+
const result = toCrudDrawerResult(closedValue);
|
|
212
|
+
drawerCallbacks?.onClose?.();
|
|
213
|
+
if (result?.type) {
|
|
214
|
+
drawerCallbacks?.onResult?.(result);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
drawerCallbacks?.onResult?.({ type: 'close' });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return { mode, ref };
|
|
221
|
+
}
|
|
164
222
|
return { mode, ref };
|
|
165
223
|
}
|
|
166
224
|
resolveOpenMode(action, metadata) {
|
|
@@ -248,6 +306,9 @@ class CrudLauncherService {
|
|
|
248
306
|
if (resolved.apiUrlEntry != null) {
|
|
249
307
|
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
250
308
|
}
|
|
309
|
+
if (resolved.layoutPolicy != null) {
|
|
310
|
+
inputs['layoutPolicy'] = resolved.layoutPolicy;
|
|
311
|
+
}
|
|
251
312
|
if (action.form?.initialValue != null) {
|
|
252
313
|
inputs['initialValue'] = action.form.initialValue;
|
|
253
314
|
}
|
|
@@ -257,6 +318,7 @@ class CrudLauncherService {
|
|
|
257
318
|
return inputs;
|
|
258
319
|
}
|
|
259
320
|
resolveActionForLaunch(action, metadata) {
|
|
321
|
+
action = this.normalizeActionForLaunch(action);
|
|
260
322
|
if (action.formId) {
|
|
261
323
|
return action;
|
|
262
324
|
}
|
|
@@ -264,29 +326,61 @@ class CrudLauncherService {
|
|
|
264
326
|
return action;
|
|
265
327
|
}
|
|
266
328
|
const formId = this.buildInferredFormId(action, metadata);
|
|
267
|
-
return formId
|
|
329
|
+
return formId
|
|
330
|
+
? {
|
|
331
|
+
...action,
|
|
332
|
+
formId,
|
|
333
|
+
form: {
|
|
334
|
+
...(action.form || {}),
|
|
335
|
+
layoutPolicy: action.form?.layoutPolicy ?? this.defaultInferredLayoutPolicy(action),
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
: action;
|
|
339
|
+
}
|
|
340
|
+
resolveSubmitUrlTemplate(submitUrl, row, metadata) {
|
|
341
|
+
const template = String(submitUrl || '').trim();
|
|
342
|
+
if (!template || !template.includes('{')) {
|
|
343
|
+
return template;
|
|
344
|
+
}
|
|
345
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
346
|
+
const rowId = row?.[idField];
|
|
347
|
+
if (rowId == null) {
|
|
348
|
+
return template;
|
|
349
|
+
}
|
|
350
|
+
const encodedId = encodeURIComponent(String(rowId));
|
|
351
|
+
const idFieldPattern = new RegExp(`\\{${escapeRegExp(idField)}\\}`, 'g');
|
|
352
|
+
return template
|
|
353
|
+
.replace(idFieldPattern, encodedId)
|
|
354
|
+
.replace(/\{id\}/g, encodedId)
|
|
355
|
+
.replace(/\{resourceId\}/g, encodedId);
|
|
268
356
|
}
|
|
269
357
|
resolveActionFormContract(action, row, metadata, runtime) {
|
|
270
358
|
if (this.isExplicitCrudAction(action)) {
|
|
271
359
|
return {
|
|
272
360
|
schemaUrl: action.form?.schemaUrl ?? null,
|
|
273
|
-
submitUrl: action.form?.submitUrl
|
|
361
|
+
submitUrl: action.form?.submitUrl
|
|
362
|
+
? this.resolveSubmitUrlTemplate(action.form.submitUrl, row, metadata)
|
|
363
|
+
: null,
|
|
274
364
|
submitMethod: action.form?.submitMethod ?? null,
|
|
275
365
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
276
366
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
367
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
277
368
|
};
|
|
278
369
|
}
|
|
279
370
|
if (!this.isCanonicalCrudAction(action.action)) {
|
|
280
371
|
return {
|
|
281
372
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
282
373
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
374
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
283
375
|
};
|
|
284
376
|
}
|
|
285
377
|
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
378
|
+
const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
|
|
286
379
|
if (!resourcePath) {
|
|
287
380
|
return {
|
|
288
381
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
289
382
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
383
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
290
384
|
};
|
|
291
385
|
}
|
|
292
386
|
const idField = String(metadata.resource?.idField ?? 'id');
|
|
@@ -294,6 +388,7 @@ class CrudLauncherService {
|
|
|
294
388
|
const resolved = this.operationResolver.resolve({
|
|
295
389
|
operation: action.action,
|
|
296
390
|
resourcePath,
|
|
391
|
+
schemaResourcePath,
|
|
297
392
|
resourceId: resourceId ?? null,
|
|
298
393
|
capabilities: runtime?.capabilities ?? null,
|
|
299
394
|
links: runtime?.links ?? null,
|
|
@@ -301,14 +396,15 @@ class CrudLauncherService {
|
|
|
301
396
|
endpointKey: action.form?.apiEndpointKey ??
|
|
302
397
|
metadata.resource?.endpointKey ??
|
|
303
398
|
undefined,
|
|
304
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
399
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
305
400
|
});
|
|
306
401
|
return {
|
|
307
402
|
schemaUrl: resolved?.schemaUrl ?? null,
|
|
308
403
|
submitUrl: resolved?.submitUrl ?? null,
|
|
309
404
|
submitMethod: resolved?.submitMethod ?? null,
|
|
310
405
|
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
311
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
406
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
407
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
312
408
|
};
|
|
313
409
|
}
|
|
314
410
|
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
@@ -327,9 +423,20 @@ class CrudLauncherService {
|
|
|
327
423
|
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
328
424
|
}
|
|
329
425
|
isCanonicalCrudAction(actionName) {
|
|
330
|
-
const normalized =
|
|
426
|
+
const normalized = this.normalizeCrudOperationName(actionName);
|
|
331
427
|
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
332
428
|
}
|
|
429
|
+
normalizeActionForLaunch(action) {
|
|
430
|
+
const normalized = this.normalizeCrudOperationName(action.action);
|
|
431
|
+
return normalized === action.action ? action : { ...action, action: normalized };
|
|
432
|
+
}
|
|
433
|
+
normalizeCrudOperationName(actionName) {
|
|
434
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
435
|
+
if (['add', 'novo', 'new', 'incluir', 'inserir'].includes(normalized)) {
|
|
436
|
+
return 'create';
|
|
437
|
+
}
|
|
438
|
+
return normalized;
|
|
439
|
+
}
|
|
333
440
|
isExplicitCrudAction(action) {
|
|
334
441
|
if (action.mode === 'explicit') {
|
|
335
442
|
return true;
|
|
@@ -338,6 +445,19 @@ class CrudLauncherService {
|
|
|
338
445
|
String(action.form?.submitUrl || '').trim() ||
|
|
339
446
|
String(action.form?.submitMethod || '').trim());
|
|
340
447
|
}
|
|
448
|
+
defaultInferredLayoutPolicy(action) {
|
|
449
|
+
const operation = this.normalizeCrudOperationName(action.action);
|
|
450
|
+
if (operation !== 'create' && operation !== 'edit') {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
source: 'schema',
|
|
455
|
+
intent: 'command',
|
|
456
|
+
preset: 'groupedCommand',
|
|
457
|
+
persistence: 'transient',
|
|
458
|
+
schemaType: 'request',
|
|
459
|
+
};
|
|
460
|
+
}
|
|
341
461
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
342
462
|
try {
|
|
343
463
|
if (!componentKeyId)
|
|
@@ -392,6 +512,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
392
512
|
type: Injectable,
|
|
393
513
|
args: [{ providedIn: 'root' }]
|
|
394
514
|
}] });
|
|
515
|
+
function mergeClassList(base, extra) {
|
|
516
|
+
if (!extra) {
|
|
517
|
+
return base;
|
|
518
|
+
}
|
|
519
|
+
if (typeof extra === 'string') {
|
|
520
|
+
return [...base, extra];
|
|
521
|
+
}
|
|
522
|
+
if (Array.isArray(extra)) {
|
|
523
|
+
return [...base, ...extra];
|
|
524
|
+
}
|
|
525
|
+
return [
|
|
526
|
+
...base,
|
|
527
|
+
...Object.keys(extra).filter((className) => !!extra[className]),
|
|
528
|
+
];
|
|
529
|
+
}
|
|
530
|
+
function escapeRegExp(value) {
|
|
531
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
532
|
+
}
|
|
533
|
+
function toCrudDrawerResult(value) {
|
|
534
|
+
if (!value || typeof value !== 'object') {
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
const result = value;
|
|
538
|
+
return result.type === 'save' || result.type === 'delete' || result.type === 'close'
|
|
539
|
+
? result
|
|
540
|
+
: undefined;
|
|
541
|
+
}
|
|
395
542
|
|
|
396
543
|
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
397
544
|
const DOCUMENT_VERSION = 1;
|
|
@@ -510,10 +657,15 @@ function normalizeCrudMetadata(metadata) {
|
|
|
510
657
|
if (base.resource && typeof base.resource === 'object') {
|
|
511
658
|
normalized.resource = stripUndefinedShallow({
|
|
512
659
|
path: trimString(base.resource.path) || undefined,
|
|
660
|
+
schemaPath: trimString(base.resource.schemaPath) || undefined,
|
|
513
661
|
idField: typeof base.resource.idField === 'string'
|
|
514
662
|
? trimString(base.resource.idField) || undefined
|
|
515
663
|
: base.resource.idField,
|
|
664
|
+
title: trimString(base.resource.title) || undefined,
|
|
665
|
+
label: trimString(base.resource.label) || undefined,
|
|
666
|
+
formTitle: trimString(base.resource.formTitle) || undefined,
|
|
516
667
|
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
668
|
+
apiUrlEntry: base.resource.apiUrlEntry || undefined,
|
|
517
669
|
});
|
|
518
670
|
}
|
|
519
671
|
if (base.defaults && typeof base.defaults === 'object') {
|
|
@@ -564,6 +716,7 @@ function normalizeCrudAction(action) {
|
|
|
564
716
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
717
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
718
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
719
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
720
|
}
|
|
568
721
|
: undefined,
|
|
569
722
|
});
|
|
@@ -3498,6 +3651,7 @@ class PraxisCrudComponent {
|
|
|
3498
3651
|
resourceDiscoveryInstance;
|
|
3499
3652
|
actionOpenAdapterInstance;
|
|
3500
3653
|
surfaceOpenAdapterInstance;
|
|
3654
|
+
materializedRefreshSequence = 0;
|
|
3501
3655
|
global = (() => {
|
|
3502
3656
|
try {
|
|
3503
3657
|
return inject(GlobalConfigService);
|
|
@@ -3507,6 +3661,7 @@ class PraxisCrudComponent {
|
|
|
3507
3661
|
}
|
|
3508
3662
|
})();
|
|
3509
3663
|
surfaceService = inject(GLOBAL_SURFACE_SERVICE, { optional: true });
|
|
3664
|
+
surfaceOutlets = inject(SurfaceOutletRegistryService);
|
|
3510
3665
|
componentKeys = inject(ComponentKeyService);
|
|
3511
3666
|
route = (() => {
|
|
3512
3667
|
try {
|
|
@@ -3527,6 +3682,7 @@ class PraxisCrudComponent {
|
|
|
3527
3682
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3683
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3684
|
currentAuthoringDocument;
|
|
3685
|
+
selectedRow = null;
|
|
3530
3686
|
getResourceDiscovery() {
|
|
3531
3687
|
const assigned = this.resourceDiscovery;
|
|
3532
3688
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,15 +3709,32 @@ class PraxisCrudComponent {
|
|
|
3553
3709
|
catch { }
|
|
3554
3710
|
}
|
|
3555
3711
|
onTableRowClick(event) {
|
|
3712
|
+
const row = this.extractRowFromEvent(event);
|
|
3713
|
+
if (row) {
|
|
3714
|
+
this.selectedRow = row;
|
|
3715
|
+
}
|
|
3556
3716
|
this.rowClick.emit(event);
|
|
3557
3717
|
}
|
|
3558
3718
|
onTableSelectionChange(event) {
|
|
3719
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3720
|
this.selectionChange.emit(event);
|
|
3560
3721
|
}
|
|
3722
|
+
async onBulkAction(event) {
|
|
3723
|
+
const action = String(event?.action || '').trim();
|
|
3724
|
+
if (!action) {
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3728
|
+
if (row) {
|
|
3729
|
+
this.selectedRow = row;
|
|
3730
|
+
}
|
|
3731
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3732
|
+
}
|
|
3561
3733
|
ngOnChanges(changes) {
|
|
3562
3734
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3735
|
return;
|
|
3564
3736
|
}
|
|
3737
|
+
this.materializedRefreshSequence += 1;
|
|
3565
3738
|
try {
|
|
3566
3739
|
const parsed = typeof this.metadata === 'string'
|
|
3567
3740
|
? JSON.parse(this.metadata)
|
|
@@ -3595,33 +3768,41 @@ class PraxisCrudComponent {
|
|
|
3595
3768
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3769
|
try {
|
|
3597
3770
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3771
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3772
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3773
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3774
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3775
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3776
|
+
}
|
|
3599
3777
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3778
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3779
|
if (ctxAction) {
|
|
3602
3780
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3781
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3782
|
openMode: ctxAction.openMode,
|
|
3605
3783
|
formId: ctxAction.formId,
|
|
3606
3784
|
route: ctxAction.route,
|
|
3607
3785
|
};
|
|
3608
3786
|
}
|
|
3609
3787
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3788
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3789
|
}
|
|
3612
3790
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3791
|
+
const effectiveAction = {
|
|
3792
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3793
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3794
|
+
};
|
|
3614
3795
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3796
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3797
|
if (openedByDiscovery) {
|
|
3617
3798
|
return;
|
|
3618
3799
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3800
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3801
|
if (handledByWorkflowAction) {
|
|
3621
3802
|
return;
|
|
3622
3803
|
}
|
|
3623
3804
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3805
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3806
|
if (handledByDelete) {
|
|
3626
3807
|
return;
|
|
3627
3808
|
}
|
|
@@ -3632,7 +3813,7 @@ class PraxisCrudComponent {
|
|
|
3632
3813
|
drawerCloseEmitted = true;
|
|
3633
3814
|
this.afterClose.emit();
|
|
3634
3815
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3816
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3817
|
onClose: () => emitDrawerClose(),
|
|
3637
3818
|
onResult: (result) => {
|
|
3638
3819
|
emitDrawerClose();
|
|
@@ -3653,9 +3834,12 @@ class PraxisCrudComponent {
|
|
|
3653
3834
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3835
|
? this.collectionCapabilities
|
|
3655
3836
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3837
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3657
3838
|
});
|
|
3658
3839
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3840
|
+
if (mode === 'drawer') {
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3659
3843
|
if (!ref) {
|
|
3660
3844
|
return;
|
|
3661
3845
|
}
|
|
@@ -3842,8 +4026,77 @@ class PraxisCrudComponent {
|
|
|
3842
4026
|
return true;
|
|
3843
4027
|
}
|
|
3844
4028
|
refreshTable() {
|
|
4029
|
+
if (this.tryRefreshMaterializedLocalData()) {
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
3845
4032
|
this.table.refetch();
|
|
3846
4033
|
}
|
|
4034
|
+
tryRefreshMaterializedLocalData() {
|
|
4035
|
+
const readUrl = this.resolveMaterializedReadUrl();
|
|
4036
|
+
if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
|
|
4037
|
+
return false;
|
|
4038
|
+
}
|
|
4039
|
+
const refreshSequence = ++this.materializedRefreshSequence;
|
|
4040
|
+
void firstValueFrom(this.getResourceDiscovery().fetchJson(readUrl, {
|
|
4041
|
+
endpointKey: this.resolvedMetadata.resource?.endpointKey,
|
|
4042
|
+
apiUrlEntry: this.resolvedMetadata.resource?.apiUrlEntry,
|
|
4043
|
+
}))
|
|
4044
|
+
.then((response) => {
|
|
4045
|
+
if (!this.isCurrentMaterializedRefresh(refreshSequence, readUrl)) {
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
const data = this.extractCollectionData(this.unwrapRestData(response));
|
|
4049
|
+
if (!data) {
|
|
4050
|
+
this.error.emit({
|
|
4051
|
+
code: 'CRUD_MATERIALIZED_REFRESH_INVALID_COLLECTION',
|
|
4052
|
+
readUrl,
|
|
4053
|
+
});
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
this.resolvedMetadata = {
|
|
4057
|
+
...this.resolvedMetadata,
|
|
4058
|
+
data,
|
|
4059
|
+
};
|
|
4060
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4061
|
+
this.cdr.markForCheck();
|
|
4062
|
+
})
|
|
4063
|
+
.catch((error) => this.error.emit(error));
|
|
4064
|
+
return true;
|
|
4065
|
+
}
|
|
4066
|
+
isCurrentMaterializedRefresh(refreshSequence, readUrl) {
|
|
4067
|
+
return (refreshSequence === this.materializedRefreshSequence &&
|
|
4068
|
+
this.resolveMaterializedReadUrl() === readUrl &&
|
|
4069
|
+
this.resolveResourcePath(this.resolvedMetadata).length === 0);
|
|
4070
|
+
}
|
|
4071
|
+
resolveMaterializedReadUrl() {
|
|
4072
|
+
const materialization = this.context?.['materialization'];
|
|
4073
|
+
return String(materialization?.['readUrl'] || '').trim();
|
|
4074
|
+
}
|
|
4075
|
+
unwrapRestData(value) {
|
|
4076
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
|
|
4077
|
+
return value.data;
|
|
4078
|
+
}
|
|
4079
|
+
return value;
|
|
4080
|
+
}
|
|
4081
|
+
extractCollectionData(data) {
|
|
4082
|
+
if (Array.isArray(data)) {
|
|
4083
|
+
return data;
|
|
4084
|
+
}
|
|
4085
|
+
if (!data || typeof data !== 'object') {
|
|
4086
|
+
return null;
|
|
4087
|
+
}
|
|
4088
|
+
const record = data;
|
|
4089
|
+
if (Array.isArray(record['content'])) {
|
|
4090
|
+
return record['content'];
|
|
4091
|
+
}
|
|
4092
|
+
if (Array.isArray(record['items'])) {
|
|
4093
|
+
return record['items'];
|
|
4094
|
+
}
|
|
4095
|
+
if (Array.isArray(record['data'])) {
|
|
4096
|
+
return record['data'];
|
|
4097
|
+
}
|
|
4098
|
+
return null;
|
|
4099
|
+
}
|
|
3847
4100
|
emitTableRuntimeConfigSnapshot() {
|
|
3848
4101
|
const snapshot = this.getCurrentTableConfigSnapshot();
|
|
3849
4102
|
if (!snapshot)
|
|
@@ -3901,11 +4154,47 @@ class PraxisCrudComponent {
|
|
|
3901
4154
|
}
|
|
3902
4155
|
}
|
|
3903
4156
|
resolveQueryContext(meta) {
|
|
4157
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
3904
4160
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4161
|
}
|
|
3906
4162
|
resolveFilterCriteria(meta) {
|
|
3907
4163
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4164
|
}
|
|
4165
|
+
extractRowFromEvent(event) {
|
|
4166
|
+
if (!this.isRecord(event)) {
|
|
4167
|
+
return null;
|
|
4168
|
+
}
|
|
4169
|
+
const row = event['row'];
|
|
4170
|
+
return this.isRecord(row) ? row : null;
|
|
4171
|
+
}
|
|
4172
|
+
extractSelectedRowFromEvent(event) {
|
|
4173
|
+
if (!this.isRecord(event)) {
|
|
4174
|
+
return null;
|
|
4175
|
+
}
|
|
4176
|
+
const selection = event['selection'];
|
|
4177
|
+
if (Array.isArray(selection)) {
|
|
4178
|
+
const first = selection[0];
|
|
4179
|
+
return this.isRecord(first) ? first : null;
|
|
4180
|
+
}
|
|
4181
|
+
const row = event['row'];
|
|
4182
|
+
if (this.isRecord(row)) {
|
|
4183
|
+
return row;
|
|
4184
|
+
}
|
|
4185
|
+
return null;
|
|
4186
|
+
}
|
|
4187
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4188
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4189
|
+
return this.isRecord(first) ? first : null;
|
|
4190
|
+
}
|
|
4191
|
+
resolveSelectedRowForAction(action) {
|
|
4192
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4193
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4194
|
+
return undefined;
|
|
4195
|
+
}
|
|
4196
|
+
return this.selectedRow ?? undefined;
|
|
4197
|
+
}
|
|
3909
4198
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4199
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4200
|
if (!this.surfaceService) {
|
|
@@ -3942,7 +4231,7 @@ class PraxisCrudComponent {
|
|
|
3942
4231
|
}
|
|
3943
4232
|
let openPromise;
|
|
3944
4233
|
try {
|
|
3945
|
-
|
|
4234
|
+
const surfaceContext = {
|
|
3946
4235
|
sourceId: this.componentKeyId() || undefined,
|
|
3947
4236
|
payload: { action: normalizedAction, row: row ?? null },
|
|
3948
4237
|
meta: {
|
|
@@ -3959,7 +4248,9 @@ class PraxisCrudComponent {
|
|
|
3959
4248
|
resourcePath,
|
|
3960
4249
|
},
|
|
3961
4250
|
},
|
|
3962
|
-
}
|
|
4251
|
+
};
|
|
4252
|
+
const handledInline = await this.surfaceOutlets.tryActivate(payload, surfaceContext);
|
|
4253
|
+
openPromise = handledInline ? Promise.resolve(undefined) : Promise.resolve(this.surfaceService.open(payload, surfaceContext));
|
|
3963
4254
|
}
|
|
3964
4255
|
catch {
|
|
3965
4256
|
return false;
|
|
@@ -3999,7 +4290,7 @@ class PraxisCrudComponent {
|
|
|
3999
4290
|
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent?.actionConfig);
|
|
4000
4291
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4292
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4293
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4294
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4295
|
return false;
|
|
4005
4296
|
}
|
|
@@ -4222,12 +4513,19 @@ class PraxisCrudComponent {
|
|
|
4222
4513
|
}
|
|
4223
4514
|
return cfg.toolbar;
|
|
4224
4515
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4516
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4517
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4518
|
if (addAction) {
|
|
4228
4519
|
if (hasToolbarAdd) {
|
|
4229
4520
|
const toolbar = ensureToolbar();
|
|
4230
4521
|
toolbar.visible = true;
|
|
4522
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4523
|
+
? {
|
|
4524
|
+
...action,
|
|
4525
|
+
action: 'create',
|
|
4526
|
+
}
|
|
4527
|
+
: action);
|
|
4528
|
+
changed = true;
|
|
4231
4529
|
}
|
|
4232
4530
|
else {
|
|
4233
4531
|
const toolbar = ensureToolbar();
|
|
@@ -4362,6 +4660,28 @@ class PraxisCrudComponent {
|
|
|
4362
4660
|
}
|
|
4363
4661
|
return false;
|
|
4364
4662
|
}
|
|
4663
|
+
normalizeCrudActionName(action) {
|
|
4664
|
+
const raw = String(action || '').trim();
|
|
4665
|
+
const normalized = raw.toLowerCase();
|
|
4666
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4667
|
+
}
|
|
4668
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4669
|
+
if (!action)
|
|
4670
|
+
return false;
|
|
4671
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4672
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4673
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4674
|
+
return true;
|
|
4675
|
+
if (explicitId)
|
|
4676
|
+
return false;
|
|
4677
|
+
const icon = normalize(action.icon);
|
|
4678
|
+
const label = normalize(action.label);
|
|
4679
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4680
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4681
|
+
}
|
|
4682
|
+
isCreateActionAliasName(actionName) {
|
|
4683
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4684
|
+
}
|
|
4365
4685
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4686
|
if (!meta)
|
|
4367
4687
|
return undefined;
|
|
@@ -4642,6 +4962,7 @@ class PraxisCrudComponent {
|
|
|
4642
4962
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
4963
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
4964
|
(toolbarAction)="onAction($event.action)"
|
|
4965
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
4966
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
4967
|
(reset)="onResetPreferences()"
|
|
4647
4968
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4657,7 +4978,7 @@ class PraxisCrudComponent {
|
|
|
4657
4978
|
/>
|
|
4658
4979
|
}
|
|
4659
4980
|
}
|
|
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"] }] });
|
|
4981
|
+
`, 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
4982
|
}
|
|
4662
4983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
4984
|
type: Component,
|
|
@@ -4679,6 +5000,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4679
5000
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
5001
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
5002
|
(toolbarAction)="onAction($event.action)"
|
|
5003
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
5004
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
5005
|
(reset)="onResetPreferences()"
|
|
4684
5006
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4734,6 +5056,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
5056
|
args: [PraxisTable]
|
|
4735
5057
|
}] } });
|
|
4736
5058
|
|
|
5059
|
+
function isCrudDebugEnabled() {
|
|
5060
|
+
try {
|
|
5061
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
5062
|
+
return true;
|
|
5063
|
+
}
|
|
5064
|
+
if (typeof localStorage !== 'undefined') {
|
|
5065
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
catch { }
|
|
5069
|
+
return false;
|
|
5070
|
+
}
|
|
5071
|
+
function debugCrudHost(message, data) {
|
|
5072
|
+
if (!isCrudDebugEnabled()) {
|
|
5073
|
+
return;
|
|
5074
|
+
}
|
|
5075
|
+
try {
|
|
5076
|
+
console.debug(message, data);
|
|
5077
|
+
}
|
|
5078
|
+
catch { }
|
|
5079
|
+
}
|
|
4737
5080
|
class DynamicFormDialogHostComponent {
|
|
4738
5081
|
dialogRef;
|
|
4739
5082
|
data;
|
|
@@ -4742,6 +5085,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
5085
|
configStorage;
|
|
4743
5086
|
formComp;
|
|
4744
5087
|
modal = {};
|
|
5088
|
+
presentation = 'modal';
|
|
4745
5089
|
maximized = false;
|
|
4746
5090
|
initialSize = {};
|
|
4747
5091
|
rememberState = false;
|
|
@@ -4756,7 +5100,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4756
5100
|
submitMethod;
|
|
4757
5101
|
apiEndpointKey;
|
|
4758
5102
|
apiUrlEntry;
|
|
5103
|
+
layoutPolicy;
|
|
5104
|
+
formConfig = {};
|
|
4759
5105
|
formActions;
|
|
5106
|
+
formConfigPersistenceStrategy = 'input-first';
|
|
4760
5107
|
mode = 'create';
|
|
4761
5108
|
backConfig;
|
|
4762
5109
|
idField = 'id';
|
|
@@ -4778,6 +5125,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5125
|
this.crud = crud;
|
|
4779
5126
|
this.configStorage = configStorage;
|
|
4780
5127
|
this.dialogRef.disableClose = true;
|
|
5128
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5129
|
// i18n
|
|
4782
5130
|
this.texts = {
|
|
4783
5131
|
...this.texts,
|
|
@@ -4812,12 +5160,14 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5160
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5161
|
const act = this.data.action?.action;
|
|
4814
5162
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5163
|
+
this.formConfig = this.resolveFormConfig();
|
|
5164
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5165
|
this.formActions = this.resolveFormActions();
|
|
4816
5166
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5167
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5168
|
this.backDefaults = defaults;
|
|
4819
5169
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5170
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5171
|
action: this.data?.action,
|
|
4822
5172
|
resourcePath: this.resourcePath,
|
|
4823
5173
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5207,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5207
|
'apiEndpointKey',
|
|
4858
5208
|
'apiUrlEntry',
|
|
4859
5209
|
'initialValue',
|
|
5210
|
+
'layoutPolicy',
|
|
4860
5211
|
]);
|
|
4861
5212
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5213
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5220,50 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5220
|
}
|
|
4870
5221
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5222
|
}
|
|
5223
|
+
resolveFormConfig() {
|
|
5224
|
+
const config = this.data.metadata?.form;
|
|
5225
|
+
return config && typeof config === 'object' ? config : {};
|
|
5226
|
+
}
|
|
5227
|
+
get dialogTitle() {
|
|
5228
|
+
const actionLabel = this.resolveActionDialogTitle();
|
|
5229
|
+
return actionLabel ||
|
|
5230
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5231
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5232
|
+
this.texts.title;
|
|
5233
|
+
}
|
|
5234
|
+
resolveActionDialogTitle() {
|
|
5235
|
+
const actionLabel = stringOrUndefined(this.data.action?.label);
|
|
5236
|
+
if (!actionLabel) {
|
|
5237
|
+
return undefined;
|
|
5238
|
+
}
|
|
5239
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5240
|
+
const table = this.data.metadata?.table ?? {};
|
|
5241
|
+
const businessTitle = stringOrUndefined(resource.formTitle ??
|
|
5242
|
+
resource.title ??
|
|
5243
|
+
resource.label ??
|
|
5244
|
+
table.title ??
|
|
5245
|
+
table.label);
|
|
5246
|
+
const routeFallbackTitle = deriveModeTitle(this.mode, titleFromResourcePath(this.resourcePath));
|
|
5247
|
+
return businessTitle && routeFallbackTitle && sameTitle(actionLabel, routeFallbackTitle)
|
|
5248
|
+
? undefined
|
|
5249
|
+
: actionLabel;
|
|
5250
|
+
}
|
|
5251
|
+
resolveResourceTitle() {
|
|
5252
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5253
|
+
const table = this.data.metadata?.table ?? {};
|
|
5254
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5255
|
+
resource.title ??
|
|
5256
|
+
resource.label ??
|
|
5257
|
+
table.title ??
|
|
5258
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5259
|
+
}
|
|
4872
5260
|
resolveFormActions() {
|
|
4873
5261
|
if (this.mode === 'view') {
|
|
4874
5262
|
return undefined;
|
|
4875
5263
|
}
|
|
5264
|
+
if (this.formConfig?.actions) {
|
|
5265
|
+
return undefined;
|
|
5266
|
+
}
|
|
4876
5267
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5268
|
if (!submitLabel) {
|
|
4878
5269
|
return undefined;
|
|
@@ -4890,6 +5281,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5281
|
},
|
|
4891
5282
|
};
|
|
4892
5283
|
}
|
|
5284
|
+
resolveLayoutPolicy() {
|
|
5285
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5286
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5287
|
+
this.data.action?.layoutPolicy;
|
|
5288
|
+
if (explicit && typeof explicit === 'object') {
|
|
5289
|
+
return explicit;
|
|
5290
|
+
}
|
|
5291
|
+
return null;
|
|
5292
|
+
}
|
|
4893
5293
|
resolveSubmitLabel() {
|
|
4894
5294
|
const action = this.data.action ?? {};
|
|
4895
5295
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,10 +5362,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5362
|
ref
|
|
4963
5363
|
.afterClosed()
|
|
4964
5364
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5365
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5366
|
}
|
|
4967
5367
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5368
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5369
|
}
|
|
4970
5370
|
}
|
|
4971
5371
|
toggleMaximize(initial = false) {
|
|
@@ -5008,7 +5408,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5408
|
width: saved?.width ?? this.modal.width,
|
|
5009
5409
|
height: saved?.height ?? this.modal.height,
|
|
5010
5410
|
};
|
|
5011
|
-
|
|
5411
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5412
|
initialSize: this.initialSize,
|
|
5013
5413
|
startMaximized: this.modal.startMaximized,
|
|
5014
5414
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5032,10 +5432,10 @@ class DynamicFormDialogHostComponent {
|
|
|
5032
5432
|
}
|
|
5033
5433
|
}
|
|
5034
5434
|
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: `
|
|
5435
|
+
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
5436
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5437
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5438
|
+
{{ dialogTitle }}
|
|
5039
5439
|
</h2>
|
|
5040
5440
|
<span class="spacer"></span>
|
|
5041
5441
|
@if (modal.canMaximize) {
|
|
@@ -5069,11 +5469,14 @@ class DynamicFormDialogHostComponent {
|
|
|
5069
5469
|
[resourceId]="resourceId"
|
|
5070
5470
|
[initialValue]="initialValue"
|
|
5071
5471
|
[mode]="mode"
|
|
5472
|
+
[config]="formConfig"
|
|
5072
5473
|
[schemaUrl]="schemaUrl"
|
|
5073
5474
|
[submitUrl]="submitUrl"
|
|
5074
5475
|
[submitMethod]="submitMethod"
|
|
5075
5476
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5477
|
[apiUrlEntry]="apiUrlEntry"
|
|
5478
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5479
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5480
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5481
|
[backConfig]="backConfig"
|
|
5079
5482
|
[actions]="formActions"
|
|
@@ -5081,7 +5484,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5484
|
(formCancel)="onCancel()"
|
|
5082
5485
|
></praxis-dynamic-form>
|
|
5083
5486
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5487
|
+
`, 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
5488
|
}
|
|
5086
5489
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5490
|
type: Component,
|
|
@@ -5091,13 +5494,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5091
5494
|
MatIconModule,
|
|
5092
5495
|
PraxisIconDirective,
|
|
5093
5496
|
PraxisDynamicForm
|
|
5094
|
-
], providers: [GenericCrudService], host: {
|
|
5497
|
+
], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
|
|
5095
5498
|
class: 'praxis-dialog',
|
|
5096
5499
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5500
|
+
'[attr.data-presentation]': 'presentation',
|
|
5501
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5502
|
}, template: `
|
|
5098
5503
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5504
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5505
|
+
{{ dialogTitle }}
|
|
5101
5506
|
</h2>
|
|
5102
5507
|
<span class="spacer"></span>
|
|
5103
5508
|
@if (modal.canMaximize) {
|
|
@@ -5131,11 +5536,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5131
5536
|
[resourceId]="resourceId"
|
|
5132
5537
|
[initialValue]="initialValue"
|
|
5133
5538
|
[mode]="mode"
|
|
5539
|
+
[config]="formConfig"
|
|
5134
5540
|
[schemaUrl]="schemaUrl"
|
|
5135
5541
|
[submitUrl]="submitUrl"
|
|
5136
5542
|
[submitMethod]="submitMethod"
|
|
5137
5543
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5544
|
[apiUrlEntry]="apiUrlEntry"
|
|
5545
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5546
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5547
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5548
|
[backConfig]="backConfig"
|
|
5141
5549
|
[actions]="formActions"
|
|
@@ -5143,7 +5551,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5551
|
(formCancel)="onCancel()"
|
|
5144
5552
|
></praxis-dynamic-form>
|
|
5145
5553
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5554
|
+
`, 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
5555
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5556
|
type: Inject,
|
|
5149
5557
|
args: [MatDialogRef]
|
|
@@ -5170,6 +5578,17 @@ function stringOrUndefined(value) {
|
|
|
5170
5578
|
const text = String(value ?? '').trim();
|
|
5171
5579
|
return text || undefined;
|
|
5172
5580
|
}
|
|
5581
|
+
function sameTitle(left, right) {
|
|
5582
|
+
return normalizeTitle(left) === normalizeTitle(right);
|
|
5583
|
+
}
|
|
5584
|
+
function normalizeTitle(value) {
|
|
5585
|
+
return value
|
|
5586
|
+
.normalize('NFD')
|
|
5587
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5588
|
+
.replace(/\s+/g, ' ')
|
|
5589
|
+
.trim()
|
|
5590
|
+
.toLocaleLowerCase('pt-BR');
|
|
5591
|
+
}
|
|
5173
5592
|
function deriveCreateSubmitLabel(actionLabel) {
|
|
5174
5593
|
if (!actionLabel) {
|
|
5175
5594
|
return 'Criar';
|
|
@@ -5180,6 +5599,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5599
|
}
|
|
5181
5600
|
return actionLabel;
|
|
5182
5601
|
}
|
|
5602
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5603
|
+
if (!resourceTitle) {
|
|
5604
|
+
return undefined;
|
|
5605
|
+
}
|
|
5606
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5607
|
+
if (mode === 'create') {
|
|
5608
|
+
return `Adicionar ${singular}`;
|
|
5609
|
+
}
|
|
5610
|
+
if (mode === 'edit') {
|
|
5611
|
+
return `Editar ${singular}`;
|
|
5612
|
+
}
|
|
5613
|
+
return `Visualizar ${singular}`;
|
|
5614
|
+
}
|
|
5615
|
+
function titleFromResourcePath(resourcePath) {
|
|
5616
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5617
|
+
if (!lastSegment) {
|
|
5618
|
+
return undefined;
|
|
5619
|
+
}
|
|
5620
|
+
return lastSegment
|
|
5621
|
+
.replace(/[-_]+/g, ' ')
|
|
5622
|
+
.replace(/\s+/g, ' ')
|
|
5623
|
+
.trim()
|
|
5624
|
+
.toLocaleLowerCase('pt-BR');
|
|
5625
|
+
}
|
|
5626
|
+
function singularizePtBrResourceTitle(title) {
|
|
5627
|
+
const normalized = title.trim();
|
|
5628
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5629
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5630
|
+
return 'código de frequência';
|
|
5631
|
+
}
|
|
5632
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5633
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5634
|
+
return [singularFirst, ...rest].join(' ');
|
|
5635
|
+
}
|
|
5636
|
+
function singularizePtBrWord(word) {
|
|
5637
|
+
if (/ões$/i.test(word)) {
|
|
5638
|
+
return word.replace(/ões$/i, 'ão');
|
|
5639
|
+
}
|
|
5640
|
+
if (/ais$/i.test(word)) {
|
|
5641
|
+
return word.replace(/ais$/i, 'al');
|
|
5642
|
+
}
|
|
5643
|
+
if (/eis$/i.test(word)) {
|
|
5644
|
+
return word.replace(/eis$/i, 'el');
|
|
5645
|
+
}
|
|
5646
|
+
if (/res$/i.test(word)) {
|
|
5647
|
+
return word.replace(/es$/i, '');
|
|
5648
|
+
}
|
|
5649
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5650
|
+
return word.replace(/s$/i, '');
|
|
5651
|
+
}
|
|
5652
|
+
return word;
|
|
5653
|
+
}
|
|
5654
|
+
function lowerFirstLetter(value) {
|
|
5655
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5656
|
+
}
|
|
5183
5657
|
|
|
5184
5658
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5659
|
__proto__: null,
|
|
@@ -5325,11 +5799,31 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
5799
|
type: 'PraxisDataQueryContext | null',
|
|
5326
5800
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
5801
|
},
|
|
5802
|
+
{
|
|
5803
|
+
name: 'metadata.resource.schemaPath',
|
|
5804
|
+
type: 'string | null',
|
|
5805
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
5806
|
+
},
|
|
5807
|
+
{
|
|
5808
|
+
name: 'metadata.resource.title',
|
|
5809
|
+
type: 'string | null',
|
|
5810
|
+
description: 'Titulo documental do recurso usado pelo shell CRUD para derivar titulos contextuais de criacao, edicao e visualizacao quando a action nao informa label.',
|
|
5811
|
+
},
|
|
5812
|
+
{
|
|
5813
|
+
name: 'metadata.resource.formTitle',
|
|
5814
|
+
type: 'string | null',
|
|
5815
|
+
description: 'Titulo documental especifico para formularios do recurso, usado antes de metadata.resource.title no shell modal/drawer.',
|
|
5816
|
+
},
|
|
5328
5817
|
{
|
|
5329
5818
|
name: 'metadata.filterCriteria',
|
|
5330
5819
|
type: 'Record<string, unknown> | null',
|
|
5331
5820
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
5821
|
},
|
|
5822
|
+
{
|
|
5823
|
+
name: 'metadata.form',
|
|
5824
|
+
type: 'FormConfig',
|
|
5825
|
+
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.',
|
|
5826
|
+
},
|
|
5333
5827
|
{
|
|
5334
5828
|
name: 'metadata.actions[].form',
|
|
5335
5829
|
type: 'CrudActionFormContract',
|
|
@@ -5699,6 +6193,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6193
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6194
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6195
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6196
|
+
{ 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
6197
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6198
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6199
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6231,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6231
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6232
|
apiEndpointKey: { type: 'string' },
|
|
5738
6233
|
initialValue: { type: 'object' },
|
|
6234
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6235
|
},
|
|
5740
6236
|
},
|
|
5741
6237
|
params: {
|
|
@@ -5854,14 +6350,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5854
6350
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6351
|
],
|
|
5856
6352
|
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.' },
|
|
6353
|
+
{ 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
6354
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6355
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6356
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6357
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6358
|
{ 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.' },
|
|
6359
|
+
{ 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.' },
|
|
6360
|
+
{ 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
6361
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6362
|
{ 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
6363
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6373,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6373
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6374
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6375
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6376
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6377
|
inputSchema: resourceBindSchema,
|
|
5882
6378
|
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.',
|
|
6379
|
+
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
6380
|
} }],
|
|
5885
6381
|
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'],
|
|
6382
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6383
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6384
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6385
|
},
|
|
@@ -6017,14 +6513,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6513
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6514
|
inputSchema: dialogHostSchema,
|
|
6019
6515
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6516
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6517
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6518
|
identityKeys: ['crudId'],
|
|
6023
6519
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6520
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
|
|
6521
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6522
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6523
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6524
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6525
|
submissionImpact: 'config-only',
|
|
6030
6526
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6596,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6596
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6597
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6598
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6599
|
+
{ 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
6600
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6601
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6602
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|