@praxisui/crud 10.0.0-rc.40 → 10.0.0-rc.42

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 CHANGED
@@ -333,15 +333,16 @@ guided states without host CSS or a parallel empty-state component.
333
333
 
334
334
  `PraxisCrudComponent` recognizes the canonical optional `duplicate-draft`
335
335
  operation published by `praxis-metadata-starter`. When a selected row exposes
336
- `_links['duplicate-draft']`, the runtime:
336
+ `_links['duplicate-draft']` (and is not denied by runtime availability), the runtime:
337
337
 
338
- 1. invokes the non-mutating draft endpoint;
339
- 2. unwraps the returned draft DTO;
340
- 3. opens the canonical `create` form with that DTO as `initialValue`;
341
- 4. refreshes the table only after the user saves the new record.
338
+ 1. invokes the non-mutating `POST /{resource}/{id}/duplicate-draft` endpoint;
339
+ 2. unwraps the returned draft DTO (`RestApiResponse.data`);
340
+ 3. opens the canonical `create` form (`mode='create'`) with that DTO as `initialValue`, contextual copy dialog/submit labels (`crud.duplicate.dialogTitleWithSource` / `crud.duplicate.dialogTitleFallback`), `contextIdentity` set to the source record identity (`resourceIdentity` stays unset because a new record is being created), and `focusOrigin` preserved for focus restoration;
341
+ 4. in `openMode='route'`, hands off the create seed through `@praxisui/crud`'s `CrudRouteFormHandoffService` (`NavigationExtras.info` opaque marker claimed once by `createCrudFormRoute` / `CrudRouteFormComponent`, scoped by `resourcePath`, `formId`, and route URL, with `canDeactivate` dirty-exit guard and `crud.route.unavailable` status when reloaded without a fresh handoff) without serializing personal or domain fields into URL query parameters, `history.state`, or persistent storage;
342
+ 5. refreshes the table only after the user saves the new record via `POST /{resource}`.
342
343
 
343
344
  The host does not need to call `HttpClient`, rebuild create schema URLs, or own
344
- a second launcher. Absence of the HATEOAS link keeps the operation unavailable;
345
+ a second launcher. Absence of the HATEOAS link or an explicit `allowed: false` availability decision keeps the operation unavailable;
345
346
  the runtime does not infer permission from labels, tags, or frontend flags.
346
347
 
347
348
  ## Governed Two-Phase Bulk Operations
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-26T18:09:20.136Z",
3
+ "generatedAt": "2026-11-01T13:59:56.156Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "10.0.0-rc.40",
5
+ "packageVersion": "10.0.0-rc.42",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -0,0 +1,72 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injector, DestroyRef, signal, ViewChild, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgComponentOutlet } from '@angular/common';
4
+ import { Router, ActivatedRoute } from '@angular/router';
5
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
+ import { EnterpriseRuntimeContextService, PraxisI18nService, providePraxisI18nConfig } from '@praxisui/core';
7
+ import { d as CrudRouteFormHandoffService, e as DynamicFormDialogHostComponent, y as translateCrudRuntimeText, q as crudRouteContextKey, g as PRAXIS_CRUD_RUNTIME_I18N_CONFIG } from './praxisui-crud-praxisui-crud-CndT0h40.mjs';
8
+ import { MAT_DIALOG_DATA } from '@angular/material/dialog';
9
+
10
+ /** Created only by createCrudFormRoute; owns the transferred form instance. */
11
+ class CrudRouteFormComponent {
12
+ router = inject(Router);
13
+ route = inject(ActivatedRoute);
14
+ parentInjector = inject(Injector);
15
+ destroyRef = inject(DestroyRef);
16
+ context = inject(EnterpriseRuntimeContextService, { optional: true });
17
+ i18n = inject(PraxisI18nService);
18
+ data = inject(CrudRouteFormHandoffService).claim(this.route.snapshot.data['crudFormTarget']);
19
+ available = signal(!!this.data, ...(ngDevMode ? [{ debugName: "available" }] : /* istanbul ignore next */ []));
20
+ host = DynamicFormDialogHostComponent;
21
+ unavailableText = translateCrudRuntimeText(this.i18n, 'crud.route.unavailable', 'Esta cópia não está mais disponível. Volte à lista e prepare uma nova cópia.');
22
+ formInjector = Injector.create({
23
+ parent: this.parentInjector,
24
+ providers: [{ provide: MAT_DIALOG_DATA, useValue: {
25
+ ...this.data,
26
+ presentation: 'route',
27
+ closeRoute: () => this.router.navigateByUrl(this.data.returnUrl),
28
+ } }],
29
+ });
30
+ outlet;
31
+ constructor() {
32
+ // A route owns its context after handoff; it must not depend on the destroyed source.
33
+ const contextKey = crudRouteContextKey(this.context?.snapshot);
34
+ this.context?.contextChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(current => {
35
+ if (crudRouteContextKey(current) !== contextKey)
36
+ this.available.set(false);
37
+ });
38
+ this.destroyRef.onDestroy(() => this.formInjector.destroy());
39
+ }
40
+ canLeave() {
41
+ return this.outlet?.componentInstance?.canLeaveForm() ?? true;
42
+ }
43
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudRouteFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
44
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CrudRouteFormComponent, isStandalone: true, selector: "praxis-crud-route-form", providers: [providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG)], viewQueries: [{ propertyName: "outlet", first: true, predicate: NgComponentOutlet, descendants: true }], ngImport: i0, template: `
45
+ @if (available()) {
46
+ <ng-container *ngComponentOutlet="host; injector: formInjector"></ng-container>
47
+ } @else {
48
+ <p role="status">{{ unavailableText }}</p>
49
+ }
50
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
51
+ }
52
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudRouteFormComponent, decorators: [{
53
+ type: Component,
54
+ args: [{
55
+ selector: 'praxis-crud-route-form',
56
+ imports: [NgComponentOutlet],
57
+ providers: [providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG)],
58
+ changeDetection: ChangeDetectionStrategy.OnPush,
59
+ template: `
60
+ @if (available()) {
61
+ <ng-container *ngComponentOutlet="host; injector: formInjector"></ng-container>
62
+ } @else {
63
+ <p role="status">{{ unavailableText }}</p>
64
+ }
65
+ `,
66
+ }]
67
+ }], ctorParameters: () => [], propDecorators: { outlet: [{
68
+ type: ViewChild,
69
+ args: [NgComponentOutlet]
70
+ }] } });
71
+
72
+ export { CrudRouteFormComponent };