@praxisui/crud 9.0.0-beta.8 → 9.0.0-beta.81
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 +64 -9
- package/ai/component-registry.json +11422 -0
- package/fesm2022/praxisui-crud.mjs +868 -86
- package/package.json +12 -8
- package/types/praxisui-crud.d.ts +70 -3
|
@@ -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, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisResourceIdentityComponent, 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,29 @@ 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
|
-
|
|
137
|
-
|
|
158
|
+
const drawerMode = mode === 'drawer';
|
|
159
|
+
const dialogPosition = drawerMode
|
|
160
|
+
? (modalCfg.position ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.position)
|
|
161
|
+
: modalCfg.position;
|
|
162
|
+
const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
|
|
138
163
|
// Backdrop style presets
|
|
139
164
|
const backdropClasses = [];
|
|
140
165
|
const style = modalCfg.backdropStyle;
|
|
@@ -147,20 +172,58 @@ class CrudLauncherService {
|
|
|
147
172
|
else if (style === 'transparent') {
|
|
148
173
|
backdropClasses.push('pfx-transparent-backdrop');
|
|
149
174
|
}
|
|
150
|
-
|
|
151
|
-
backdropClasses.push(modalCfg.backdropClass);
|
|
152
|
-
}
|
|
175
|
+
const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
|
|
153
176
|
const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
|
|
154
177
|
...modalCfg,
|
|
155
178
|
panelClass: panelClasses,
|
|
156
|
-
backdropClass:
|
|
179
|
+
backdropClass: mergedBackdropClasses,
|
|
157
180
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
158
181
|
restoreFocus: modalCfg.restoreFocus ?? true,
|
|
159
|
-
minWidth:
|
|
160
|
-
|
|
182
|
+
minWidth: drawerMode
|
|
183
|
+
? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
|
|
184
|
+
: '360px',
|
|
185
|
+
width: drawerMode
|
|
186
|
+
? (modalCfg.width ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.width)
|
|
187
|
+
: modalCfg.width,
|
|
188
|
+
height: drawerMode
|
|
189
|
+
? (modalCfg.height ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.height)
|
|
190
|
+
: modalCfg.height,
|
|
191
|
+
maxWidth: drawerMode
|
|
192
|
+
? (modalCfg.maxWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxWidth)
|
|
193
|
+
: '95vw',
|
|
194
|
+
maxHeight: drawerMode
|
|
195
|
+
? (modalCfg.maxHeight ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxHeight)
|
|
196
|
+
: modalCfg.maxHeight,
|
|
197
|
+
position: dialogPosition,
|
|
161
198
|
ariaLabelledBy: 'crudDialogTitle',
|
|
162
|
-
data: {
|
|
199
|
+
data: {
|
|
200
|
+
action: actionForLaunch,
|
|
201
|
+
row,
|
|
202
|
+
metadata: merged.metadata,
|
|
203
|
+
inputs,
|
|
204
|
+
presentation: drawerMode ? 'drawer' : 'modal',
|
|
205
|
+
dialogPosition,
|
|
206
|
+
resourceIdentity: drawerMode && actionForLaunch.action === 'edit'
|
|
207
|
+
? runtime?.resourceIdentity ?? null
|
|
208
|
+
: null,
|
|
209
|
+
},
|
|
163
210
|
});
|
|
211
|
+
if (drawerMode) {
|
|
212
|
+
ref
|
|
213
|
+
.afterClosed()
|
|
214
|
+
.pipe(take(1))
|
|
215
|
+
.subscribe((closedValue) => {
|
|
216
|
+
const result = toCrudDrawerResult(closedValue);
|
|
217
|
+
drawerCallbacks?.onClose?.();
|
|
218
|
+
if (result?.type) {
|
|
219
|
+
drawerCallbacks?.onResult?.(result);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
drawerCallbacks?.onResult?.({ type: 'close' });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return { mode, ref };
|
|
226
|
+
}
|
|
164
227
|
return { mode, ref };
|
|
165
228
|
}
|
|
166
229
|
resolveOpenMode(action, metadata) {
|
|
@@ -168,12 +231,17 @@ class CrudLauncherService {
|
|
|
168
231
|
const local = action.openMode ?? metadata.defaults?.openMode;
|
|
169
232
|
if (local)
|
|
170
233
|
return local;
|
|
171
|
-
// Global fallback (action-specific, then general)
|
|
234
|
+
// Global fallback (action-specific, then general). A canonical CRUD
|
|
235
|
+
// operation without an explicit route can still be materialized through
|
|
236
|
+
// the inferred form contract, so it must not fall through to a route that
|
|
237
|
+
// does not exist.
|
|
172
238
|
try {
|
|
173
239
|
const globalCrud = this.global.getCrud();
|
|
174
240
|
const actionName = action.action;
|
|
175
241
|
const globalMode = (actionName && globalCrud?.actionDefaults?.[actionName]?.openMode) ?? globalCrud?.defaults?.openMode;
|
|
176
|
-
let resolved = globalMode ??
|
|
242
|
+
let resolved = globalMode ?? (this.isCanonicalCrudAction(action.action) && !action.route
|
|
243
|
+
? 'drawer'
|
|
244
|
+
: 'route');
|
|
177
245
|
// Safety: if modal/drawer but there is no formId and the action cannot be inferred, degrade to route.
|
|
178
246
|
if ((resolved === 'modal' || resolved === 'drawer') &&
|
|
179
247
|
!action.formId &&
|
|
@@ -248,6 +316,9 @@ class CrudLauncherService {
|
|
|
248
316
|
if (resolved.apiUrlEntry != null) {
|
|
249
317
|
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
250
318
|
}
|
|
319
|
+
if (resolved.layoutPolicy != null) {
|
|
320
|
+
inputs['layoutPolicy'] = resolved.layoutPolicy;
|
|
321
|
+
}
|
|
251
322
|
if (action.form?.initialValue != null) {
|
|
252
323
|
inputs['initialValue'] = action.form.initialValue;
|
|
253
324
|
}
|
|
@@ -257,6 +328,7 @@ class CrudLauncherService {
|
|
|
257
328
|
return inputs;
|
|
258
329
|
}
|
|
259
330
|
resolveActionForLaunch(action, metadata) {
|
|
331
|
+
action = this.normalizeActionForLaunch(action);
|
|
260
332
|
if (action.formId) {
|
|
261
333
|
return action;
|
|
262
334
|
}
|
|
@@ -264,29 +336,61 @@ class CrudLauncherService {
|
|
|
264
336
|
return action;
|
|
265
337
|
}
|
|
266
338
|
const formId = this.buildInferredFormId(action, metadata);
|
|
267
|
-
return formId
|
|
339
|
+
return formId
|
|
340
|
+
? {
|
|
341
|
+
...action,
|
|
342
|
+
formId,
|
|
343
|
+
form: {
|
|
344
|
+
...(action.form || {}),
|
|
345
|
+
layoutPolicy: action.form?.layoutPolicy ?? this.defaultInferredLayoutPolicy(action),
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
: action;
|
|
349
|
+
}
|
|
350
|
+
resolveSubmitUrlTemplate(submitUrl, row, metadata) {
|
|
351
|
+
const template = String(submitUrl || '').trim();
|
|
352
|
+
if (!template || !template.includes('{')) {
|
|
353
|
+
return template;
|
|
354
|
+
}
|
|
355
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
356
|
+
const rowId = row?.[idField];
|
|
357
|
+
if (rowId == null) {
|
|
358
|
+
return template;
|
|
359
|
+
}
|
|
360
|
+
const encodedId = encodeURIComponent(String(rowId));
|
|
361
|
+
const idFieldPattern = new RegExp(`\\{${escapeRegExp(idField)}\\}`, 'g');
|
|
362
|
+
return template
|
|
363
|
+
.replace(idFieldPattern, encodedId)
|
|
364
|
+
.replace(/\{id\}/g, encodedId)
|
|
365
|
+
.replace(/\{resourceId\}/g, encodedId);
|
|
268
366
|
}
|
|
269
367
|
resolveActionFormContract(action, row, metadata, runtime) {
|
|
270
368
|
if (this.isExplicitCrudAction(action)) {
|
|
271
369
|
return {
|
|
272
370
|
schemaUrl: action.form?.schemaUrl ?? null,
|
|
273
|
-
submitUrl: action.form?.submitUrl
|
|
371
|
+
submitUrl: action.form?.submitUrl
|
|
372
|
+
? this.resolveSubmitUrlTemplate(action.form.submitUrl, row, metadata)
|
|
373
|
+
: null,
|
|
274
374
|
submitMethod: action.form?.submitMethod ?? null,
|
|
275
375
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
276
376
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
377
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
277
378
|
};
|
|
278
379
|
}
|
|
279
380
|
if (!this.isCanonicalCrudAction(action.action)) {
|
|
280
381
|
return {
|
|
281
382
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
282
383
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
384
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
283
385
|
};
|
|
284
386
|
}
|
|
285
387
|
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
388
|
+
const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
|
|
286
389
|
if (!resourcePath) {
|
|
287
390
|
return {
|
|
288
391
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
289
392
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
393
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
290
394
|
};
|
|
291
395
|
}
|
|
292
396
|
const idField = String(metadata.resource?.idField ?? 'id');
|
|
@@ -294,6 +398,7 @@ class CrudLauncherService {
|
|
|
294
398
|
const resolved = this.operationResolver.resolve({
|
|
295
399
|
operation: action.action,
|
|
296
400
|
resourcePath,
|
|
401
|
+
schemaResourcePath,
|
|
297
402
|
resourceId: resourceId ?? null,
|
|
298
403
|
capabilities: runtime?.capabilities ?? null,
|
|
299
404
|
links: runtime?.links ?? null,
|
|
@@ -301,14 +406,15 @@ class CrudLauncherService {
|
|
|
301
406
|
endpointKey: action.form?.apiEndpointKey ??
|
|
302
407
|
metadata.resource?.endpointKey ??
|
|
303
408
|
undefined,
|
|
304
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
409
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
305
410
|
});
|
|
306
411
|
return {
|
|
307
412
|
schemaUrl: resolved?.schemaUrl ?? null,
|
|
308
413
|
submitUrl: resolved?.submitUrl ?? null,
|
|
309
414
|
submitMethod: resolved?.submitMethod ?? null,
|
|
310
415
|
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
311
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
416
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
417
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
312
418
|
};
|
|
313
419
|
}
|
|
314
420
|
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
@@ -327,9 +433,20 @@ class CrudLauncherService {
|
|
|
327
433
|
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
328
434
|
}
|
|
329
435
|
isCanonicalCrudAction(actionName) {
|
|
330
|
-
const normalized =
|
|
436
|
+
const normalized = this.normalizeCrudOperationName(actionName);
|
|
331
437
|
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
332
438
|
}
|
|
439
|
+
normalizeActionForLaunch(action) {
|
|
440
|
+
const normalized = this.normalizeCrudOperationName(action.action);
|
|
441
|
+
return normalized === action.action ? action : { ...action, action: normalized };
|
|
442
|
+
}
|
|
443
|
+
normalizeCrudOperationName(actionName) {
|
|
444
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
445
|
+
if (['add', 'novo', 'new', 'incluir', 'inserir'].includes(normalized)) {
|
|
446
|
+
return 'create';
|
|
447
|
+
}
|
|
448
|
+
return normalized;
|
|
449
|
+
}
|
|
333
450
|
isExplicitCrudAction(action) {
|
|
334
451
|
if (action.mode === 'explicit') {
|
|
335
452
|
return true;
|
|
@@ -338,6 +455,19 @@ class CrudLauncherService {
|
|
|
338
455
|
String(action.form?.submitUrl || '').trim() ||
|
|
339
456
|
String(action.form?.submitMethod || '').trim());
|
|
340
457
|
}
|
|
458
|
+
defaultInferredLayoutPolicy(action) {
|
|
459
|
+
const operation = this.normalizeCrudOperationName(action.action);
|
|
460
|
+
if (operation !== 'create' && operation !== 'edit') {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
source: 'schema',
|
|
465
|
+
intent: 'command',
|
|
466
|
+
preset: 'groupedCommand',
|
|
467
|
+
persistence: 'transient',
|
|
468
|
+
schemaType: 'request',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
341
471
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
342
472
|
try {
|
|
343
473
|
if (!componentKeyId)
|
|
@@ -392,6 +522,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
392
522
|
type: Injectable,
|
|
393
523
|
args: [{ providedIn: 'root' }]
|
|
394
524
|
}] });
|
|
525
|
+
function mergeClassList(base, extra) {
|
|
526
|
+
if (!extra) {
|
|
527
|
+
return base;
|
|
528
|
+
}
|
|
529
|
+
if (typeof extra === 'string') {
|
|
530
|
+
return [...base, extra];
|
|
531
|
+
}
|
|
532
|
+
if (Array.isArray(extra)) {
|
|
533
|
+
return [...base, ...extra];
|
|
534
|
+
}
|
|
535
|
+
return [
|
|
536
|
+
...base,
|
|
537
|
+
...Object.keys(extra).filter((className) => !!extra[className]),
|
|
538
|
+
];
|
|
539
|
+
}
|
|
540
|
+
function escapeRegExp(value) {
|
|
541
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
542
|
+
}
|
|
543
|
+
function toCrudDrawerResult(value) {
|
|
544
|
+
if (!value || typeof value !== 'object') {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
const result = value;
|
|
548
|
+
return result.type === 'save' || result.type === 'delete' || result.type === 'close'
|
|
549
|
+
? result
|
|
550
|
+
: undefined;
|
|
551
|
+
}
|
|
395
552
|
|
|
396
553
|
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
397
554
|
const DOCUMENT_VERSION = 1;
|
|
@@ -510,10 +667,15 @@ function normalizeCrudMetadata(metadata) {
|
|
|
510
667
|
if (base.resource && typeof base.resource === 'object') {
|
|
511
668
|
normalized.resource = stripUndefinedShallow({
|
|
512
669
|
path: trimString(base.resource.path) || undefined,
|
|
670
|
+
schemaPath: trimString(base.resource.schemaPath) || undefined,
|
|
513
671
|
idField: typeof base.resource.idField === 'string'
|
|
514
672
|
? trimString(base.resource.idField) || undefined
|
|
515
673
|
: base.resource.idField,
|
|
674
|
+
title: trimString(base.resource.title) || undefined,
|
|
675
|
+
label: trimString(base.resource.label) || undefined,
|
|
676
|
+
formTitle: trimString(base.resource.formTitle) || undefined,
|
|
516
677
|
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
678
|
+
apiUrlEntry: base.resource.apiUrlEntry || undefined,
|
|
517
679
|
});
|
|
518
680
|
}
|
|
519
681
|
if (base.defaults && typeof base.defaults === 'object') {
|
|
@@ -564,6 +726,7 @@ function normalizeCrudAction(action) {
|
|
|
564
726
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
727
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
728
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
729
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
730
|
}
|
|
568
731
|
: undefined,
|
|
569
732
|
});
|
|
@@ -721,6 +884,11 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
721
884
|
'crud.emptyState.title': 'Conecte o CRUD a um recurso',
|
|
722
885
|
'crud.emptyState.description': 'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e as ações.',
|
|
723
886
|
'crud.emptyState.primaryAction': 'Configurar metadados',
|
|
887
|
+
'crud.table.emptyState.initial.title': 'Sem registros em {label}',
|
|
888
|
+
'crud.table.emptyState.initial.titleFallback': 'Nenhum registro disponível.',
|
|
889
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.',
|
|
890
|
+
'crud.table.emptyState.filtered.title': 'Nenhum resultado encontrado.',
|
|
891
|
+
'crud.table.emptyState.filtered.description': 'Revise os filtros ou ajuste o termo de busca.',
|
|
724
892
|
'crud.preferences.resetSuccess': 'Overrides de CRUD redefinidos',
|
|
725
893
|
'crud.actions.create': 'Adicionar',
|
|
726
894
|
'crud.actions.view': 'Ver',
|
|
@@ -733,6 +901,11 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
733
901
|
'crud.emptyState.title': 'Connect CRUD to a resource',
|
|
734
902
|
'crud.emptyState.description': 'Provide metadata (resourcePath / schema) or metadata.data to enable the table and actions.',
|
|
735
903
|
'crud.emptyState.primaryAction': 'Configure metadata',
|
|
904
|
+
'crud.table.emptyState.initial.title': 'No records in {label}',
|
|
905
|
+
'crud.table.emptyState.initial.titleFallback': 'No records available.',
|
|
906
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use the primary action to add the first record when there is information to register.',
|
|
907
|
+
'crud.table.emptyState.filtered.title': 'No results found.',
|
|
908
|
+
'crud.table.emptyState.filtered.description': 'Review the filters or adjust the search term.',
|
|
736
909
|
'crud.preferences.resetSuccess': 'CRUD overrides reset',
|
|
737
910
|
'crud.actions.create': 'Add',
|
|
738
911
|
'crud.actions.view': 'View',
|
|
@@ -3467,6 +3640,8 @@ class PraxisCrudComponent {
|
|
|
3467
3640
|
componentInstanceId;
|
|
3468
3641
|
context;
|
|
3469
3642
|
enableCustomization = false;
|
|
3643
|
+
/** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
|
|
3644
|
+
authoringCapability = null;
|
|
3470
3645
|
configureRequested = new EventEmitter();
|
|
3471
3646
|
afterOpen = new EventEmitter();
|
|
3472
3647
|
afterClose = new EventEmitter();
|
|
@@ -3491,6 +3666,7 @@ class PraxisCrudComponent {
|
|
|
3491
3666
|
table;
|
|
3492
3667
|
storage = inject(ASYNC_CONFIG_STORAGE);
|
|
3493
3668
|
settingsPanel = inject(SettingsPanelService);
|
|
3669
|
+
enterpriseRuntimeContext = inject(EnterpriseRuntimeContextService, { optional: true });
|
|
3494
3670
|
snack = inject(MatSnackBar);
|
|
3495
3671
|
dialog = inject(DialogService);
|
|
3496
3672
|
i18n = inject(PraxisI18nService);
|
|
@@ -3498,6 +3674,7 @@ class PraxisCrudComponent {
|
|
|
3498
3674
|
resourceDiscoveryInstance;
|
|
3499
3675
|
actionOpenAdapterInstance;
|
|
3500
3676
|
surfaceOpenAdapterInstance;
|
|
3677
|
+
materializedRefreshSequence = 0;
|
|
3501
3678
|
global = (() => {
|
|
3502
3679
|
try {
|
|
3503
3680
|
return inject(GlobalConfigService);
|
|
@@ -3507,6 +3684,7 @@ class PraxisCrudComponent {
|
|
|
3507
3684
|
}
|
|
3508
3685
|
})();
|
|
3509
3686
|
surfaceService = inject(GLOBAL_SURFACE_SERVICE, { optional: true });
|
|
3687
|
+
surfaceOutlets = inject(SurfaceOutletRegistryService);
|
|
3510
3688
|
componentKeys = inject(ComponentKeyService);
|
|
3511
3689
|
route = (() => {
|
|
3512
3690
|
try {
|
|
@@ -3523,10 +3701,23 @@ class PraxisCrudComponent {
|
|
|
3523
3701
|
lastAppliedResourceIdentity = null;
|
|
3524
3702
|
tableCollectionLinks = null;
|
|
3525
3703
|
collectionCapabilities = null;
|
|
3704
|
+
activeAuthoringPanelRef;
|
|
3705
|
+
constructor() {
|
|
3706
|
+
this.enterpriseRuntimeContext?.contextChanges$
|
|
3707
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3708
|
+
.subscribe(() => {
|
|
3709
|
+
if (!this.isCustomizationAvailable()) {
|
|
3710
|
+
this.activeAuthoringPanelRef?.close();
|
|
3711
|
+
this.activeAuthoringPanelRef = undefined;
|
|
3712
|
+
}
|
|
3713
|
+
this.cdr.markForCheck();
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3526
3716
|
collectionCapabilitiesRequestHref = null;
|
|
3527
3717
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3718
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3719
|
currentAuthoringDocument;
|
|
3720
|
+
selectedRow = null;
|
|
3530
3721
|
getResourceDiscovery() {
|
|
3531
3722
|
const assigned = this.resourceDiscovery;
|
|
3532
3723
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,15 +3744,41 @@ class PraxisCrudComponent {
|
|
|
3553
3744
|
catch { }
|
|
3554
3745
|
}
|
|
3555
3746
|
onTableRowClick(event) {
|
|
3747
|
+
const row = this.extractRowFromEvent(event);
|
|
3748
|
+
if (row) {
|
|
3749
|
+
this.selectedRow = row;
|
|
3750
|
+
}
|
|
3556
3751
|
this.rowClick.emit(event);
|
|
3557
3752
|
}
|
|
3558
3753
|
onTableSelectionChange(event) {
|
|
3754
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3755
|
this.selectionChange.emit(event);
|
|
3560
3756
|
}
|
|
3757
|
+
async onBulkAction(event) {
|
|
3758
|
+
const action = String(event?.action || '').trim();
|
|
3759
|
+
if (!action) {
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3763
|
+
if (row) {
|
|
3764
|
+
this.selectedRow = row;
|
|
3765
|
+
}
|
|
3766
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3767
|
+
}
|
|
3768
|
+
async onToolbarAction(event) {
|
|
3769
|
+
const action = String(event?.action || '').trim();
|
|
3770
|
+
if (!action)
|
|
3771
|
+
return;
|
|
3772
|
+
const row = this.extractSelectedRowFromToolbarAction(event);
|
|
3773
|
+
if (row)
|
|
3774
|
+
this.selectedRow = row;
|
|
3775
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3776
|
+
}
|
|
3561
3777
|
ngOnChanges(changes) {
|
|
3562
3778
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3779
|
return;
|
|
3564
3780
|
}
|
|
3781
|
+
this.materializedRefreshSequence += 1;
|
|
3565
3782
|
try {
|
|
3566
3783
|
const parsed = typeof this.metadata === 'string'
|
|
3567
3784
|
? JSON.parse(this.metadata)
|
|
@@ -3595,33 +3812,46 @@ class PraxisCrudComponent {
|
|
|
3595
3812
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3813
|
try {
|
|
3597
3814
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3815
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3816
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3817
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3818
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3819
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3820
|
+
}
|
|
3599
3821
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3822
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3823
|
if (ctxAction) {
|
|
3602
3824
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3825
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3826
|
openMode: ctxAction.openMode,
|
|
3605
3827
|
formId: ctxAction.formId,
|
|
3606
3828
|
route: ctxAction.route,
|
|
3607
3829
|
};
|
|
3608
3830
|
}
|
|
3609
3831
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3832
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3833
|
}
|
|
3612
3834
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3835
|
+
const effectiveAction = {
|
|
3836
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3837
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3838
|
+
};
|
|
3839
|
+
const resourceIdentity = runtimeEvent?.resourceIdentity ?? null;
|
|
3840
|
+
const handledByDuplicateDraft = await this.tryHandleCanonicalDuplicateDraftAction(effectiveAction, contextualRow);
|
|
3841
|
+
if (handledByDuplicateDraft) {
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3614
3844
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3845
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3846
|
if (openedByDiscovery) {
|
|
3617
3847
|
return;
|
|
3618
3848
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3849
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3850
|
if (handledByWorkflowAction) {
|
|
3621
3851
|
return;
|
|
3622
3852
|
}
|
|
3623
3853
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3854
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3855
|
if (handledByDelete) {
|
|
3626
3856
|
return;
|
|
3627
3857
|
}
|
|
@@ -3632,7 +3862,7 @@ class PraxisCrudComponent {
|
|
|
3632
3862
|
drawerCloseEmitted = true;
|
|
3633
3863
|
this.afterClose.emit();
|
|
3634
3864
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3865
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3866
|
onClose: () => emitDrawerClose(),
|
|
3637
3867
|
onResult: (result) => {
|
|
3638
3868
|
emitDrawerClose();
|
|
@@ -3653,9 +3883,13 @@ class PraxisCrudComponent {
|
|
|
3653
3883
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3884
|
? this.collectionCapabilities
|
|
3655
3885
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3886
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3887
|
+
resourceIdentity,
|
|
3657
3888
|
});
|
|
3658
3889
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3890
|
+
if (mode === 'drawer') {
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3659
3893
|
if (!ref) {
|
|
3660
3894
|
return;
|
|
3661
3895
|
}
|
|
@@ -3791,6 +4025,9 @@ class PraxisCrudComponent {
|
|
|
3791
4025
|
return this.tx('crud.emptyState.primaryAction', 'Configure metadata');
|
|
3792
4026
|
}
|
|
3793
4027
|
onConfigureRequested() {
|
|
4028
|
+
if (!this.isCustomizationAvailable()) {
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
3794
4031
|
this.configureRequested.emit();
|
|
3795
4032
|
this.openCrudAuthoringFromTable();
|
|
3796
4033
|
}
|
|
@@ -3841,9 +4078,140 @@ class PraxisCrudComponent {
|
|
|
3841
4078
|
this.refreshTable();
|
|
3842
4079
|
return true;
|
|
3843
4080
|
}
|
|
4081
|
+
async tryHandleCanonicalDuplicateDraftAction(action, row) {
|
|
4082
|
+
const normalizedAction = this.normalizeCrudActionName(action.action);
|
|
4083
|
+
if (normalizedAction !== 'duplicate-draft' || !row) {
|
|
4084
|
+
return false;
|
|
4085
|
+
}
|
|
4086
|
+
const duplicateDraftUrl = this.getResourceDiscovery().resolveLinkHref(row['_links'], 'duplicate-draft', this.buildDiscoveryOptions());
|
|
4087
|
+
if (!duplicateDraftUrl) {
|
|
4088
|
+
return false;
|
|
4089
|
+
}
|
|
4090
|
+
const response = await firstValueFrom(this.http.post(duplicateDraftUrl, {}));
|
|
4091
|
+
const draft = this.unwrapRestData(response);
|
|
4092
|
+
if (!this.isRecord(draft)) {
|
|
4093
|
+
throw new Error('Duplicate draft action returned an invalid create draft.');
|
|
4094
|
+
}
|
|
4095
|
+
const configuredCreateAction = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === 'create') ?? { action: 'create' };
|
|
4096
|
+
const createAction = {
|
|
4097
|
+
...configuredCreateAction,
|
|
4098
|
+
action: 'create',
|
|
4099
|
+
openMode: configuredCreateAction.openMode ??
|
|
4100
|
+
action.openMode ??
|
|
4101
|
+
this.resolvedMetadata.defaults?.openMode ??
|
|
4102
|
+
'drawer',
|
|
4103
|
+
form: {
|
|
4104
|
+
...(configuredCreateAction.form ?? {}),
|
|
4105
|
+
initialValue: { ...draft },
|
|
4106
|
+
},
|
|
4107
|
+
};
|
|
4108
|
+
let drawerCloseEmitted = false;
|
|
4109
|
+
const emitDrawerClose = () => {
|
|
4110
|
+
if (drawerCloseEmitted)
|
|
4111
|
+
return;
|
|
4112
|
+
drawerCloseEmitted = true;
|
|
4113
|
+
this.afterClose.emit();
|
|
4114
|
+
};
|
|
4115
|
+
const handleResult = (result) => {
|
|
4116
|
+
if (result?.type !== 'save') {
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
const data = this.isRecord(result.data) ? result.data : {};
|
|
4120
|
+
const id = data[this.getIdField()];
|
|
4121
|
+
this.afterSave.emit({ id, data });
|
|
4122
|
+
this.refreshTable();
|
|
4123
|
+
};
|
|
4124
|
+
const { mode, ref } = await this.launcher.launch(createAction, undefined, this.resolvedMetadata, this.componentKeyId(), {
|
|
4125
|
+
onClose: () => emitDrawerClose(),
|
|
4126
|
+
onResult: (result) => handleResult(result),
|
|
4127
|
+
}, {
|
|
4128
|
+
capabilities: this.collectionCapabilities,
|
|
4129
|
+
links: this.tableCollectionLinks,
|
|
4130
|
+
});
|
|
4131
|
+
this.afterOpen.emit({ mode, action: normalizedAction });
|
|
4132
|
+
if (mode !== 'drawer' && ref) {
|
|
4133
|
+
ref
|
|
4134
|
+
.afterClosed()
|
|
4135
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4136
|
+
.subscribe((result) => {
|
|
4137
|
+
this.afterClose.emit();
|
|
4138
|
+
handleResult(result);
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
return true;
|
|
4142
|
+
}
|
|
3844
4143
|
refreshTable() {
|
|
4144
|
+
if (this.tryRefreshMaterializedLocalData()) {
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
3845
4147
|
this.table.refetch();
|
|
3846
4148
|
}
|
|
4149
|
+
tryRefreshMaterializedLocalData() {
|
|
4150
|
+
const readUrl = this.resolveMaterializedReadUrl();
|
|
4151
|
+
if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
|
|
4152
|
+
return false;
|
|
4153
|
+
}
|
|
4154
|
+
const refreshSequence = ++this.materializedRefreshSequence;
|
|
4155
|
+
void firstValueFrom(this.getResourceDiscovery().fetchJson(readUrl, {
|
|
4156
|
+
endpointKey: this.resolvedMetadata.resource?.endpointKey,
|
|
4157
|
+
apiUrlEntry: this.resolvedMetadata.resource?.apiUrlEntry,
|
|
4158
|
+
}))
|
|
4159
|
+
.then((response) => {
|
|
4160
|
+
if (!this.isCurrentMaterializedRefresh(refreshSequence, readUrl)) {
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
const data = this.extractCollectionData(this.unwrapRestData(response));
|
|
4164
|
+
if (!data) {
|
|
4165
|
+
this.error.emit({
|
|
4166
|
+
code: 'CRUD_MATERIALIZED_REFRESH_INVALID_COLLECTION',
|
|
4167
|
+
readUrl,
|
|
4168
|
+
});
|
|
4169
|
+
return;
|
|
4170
|
+
}
|
|
4171
|
+
this.resolvedMetadata = {
|
|
4172
|
+
...this.resolvedMetadata,
|
|
4173
|
+
data,
|
|
4174
|
+
};
|
|
4175
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4176
|
+
this.cdr.markForCheck();
|
|
4177
|
+
})
|
|
4178
|
+
.catch((error) => this.error.emit(error));
|
|
4179
|
+
return true;
|
|
4180
|
+
}
|
|
4181
|
+
isCurrentMaterializedRefresh(refreshSequence, readUrl) {
|
|
4182
|
+
return (refreshSequence === this.materializedRefreshSequence &&
|
|
4183
|
+
this.resolveMaterializedReadUrl() === readUrl &&
|
|
4184
|
+
this.resolveResourcePath(this.resolvedMetadata).length === 0);
|
|
4185
|
+
}
|
|
4186
|
+
resolveMaterializedReadUrl() {
|
|
4187
|
+
const materialization = this.context?.['materialization'];
|
|
4188
|
+
return String(materialization?.['readUrl'] || '').trim();
|
|
4189
|
+
}
|
|
4190
|
+
unwrapRestData(value) {
|
|
4191
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
|
|
4192
|
+
return value.data;
|
|
4193
|
+
}
|
|
4194
|
+
return value;
|
|
4195
|
+
}
|
|
4196
|
+
extractCollectionData(data) {
|
|
4197
|
+
if (Array.isArray(data)) {
|
|
4198
|
+
return data;
|
|
4199
|
+
}
|
|
4200
|
+
if (!data || typeof data !== 'object') {
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
const record = data;
|
|
4204
|
+
if (Array.isArray(record['content'])) {
|
|
4205
|
+
return record['content'];
|
|
4206
|
+
}
|
|
4207
|
+
if (Array.isArray(record['items'])) {
|
|
4208
|
+
return record['items'];
|
|
4209
|
+
}
|
|
4210
|
+
if (Array.isArray(record['data'])) {
|
|
4211
|
+
return record['data'];
|
|
4212
|
+
}
|
|
4213
|
+
return null;
|
|
4214
|
+
}
|
|
3847
4215
|
emitTableRuntimeConfigSnapshot() {
|
|
3848
4216
|
const snapshot = this.getCurrentTableConfigSnapshot();
|
|
3849
4217
|
if (!snapshot)
|
|
@@ -3901,17 +4269,67 @@ class PraxisCrudComponent {
|
|
|
3901
4269
|
}
|
|
3902
4270
|
}
|
|
3903
4271
|
resolveQueryContext(meta) {
|
|
4272
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4273
|
+
return null;
|
|
4274
|
+
}
|
|
3904
4275
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4276
|
}
|
|
3906
4277
|
resolveFilterCriteria(meta) {
|
|
3907
4278
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4279
|
}
|
|
4280
|
+
extractRowFromEvent(event) {
|
|
4281
|
+
if (!this.isRecord(event)) {
|
|
4282
|
+
return null;
|
|
4283
|
+
}
|
|
4284
|
+
const row = event['row'];
|
|
4285
|
+
return this.isRecord(row) ? row : null;
|
|
4286
|
+
}
|
|
4287
|
+
extractSelectedRowFromEvent(event) {
|
|
4288
|
+
if (!this.isRecord(event)) {
|
|
4289
|
+
return null;
|
|
4290
|
+
}
|
|
4291
|
+
const selection = event['selection'];
|
|
4292
|
+
if (Array.isArray(selection)) {
|
|
4293
|
+
const first = selection[0];
|
|
4294
|
+
return this.isRecord(first) ? first : null;
|
|
4295
|
+
}
|
|
4296
|
+
const row = event['row'];
|
|
4297
|
+
if (this.isRecord(row)) {
|
|
4298
|
+
return row;
|
|
4299
|
+
}
|
|
4300
|
+
return null;
|
|
4301
|
+
}
|
|
4302
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4303
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4304
|
+
return this.isRecord(first) ? first : null;
|
|
4305
|
+
}
|
|
4306
|
+
extractSelectedRowFromToolbarAction(event) {
|
|
4307
|
+
if (this.isRecord(event?.selectedRow))
|
|
4308
|
+
return event.selectedRow;
|
|
4309
|
+
if (this.isRecord(event?.row))
|
|
4310
|
+
return event.row;
|
|
4311
|
+
const first = Array.isArray(event?.selectedRows) ? event.selectedRows[0] : null;
|
|
4312
|
+
return this.isRecord(first) ? first : null;
|
|
4313
|
+
}
|
|
4314
|
+
extractDiscoveryActionConfig(event) {
|
|
4315
|
+
const config = event?.actionConfig;
|
|
4316
|
+
return this.isRecord(config) && typeof config['resourceKey'] === 'string'
|
|
4317
|
+
? config
|
|
4318
|
+
: null;
|
|
4319
|
+
}
|
|
4320
|
+
resolveSelectedRowForAction(action) {
|
|
4321
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4322
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4323
|
+
return undefined;
|
|
4324
|
+
}
|
|
4325
|
+
return this.selectedRow ?? undefined;
|
|
4326
|
+
}
|
|
3909
4327
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4328
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4329
|
if (!this.surfaceService) {
|
|
3912
4330
|
return false;
|
|
3913
4331
|
}
|
|
3914
|
-
const providedSurface = this.resolveProvidedSurface(normalizedAction, runtimeEvent
|
|
4332
|
+
const providedSurface = this.resolveProvidedSurface(normalizedAction, this.extractDiscoveryActionConfig(runtimeEvent));
|
|
3915
4333
|
if (!providedSurface && !this.isDiscoveryManagedCrudAction(normalizedAction)) {
|
|
3916
4334
|
return false;
|
|
3917
4335
|
}
|
|
@@ -3942,7 +4360,7 @@ class PraxisCrudComponent {
|
|
|
3942
4360
|
}
|
|
3943
4361
|
let openPromise;
|
|
3944
4362
|
try {
|
|
3945
|
-
|
|
4363
|
+
const surfaceContext = {
|
|
3946
4364
|
sourceId: this.componentKeyId() || undefined,
|
|
3947
4365
|
payload: { action: normalizedAction, row: row ?? null },
|
|
3948
4366
|
meta: {
|
|
@@ -3959,7 +4377,9 @@ class PraxisCrudComponent {
|
|
|
3959
4377
|
resourcePath,
|
|
3960
4378
|
},
|
|
3961
4379
|
},
|
|
3962
|
-
}
|
|
4380
|
+
};
|
|
4381
|
+
const handledInline = await this.surfaceOutlets.tryActivate(payload, surfaceContext);
|
|
4382
|
+
openPromise = handledInline ? Promise.resolve(undefined) : Promise.resolve(this.surfaceService.open(payload, surfaceContext));
|
|
3963
4383
|
}
|
|
3964
4384
|
catch {
|
|
3965
4385
|
return false;
|
|
@@ -3996,10 +4416,10 @@ class PraxisCrudComponent {
|
|
|
3996
4416
|
if (!normalizedAction || this.isDiscoveryManagedCrudAction(normalizedAction) || !this.surfaceService) {
|
|
3997
4417
|
return false;
|
|
3998
4418
|
}
|
|
3999
|
-
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent
|
|
4419
|
+
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, this.extractDiscoveryActionConfig(runtimeEvent));
|
|
4000
4420
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4421
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4422
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4423
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4424
|
return false;
|
|
4005
4425
|
}
|
|
@@ -4222,12 +4642,19 @@ class PraxisCrudComponent {
|
|
|
4222
4642
|
}
|
|
4223
4643
|
return cfg.toolbar;
|
|
4224
4644
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4645
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4646
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4647
|
if (addAction) {
|
|
4228
4648
|
if (hasToolbarAdd) {
|
|
4229
4649
|
const toolbar = ensureToolbar();
|
|
4230
4650
|
toolbar.visible = true;
|
|
4651
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4652
|
+
? {
|
|
4653
|
+
...action,
|
|
4654
|
+
action: 'create',
|
|
4655
|
+
}
|
|
4656
|
+
: action);
|
|
4657
|
+
changed = true;
|
|
4231
4658
|
}
|
|
4232
4659
|
else {
|
|
4233
4660
|
const toolbar = ensureToolbar();
|
|
@@ -4246,6 +4673,7 @@ class PraxisCrudComponent {
|
|
|
4246
4673
|
});
|
|
4247
4674
|
}
|
|
4248
4675
|
changed = true;
|
|
4676
|
+
changed = this.ensureCanonicalCollectionEmptyState(cfg, meta, addAction) || changed;
|
|
4249
4677
|
}
|
|
4250
4678
|
const discoveredToolbarActions = this.resolveCollectionWorkflowToolbarActions(capabilities);
|
|
4251
4679
|
if (discoveredToolbarActions.length) {
|
|
@@ -4362,6 +4790,28 @@ class PraxisCrudComponent {
|
|
|
4362
4790
|
}
|
|
4363
4791
|
return false;
|
|
4364
4792
|
}
|
|
4793
|
+
normalizeCrudActionName(action) {
|
|
4794
|
+
const raw = String(action || '').trim();
|
|
4795
|
+
const normalized = raw.toLowerCase();
|
|
4796
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4797
|
+
}
|
|
4798
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4799
|
+
if (!action)
|
|
4800
|
+
return false;
|
|
4801
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4802
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4803
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4804
|
+
return true;
|
|
4805
|
+
if (explicitId)
|
|
4806
|
+
return false;
|
|
4807
|
+
const icon = normalize(action.icon);
|
|
4808
|
+
const label = normalize(action.label);
|
|
4809
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4810
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4811
|
+
}
|
|
4812
|
+
isCreateActionAliasName(actionName) {
|
|
4813
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4814
|
+
}
|
|
4365
4815
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4816
|
if (!meta)
|
|
4367
4817
|
return undefined;
|
|
@@ -4384,6 +4834,9 @@ class PraxisCrudComponent {
|
|
|
4384
4834
|
};
|
|
4385
4835
|
}
|
|
4386
4836
|
openCrudAuthoringFromTable = () => {
|
|
4837
|
+
if (!this.isCustomizationAvailable()) {
|
|
4838
|
+
return;
|
|
4839
|
+
}
|
|
4387
4840
|
const seed = this.currentAuthoringDocument
|
|
4388
4841
|
|| createCrudAuthoringDocument({ metadata: this.resolvedMetadata });
|
|
4389
4842
|
const ref = openCrudMetadataEditor(this.settingsPanel, {
|
|
@@ -4392,13 +4845,33 @@ class PraxisCrudComponent {
|
|
|
4392
4845
|
title: this.tx('crud.authoring.title', 'Configurações do CRUD'),
|
|
4393
4846
|
titleIcon: 'table_chart',
|
|
4394
4847
|
});
|
|
4848
|
+
this.activeAuthoringPanelRef = ref;
|
|
4395
4849
|
ref.applied$
|
|
4396
4850
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4397
4851
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'applied'));
|
|
4398
4852
|
ref.saved$
|
|
4399
4853
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4400
4854
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'saved'));
|
|
4855
|
+
ref.closed$
|
|
4856
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4857
|
+
.subscribe(() => {
|
|
4858
|
+
if (this.activeAuthoringPanelRef === ref) {
|
|
4859
|
+
this.activeAuthoringPanelRef = undefined;
|
|
4860
|
+
}
|
|
4861
|
+
});
|
|
4401
4862
|
};
|
|
4863
|
+
isCustomizationAvailable() {
|
|
4864
|
+
if (!this.enableCustomization) {
|
|
4865
|
+
return false;
|
|
4866
|
+
}
|
|
4867
|
+
const requiredCapability = String(this.authoringCapability || '').trim();
|
|
4868
|
+
if (!requiredCapability) {
|
|
4869
|
+
return true;
|
|
4870
|
+
}
|
|
4871
|
+
const capabilities = this.enterpriseRuntimeContext?.snapshot?.capabilities;
|
|
4872
|
+
return Array.isArray(capabilities)
|
|
4873
|
+
&& capabilities.some((capability) => capability === requiredCapability);
|
|
4874
|
+
}
|
|
4402
4875
|
applyCrudAuthoringPayload(payload, eventName) {
|
|
4403
4876
|
const next = parseLegacyOrCrudDocument(payload);
|
|
4404
4877
|
this.currentAuthoringDocument = next;
|
|
@@ -4615,6 +5088,48 @@ class PraxisCrudComponent {
|
|
|
4615
5088
|
return action;
|
|
4616
5089
|
}
|
|
4617
5090
|
}
|
|
5091
|
+
ensureCanonicalCollectionEmptyState(config, metadata, createAction) {
|
|
5092
|
+
const behavior = config.behavior || {};
|
|
5093
|
+
if (behavior.emptyState) {
|
|
5094
|
+
return false;
|
|
5095
|
+
}
|
|
5096
|
+
const resourceLabel = String(metadata.resource?.label
|
|
5097
|
+
|| metadata.resource?.title
|
|
5098
|
+
|| metadata.resource?.path
|
|
5099
|
+
|| '').trim();
|
|
5100
|
+
const title = resourceLabel
|
|
5101
|
+
? this.txWithParams('crud.table.emptyState.initial.title', 'Sem registros em {label}', { label: resourceLabel })
|
|
5102
|
+
: this.tx('crud.table.emptyState.initial.titleFallback', 'Nenhum registro disponível.');
|
|
5103
|
+
const actionId = String(createAction.action || createAction.id || 'create').trim() || 'create';
|
|
5104
|
+
const actionLabel = String(createAction.label || this.getCrudActionLabel('create')).trim();
|
|
5105
|
+
const actionIcon = String(createAction.icon || 'add').trim();
|
|
5106
|
+
config.behavior = {
|
|
5107
|
+
...behavior,
|
|
5108
|
+
emptyState: {
|
|
5109
|
+
message: '',
|
|
5110
|
+
contexts: {
|
|
5111
|
+
initial: {
|
|
5112
|
+
title,
|
|
5113
|
+
description: this.tx('crud.table.emptyState.initial.descriptionWithAction', 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.'),
|
|
5114
|
+
actions: [
|
|
5115
|
+
{
|
|
5116
|
+
label: actionLabel,
|
|
5117
|
+
action: actionId,
|
|
5118
|
+
icon: actionIcon,
|
|
5119
|
+
primary: true,
|
|
5120
|
+
},
|
|
5121
|
+
],
|
|
5122
|
+
},
|
|
5123
|
+
filtered: {
|
|
5124
|
+
title: this.tx('crud.table.emptyState.filtered.title', 'Nenhum resultado encontrado.'),
|
|
5125
|
+
description: this.tx('crud.table.emptyState.filtered.description', 'Revise os filtros ou ajuste o termo de busca.'),
|
|
5126
|
+
actions: [],
|
|
5127
|
+
},
|
|
5128
|
+
},
|
|
5129
|
+
},
|
|
5130
|
+
};
|
|
5131
|
+
return true;
|
|
5132
|
+
}
|
|
4618
5133
|
tx(key, fallback) {
|
|
4619
5134
|
try {
|
|
4620
5135
|
return translateCrudRuntimeText(this.i18n, key, fallback);
|
|
@@ -4623,8 +5138,19 @@ class PraxisCrudComponent {
|
|
|
4623
5138
|
return fallback;
|
|
4624
5139
|
}
|
|
4625
5140
|
}
|
|
5141
|
+
txWithParams(key, fallback, params) {
|
|
5142
|
+
try {
|
|
5143
|
+
return this.interpolateTranslationParams(translateCrudRuntimeText(this.i18n, key, fallback, params), params);
|
|
5144
|
+
}
|
|
5145
|
+
catch {
|
|
5146
|
+
return this.interpolateTranslationParams(fallback, params);
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
interpolateTranslationParams(text, params) {
|
|
5150
|
+
return Object.entries(params).reduce((current, [name, value]) => current.split(`{${name}}`).join(value), text);
|
|
5151
|
+
}
|
|
4626
5152
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4627
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
|
|
5153
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization", authoringCapability: "authoringCapability" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
|
|
4628
5154
|
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
4629
5155
|
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
4630
5156
|
], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
@@ -4638,17 +5164,19 @@ class PraxisCrudComponent {
|
|
|
4638
5164
|
[tableId]="crudId || 'default'"
|
|
4639
5165
|
[crudContext]="tableCrudContext"
|
|
4640
5166
|
[enableCustomization]="enableCustomization"
|
|
5167
|
+
[authoringCapability]="authoringCapability"
|
|
4641
5168
|
(rowClick)="onTableRowClick($event)"
|
|
4642
5169
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
5170
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
|
-
(toolbarAction)="
|
|
5171
|
+
(toolbarAction)="onToolbarAction($event)"
|
|
5172
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
5173
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
5174
|
(reset)="onResetPreferences()"
|
|
4647
5175
|
(metadataChange)="onTableMetadataChange()"
|
|
4648
5176
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4649
5177
|
></praxis-table>
|
|
4650
5178
|
} @else {
|
|
4651
|
-
@if (
|
|
5179
|
+
@if (isCustomizationAvailable()) {
|
|
4652
5180
|
<praxis-empty-state-card
|
|
4653
5181
|
icon="table_rows"
|
|
4654
5182
|
[title]="getEmptyStateTitle()"
|
|
@@ -4657,7 +5185,7 @@ class PraxisCrudComponent {
|
|
|
4657
5185
|
/>
|
|
4658
5186
|
}
|
|
4659
5187
|
}
|
|
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"] }] });
|
|
5188
|
+
`, 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", "authoringCapability", "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
5189
|
}
|
|
4662
5190
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
5191
|
type: Component,
|
|
@@ -4675,17 +5203,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4675
5203
|
[tableId]="crudId || 'default'"
|
|
4676
5204
|
[crudContext]="tableCrudContext"
|
|
4677
5205
|
[enableCustomization]="enableCustomization"
|
|
5206
|
+
[authoringCapability]="authoringCapability"
|
|
4678
5207
|
(rowClick)="onTableRowClick($event)"
|
|
4679
5208
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
5209
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
|
-
(toolbarAction)="
|
|
5210
|
+
(toolbarAction)="onToolbarAction($event)"
|
|
5211
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
5212
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
5213
|
(reset)="onResetPreferences()"
|
|
4684
5214
|
(metadataChange)="onTableMetadataChange()"
|
|
4685
5215
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4686
5216
|
></praxis-table>
|
|
4687
5217
|
} @else {
|
|
4688
|
-
@if (
|
|
5218
|
+
@if (isCustomizationAvailable()) {
|
|
4689
5219
|
<praxis-empty-state-card
|
|
4690
5220
|
icon="table_rows"
|
|
4691
5221
|
[title]="getEmptyStateTitle()"
|
|
@@ -4695,7 +5225,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4695
5225
|
}
|
|
4696
5226
|
}
|
|
4697
5227
|
`, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"] }]
|
|
4698
|
-
}], propDecorators: { metadata: [{
|
|
5228
|
+
}], ctorParameters: () => [], propDecorators: { metadata: [{
|
|
4699
5229
|
type: Input,
|
|
4700
5230
|
args: [{ required: true }]
|
|
4701
5231
|
}], crudId: [{
|
|
@@ -4707,6 +5237,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4707
5237
|
type: Input
|
|
4708
5238
|
}], enableCustomization: [{
|
|
4709
5239
|
type: Input
|
|
5240
|
+
}], authoringCapability: [{
|
|
5241
|
+
type: Input
|
|
4710
5242
|
}], configureRequested: [{
|
|
4711
5243
|
type: Output
|
|
4712
5244
|
}], afterOpen: [{
|
|
@@ -4734,6 +5266,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
5266
|
args: [PraxisTable]
|
|
4735
5267
|
}] } });
|
|
4736
5268
|
|
|
5269
|
+
function isCrudDebugEnabled() {
|
|
5270
|
+
try {
|
|
5271
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
5272
|
+
return true;
|
|
5273
|
+
}
|
|
5274
|
+
if (typeof localStorage !== 'undefined') {
|
|
5275
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
catch { }
|
|
5279
|
+
return false;
|
|
5280
|
+
}
|
|
5281
|
+
function debugCrudHost(message, data) {
|
|
5282
|
+
if (!isCrudDebugEnabled()) {
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
try {
|
|
5286
|
+
console.debug(message, data);
|
|
5287
|
+
}
|
|
5288
|
+
catch { }
|
|
5289
|
+
}
|
|
4737
5290
|
class DynamicFormDialogHostComponent {
|
|
4738
5291
|
dialogRef;
|
|
4739
5292
|
data;
|
|
@@ -4742,8 +5295,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
5295
|
configStorage;
|
|
4743
5296
|
formComp;
|
|
4744
5297
|
modal = {};
|
|
5298
|
+
presentation = 'modal';
|
|
4745
5299
|
maximized = false;
|
|
4746
5300
|
initialSize = {};
|
|
5301
|
+
initialPosition;
|
|
4747
5302
|
rememberState = false;
|
|
4748
5303
|
stateKey;
|
|
4749
5304
|
backDefaults = {};
|
|
@@ -4751,12 +5306,16 @@ class DynamicFormDialogHostComponent {
|
|
|
4751
5306
|
resourcePath;
|
|
4752
5307
|
resourceId;
|
|
4753
5308
|
initialValue;
|
|
5309
|
+
resourceIdentity = null;
|
|
4754
5310
|
schemaUrl;
|
|
4755
5311
|
submitUrl;
|
|
4756
5312
|
submitMethod;
|
|
4757
5313
|
apiEndpointKey;
|
|
4758
5314
|
apiUrlEntry;
|
|
5315
|
+
layoutPolicy;
|
|
5316
|
+
formConfig = {};
|
|
4759
5317
|
formActions;
|
|
5318
|
+
formConfigPersistenceStrategy = 'input-first';
|
|
4760
5319
|
mode = 'create';
|
|
4761
5320
|
backConfig;
|
|
4762
5321
|
idField = 'id';
|
|
@@ -4778,6 +5337,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5337
|
this.crud = crud;
|
|
4779
5338
|
this.configStorage = configStorage;
|
|
4780
5339
|
this.dialogRef.disableClose = true;
|
|
5340
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5341
|
// i18n
|
|
4782
5342
|
this.texts = {
|
|
4783
5343
|
...this.texts,
|
|
@@ -4788,6 +5348,9 @@ class DynamicFormDialogHostComponent {
|
|
|
4788
5348
|
canMaximize: true,
|
|
4789
5349
|
...(this.data.metadata?.defaults?.modal || {}),
|
|
4790
5350
|
};
|
|
5351
|
+
this.initialPosition = this.presentation === 'drawer'
|
|
5352
|
+
? (this.data.dialogPosition ?? this.modal.position ?? { right: '0', top: '0' })
|
|
5353
|
+
: undefined;
|
|
4791
5354
|
this.rememberState = !!this.modal.rememberLastState;
|
|
4792
5355
|
this.stateKey = this.data.action?.formId
|
|
4793
5356
|
? `crud-dialog-state:${this.data.action.formId}`
|
|
@@ -4812,12 +5375,17 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5375
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5376
|
const act = this.data.action?.action;
|
|
4814
5377
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5378
|
+
this.resourceIdentity = this.presentation === 'drawer' && this.mode === 'edit' && this.data.resourceIdentity
|
|
5379
|
+
? this.data.resourceIdentity
|
|
5380
|
+
: null;
|
|
5381
|
+
this.formConfig = this.resolveFormConfig();
|
|
5382
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5383
|
this.formActions = this.resolveFormActions();
|
|
4816
5384
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5385
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5386
|
this.backDefaults = defaults;
|
|
4819
5387
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5388
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5389
|
action: this.data?.action,
|
|
4822
5390
|
resourcePath: this.resourcePath,
|
|
4823
5391
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5425,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5425
|
'apiEndpointKey',
|
|
4858
5426
|
'apiUrlEntry',
|
|
4859
5427
|
'initialValue',
|
|
5428
|
+
'layoutPolicy',
|
|
4860
5429
|
]);
|
|
4861
5430
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5431
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5438,50 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5438
|
}
|
|
4870
5439
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5440
|
}
|
|
5441
|
+
resolveFormConfig() {
|
|
5442
|
+
const config = this.data.metadata?.form;
|
|
5443
|
+
return config && typeof config === 'object' ? config : {};
|
|
5444
|
+
}
|
|
5445
|
+
get dialogTitle() {
|
|
5446
|
+
const actionLabel = this.resolveActionDialogTitle();
|
|
5447
|
+
return actionLabel ||
|
|
5448
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5449
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5450
|
+
this.texts.title;
|
|
5451
|
+
}
|
|
5452
|
+
resolveActionDialogTitle() {
|
|
5453
|
+
const actionLabel = stringOrUndefined(this.data.action?.label);
|
|
5454
|
+
if (!actionLabel) {
|
|
5455
|
+
return undefined;
|
|
5456
|
+
}
|
|
5457
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5458
|
+
const table = this.data.metadata?.table ?? {};
|
|
5459
|
+
const businessTitle = stringOrUndefined(resource.formTitle ??
|
|
5460
|
+
resource.title ??
|
|
5461
|
+
resource.label ??
|
|
5462
|
+
table.title ??
|
|
5463
|
+
table.label);
|
|
5464
|
+
const routeFallbackTitle = deriveModeTitle(this.mode, titleFromResourcePath(this.resourcePath));
|
|
5465
|
+
return businessTitle && routeFallbackTitle && sameTitle(actionLabel, routeFallbackTitle)
|
|
5466
|
+
? undefined
|
|
5467
|
+
: actionLabel;
|
|
5468
|
+
}
|
|
5469
|
+
resolveResourceTitle() {
|
|
5470
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5471
|
+
const table = this.data.metadata?.table ?? {};
|
|
5472
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5473
|
+
resource.title ??
|
|
5474
|
+
resource.label ??
|
|
5475
|
+
table.title ??
|
|
5476
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5477
|
+
}
|
|
4872
5478
|
resolveFormActions() {
|
|
4873
5479
|
if (this.mode === 'view') {
|
|
4874
5480
|
return undefined;
|
|
4875
5481
|
}
|
|
5482
|
+
if (this.formConfig?.actions) {
|
|
5483
|
+
return undefined;
|
|
5484
|
+
}
|
|
4876
5485
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5486
|
if (!submitLabel) {
|
|
4878
5487
|
return undefined;
|
|
@@ -4890,6 +5499,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5499
|
},
|
|
4891
5500
|
};
|
|
4892
5501
|
}
|
|
5502
|
+
resolveLayoutPolicy() {
|
|
5503
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5504
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5505
|
+
this.data.action?.layoutPolicy;
|
|
5506
|
+
if (explicit && typeof explicit === 'object') {
|
|
5507
|
+
return explicit;
|
|
5508
|
+
}
|
|
5509
|
+
return null;
|
|
5510
|
+
}
|
|
4893
5511
|
resolveSubmitLabel() {
|
|
4894
5512
|
const action = this.data.action ?? {};
|
|
4895
5513
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,10 +5580,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5580
|
ref
|
|
4963
5581
|
.afterClosed()
|
|
4964
5582
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5583
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5584
|
}
|
|
4967
5585
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5586
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5587
|
}
|
|
4970
5588
|
}
|
|
4971
5589
|
toggleMaximize(initial = false) {
|
|
@@ -4978,11 +5596,22 @@ class DynamicFormDialogHostComponent {
|
|
|
4978
5596
|
? `calc(100dvh - ${2 * (gap ?? 0)}px)`
|
|
4979
5597
|
: this.initialSize.height;
|
|
4980
5598
|
this.dialogRef.updateSize(width, height);
|
|
4981
|
-
this.
|
|
5599
|
+
this.updateHostPosition();
|
|
4982
5600
|
const pane = this.dialogRef
|
|
4983
5601
|
?._containerInstance?._elementRef?.nativeElement?.parentElement;
|
|
4984
5602
|
if (pane && pane.classList.contains('pfx-dialog-pane')) {
|
|
4985
|
-
|
|
5603
|
+
if (this.presentation === 'drawer') {
|
|
5604
|
+
pane.classList.toggle('pfx-drawer-maximized', this.maximized);
|
|
5605
|
+
if (this.maximized) {
|
|
5606
|
+
pane.style.setProperty('--pfx-drawer-edge-gap', `${gap ?? 0}px`);
|
|
5607
|
+
}
|
|
5608
|
+
else {
|
|
5609
|
+
pane.style.removeProperty('--pfx-drawer-edge-gap');
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
else {
|
|
5613
|
+
pane.style.margin = this.maximized ? `${gap}px` : '';
|
|
5614
|
+
}
|
|
4986
5615
|
}
|
|
4987
5616
|
this.saveState();
|
|
4988
5617
|
}
|
|
@@ -5008,7 +5637,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5637
|
width: saved?.width ?? this.modal.width,
|
|
5009
5638
|
height: saved?.height ?? this.modal.height,
|
|
5010
5639
|
};
|
|
5011
|
-
|
|
5640
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5641
|
initialSize: this.initialSize,
|
|
5013
5642
|
startMaximized: this.modal.startMaximized,
|
|
5014
5643
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5028,14 +5657,34 @@ class DynamicFormDialogHostComponent {
|
|
|
5028
5657
|
}
|
|
5029
5658
|
else if (this.initialSize.width || this.initialSize.height) {
|
|
5030
5659
|
this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
|
|
5660
|
+
this.updateHostPosition();
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
updateHostPosition() {
|
|
5664
|
+
if (this.presentation !== 'drawer') {
|
|
5031
5665
|
this.dialogRef.updatePosition();
|
|
5666
|
+
return;
|
|
5032
5667
|
}
|
|
5668
|
+
this.dialogRef.updatePosition(this.maximized
|
|
5669
|
+
? this.resolveMaximizedDrawerPosition()
|
|
5670
|
+
: this.initialPosition);
|
|
5671
|
+
}
|
|
5672
|
+
resolveMaximizedDrawerPosition() {
|
|
5673
|
+
const position = this.initialPosition ?? { right: '0', top: '0' };
|
|
5674
|
+
const offset = `${this.modal.edgeGap ?? 8}px`;
|
|
5675
|
+
const horizontal = position.left != null
|
|
5676
|
+
? { left: offset }
|
|
5677
|
+
: { right: offset };
|
|
5678
|
+
const vertical = position.bottom != null && position.top == null
|
|
5679
|
+
? { bottom: offset }
|
|
5680
|
+
: { top: offset };
|
|
5681
|
+
return { ...horizontal, ...vertical };
|
|
5033
5682
|
}
|
|
5034
5683
|
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: `
|
|
5684
|
+
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
5685
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5686
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5687
|
+
{{ dialogTitle }}
|
|
5039
5688
|
</h2>
|
|
5040
5689
|
<span class="spacer"></span>
|
|
5041
5690
|
@if (modal.canMaximize) {
|
|
@@ -5063,17 +5712,25 @@ class DynamicFormDialogHostComponent {
|
|
|
5063
5712
|
class="dialog-content"
|
|
5064
5713
|
aria-labelledby="crudDialogTitle"
|
|
5065
5714
|
>
|
|
5715
|
+
@if (resourceIdentity) {
|
|
5716
|
+
<section class="crud-drawer-resource-identity" data-testid="crud-drawer-resource-identity">
|
|
5717
|
+
<praxis-resource-identity [identity]="resourceIdentity"></praxis-resource-identity>
|
|
5718
|
+
</section>
|
|
5719
|
+
}
|
|
5066
5720
|
<praxis-dynamic-form
|
|
5067
5721
|
[formId]="data.action?.formId"
|
|
5068
5722
|
[resourcePath]="resourcePath"
|
|
5069
5723
|
[resourceId]="resourceId"
|
|
5070
5724
|
[initialValue]="initialValue"
|
|
5071
5725
|
[mode]="mode"
|
|
5726
|
+
[config]="formConfig"
|
|
5072
5727
|
[schemaUrl]="schemaUrl"
|
|
5073
5728
|
[submitUrl]="submitUrl"
|
|
5074
5729
|
[submitMethod]="submitMethod"
|
|
5075
5730
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5731
|
[apiUrlEntry]="apiUrlEntry"
|
|
5732
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5733
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5734
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5735
|
[backConfig]="backConfig"
|
|
5079
5736
|
[actions]="formActions"
|
|
@@ -5081,7 +5738,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5738
|
(formCancel)="onCancel()"
|
|
5082
5739
|
></praxis-dynamic-form>
|
|
5083
5740
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5741
|
+
`, 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.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.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: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels"] }, { 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
5742
|
}
|
|
5086
5743
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5744
|
type: Component,
|
|
@@ -5090,14 +5747,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5090
5747
|
MatButtonModule,
|
|
5091
5748
|
MatIconModule,
|
|
5092
5749
|
PraxisIconDirective,
|
|
5750
|
+
PraxisResourceIdentityComponent,
|
|
5093
5751
|
PraxisDynamicForm
|
|
5094
|
-
], providers: [GenericCrudService], host: {
|
|
5752
|
+
], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
|
|
5095
5753
|
class: 'praxis-dialog',
|
|
5096
5754
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5755
|
+
'[attr.data-presentation]': 'presentation',
|
|
5756
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5757
|
}, template: `
|
|
5098
5758
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5759
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5760
|
+
{{ dialogTitle }}
|
|
5101
5761
|
</h2>
|
|
5102
5762
|
<span class="spacer"></span>
|
|
5103
5763
|
@if (modal.canMaximize) {
|
|
@@ -5125,17 +5785,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5125
5785
|
class="dialog-content"
|
|
5126
5786
|
aria-labelledby="crudDialogTitle"
|
|
5127
5787
|
>
|
|
5788
|
+
@if (resourceIdentity) {
|
|
5789
|
+
<section class="crud-drawer-resource-identity" data-testid="crud-drawer-resource-identity">
|
|
5790
|
+
<praxis-resource-identity [identity]="resourceIdentity"></praxis-resource-identity>
|
|
5791
|
+
</section>
|
|
5792
|
+
}
|
|
5128
5793
|
<praxis-dynamic-form
|
|
5129
5794
|
[formId]="data.action?.formId"
|
|
5130
5795
|
[resourcePath]="resourcePath"
|
|
5131
5796
|
[resourceId]="resourceId"
|
|
5132
5797
|
[initialValue]="initialValue"
|
|
5133
5798
|
[mode]="mode"
|
|
5799
|
+
[config]="formConfig"
|
|
5134
5800
|
[schemaUrl]="schemaUrl"
|
|
5135
5801
|
[submitUrl]="submitUrl"
|
|
5136
5802
|
[submitMethod]="submitMethod"
|
|
5137
5803
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5804
|
[apiUrlEntry]="apiUrlEntry"
|
|
5805
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5806
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5807
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5808
|
[backConfig]="backConfig"
|
|
5141
5809
|
[actions]="formActions"
|
|
@@ -5143,7 +5811,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5811
|
(formCancel)="onCancel()"
|
|
5144
5812
|
></praxis-dynamic-form>
|
|
5145
5813
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5814
|
+
`, 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.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.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
5815
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5816
|
type: Inject,
|
|
5149
5817
|
args: [MatDialogRef]
|
|
@@ -5170,6 +5838,17 @@ function stringOrUndefined(value) {
|
|
|
5170
5838
|
const text = String(value ?? '').trim();
|
|
5171
5839
|
return text || undefined;
|
|
5172
5840
|
}
|
|
5841
|
+
function sameTitle(left, right) {
|
|
5842
|
+
return normalizeTitle(left) === normalizeTitle(right);
|
|
5843
|
+
}
|
|
5844
|
+
function normalizeTitle(value) {
|
|
5845
|
+
return value
|
|
5846
|
+
.normalize('NFD')
|
|
5847
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5848
|
+
.replace(/\s+/g, ' ')
|
|
5849
|
+
.trim()
|
|
5850
|
+
.toLocaleLowerCase('pt-BR');
|
|
5851
|
+
}
|
|
5173
5852
|
function deriveCreateSubmitLabel(actionLabel) {
|
|
5174
5853
|
if (!actionLabel) {
|
|
5175
5854
|
return 'Criar';
|
|
@@ -5180,6 +5859,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5859
|
}
|
|
5181
5860
|
return actionLabel;
|
|
5182
5861
|
}
|
|
5862
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5863
|
+
if (!resourceTitle) {
|
|
5864
|
+
return undefined;
|
|
5865
|
+
}
|
|
5866
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5867
|
+
if (mode === 'create') {
|
|
5868
|
+
return `Adicionar ${singular}`;
|
|
5869
|
+
}
|
|
5870
|
+
if (mode === 'edit') {
|
|
5871
|
+
return `Editar ${singular}`;
|
|
5872
|
+
}
|
|
5873
|
+
return `Visualizar ${singular}`;
|
|
5874
|
+
}
|
|
5875
|
+
function titleFromResourcePath(resourcePath) {
|
|
5876
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5877
|
+
if (!lastSegment) {
|
|
5878
|
+
return undefined;
|
|
5879
|
+
}
|
|
5880
|
+
return lastSegment
|
|
5881
|
+
.replace(/[-_]+/g, ' ')
|
|
5882
|
+
.replace(/\s+/g, ' ')
|
|
5883
|
+
.trim()
|
|
5884
|
+
.toLocaleLowerCase('pt-BR');
|
|
5885
|
+
}
|
|
5886
|
+
function singularizePtBrResourceTitle(title) {
|
|
5887
|
+
const normalized = title.trim();
|
|
5888
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5889
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5890
|
+
return 'código de frequência';
|
|
5891
|
+
}
|
|
5892
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5893
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5894
|
+
return [singularFirst, ...rest].join(' ');
|
|
5895
|
+
}
|
|
5896
|
+
function singularizePtBrWord(word) {
|
|
5897
|
+
if (/ões$/i.test(word)) {
|
|
5898
|
+
return word.replace(/ões$/i, 'ão');
|
|
5899
|
+
}
|
|
5900
|
+
if (/ais$/i.test(word)) {
|
|
5901
|
+
return word.replace(/ais$/i, 'al');
|
|
5902
|
+
}
|
|
5903
|
+
if (/eis$/i.test(word)) {
|
|
5904
|
+
return word.replace(/eis$/i, 'el');
|
|
5905
|
+
}
|
|
5906
|
+
if (/res$/i.test(word)) {
|
|
5907
|
+
return word.replace(/es$/i, '');
|
|
5908
|
+
}
|
|
5909
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5910
|
+
return word.replace(/s$/i, '');
|
|
5911
|
+
}
|
|
5912
|
+
return word;
|
|
5913
|
+
}
|
|
5914
|
+
function lowerFirstLetter(value) {
|
|
5915
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5916
|
+
}
|
|
5183
5917
|
|
|
5184
5918
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5919
|
__proto__: null,
|
|
@@ -5229,6 +5963,7 @@ class PraxisCrudWidgetConfigEditor {
|
|
|
5229
5963
|
componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
|
|
5230
5964
|
context: this.inputs?.context,
|
|
5231
5965
|
enableCustomization: this.inputs?.enableCustomization ?? false,
|
|
5966
|
+
authoringCapability: this.inputs?.authoringCapability ?? null,
|
|
5232
5967
|
};
|
|
5233
5968
|
return {
|
|
5234
5969
|
inputs,
|
|
@@ -5325,11 +6060,31 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
6060
|
type: 'PraxisDataQueryContext | null',
|
|
5326
6061
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
6062
|
},
|
|
6063
|
+
{
|
|
6064
|
+
name: 'metadata.resource.schemaPath',
|
|
6065
|
+
type: 'string | null',
|
|
6066
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
6067
|
+
},
|
|
6068
|
+
{
|
|
6069
|
+
name: 'metadata.resource.title',
|
|
6070
|
+
type: 'string | null',
|
|
6071
|
+
description: 'Titulo documental do recurso usado pelo shell CRUD para derivar titulos contextuais de criacao, edicao e visualizacao quando a action nao informa label.',
|
|
6072
|
+
},
|
|
6073
|
+
{
|
|
6074
|
+
name: 'metadata.resource.formTitle',
|
|
6075
|
+
type: 'string | null',
|
|
6076
|
+
description: 'Titulo documental especifico para formularios do recurso, usado antes de metadata.resource.title no shell modal/drawer.',
|
|
6077
|
+
},
|
|
5328
6078
|
{
|
|
5329
6079
|
name: 'metadata.filterCriteria',
|
|
5330
6080
|
type: 'Record<string, unknown> | null',
|
|
5331
6081
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
6082
|
},
|
|
6083
|
+
{
|
|
6084
|
+
name: 'metadata.form',
|
|
6085
|
+
type: 'FormConfig',
|
|
6086
|
+
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.',
|
|
6087
|
+
},
|
|
5333
6088
|
{
|
|
5334
6089
|
name: 'metadata.actions[].form',
|
|
5335
6090
|
type: 'CrudActionFormContract',
|
|
@@ -5341,6 +6096,12 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5341
6096
|
default: false,
|
|
5342
6097
|
description: 'Habilita modo de customização do layout.',
|
|
5343
6098
|
},
|
|
6099
|
+
{
|
|
6100
|
+
name: 'authoringCapability',
|
|
6101
|
+
type: 'string | null',
|
|
6102
|
+
default: null,
|
|
6103
|
+
description: 'Capability publica do contexto corporativo exigida para disponibilizar authoring governado no CRUD e na tabela interna.',
|
|
6104
|
+
},
|
|
5344
6105
|
],
|
|
5345
6106
|
outputs: [
|
|
5346
6107
|
{
|
|
@@ -5699,6 +6460,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6460
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6461
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6462
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6463
|
+
{ 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
6464
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6465
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6466
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6498,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6498
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6499
|
apiEndpointKey: { type: 'string' },
|
|
5738
6500
|
initialValue: { type: 'object' },
|
|
6501
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6502
|
},
|
|
5740
6503
|
},
|
|
5741
6504
|
params: {
|
|
@@ -5789,14 +6552,28 @@ const dialogHostSchema = {
|
|
|
5789
6552
|
width: { type: 'string' },
|
|
5790
6553
|
height: { type: 'string' },
|
|
5791
6554
|
minWidth: { type: 'string' },
|
|
6555
|
+
minHeight: { type: 'string' },
|
|
5792
6556
|
maxWidth: { type: 'string' },
|
|
6557
|
+
maxHeight: { type: 'string' },
|
|
5793
6558
|
density: { type: 'string' },
|
|
6559
|
+
backdropStyle: { enum: ['blur', 'dim', 'transparent'] },
|
|
5794
6560
|
canMaximize: { type: 'boolean' },
|
|
5795
6561
|
rememberLastState: { type: 'boolean' },
|
|
6562
|
+
closeOnSave: { type: 'boolean' },
|
|
5796
6563
|
startMaximized: { type: 'boolean' },
|
|
5797
6564
|
disableCloseOnEsc: { type: 'boolean' },
|
|
5798
6565
|
disableCloseOnBackdrop: { type: 'boolean' },
|
|
5799
|
-
fullscreenBreakpoint: { type: '
|
|
6566
|
+
fullscreenBreakpoint: { type: 'number' },
|
|
6567
|
+
position: {
|
|
6568
|
+
type: 'object',
|
|
6569
|
+
properties: {
|
|
6570
|
+
top: { type: 'string' },
|
|
6571
|
+
bottom: { type: 'string' },
|
|
6572
|
+
left: { type: 'string' },
|
|
6573
|
+
right: { type: 'string' },
|
|
6574
|
+
},
|
|
6575
|
+
},
|
|
6576
|
+
edgeGap: { type: 'number' },
|
|
5800
6577
|
},
|
|
5801
6578
|
},
|
|
5802
6579
|
back: { type: 'object' },
|
|
@@ -5849,19 +6626,21 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5849
6626
|
{ name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
|
|
5850
6627
|
{ name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
|
|
5851
6628
|
{ name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
|
|
6629
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Explicit host opt-in for CRUD authoring surfaces.' },
|
|
6630
|
+
{ name: 'authoringCapability', type: 'string | null', description: 'Public runtime capability required before CRUD and delegated table authoring can open.' },
|
|
5852
6631
|
{ name: 'afterOpen', type: '{ mode: FormOpenMode; action: string }', description: 'Emitted after a CRUD action opens.' },
|
|
5853
6632
|
{ name: 'afterSave', type: '{ id: string | number; data: unknown }', description: 'Emitted after save; CRUD refetches the list.' },
|
|
5854
6633
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6634
|
],
|
|
5856
6635
|
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.' },
|
|
6636
|
+
{ 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
6637
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6638
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6639
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6640
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6641
|
{ 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.' },
|
|
6642
|
+
{ 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.' },
|
|
6643
|
+
{ 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
6644
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6645
|
{ 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
6646
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6656,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6656
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6657
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6658
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6659
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6660
|
inputSchema: resourceBindSchema,
|
|
5882
6661
|
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.',
|
|
6662
|
+
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
6663
|
} }],
|
|
5885
6664
|
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'],
|
|
6665
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6666
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6667
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6668
|
},
|
|
@@ -6017,14 +6796,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6796
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6797
|
inputSchema: dialogHostSchema,
|
|
6019
6798
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6799
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6800
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6801
|
identityKeys: ['crudId'],
|
|
6023
6802
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6803
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'drawer-position-lifecycle-unstable', 'back-policy-invalid'],
|
|
6804
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6805
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6806
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'drawer-position-lifecycle-stable', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6807
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6808
|
submissionImpact: 'config-only',
|
|
6030
6809
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6879,8 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6879
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6880
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6881
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6882
|
+
{ 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.' },
|
|
6883
|
+
{ validatorId: 'drawer-position-lifecycle-stable', level: 'error', code: 'CRUD_DRAWER_POSITION_LIFECYCLE_STABLE', description: 'A drawer must preserve its configured viewport edge through open, remembered-size restoration, maximize and restore.' },
|
|
6104
6884
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6885
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6886
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|
|
@@ -6121,6 +6901,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6121
6901
|
'FormConfig and FieldMetadata edits must delegate to dynamic-form or metadata-editor manifests.',
|
|
6122
6902
|
'TableConfig edits must delegate to praxis-table; CRUD owns only shell orchestration and list surface binding.',
|
|
6123
6903
|
'Delete behavior is destructive and requires explicit confirmation plus backend capability support.',
|
|
6904
|
+
'Drawer position and edgeGap must round-trip without changing the configured edge during open, remembered-size restoration, maximize or restore.',
|
|
6124
6905
|
'crudContext must remain reference-stable across change detection cycles.',
|
|
6125
6906
|
],
|
|
6126
6907
|
examples: [
|
|
@@ -6132,6 +6913,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6132
6913
|
{ id: 'crud-view-drawer-readonly', request: 'Open view in a drawer and keep the form readonly.', operationId: 'surface.view.configure', target: 'action:view', params: { actionId: 'view', openMode: 'drawer', formId: 'employee-view' }, isPositive: true },
|
|
6133
6914
|
{ id: 'crud-delete-enable-confirmed', request: 'Enable delete but require confirmation.', operationId: 'delete.enabled.set', target: 'action:delete', params: { enabled: true, actionId: 'delete', requiresConfirmation: true, autoDelete: true }, isPositive: true },
|
|
6134
6915
|
{ id: 'crud-dialog-size-medium', request: 'Make CRUD modals 900px wide and remember their last state.', operationId: 'dialog.size.set', target: 'dialogHost', params: { defaultOpenMode: 'modal', modal: { width: '900px', rememberLastState: true } }, isPositive: true },
|
|
6916
|
+
{ id: 'crud-drawer-left-edge', request: 'Open CRUD forms in a left drawer with an 8px maximized inset.', operationId: 'dialog.size.set', target: 'dialogHost', params: { defaultOpenMode: 'drawer', modal: { position: { left: '0', top: '0' }, edgeGap: 8 } }, isPositive: true },
|
|
6135
6917
|
{ id: 'crud-permissions-no-delete', request: 'Hide delete when the resource capabilities do not allow it.', operationId: 'permissions.set', target: 'permissions:delete', params: { requiredCapabilities: ['delete'], denyWhenMissingCapability: true }, isPositive: true },
|
|
6136
6918
|
{ id: 'crud-delegate-form-layout', request: 'Add a new field to the create form.', operationId: 'form.childOperation.delegate', target: 'praxis-dynamic-form:field.add', params: { childComponentId: 'praxis-dynamic-form', childOperationId: 'field.add', reason: 'Form field semantics belong to dynamic-form' }, isPositive: true },
|
|
6137
6919
|
{ id: 'crud-delegate-table-column', request: 'Add a salary column to the list table.', operationId: 'form.childOperation.delegate', target: 'praxis-table:column.add', params: { childComponentId: 'praxis-table', childOperationId: 'column.add', reason: 'Table column semantics belong to praxis-table' }, isPositive: true },
|