@praxisui/crud 9.0.4-rc.36 → 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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-08T10:35:56.871Z",
3
+ "generatedAt": "2026-08-09T11:18:35.694Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "9.0.4-rc.36",
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: modalCfg.restoreFocus ?? true,
279
+ restoreFocus,
237
280
  minWidth: drawerMode
238
281
  ? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
239
282
  : '360px',
@@ -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
  }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "9.0.4-rc.36",
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.36",
9
- "@praxisui/table": "^9.0.4-rc.36",
10
- "@praxisui/core": "^9.0.4-rc.36",
11
- "@praxisui/dynamic-fields": "^9.0.4-rc.36",
12
- "@praxisui/settings-panel": "^9.0.4-rc.36",
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.36",
17
+ "@praxisui/ai": "^9.0.4-rc.37",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -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;