@praxisui/crud 9.0.4-rc.35 → 9.0.4-rc.37
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/ai/component-registry.json +2 -2
- package/fesm2022/praxisui-crud.mjs +67 -15
- package/package.json +7 -7
- package/types/praxisui-crud.d.ts +10 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-09T11:18:35.694Z",
|
|
4
4
|
"packageName": "@praxisui/crud",
|
|
5
|
-
"packageVersion": "9.0.4-rc.
|
|
5
|
+
"packageVersion": "9.0.4-rc.37",
|
|
6
6
|
"sourceRegistry": "praxis-component-registry-ingestion",
|
|
7
7
|
"sourceRegistryVersion": "1.0.0",
|
|
8
8
|
"componentCount": 2,
|
|
@@ -97,6 +97,35 @@ function debugCrudLauncher(message, ...data) {
|
|
|
97
97
|
}
|
|
98
98
|
catch { }
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Captures the interaction origin before metadata, capability, or lazy-host
|
|
102
|
+
* work yields the event loop. Material can restore focus to an explicit
|
|
103
|
+
* element, which is essential when the action originated in a closing menu.
|
|
104
|
+
*/
|
|
105
|
+
function captureCrudActionFocusOrigin() {
|
|
106
|
+
if (typeof document === 'undefined') {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const active = document.activeElement;
|
|
110
|
+
if (!(active instanceof HTMLElement) || active === document.body || !active.isConnected) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return active;
|
|
114
|
+
}
|
|
115
|
+
function isRuntimeFocusOrigin(value) {
|
|
116
|
+
return typeof HTMLElement !== 'undefined'
|
|
117
|
+
&& value instanceof HTMLElement
|
|
118
|
+
&& typeof value.focus === 'function';
|
|
119
|
+
}
|
|
120
|
+
function resolveCrudRestoreFocus(configured, actionFocusOrigin) {
|
|
121
|
+
if (configured === false || typeof configured === 'string') {
|
|
122
|
+
return configured;
|
|
123
|
+
}
|
|
124
|
+
if (typeof HTMLElement !== 'undefined' && configured instanceof HTMLElement) {
|
|
125
|
+
return configured;
|
|
126
|
+
}
|
|
127
|
+
return actionFocusOrigin ?? true;
|
|
128
|
+
}
|
|
100
129
|
class CrudLauncherService {
|
|
101
130
|
router = inject(Router);
|
|
102
131
|
dialog = inject(DialogService);
|
|
@@ -115,6 +144,15 @@ class CrudLauncherService {
|
|
|
115
144
|
}
|
|
116
145
|
})();
|
|
117
146
|
async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
|
|
147
|
+
// Capture before every await below. A toolbar/row menu closes before the
|
|
148
|
+
// form host is resolved, so the browser's later activeElement is not a
|
|
149
|
+
// reliable representation of the user-facing action trigger.
|
|
150
|
+
// A responsive host can replace the menu trigger while the menu is
|
|
151
|
+
// closing. Keep the original runtime-only reference even when detached:
|
|
152
|
+
// the overlay host resolves its stable data-praxis-focus-key on close.
|
|
153
|
+
const actionFocusOrigin = isRuntimeFocusOrigin(runtime?.focusOrigin)
|
|
154
|
+
? runtime.focusOrigin
|
|
155
|
+
: captureCrudActionFocusOrigin();
|
|
118
156
|
const prefetchedDialogHost = this.isLikelyOverlayAction(action, metadata)
|
|
119
157
|
? this.loadDialogHost()
|
|
120
158
|
: undefined;
|
|
@@ -156,6 +194,8 @@ class CrudLauncherService {
|
|
|
156
194
|
resourceIdentity,
|
|
157
195
|
contextIdentity,
|
|
158
196
|
};
|
|
197
|
+
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
198
|
+
const restoreFocus = resolveCrudRestoreFocus(modalCfg.restoreFocus, actionFocusOrigin);
|
|
159
199
|
if (mode === 'drawer' && runtime?.surfaceRuntime?.push) {
|
|
160
200
|
const frameId = this.buildSurfaceFrameId(actionForLaunch);
|
|
161
201
|
const frameRef = await runtime.surfaceRuntime.push({
|
|
@@ -163,6 +203,9 @@ class CrudLauncherService {
|
|
|
163
203
|
title: this.resolveSurfaceFrameTitle(actionForLaunch, merged.metadata),
|
|
164
204
|
titleIcon: stringOrUndefined$1(actionForLaunch['icon']),
|
|
165
205
|
subtitle: this.resolveSurfaceFrameSubtitle(actionForLaunch, merged.metadata),
|
|
206
|
+
...(actionFocusOrigin
|
|
207
|
+
? { returnFocusTo: actionFocusOrigin }
|
|
208
|
+
: {}),
|
|
166
209
|
content: {
|
|
167
210
|
component: await (prefetchedDialogHost ?? this.loadDialogHost()),
|
|
168
211
|
inputs: { data: dialogData },
|
|
@@ -197,12 +240,12 @@ class CrudLauncherService {
|
|
|
197
240
|
inputs,
|
|
198
241
|
resourceIdentity,
|
|
199
242
|
contextIdentity,
|
|
243
|
+
restoreFocus,
|
|
200
244
|
onClose: drawerCallbacks?.onClose,
|
|
201
245
|
onResult: drawerCallbacks?.onResult,
|
|
202
246
|
}));
|
|
203
247
|
return { mode };
|
|
204
248
|
}
|
|
205
|
-
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
206
249
|
debugCrudLauncher('[CRUD:Launcher] opening dialog with:', {
|
|
207
250
|
action: merged.action.action,
|
|
208
251
|
formId: actionForLaunch.formId,
|
|
@@ -233,7 +276,7 @@ class CrudLauncherService {
|
|
|
233
276
|
panelClass: panelClasses,
|
|
234
277
|
backdropClass: mergedBackdropClasses,
|
|
235
278
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
236
|
-
restoreFocus
|
|
279
|
+
restoreFocus,
|
|
237
280
|
minWidth: drawerMode
|
|
238
281
|
? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
|
|
239
282
|
: '360px',
|
|
@@ -986,7 +1029,7 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
986
1029
|
'crud.emptyState.primaryAction': 'Configurar metadados',
|
|
987
1030
|
'crud.table.emptyState.initial.title': 'Sem registros em {label}',
|
|
988
1031
|
'crud.table.emptyState.initial.titleFallback': 'Nenhum registro disponível.',
|
|
989
|
-
'crud.table.emptyState.initial.
|
|
1032
|
+
'crud.table.emptyState.initial.descriptionWithToolbarAction': 'Use "{action}" na barra da tabela para adicionar o primeiro registro quando houver informações para cadastrar.',
|
|
990
1033
|
'crud.table.emptyState.filtered.title': 'Nenhum resultado encontrado.',
|
|
991
1034
|
'crud.table.emptyState.filtered.description': 'Revise os filtros ou ajuste o termo de busca.',
|
|
992
1035
|
'crud.preferences.resetSuccess': 'Overrides de CRUD redefinidos',
|
|
@@ -1009,7 +1052,7 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
1009
1052
|
'crud.emptyState.primaryAction': 'Configure metadata',
|
|
1010
1053
|
'crud.table.emptyState.initial.title': 'No records in {label}',
|
|
1011
1054
|
'crud.table.emptyState.initial.titleFallback': 'No records available.',
|
|
1012
|
-
'crud.table.emptyState.initial.
|
|
1055
|
+
'crud.table.emptyState.initial.descriptionWithToolbarAction': 'Use "{action}" in the table toolbar to add the first record when there is information to register.',
|
|
1013
1056
|
'crud.table.emptyState.filtered.title': 'No results found.',
|
|
1014
1057
|
'crud.table.emptyState.filtered.description': 'Review the filters or adjust the search term.',
|
|
1015
1058
|
'crud.preferences.resetSuccess': 'CRUD overrides reset',
|
|
@@ -4006,6 +4049,7 @@ class PraxisCrudComponent {
|
|
|
4006
4049
|
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
4007
4050
|
resourceIdentity,
|
|
4008
4051
|
contextIdentity,
|
|
4052
|
+
focusOrigin: runtimeEvent?.focusOrigin ?? null,
|
|
4009
4053
|
surfaceRuntime: this.resolveSurfaceRuntime(),
|
|
4010
4054
|
});
|
|
4011
4055
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
@@ -4467,6 +4511,17 @@ class PraxisCrudComponent {
|
|
|
4467
4511
|
const first = Array.isArray(event?.selectedRows) ? event.selectedRows[0] : null;
|
|
4468
4512
|
return this.isRecord(first) ? first : null;
|
|
4469
4513
|
}
|
|
4514
|
+
/**
|
|
4515
|
+
* Keeps the DOM trigger exclusively in transient runtime context. Discovery
|
|
4516
|
+
* payloads remain JSON-safe and can still be authored, inspected and
|
|
4517
|
+
* persisted independently from the browser that opened the surface.
|
|
4518
|
+
*/
|
|
4519
|
+
resolveRuntimeFocusOrigin(event) {
|
|
4520
|
+
const focusOrigin = event?.focusOrigin;
|
|
4521
|
+
return focusOrigin && typeof focusOrigin.focus === 'function'
|
|
4522
|
+
? focusOrigin
|
|
4523
|
+
: undefined;
|
|
4524
|
+
}
|
|
4470
4525
|
extractDiscoveryActionConfig(event) {
|
|
4471
4526
|
const config = event?.actionConfig;
|
|
4472
4527
|
return this.isRecord(config) && typeof config['resourceKey'] === 'string'
|
|
@@ -4516,6 +4571,7 @@ class PraxisCrudComponent {
|
|
|
4516
4571
|
this.snack.open(this.txWithParams('crud.surface.openFailed', 'Não foi possível abrir {title}. Tente novamente ou confirme se esse recurso continua disponível.', { title: surface.title || surface.id }), undefined, { duration: 4500 });
|
|
4517
4572
|
return true;
|
|
4518
4573
|
}
|
|
4574
|
+
const focusOrigin = this.resolveRuntimeFocusOrigin(runtimeEvent);
|
|
4519
4575
|
let openPromise;
|
|
4520
4576
|
try {
|
|
4521
4577
|
const surfaceContext = {
|
|
@@ -4534,6 +4590,7 @@ class PraxisCrudComponent {
|
|
|
4534
4590
|
action: normalizedAction,
|
|
4535
4591
|
resourcePath,
|
|
4536
4592
|
},
|
|
4593
|
+
...(focusOrigin ? { focusOrigin } : {}),
|
|
4537
4594
|
},
|
|
4538
4595
|
};
|
|
4539
4596
|
const handledInline = await this.surfaceOutlets.tryActivate(payload, surfaceContext);
|
|
@@ -4610,6 +4667,7 @@ class PraxisCrudComponent {
|
|
|
4610
4667
|
}
|
|
4611
4668
|
return false;
|
|
4612
4669
|
}
|
|
4670
|
+
const focusOrigin = this.resolveRuntimeFocusOrigin(runtimeEvent);
|
|
4613
4671
|
let openPromise;
|
|
4614
4672
|
try {
|
|
4615
4673
|
openPromise = Promise.resolve(this.surfaceService.open(payload, {
|
|
@@ -4628,6 +4686,7 @@ class PraxisCrudComponent {
|
|
|
4628
4686
|
action: normalizedAction,
|
|
4629
4687
|
resourcePath,
|
|
4630
4688
|
},
|
|
4689
|
+
...(focusOrigin ? { focusOrigin } : {}),
|
|
4631
4690
|
},
|
|
4632
4691
|
}));
|
|
4633
4692
|
}
|
|
@@ -5340,9 +5399,7 @@ class PraxisCrudComponent {
|
|
|
5340
5399
|
const title = resourceLabel
|
|
5341
5400
|
? this.txWithParams('crud.table.emptyState.initial.title', 'Sem registros em {label}', { label: resourceLabel })
|
|
5342
5401
|
: this.tx('crud.table.emptyState.initial.titleFallback', 'Nenhum registro disponível.');
|
|
5343
|
-
const actionId = String(createAction.action || createAction.id || 'create').trim() || 'create';
|
|
5344
5402
|
const actionLabel = String(createAction.label || this.getCrudActionLabel('create')).trim();
|
|
5345
|
-
const actionIcon = String(createAction.icon || 'add').trim();
|
|
5346
5403
|
config.behavior = {
|
|
5347
5404
|
...behavior,
|
|
5348
5405
|
emptyState: {
|
|
@@ -5350,15 +5407,10 @@ class PraxisCrudComponent {
|
|
|
5350
5407
|
contexts: {
|
|
5351
5408
|
initial: {
|
|
5352
5409
|
title,
|
|
5353
|
-
description: this.
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
action: actionId,
|
|
5358
|
-
icon: actionIcon,
|
|
5359
|
-
primary: true,
|
|
5360
|
-
},
|
|
5361
|
-
],
|
|
5410
|
+
description: this.txWithParams('crud.table.emptyState.initial.descriptionWithToolbarAction', 'Use "{action}" na barra da tabela para adicionar o primeiro registro quando houver informações para cadastrar.', { action: actionLabel }),
|
|
5411
|
+
// The create action is always materialized in the visible collection toolbar.
|
|
5412
|
+
// Keep the generated empty state instructional so it does not duplicate that CTA.
|
|
5413
|
+
actions: [],
|
|
5362
5414
|
},
|
|
5363
5415
|
filtered: {
|
|
5364
5416
|
title: this.tx('crud.table.emptyState.filtered.title', 'Nenhum resultado encontrado.'),
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/crud",
|
|
3
|
-
"version": "9.0.4-rc.
|
|
3
|
+
"version": "9.0.4-rc.37",
|
|
4
4
|
"description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
7
7
|
"@angular/core": "^21.0.0",
|
|
8
|
-
"@praxisui/dynamic-form": "^9.0.4-rc.
|
|
9
|
-
"@praxisui/table": "^9.0.4-rc.
|
|
10
|
-
"@praxisui/core": "^9.0.4-rc.
|
|
11
|
-
"@praxisui/dynamic-fields": "^9.0.4-rc.
|
|
12
|
-
"@praxisui/settings-panel": "^9.0.4-rc.
|
|
8
|
+
"@praxisui/dynamic-form": "^9.0.4-rc.37",
|
|
9
|
+
"@praxisui/table": "^9.0.4-rc.37",
|
|
10
|
+
"@praxisui/core": "^9.0.4-rc.37",
|
|
11
|
+
"@praxisui/dynamic-fields": "^9.0.4-rc.37",
|
|
12
|
+
"@praxisui/settings-panel": "^9.0.4-rc.37",
|
|
13
13
|
"@angular/cdk": "^21.0.0",
|
|
14
14
|
"@angular/forms": "^21.0.0",
|
|
15
15
|
"@angular/material": "^21.0.0",
|
|
16
16
|
"@angular/router": "^21.0.0",
|
|
17
|
-
"@praxisui/ai": "^9.0.4-rc.
|
|
17
|
+
"@praxisui/ai": "^9.0.4-rc.37",
|
|
18
18
|
"rxjs": "~7.8.0"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
package/types/praxisui-crud.d.ts
CHANGED
|
@@ -149,6 +149,8 @@ type CrudActionRuntimeEvent = {
|
|
|
149
149
|
selectedRows?: Record<string, unknown>[];
|
|
150
150
|
rows?: Record<string, unknown>[];
|
|
151
151
|
resourceIdentity?: MaterializedResourceIdentity | null;
|
|
152
|
+
/** Ephemeral focus origin provided by a runtime surface, never persisted. */
|
|
153
|
+
focusOrigin?: HTMLElement;
|
|
152
154
|
};
|
|
153
155
|
type CrudContextAction = {
|
|
154
156
|
action: string;
|
|
@@ -285,6 +287,12 @@ declare class PraxisCrudComponent implements OnChanges {
|
|
|
285
287
|
private extractSelectedRowFromEvent;
|
|
286
288
|
private extractSelectedRowFromBulkAction;
|
|
287
289
|
private extractSelectedRowFromToolbarAction;
|
|
290
|
+
/**
|
|
291
|
+
* Keeps the DOM trigger exclusively in transient runtime context. Discovery
|
|
292
|
+
* payloads remain JSON-safe and can still be authored, inspected and
|
|
293
|
+
* persisted independently from the browser that opened the surface.
|
|
294
|
+
*/
|
|
295
|
+
private resolveRuntimeFocusOrigin;
|
|
288
296
|
private extractDiscoveryActionConfig;
|
|
289
297
|
private resolveSelectedRowForAction;
|
|
290
298
|
private tryOpenDiscoveredCrudSurface;
|
|
@@ -356,6 +364,8 @@ interface CrudLaunchRuntime {
|
|
|
356
364
|
contextIdentity?: MaterializedResourceIdentity | null;
|
|
357
365
|
/** Active runtime drawer session. It is never persisted in CRUD metadata. */
|
|
358
366
|
surfaceRuntime?: SurfaceDrawerRef | null;
|
|
367
|
+
/** Stable runtime trigger for restoring focus after a form overlay closes. */
|
|
368
|
+
focusOrigin?: HTMLElement | null;
|
|
359
369
|
}
|
|
360
370
|
declare class CrudLauncherService {
|
|
361
371
|
private readonly router;
|