@praxisui/crud 9.0.0-beta.5 → 9.0.0-beta.50
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 +10721 -0
- package/fesm2022/praxisui-crud.mjs +425 -70
- package/package.json +12 -8
- package/types/praxisui-crud.d.ts +27 -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;
|
|
@@ -564,6 +691,7 @@ function normalizeCrudAction(action) {
|
|
|
564
691
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
692
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
693
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
694
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
695
|
}
|
|
568
696
|
: undefined,
|
|
569
697
|
});
|
|
@@ -3527,6 +3655,7 @@ class PraxisCrudComponent {
|
|
|
3527
3655
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3656
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3657
|
currentAuthoringDocument;
|
|
3658
|
+
selectedRow = null;
|
|
3530
3659
|
getResourceDiscovery() {
|
|
3531
3660
|
const assigned = this.resourceDiscovery;
|
|
3532
3661
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,11 +3682,27 @@ class PraxisCrudComponent {
|
|
|
3553
3682
|
catch { }
|
|
3554
3683
|
}
|
|
3555
3684
|
onTableRowClick(event) {
|
|
3685
|
+
const row = this.extractRowFromEvent(event);
|
|
3686
|
+
if (row) {
|
|
3687
|
+
this.selectedRow = row;
|
|
3688
|
+
}
|
|
3556
3689
|
this.rowClick.emit(event);
|
|
3557
3690
|
}
|
|
3558
3691
|
onTableSelectionChange(event) {
|
|
3692
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3693
|
this.selectionChange.emit(event);
|
|
3560
3694
|
}
|
|
3695
|
+
async onBulkAction(event) {
|
|
3696
|
+
const action = String(event?.action || '').trim();
|
|
3697
|
+
if (!action) {
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3701
|
+
if (row) {
|
|
3702
|
+
this.selectedRow = row;
|
|
3703
|
+
}
|
|
3704
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3705
|
+
}
|
|
3561
3706
|
ngOnChanges(changes) {
|
|
3562
3707
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3708
|
return;
|
|
@@ -3595,33 +3740,41 @@ class PraxisCrudComponent {
|
|
|
3595
3740
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3741
|
try {
|
|
3597
3742
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3743
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3744
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3745
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3746
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3747
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3748
|
+
}
|
|
3599
3749
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3750
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3751
|
if (ctxAction) {
|
|
3602
3752
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3753
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3754
|
openMode: ctxAction.openMode,
|
|
3605
3755
|
formId: ctxAction.formId,
|
|
3606
3756
|
route: ctxAction.route,
|
|
3607
3757
|
};
|
|
3608
3758
|
}
|
|
3609
3759
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3760
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3761
|
}
|
|
3612
3762
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3763
|
+
const effectiveAction = {
|
|
3764
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3765
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3766
|
+
};
|
|
3614
3767
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3768
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3769
|
if (openedByDiscovery) {
|
|
3617
3770
|
return;
|
|
3618
3771
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3772
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3773
|
if (handledByWorkflowAction) {
|
|
3621
3774
|
return;
|
|
3622
3775
|
}
|
|
3623
3776
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3777
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3778
|
if (handledByDelete) {
|
|
3626
3779
|
return;
|
|
3627
3780
|
}
|
|
@@ -3632,7 +3785,7 @@ class PraxisCrudComponent {
|
|
|
3632
3785
|
drawerCloseEmitted = true;
|
|
3633
3786
|
this.afterClose.emit();
|
|
3634
3787
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3788
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3789
|
onClose: () => emitDrawerClose(),
|
|
3637
3790
|
onResult: (result) => {
|
|
3638
3791
|
emitDrawerClose();
|
|
@@ -3653,9 +3806,12 @@ class PraxisCrudComponent {
|
|
|
3653
3806
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3807
|
? this.collectionCapabilities
|
|
3655
3808
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3809
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3657
3810
|
});
|
|
3658
3811
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3812
|
+
if (mode === 'drawer') {
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3659
3815
|
if (!ref) {
|
|
3660
3816
|
return;
|
|
3661
3817
|
}
|
|
@@ -3901,11 +4057,47 @@ class PraxisCrudComponent {
|
|
|
3901
4057
|
}
|
|
3902
4058
|
}
|
|
3903
4059
|
resolveQueryContext(meta) {
|
|
4060
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4061
|
+
return null;
|
|
4062
|
+
}
|
|
3904
4063
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4064
|
}
|
|
3906
4065
|
resolveFilterCriteria(meta) {
|
|
3907
4066
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4067
|
}
|
|
4068
|
+
extractRowFromEvent(event) {
|
|
4069
|
+
if (!this.isRecord(event)) {
|
|
4070
|
+
return null;
|
|
4071
|
+
}
|
|
4072
|
+
const row = event['row'];
|
|
4073
|
+
return this.isRecord(row) ? row : null;
|
|
4074
|
+
}
|
|
4075
|
+
extractSelectedRowFromEvent(event) {
|
|
4076
|
+
if (!this.isRecord(event)) {
|
|
4077
|
+
return null;
|
|
4078
|
+
}
|
|
4079
|
+
const selection = event['selection'];
|
|
4080
|
+
if (Array.isArray(selection)) {
|
|
4081
|
+
const first = selection[0];
|
|
4082
|
+
return this.isRecord(first) ? first : null;
|
|
4083
|
+
}
|
|
4084
|
+
const row = event['row'];
|
|
4085
|
+
if (this.isRecord(row)) {
|
|
4086
|
+
return row;
|
|
4087
|
+
}
|
|
4088
|
+
return null;
|
|
4089
|
+
}
|
|
4090
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4091
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4092
|
+
return this.isRecord(first) ? first : null;
|
|
4093
|
+
}
|
|
4094
|
+
resolveSelectedRowForAction(action) {
|
|
4095
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4096
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4097
|
+
return undefined;
|
|
4098
|
+
}
|
|
4099
|
+
return this.selectedRow ?? undefined;
|
|
4100
|
+
}
|
|
3909
4101
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4102
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4103
|
if (!this.surfaceService) {
|
|
@@ -3999,7 +4191,7 @@ class PraxisCrudComponent {
|
|
|
3999
4191
|
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent?.actionConfig);
|
|
4000
4192
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4193
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4194
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4195
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4196
|
return false;
|
|
4005
4197
|
}
|
|
@@ -4222,12 +4414,19 @@ class PraxisCrudComponent {
|
|
|
4222
4414
|
}
|
|
4223
4415
|
return cfg.toolbar;
|
|
4224
4416
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4417
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4418
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4419
|
if (addAction) {
|
|
4228
4420
|
if (hasToolbarAdd) {
|
|
4229
4421
|
const toolbar = ensureToolbar();
|
|
4230
4422
|
toolbar.visible = true;
|
|
4423
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4424
|
+
? {
|
|
4425
|
+
...action,
|
|
4426
|
+
action: 'create',
|
|
4427
|
+
}
|
|
4428
|
+
: action);
|
|
4429
|
+
changed = true;
|
|
4231
4430
|
}
|
|
4232
4431
|
else {
|
|
4233
4432
|
const toolbar = ensureToolbar();
|
|
@@ -4362,6 +4561,28 @@ class PraxisCrudComponent {
|
|
|
4362
4561
|
}
|
|
4363
4562
|
return false;
|
|
4364
4563
|
}
|
|
4564
|
+
normalizeCrudActionName(action) {
|
|
4565
|
+
const raw = String(action || '').trim();
|
|
4566
|
+
const normalized = raw.toLowerCase();
|
|
4567
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4568
|
+
}
|
|
4569
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4570
|
+
if (!action)
|
|
4571
|
+
return false;
|
|
4572
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4573
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4574
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4575
|
+
return true;
|
|
4576
|
+
if (explicitId)
|
|
4577
|
+
return false;
|
|
4578
|
+
const icon = normalize(action.icon);
|
|
4579
|
+
const label = normalize(action.label);
|
|
4580
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4581
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4582
|
+
}
|
|
4583
|
+
isCreateActionAliasName(actionName) {
|
|
4584
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4585
|
+
}
|
|
4365
4586
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4587
|
if (!meta)
|
|
4367
4588
|
return undefined;
|
|
@@ -4642,6 +4863,7 @@ class PraxisCrudComponent {
|
|
|
4642
4863
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
4864
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
4865
|
(toolbarAction)="onAction($event.action)"
|
|
4866
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
4867
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
4868
|
(reset)="onResetPreferences()"
|
|
4647
4869
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4657,7 +4879,7 @@ class PraxisCrudComponent {
|
|
|
4657
4879
|
/>
|
|
4658
4880
|
}
|
|
4659
4881
|
}
|
|
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"] }] });
|
|
4882
|
+
`, 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
4883
|
}
|
|
4662
4884
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
4885
|
type: Component,
|
|
@@ -4679,6 +4901,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4679
4901
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
4902
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
4903
|
(toolbarAction)="onAction($event.action)"
|
|
4904
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
4905
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
4906
|
(reset)="onResetPreferences()"
|
|
4684
4907
|
(metadataChange)="onTableMetadataChange()"
|
|
@@ -4734,6 +4957,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
4957
|
args: [PraxisTable]
|
|
4735
4958
|
}] } });
|
|
4736
4959
|
|
|
4960
|
+
function isCrudDebugEnabled() {
|
|
4961
|
+
try {
|
|
4962
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
4963
|
+
return true;
|
|
4964
|
+
}
|
|
4965
|
+
if (typeof localStorage !== 'undefined') {
|
|
4966
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
catch { }
|
|
4970
|
+
return false;
|
|
4971
|
+
}
|
|
4972
|
+
function debugCrudHost(message, data) {
|
|
4973
|
+
if (!isCrudDebugEnabled()) {
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
try {
|
|
4977
|
+
console.debug(message, data);
|
|
4978
|
+
}
|
|
4979
|
+
catch { }
|
|
4980
|
+
}
|
|
4737
4981
|
class DynamicFormDialogHostComponent {
|
|
4738
4982
|
dialogRef;
|
|
4739
4983
|
data;
|
|
@@ -4742,6 +4986,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
4986
|
configStorage;
|
|
4743
4987
|
formComp;
|
|
4744
4988
|
modal = {};
|
|
4989
|
+
presentation = 'modal';
|
|
4745
4990
|
maximized = false;
|
|
4746
4991
|
initialSize = {};
|
|
4747
4992
|
rememberState = false;
|
|
@@ -4756,6 +5001,8 @@ class DynamicFormDialogHostComponent {
|
|
|
4756
5001
|
submitMethod;
|
|
4757
5002
|
apiEndpointKey;
|
|
4758
5003
|
apiUrlEntry;
|
|
5004
|
+
layoutPolicy;
|
|
5005
|
+
formConfig = {};
|
|
4759
5006
|
formActions;
|
|
4760
5007
|
mode = 'create';
|
|
4761
5008
|
backConfig;
|
|
@@ -4778,6 +5025,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5025
|
this.crud = crud;
|
|
4779
5026
|
this.configStorage = configStorage;
|
|
4780
5027
|
this.dialogRef.disableClose = true;
|
|
5028
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5029
|
// i18n
|
|
4782
5030
|
this.texts = {
|
|
4783
5031
|
...this.texts,
|
|
@@ -4812,12 +5060,14 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5060
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5061
|
const act = this.data.action?.action;
|
|
4814
5062
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5063
|
+
this.formConfig = this.resolveFormConfig();
|
|
5064
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5065
|
this.formActions = this.resolveFormActions();
|
|
4816
5066
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5067
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5068
|
this.backDefaults = defaults;
|
|
4819
5069
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5070
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5071
|
action: this.data?.action,
|
|
4822
5072
|
resourcePath: this.resourcePath,
|
|
4823
5073
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5107,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5107
|
'apiEndpointKey',
|
|
4858
5108
|
'apiUrlEntry',
|
|
4859
5109
|
'initialValue',
|
|
5110
|
+
'layoutPolicy',
|
|
4860
5111
|
]);
|
|
4861
5112
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5113
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5120,32 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5120
|
}
|
|
4870
5121
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5122
|
}
|
|
5123
|
+
resolveFormConfig() {
|
|
5124
|
+
const config = this.data.metadata?.form;
|
|
5125
|
+
return config && typeof config === 'object' ? config : {};
|
|
5126
|
+
}
|
|
5127
|
+
get dialogTitle() {
|
|
5128
|
+
return stringOrUndefined(this.data.action?.label) ||
|
|
5129
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5130
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5131
|
+
this.texts.title;
|
|
5132
|
+
}
|
|
5133
|
+
resolveResourceTitle() {
|
|
5134
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5135
|
+
const table = this.data.metadata?.table ?? {};
|
|
5136
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5137
|
+
resource.title ??
|
|
5138
|
+
resource.label ??
|
|
5139
|
+
table.title ??
|
|
5140
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5141
|
+
}
|
|
4872
5142
|
resolveFormActions() {
|
|
4873
5143
|
if (this.mode === 'view') {
|
|
4874
5144
|
return undefined;
|
|
4875
5145
|
}
|
|
5146
|
+
if (this.formConfig?.actions) {
|
|
5147
|
+
return undefined;
|
|
5148
|
+
}
|
|
4876
5149
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5150
|
if (!submitLabel) {
|
|
4878
5151
|
return undefined;
|
|
@@ -4890,6 +5163,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5163
|
},
|
|
4891
5164
|
};
|
|
4892
5165
|
}
|
|
5166
|
+
resolveLayoutPolicy() {
|
|
5167
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5168
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5169
|
+
this.data.action?.layoutPolicy;
|
|
5170
|
+
if (explicit && typeof explicit === 'object') {
|
|
5171
|
+
return explicit;
|
|
5172
|
+
}
|
|
5173
|
+
return null;
|
|
5174
|
+
}
|
|
4893
5175
|
resolveSubmitLabel() {
|
|
4894
5176
|
const action = this.data.action ?? {};
|
|
4895
5177
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,10 +5244,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5244
|
ref
|
|
4963
5245
|
.afterClosed()
|
|
4964
5246
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5247
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5248
|
}
|
|
4967
5249
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5250
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5251
|
}
|
|
4970
5252
|
}
|
|
4971
5253
|
toggleMaximize(initial = false) {
|
|
@@ -5008,7 +5290,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5290
|
width: saved?.width ?? this.modal.width,
|
|
5009
5291
|
height: saved?.height ?? this.modal.height,
|
|
5010
5292
|
};
|
|
5011
|
-
|
|
5293
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5294
|
initialSize: this.initialSize,
|
|
5013
5295
|
startMaximized: this.modal.startMaximized,
|
|
5014
5296
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5032,10 +5314,10 @@ class DynamicFormDialogHostComponent {
|
|
|
5032
5314
|
}
|
|
5033
5315
|
}
|
|
5034
5316
|
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: `
|
|
5317
|
+
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
5318
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5319
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5320
|
+
{{ dialogTitle }}
|
|
5039
5321
|
</h2>
|
|
5040
5322
|
<span class="spacer"></span>
|
|
5041
5323
|
@if (modal.canMaximize) {
|
|
@@ -5069,11 +5351,13 @@ class DynamicFormDialogHostComponent {
|
|
|
5069
5351
|
[resourceId]="resourceId"
|
|
5070
5352
|
[initialValue]="initialValue"
|
|
5071
5353
|
[mode]="mode"
|
|
5354
|
+
[config]="formConfig"
|
|
5072
5355
|
[schemaUrl]="schemaUrl"
|
|
5073
5356
|
[submitUrl]="submitUrl"
|
|
5074
5357
|
[submitMethod]="submitMethod"
|
|
5075
5358
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5359
|
[apiUrlEntry]="apiUrlEntry"
|
|
5360
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5361
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5362
|
[backConfig]="backConfig"
|
|
5079
5363
|
[actions]="formActions"
|
|
@@ -5081,7 +5365,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5365
|
(formCancel)="onCancel()"
|
|
5082
5366
|
></praxis-dynamic-form>
|
|
5083
5367
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5368
|
+
`, 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
5369
|
}
|
|
5086
5370
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5371
|
type: Component,
|
|
@@ -5091,13 +5375,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5091
5375
|
MatIconModule,
|
|
5092
5376
|
PraxisIconDirective,
|
|
5093
5377
|
PraxisDynamicForm
|
|
5094
|
-
], providers: [GenericCrudService], host: {
|
|
5378
|
+
], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
|
|
5095
5379
|
class: 'praxis-dialog',
|
|
5096
5380
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5381
|
+
'[attr.data-presentation]': 'presentation',
|
|
5382
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5383
|
}, template: `
|
|
5098
5384
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5385
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5386
|
+
{{ dialogTitle }}
|
|
5101
5387
|
</h2>
|
|
5102
5388
|
<span class="spacer"></span>
|
|
5103
5389
|
@if (modal.canMaximize) {
|
|
@@ -5131,11 +5417,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5131
5417
|
[resourceId]="resourceId"
|
|
5132
5418
|
[initialValue]="initialValue"
|
|
5133
5419
|
[mode]="mode"
|
|
5420
|
+
[config]="formConfig"
|
|
5134
5421
|
[schemaUrl]="schemaUrl"
|
|
5135
5422
|
[submitUrl]="submitUrl"
|
|
5136
5423
|
[submitMethod]="submitMethod"
|
|
5137
5424
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5425
|
[apiUrlEntry]="apiUrlEntry"
|
|
5426
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5427
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5428
|
[backConfig]="backConfig"
|
|
5141
5429
|
[actions]="formActions"
|
|
@@ -5143,7 +5431,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5431
|
(formCancel)="onCancel()"
|
|
5144
5432
|
></praxis-dynamic-form>
|
|
5145
5433
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5434
|
+
`, 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
5435
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5436
|
type: Inject,
|
|
5149
5437
|
args: [MatDialogRef]
|
|
@@ -5180,6 +5468,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5468
|
}
|
|
5181
5469
|
return actionLabel;
|
|
5182
5470
|
}
|
|
5471
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5472
|
+
if (!resourceTitle) {
|
|
5473
|
+
return undefined;
|
|
5474
|
+
}
|
|
5475
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5476
|
+
if (mode === 'create') {
|
|
5477
|
+
return `Adicionar ${singular}`;
|
|
5478
|
+
}
|
|
5479
|
+
if (mode === 'edit') {
|
|
5480
|
+
return `Editar ${singular}`;
|
|
5481
|
+
}
|
|
5482
|
+
return `Visualizar ${singular}`;
|
|
5483
|
+
}
|
|
5484
|
+
function titleFromResourcePath(resourcePath) {
|
|
5485
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5486
|
+
if (!lastSegment) {
|
|
5487
|
+
return undefined;
|
|
5488
|
+
}
|
|
5489
|
+
return lastSegment
|
|
5490
|
+
.replace(/[-_]+/g, ' ')
|
|
5491
|
+
.replace(/\s+/g, ' ')
|
|
5492
|
+
.trim()
|
|
5493
|
+
.toLocaleLowerCase('pt-BR');
|
|
5494
|
+
}
|
|
5495
|
+
function singularizePtBrResourceTitle(title) {
|
|
5496
|
+
const normalized = title.trim();
|
|
5497
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5498
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5499
|
+
return 'código de frequência';
|
|
5500
|
+
}
|
|
5501
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5502
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5503
|
+
return [singularFirst, ...rest].join(' ');
|
|
5504
|
+
}
|
|
5505
|
+
function singularizePtBrWord(word) {
|
|
5506
|
+
if (/ões$/i.test(word)) {
|
|
5507
|
+
return word.replace(/ões$/i, 'ão');
|
|
5508
|
+
}
|
|
5509
|
+
if (/ais$/i.test(word)) {
|
|
5510
|
+
return word.replace(/ais$/i, 'al');
|
|
5511
|
+
}
|
|
5512
|
+
if (/eis$/i.test(word)) {
|
|
5513
|
+
return word.replace(/eis$/i, 'el');
|
|
5514
|
+
}
|
|
5515
|
+
if (/res$/i.test(word)) {
|
|
5516
|
+
return word.replace(/es$/i, '');
|
|
5517
|
+
}
|
|
5518
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5519
|
+
return word.replace(/s$/i, '');
|
|
5520
|
+
}
|
|
5521
|
+
return word;
|
|
5522
|
+
}
|
|
5523
|
+
function lowerFirstLetter(value) {
|
|
5524
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5525
|
+
}
|
|
5183
5526
|
|
|
5184
5527
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5528
|
__proto__: null,
|
|
@@ -5325,11 +5668,21 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
5668
|
type: 'PraxisDataQueryContext | null',
|
|
5326
5669
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
5670
|
},
|
|
5671
|
+
{
|
|
5672
|
+
name: 'metadata.resource.schemaPath',
|
|
5673
|
+
type: 'string | null',
|
|
5674
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
5675
|
+
},
|
|
5328
5676
|
{
|
|
5329
5677
|
name: 'metadata.filterCriteria',
|
|
5330
5678
|
type: 'Record<string, unknown> | null',
|
|
5331
5679
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
5680
|
},
|
|
5681
|
+
{
|
|
5682
|
+
name: 'metadata.form',
|
|
5683
|
+
type: 'FormConfig',
|
|
5684
|
+
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.',
|
|
5685
|
+
},
|
|
5333
5686
|
{
|
|
5334
5687
|
name: 'metadata.actions[].form',
|
|
5335
5688
|
type: 'CrudActionFormContract',
|
|
@@ -5699,6 +6052,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6052
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6053
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6054
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6055
|
+
{ 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
6056
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6057
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6058
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6090,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6090
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6091
|
apiEndpointKey: { type: 'string' },
|
|
5738
6092
|
initialValue: { type: 'object' },
|
|
6093
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6094
|
},
|
|
5740
6095
|
},
|
|
5741
6096
|
params: {
|
|
@@ -5854,14 +6209,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5854
6209
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6210
|
],
|
|
5856
6211
|
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.' },
|
|
6212
|
+
{ 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
6213
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6214
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6215
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6216
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6217
|
{ 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.' },
|
|
6218
|
+
{ 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.' },
|
|
6219
|
+
{ 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
6220
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6221
|
{ 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
6222
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6232,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6232
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6233
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6234
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6235
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6236
|
inputSchema: resourceBindSchema,
|
|
5882
6237
|
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.',
|
|
6238
|
+
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
6239
|
} }],
|
|
5885
6240
|
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'],
|
|
6241
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6242
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6243
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6244
|
},
|
|
@@ -6017,14 +6372,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6372
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6373
|
inputSchema: dialogHostSchema,
|
|
6019
6374
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6375
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6376
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6377
|
identityKeys: ['crudId'],
|
|
6023
6378
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6379
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
|
|
6380
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6381
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6382
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6383
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6384
|
submissionImpact: 'config-only',
|
|
6030
6385
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6455,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6455
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6456
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6457
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6458
|
+
{ 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
6459
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6460
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6461
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|