@praxisui/files-upload 8.0.0-beta.3 → 8.0.0-beta.30

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
@@ -271,6 +271,32 @@ const cfg: FilesUploadConfig = {
271
271
  - `maxFilesPerBulkValidator`
272
272
  - `maxBulkSizeValidator`
273
273
 
274
+ ## Agentic Authoring Contract
275
+
276
+ `@praxisui/files-upload` publishes an executable `ComponentAuthoringManifest` through
277
+ `PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST`.
278
+
279
+ The contract exposes typed operation families for:
280
+
281
+ - accepted types: `accept.types.set`
282
+ - single-file and batch limits: `limit.fileSize.set`, `limit.fileCount.set`
283
+ - backend upload surfaces: `endpoint.presign.set`, `endpoint.upload.set`
284
+ - upload policy: `security.policy.set`
285
+ - error message overrides: `message.error.set`
286
+ - display mode and UI flags: `display.mode.set`
287
+
288
+ Endpoint authoring intentionally changes only the canonical `baseUrl` runtime input
289
+ and the upload `strategy`. The direct, bulk and presign paths remain fixed as
290
+ `baseUrl/upload`, `baseUrl/bulk` and `baseUrl/upload/presign?filename=...` so hosts
291
+ do not create parallel endpoint semantics outside `FilesApiClient`.
292
+
293
+ Every manifest operation declares its own editable target resolver, ambiguity policy,
294
+ preconditions, validators, affected paths, effects and typed submission impact. Upload
295
+ limits and accepted types are marked as submission-affecting; endpoint and security
296
+ policy edits are marked as remote-binding changes and require confirmation; error
297
+ messages are config-only; display changes are visual-only. These categories are the
298
+ same metadata projected into `praxis-component-registry-ingestion.json`.
299
+
274
300
  ## Error handling
275
301
 
276
302
  `ErrorMapperService` translates backend error codes from `errors[0]` into user-facing messages and exposes rate limit information.
@@ -32,7 +32,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
32
32
  import * as i2 from '@angular/material/snack-bar';
33
33
  import { MatSnackBarModule } from '@angular/material/snack-bar';
34
34
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
35
- import { of, throwError, BehaviorSubject, map as map$1, startWith, retry, debounceTime, from } from 'rxjs';
35
+ import { of, throwError, BehaviorSubject, map as map$1, startWith, retry, debounceTime, from, Subscription } from 'rxjs';
36
36
  import * as i1$3 from '@praxisui/settings-panel';
37
37
  import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
38
38
  import { map, catchError, take, concatMap, toArray, tap, filter, defaultIfEmpty } from 'rxjs/operators';
@@ -6232,6 +6232,114 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6232
6232
  args: ['pendingPanel']
6233
6233
  }] } });
6234
6234
 
6235
+ class PraxisFilesUploadWidgetConfigEditor {
6236
+ inputs = null;
6237
+ widgetKey;
6238
+ uploadEditor;
6239
+ isDirty$ = new BehaviorSubject(false);
6240
+ isValid$ = new BehaviorSubject(true);
6241
+ isBusy$ = new BehaviorSubject(false);
6242
+ subscription = new Subscription();
6243
+ ngAfterViewInit() {
6244
+ if (!this.uploadEditor) {
6245
+ return;
6246
+ }
6247
+ this.initializeChildEditor();
6248
+ this.subscription.add(this.uploadEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
6249
+ this.subscription.add(this.uploadEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
6250
+ this.subscription.add(this.uploadEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
6251
+ }
6252
+ ngOnDestroy() {
6253
+ this.subscription.unsubscribe();
6254
+ }
6255
+ getSettingsValue() {
6256
+ return this.buildValue(this.uploadEditor?.getSettingsValue());
6257
+ }
6258
+ onSave() {
6259
+ return this.buildValue(this.uploadEditor?.getSettingsValue() ?? this.inputs?.config);
6260
+ }
6261
+ reset() {
6262
+ this.initializeChildEditor();
6263
+ }
6264
+ initializeChildEditor() {
6265
+ const config = this.normalizeEditorPatch(this.inputs?.config ?? {});
6266
+ this.uploadEditor?.form.patchValue(config, { emitEvent: false });
6267
+ this.uploadEditor?.isDirty$.next(false);
6268
+ }
6269
+ buildValue(rawConfig) {
6270
+ return {
6271
+ inputs: {
6272
+ ...(this.inputs ?? {}),
6273
+ config: (rawConfig ?? {}),
6274
+ filesUploadId: this.inputs?.filesUploadId ?? this.widgetKey,
6275
+ componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
6276
+ baseUrl: this.inputs?.baseUrl,
6277
+ displayMode: this.inputs?.displayMode ?? 'full',
6278
+ context: this.inputs?.context ?? null,
6279
+ enableCustomization: this.inputs?.enableCustomization ?? false,
6280
+ },
6281
+ };
6282
+ }
6283
+ normalizeEditorPatch(config) {
6284
+ const patch = structuredClone(config ?? {});
6285
+ if (Array.isArray(patch.ui?.accept)) {
6286
+ patch.ui = {
6287
+ ...patch.ui,
6288
+ accept: patch.ui.accept.join(','),
6289
+ };
6290
+ }
6291
+ if (Array.isArray(patch.ui?.list?.detailsFields)) {
6292
+ patch.ui = {
6293
+ ...patch.ui,
6294
+ list: {
6295
+ ...patch.ui.list,
6296
+ detailsFields: patch.ui.list.detailsFields.join(','),
6297
+ },
6298
+ };
6299
+ }
6300
+ if (Array.isArray(patch.options?.allowedExtensions)) {
6301
+ patch.options = {
6302
+ ...patch.options,
6303
+ allowedExtensions: patch.options.allowedExtensions.join(','),
6304
+ };
6305
+ }
6306
+ if (Array.isArray(patch.options?.acceptMimeTypes)) {
6307
+ patch.options = {
6308
+ ...patch.options,
6309
+ acceptMimeTypes: patch.options.acceptMimeTypes.join(','),
6310
+ };
6311
+ }
6312
+ return patch;
6313
+ }
6314
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisFilesUploadWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
6315
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisFilesUploadWidgetConfigEditor, isStandalone: true, selector: "praxis-files-upload-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "uploadEditor", first: true, predicate: ["uploadEditor"], descendants: true }], ngImport: i0, template: `
6316
+ <section data-testid="files-upload-widget-config-editor">
6317
+ <praxis-files-upload-config-editor #uploadEditor />
6318
+ </section>
6319
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisFilesUploadConfigEditor, selector: "praxis-files-upload-config-editor" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6320
+ }
6321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisFilesUploadWidgetConfigEditor, decorators: [{
6322
+ type: Component,
6323
+ args: [{
6324
+ selector: 'praxis-files-upload-widget-config-editor',
6325
+ standalone: true,
6326
+ imports: [CommonModule, PraxisFilesUploadConfigEditor],
6327
+ template: `
6328
+ <section data-testid="files-upload-widget-config-editor">
6329
+ <praxis-files-upload-config-editor #uploadEditor />
6330
+ </section>
6331
+ `,
6332
+ changeDetection: ChangeDetectionStrategy.OnPush,
6333
+ }]
6334
+ }], propDecorators: { inputs: [{
6335
+ type: Input
6336
+ }], widgetKey: [{
6337
+ type: Input
6338
+ }], uploadEditor: [{
6339
+ type: ViewChild,
6340
+ args: ['uploadEditor']
6341
+ }] } });
6342
+
6235
6343
  const PRAXIS_FILES_UPLOAD_COMPONENT_METADATA = {
6236
6344
  id: 'praxis-files-upload',
6237
6345
  selector: 'praxis-files-upload',
@@ -6239,6 +6347,14 @@ const PRAXIS_FILES_UPLOAD_COMPONENT_METADATA = {
6239
6347
  friendlyName: 'Praxis Files Upload',
6240
6348
  description: 'Componente para upload de arquivos com suporte a configuracao dinamica, eventos e integracao com backend.',
6241
6349
  icon: 'upload_file',
6350
+ authoringManifestRef: {
6351
+ componentId: 'praxis-files-upload',
6352
+ source: 'PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST',
6353
+ },
6354
+ configEditor: {
6355
+ component: PraxisFilesUploadWidgetConfigEditor,
6356
+ title: 'Configurar upload de arquivos',
6357
+ },
6242
6358
  inputs: [
6243
6359
  {
6244
6360
  name: 'config',
@@ -6358,6 +6474,12 @@ const PRAXIS_FILES_UPLOAD_COMPONENT_METADATA = {
6358
6474
  label: 'Arquivos pendentes',
6359
6475
  description: 'Disparado quando o estado de selecao pendente muda.',
6360
6476
  },
6477
+ {
6478
+ name: 'readinessChange',
6479
+ type: 'FilesUploadReadinessEvent',
6480
+ label: 'Prontidao',
6481
+ description: 'Disparado quando o componente alterna entre configuracao local, servidor ou fallback.',
6482
+ },
6361
6483
  {
6362
6484
  name: 'proximityChange',
6363
6485
  type: 'boolean',
@@ -6559,10 +6681,335 @@ const FILES_UPLOAD_AI_CAPABILITIES = {
6559
6681
  ],
6560
6682
  };
6561
6683
 
6684
+ const PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST = {
6685
+ schemaVersion: '1.0.0',
6686
+ componentId: 'praxis-files-upload',
6687
+ ownerPackage: '@praxisui/files-upload',
6688
+ configSchemaId: 'FilesUploadConfig',
6689
+ manifestVersion: '1.0.0',
6690
+ runtimeInputs: [
6691
+ { name: 'config', type: 'FilesUploadConfig', description: 'Upload strategy, UI, limits, backend options, bulk execution, quota/rate feedback, headers and messages.' },
6692
+ { name: 'filesUploadId', type: 'string', description: 'Stable upload id used for config persistence and telemetry correlation.' },
6693
+ { name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator when the same upload id appears more than once.' },
6694
+ { name: 'baseUrl', type: 'string', description: 'Canonical files API base URL. Direct upload uses baseUrl/upload, bulk uses baseUrl/bulk and presign uses baseUrl/upload/presign.' },
6695
+ { name: 'displayMode', type: "'full' | 'compact'", allowedValues: ['full', 'compact'], description: 'Runtime display mode input.' },
6696
+ { name: 'context', type: 'Record<string, unknown>', description: 'Host context available to metadata and event consumers.' },
6697
+ { name: 'enableCustomization', type: 'boolean', description: 'Enables the Settings Panel configuration editor.' },
6698
+ ],
6699
+ editableTargets: [
6700
+ { kind: 'acceptedTypes', resolver: 'files-accept-list', description: 'Allowed file extensions and MIME types projected to ui.accept, options.allowedExtensions and options.acceptMimeTypes.' },
6701
+ { kind: 'sizeLimit', resolver: 'max-file-size-policy', description: 'Maximum single-file size in limits.maxFileSizeBytes and backend-facing options.maxUploadSizeMb.' },
6702
+ { kind: 'fileCountLimit', resolver: 'max-files-per-bulk-policy', description: 'Maximum number of files accepted in a bulk selection.' },
6703
+ { kind: 'presignEndpoint', resolver: 'files-api-base-url-presign-contract', description: 'Presign operation endpoint derived from baseUrl + /upload/presign; arbitrary per-operation URLs are not accepted.' },
6704
+ { kind: 'uploadEndpoint', resolver: 'files-api-base-url-upload-contract', description: 'Direct and bulk upload endpoints derived from baseUrl + /upload and /bulk.' },
6705
+ { kind: 'securityPolicy', resolver: 'upload-security-policy', description: 'Strict validation, virus scanning, conflict policy, target directory, quotas, rate limit and contextual headers.' },
6706
+ { kind: 'errorMessages', resolver: 'upload-error-code-message', description: 'Host-provided message overrides under messages.errors keyed by backend error code.' },
6707
+ { kind: 'display', resolver: 'files-upload-display-mode-and-ui', description: 'Runtime displayMode plus non-backend visual UI flags.' },
6708
+ ],
6709
+ operations: [
6710
+ {
6711
+ operationId: 'accept.types.set',
6712
+ title: 'Set accepted file types',
6713
+ scope: 'global',
6714
+ targetKind: 'acceptedTypes',
6715
+ target: { kind: 'acceptedTypes', resolver: 'files-accept-list', ambiguityPolicy: 'fail', required: false },
6716
+ inputSchema: {
6717
+ type: 'object',
6718
+ required: ['accept'],
6719
+ properties: {
6720
+ accept: { type: 'array', items: { type: 'string' } },
6721
+ allowedExtensions: { type: 'array', items: { type: 'string' } },
6722
+ acceptMimeTypes: { type: 'array', items: { type: 'string' } },
6723
+ },
6724
+ },
6725
+ effects: [
6726
+ { kind: 'set-value', path: 'ui.accept' },
6727
+ { kind: 'set-value', path: 'options.allowedExtensions' },
6728
+ { kind: 'set-value', path: 'options.acceptMimeTypes' },
6729
+ ],
6730
+ destructive: false,
6731
+ requiresConfirmation: false,
6732
+ validators: ['file-type-list-explicit', 'file-type-entry-valid', 'backend-accept-options-consistent', 'editor-runtime-round-trip'],
6733
+ affectedPaths: ['ui.accept', 'options.allowedExtensions', 'options.acceptMimeTypes'],
6734
+ submissionImpact: 'affects-submission',
6735
+ preconditions: ['config-initialized'],
6736
+ },
6737
+ {
6738
+ operationId: 'limit.fileSize.set',
6739
+ title: 'Set max file size',
6740
+ scope: 'global',
6741
+ targetKind: 'sizeLimit',
6742
+ target: { kind: 'sizeLimit', resolver: 'max-file-size-policy', ambiguityPolicy: 'fail', required: false },
6743
+ inputSchema: {
6744
+ type: 'object',
6745
+ required: ['maxFileSizeBytes'],
6746
+ properties: {
6747
+ maxFileSizeBytes: { type: 'number' },
6748
+ maxUploadSizeMb: { type: 'number' },
6749
+ },
6750
+ },
6751
+ effects: [
6752
+ { kind: 'set-value', path: 'limits.maxFileSizeBytes' },
6753
+ { kind: 'set-value', path: 'options.maxUploadSizeMb' },
6754
+ ],
6755
+ destructive: false,
6756
+ requiresConfirmation: false,
6757
+ validators: ['size-limit-numeric', 'size-limit-within-platform-policy', 'size-limit-client-backend-consistent', 'editor-runtime-round-trip'],
6758
+ affectedPaths: ['limits.maxFileSizeBytes', 'options.maxUploadSizeMb'],
6759
+ submissionImpact: 'affects-submission',
6760
+ preconditions: ['config-initialized'],
6761
+ },
6762
+ {
6763
+ operationId: 'limit.fileCount.set',
6764
+ title: 'Set max file count',
6765
+ scope: 'global',
6766
+ targetKind: 'fileCountLimit',
6767
+ target: { kind: 'fileCountLimit', resolver: 'max-files-per-bulk-policy', ambiguityPolicy: 'fail', required: false },
6768
+ inputSchema: {
6769
+ type: 'object',
6770
+ required: ['maxFilesPerBulk'],
6771
+ properties: {
6772
+ maxFilesPerBulk: { type: 'number' },
6773
+ maxBulkSizeBytes: { type: 'number' },
6774
+ },
6775
+ },
6776
+ effects: [
6777
+ { kind: 'set-value', path: 'limits.maxFilesPerBulk' },
6778
+ { kind: 'set-value', path: 'limits.maxBulkSizeBytes' },
6779
+ ],
6780
+ destructive: false,
6781
+ requiresConfirmation: false,
6782
+ validators: ['file-count-limit-numeric', 'bulk-size-limit-numeric', 'bulk-limit-within-platform-policy', 'editor-runtime-round-trip'],
6783
+ affectedPaths: ['limits.maxFilesPerBulk', 'limits.maxBulkSizeBytes'],
6784
+ submissionImpact: 'affects-submission',
6785
+ preconditions: ['config-initialized'],
6786
+ },
6787
+ {
6788
+ operationId: 'endpoint.presign.set',
6789
+ title: 'Set presign endpoint',
6790
+ scope: 'global',
6791
+ targetKind: 'presignEndpoint',
6792
+ target: { kind: 'presignEndpoint', resolver: 'files-api-base-url-presign-contract', ambiguityPolicy: 'fail', required: false },
6793
+ inputSchema: {
6794
+ type: 'object',
6795
+ required: ['baseUrl'],
6796
+ properties: {
6797
+ baseUrl: { type: 'string' },
6798
+ strategy: { enum: ['direct', 'presign', 'auto'] },
6799
+ allowCrossOriginPresignedTarget: { type: 'boolean' },
6800
+ },
6801
+ },
6802
+ effects: [{
6803
+ kind: 'compile-domain-patch',
6804
+ handler: 'files-upload-presign-base-url',
6805
+ handlerContract: {
6806
+ reads: ['baseUrl', 'strategy'],
6807
+ writes: ['baseUrl', 'strategy'],
6808
+ identityKeys: ['baseUrl'],
6809
+ inputSchema: {
6810
+ type: 'object',
6811
+ required: ['baseUrl'],
6812
+ properties: {
6813
+ baseUrl: { type: 'string' },
6814
+ strategy: { enum: ['direct', 'presign', 'auto'] },
6815
+ allowCrossOriginPresignedTarget: { type: 'boolean' },
6816
+ },
6817
+ },
6818
+ failureModes: ['unsafe-url', 'non-praxis-backend-surface', 'presign-path-override', 'cross-origin-presigned-fields-leak'],
6819
+ description: 'Validates the files API base URL and preserves the canonical POST baseUrl/upload/presign?filename=... contract; arbitrary presign URLs are rejected.',
6820
+ },
6821
+ }],
6822
+ destructive: true,
6823
+ requiresConfirmation: true,
6824
+ validators: ['endpoint-url-safe', 'endpoint-uses-praxis-backend-surface', 'presign-endpoint-fixed-contract', 'presign-cross-origin-fields-safe', 'security-change-confirmed'],
6825
+ affectedPaths: ['baseUrl', 'strategy'],
6826
+ submissionImpact: 'affects-remote-binding',
6827
+ preconditions: ['config-initialized', 'confirmation-collected'],
6828
+ },
6829
+ {
6830
+ operationId: 'endpoint.upload.set',
6831
+ title: 'Set upload endpoint',
6832
+ scope: 'global',
6833
+ targetKind: 'uploadEndpoint',
6834
+ target: { kind: 'uploadEndpoint', resolver: 'files-api-base-url-upload-contract', ambiguityPolicy: 'fail', required: false },
6835
+ inputSchema: {
6836
+ type: 'object',
6837
+ required: ['baseUrl'],
6838
+ properties: {
6839
+ baseUrl: { type: 'string' },
6840
+ strategy: { enum: ['direct', 'presign', 'auto'] },
6841
+ allowCrossOriginPresignedTarget: { type: 'boolean' },
6842
+ },
6843
+ },
6844
+ effects: [{
6845
+ kind: 'compile-domain-patch',
6846
+ handler: 'files-upload-direct-base-url',
6847
+ handlerContract: {
6848
+ reads: ['baseUrl', 'strategy'],
6849
+ writes: ['baseUrl', 'strategy'],
6850
+ identityKeys: ['baseUrl'],
6851
+ inputSchema: {
6852
+ type: 'object',
6853
+ required: ['baseUrl'],
6854
+ properties: {
6855
+ baseUrl: { type: 'string' },
6856
+ strategy: { enum: ['direct', 'presign', 'auto'] },
6857
+ allowCrossOriginPresignedTarget: { type: 'boolean' },
6858
+ },
6859
+ },
6860
+ failureModes: ['unsafe-url', 'non-praxis-backend-surface', 'upload-path-override', 'bulk-path-override'],
6861
+ description: 'Validates the files API base URL and preserves the canonical POST baseUrl/upload and POST baseUrl/bulk contracts.',
6862
+ },
6863
+ }],
6864
+ destructive: true,
6865
+ requiresConfirmation: true,
6866
+ validators: ['endpoint-url-safe', 'endpoint-uses-praxis-backend-surface', 'upload-endpoint-fixed-contract', 'bulk-endpoint-fixed-contract', 'security-change-confirmed'],
6867
+ affectedPaths: ['baseUrl', 'strategy'],
6868
+ submissionImpact: 'affects-remote-binding',
6869
+ preconditions: ['config-initialized', 'confirmation-collected'],
6870
+ },
6871
+ {
6872
+ operationId: 'security.policy.set',
6873
+ title: 'Set upload security policy',
6874
+ scope: 'global',
6875
+ targetKind: 'securityPolicy',
6876
+ target: { kind: 'securityPolicy', resolver: 'upload-security-policy', ambiguityPolicy: 'fail', required: false },
6877
+ inputSchema: {
6878
+ type: 'object',
6879
+ properties: {
6880
+ strictValidation: { type: 'boolean' },
6881
+ enableVirusScanning: { type: 'boolean' },
6882
+ defaultConflictPolicy: { enum: ['ERROR', 'SKIP', 'OVERWRITE', 'MAKE_UNIQUE', 'RENAME'] },
6883
+ targetDirectory: { type: 'string' },
6884
+ showQuotaWarnings: { type: 'boolean' },
6885
+ blockOnExceed: { type: 'boolean' },
6886
+ autoRetryOn429: { type: 'boolean' },
6887
+ showBannerOn429: { type: 'boolean' },
6888
+ maxAutoRetry: { type: 'number' },
6889
+ baseBackoffMs: { type: 'number' },
6890
+ tenantHeader: { type: 'string' },
6891
+ userHeader: { type: 'string' },
6892
+ },
6893
+ },
6894
+ effects: [
6895
+ { kind: 'merge-object', path: 'options' },
6896
+ { kind: 'merge-object', path: 'quotas' },
6897
+ { kind: 'merge-object', path: 'rateLimit' },
6898
+ { kind: 'merge-object', path: 'headers' },
6899
+ ],
6900
+ destructive: true,
6901
+ requiresConfirmation: true,
6902
+ validators: ['security-change-confirmed', 'security-policy-compatible', 'target-directory-safe', 'conflict-policy-valid', 'quota-rate-policy-valid', 'header-names-safe', 'editor-runtime-round-trip'],
6903
+ affectedPaths: ['options.strictValidation', 'options.enableVirusScanning', 'options.defaultConflictPolicy', 'options.targetDirectory', 'quotas', 'rateLimit', 'headers'],
6904
+ submissionImpact: 'config-only',
6905
+ preconditions: ['config-initialized', 'confirmation-collected'],
6906
+ },
6907
+ {
6908
+ operationId: 'message.error.set',
6909
+ title: 'Set error message override',
6910
+ scope: 'global',
6911
+ targetKind: 'errorMessages',
6912
+ target: { kind: 'errorMessages', resolver: 'upload-error-code-message', ambiguityPolicy: 'fail', required: false },
6913
+ inputSchema: {
6914
+ type: 'object',
6915
+ required: ['code', 'message'],
6916
+ properties: {
6917
+ code: { type: 'string' },
6918
+ message: { type: 'string' },
6919
+ },
6920
+ },
6921
+ effects: [{ kind: 'merge-object', path: 'messages.errors' }],
6922
+ destructive: false,
6923
+ requiresConfirmation: false,
6924
+ validators: ['error-code-known', 'error-message-i18n-compatible', 'message-not-empty', 'editor-runtime-round-trip'],
6925
+ affectedPaths: ['messages.errors'],
6926
+ submissionImpact: 'config-only',
6927
+ preconditions: ['config-initialized'],
6928
+ },
6929
+ {
6930
+ operationId: 'display.mode.set',
6931
+ title: 'Set display mode',
6932
+ scope: 'global',
6933
+ targetKind: 'display',
6934
+ target: { kind: 'display', resolver: 'files-upload-display-mode-and-ui', ambiguityPolicy: 'fail', required: false },
6935
+ inputSchema: {
6936
+ type: 'object',
6937
+ properties: {
6938
+ displayMode: { enum: ['full', 'compact'] },
6939
+ dense: { type: 'boolean' },
6940
+ showDropzone: { type: 'boolean' },
6941
+ showProgress: { type: 'boolean' },
6942
+ manualUpload: { type: 'boolean' },
6943
+ showMetadataForm: { type: 'boolean' },
6944
+ showConflictPolicySelector: { type: 'boolean' },
6945
+ },
6946
+ },
6947
+ effects: [
6948
+ { kind: 'set-value', path: 'displayMode' },
6949
+ { kind: 'merge-object', path: 'ui' },
6950
+ ],
6951
+ destructive: false,
6952
+ requiresConfirmation: false,
6953
+ validators: ['display-mode-valid', 'display-ui-compatible', 'editor-runtime-round-trip'],
6954
+ affectedPaths: ['displayMode', 'ui.dense', 'ui.showDropzone', 'ui.showProgress', 'ui.manualUpload', 'ui.showMetadataForm', 'ui.showConflictPolicySelector'],
6955
+ submissionImpact: 'visual-only',
6956
+ preconditions: ['config-initialized'],
6957
+ },
6958
+ ],
6959
+ validators: [
6960
+ { validatorId: 'file-type-list-explicit', level: 'error', code: 'PFU001', description: 'Accepted type edits must provide a non-empty explicit list and may not rely on wildcard-only policy.' },
6961
+ { validatorId: 'file-type-entry-valid', level: 'error', code: 'PFU002', description: 'Each accepted type must be a valid extension, exact MIME type or controlled subtype wildcard.' },
6962
+ { validatorId: 'backend-accept-options-consistent', level: 'error', code: 'PFU003', description: 'ui.accept hints must remain consistent with options.allowedExtensions and options.acceptMimeTypes when backend validation is configured.' },
6963
+ { validatorId: 'size-limit-numeric', level: 'error', code: 'PFU004', description: 'Single file size limits must be positive numeric byte and MB values.' },
6964
+ { validatorId: 'size-limit-within-platform-policy', level: 'error', code: 'PFU005', description: 'Single file size limits must not exceed the platform upload policy for the selected backend surface.' },
6965
+ { validatorId: 'size-limit-client-backend-consistent', level: 'error', code: 'PFU006', description: 'limits.maxFileSizeBytes and options.maxUploadSizeMb must describe the same effective maximum when both are present.' },
6966
+ { validatorId: 'file-count-limit-numeric', level: 'error', code: 'PFU007', description: 'Bulk file count limits must be positive integers.' },
6967
+ { validatorId: 'bulk-size-limit-numeric', level: 'error', code: 'PFU008', description: 'Bulk size limits must be positive numeric byte values when provided.' },
6968
+ { validatorId: 'bulk-limit-within-platform-policy', level: 'error', code: 'PFU009', description: 'Bulk count and aggregate size limits must not exceed backend batch policy.' },
6969
+ { validatorId: 'endpoint-url-safe', level: 'error', code: 'PFU010', description: 'Endpoint edits must use a safe absolute or approved relative baseUrl and reject javascript:, data:, credentials-in-URL and path traversal.' },
6970
+ { validatorId: 'endpoint-uses-praxis-backend-surface', level: 'error', code: 'PFU011', description: 'Endpoint edits must resolve to the approved Praxis files backend surface for the host environment.' },
6971
+ { validatorId: 'presign-endpoint-fixed-contract', level: 'error', code: 'PFU012', description: 'Presign authoring may change baseUrl but must preserve POST baseUrl/upload/presign?filename=... as the runtime contract.' },
6972
+ { validatorId: 'upload-endpoint-fixed-contract', level: 'error', code: 'PFU013', description: 'Direct upload authoring may change baseUrl but must preserve POST baseUrl/upload as the runtime contract.' },
6973
+ { validatorId: 'bulk-endpoint-fixed-contract', level: 'error', code: 'PFU014', description: 'Bulk upload authoring may change baseUrl but must preserve POST baseUrl/bulk as the runtime contract.' },
6974
+ { validatorId: 'presign-cross-origin-fields-safe', level: 'error', code: 'PFU015', description: 'Cross-origin presigned upload targets must not receive Praxis metadata/options fields unless explicitly approved by the presign response contract.' },
6975
+ { validatorId: 'security-change-confirmed', level: 'error', code: 'PFU016', description: 'Endpoint and security policy edits require explicit confirmation before patch compilation.' },
6976
+ { validatorId: 'security-policy-compatible', level: 'error', code: 'PFU017', description: 'Strict validation, virus scanning, conflict policy, target directory, quota and rate-limit settings must be compatible with backend effective config.' },
6977
+ { validatorId: 'target-directory-safe', level: 'error', code: 'PFU018', description: 'Target directory values must be normalized backend paths and must reject traversal or absolute filesystem paths.' },
6978
+ { validatorId: 'conflict-policy-valid', level: 'error', code: 'PFU019', description: 'Default conflict policy must be one of ERROR, SKIP, OVERWRITE, MAKE_UNIQUE or RENAME.' },
6979
+ { validatorId: 'quota-rate-policy-valid', level: 'error', code: 'PFU020', description: 'Quota and rate-limit UX settings must use non-negative retry counts/backoffs and cannot silently bypass backend enforcement.' },
6980
+ { validatorId: 'header-names-safe', level: 'warning', code: 'PFU021', description: 'Contextual header names must be explicit HTTP token names and must not override authorization, cookie or content-type headers.' },
6981
+ { validatorId: 'error-code-known', level: 'warning', code: 'PFU022', description: 'Error message overrides should target known backend error catalog codes or documented host-specific extensions.' },
6982
+ { validatorId: 'error-message-i18n-compatible', level: 'warning', code: 'PFU023', description: 'User-facing error messages must remain compatible with the host i18n/editor flow and not expose raw technical exception text.' },
6983
+ { validatorId: 'message-not-empty', level: 'error', code: 'PFU024', description: 'Error message overrides must be non-empty user-facing text.' },
6984
+ { validatorId: 'display-mode-valid', level: 'error', code: 'PFU025', description: 'Display mode must be full or compact.' },
6985
+ { validatorId: 'display-ui-compatible', level: 'warning', code: 'PFU026', description: 'Display changes must keep progress, manual upload and metadata form affordances reachable for the selected mode.' },
6986
+ { validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PFU027', description: 'Settings Panel editor, AI adapter snapshot and runtime inputs must round-trip config, baseUrl and displayMode without changing upload behavior.' },
6987
+ ],
6988
+ roundTripRequirements: [
6989
+ 'Accepted type authoring must keep UI hints and backend-facing validation options aligned; ui.accept alone is not the canonical server policy.',
6990
+ 'Endpoint authoring changes only the canonical files API baseUrl and strategy; direct, bulk and presign paths remain fixed by FilesApiClient.',
6991
+ 'Security-affecting edits require confirmation and must round-trip through Settings Panel persistence without weakening strict validation, virus scanning, quota or rate-limit policy by omission.',
6992
+ 'Error message overrides remain keyed by backend error catalog code and must stay user-facing/i18n-compatible.',
6993
+ 'Display authoring may change displayMode and UI flags, but must not mutate backend options or upload submission payload.',
6994
+ ],
6995
+ examples: [
6996
+ { id: 'accept-pdf-images', request: 'Accept only PDF and image uploads.', operationId: 'accept.types.set', params: { accept: ['application/pdf', 'image/*'], allowedExtensions: ['pdf', 'png', 'jpg', 'jpeg'], acceptMimeTypes: ['application/pdf', 'image/png', 'image/jpeg'] }, isPositive: true },
6997
+ { id: 'max-five-mb', request: 'Limit each file to 5 MB.', operationId: 'limit.fileSize.set', params: { maxFileSizeBytes: 5242880, maxUploadSizeMb: 5 }, isPositive: true },
6998
+ { id: 'max-three-files', request: 'Allow at most three files per upload batch.', operationId: 'limit.fileCount.set', params: { maxFilesPerBulk: 3 }, isPositive: true },
6999
+ { id: 'set-presign-base-url', request: 'Use the official files API for presigned uploads.', operationId: 'endpoint.presign.set', params: { baseUrl: 'https://api.praxis.example/files', strategy: 'presign' }, isPositive: true },
7000
+ { id: 'set-direct-base-url', request: 'Use the official files API for direct uploads.', operationId: 'endpoint.upload.set', params: { baseUrl: 'https://api.praxis.example/files', strategy: 'direct' }, isPositive: true },
7001
+ { id: 'reject-unsafe-endpoint', request: 'Set upload endpoint to javascript:alert(1).', operationId: 'endpoint.upload.set', params: { baseUrl: 'javascript:alert(1)', strategy: 'direct' }, isPositive: false },
7002
+ { id: 'tighten-security-policy', request: 'Enable strict validation and virus scanning for uploads.', operationId: 'security.policy.set', params: { strictValidation: true, enableVirusScanning: true, defaultConflictPolicy: 'ERROR' }, isPositive: true },
7003
+ { id: 'reject-unconfirmed-security-policy', request: 'Disable strict validation without confirmation.', operationId: 'security.policy.set', params: { strictValidation: false, enableVirusScanning: false }, isPositive: false },
7004
+ { id: 'quota-message', request: 'Show a friendly message when quota is exceeded.', operationId: 'message.error.set', params: { code: 'QUOTA_EXCEEDED', message: 'Upload quota reached for this period.' }, isPositive: true },
7005
+ { id: 'compact-display', request: 'Use compact upload display and keep progress visible.', operationId: 'display.mode.set', params: { displayMode: 'compact', dense: true, showProgress: true }, isPositive: true },
7006
+ ],
7007
+ };
7008
+
6562
7009
  // Export all public API from subdirectories
6563
7010
 
6564
7011
  /**
6565
7012
  * Generated bundle index. Do not edit.
6566
7013
  */
6567
7014
 
6568
- export { BulkUploadResultStatus, ErrorCode, ErrorMapperService, FILES_UPLOAD_AI_CAPABILITIES, FILES_UPLOAD_EN_US, FILES_UPLOAD_ERROR_MESSAGES, FILES_UPLOAD_PT_BR, FILES_UPLOAD_TEXTS, FilesApiClient, PRAXIS_FILES_UPLOAD_COMPONENT_METADATA, PdxFilesUploadFieldComponent, PraxisFilesUpload, PraxisFilesUploadConfigEditor, PresignedUploaderService, ScanStatus, TRANSLATE_LIKE, acceptValidator, createPraxisFilesUploadI18nConfig, getPrimaryErrorItem, isErrorEnvelope, maxBulkSizeValidator, maxFileSizeValidator, maxFilesPerBulkValidator, providePraxisFilesUploadI18n, providePraxisFilesUploadMetadata, resolvePraxisFilesUploadText };
7015
+ export { BulkUploadResultStatus, ErrorCode, ErrorMapperService, FILES_UPLOAD_AI_CAPABILITIES, FILES_UPLOAD_EN_US, FILES_UPLOAD_ERROR_MESSAGES, FILES_UPLOAD_PT_BR, FILES_UPLOAD_TEXTS, FilesApiClient, PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST, PRAXIS_FILES_UPLOAD_COMPONENT_METADATA, PdxFilesUploadFieldComponent, PraxisFilesUpload, PraxisFilesUploadConfigEditor, PraxisFilesUploadWidgetConfigEditor, PresignedUploaderService, ScanStatus, TRANSLATE_LIKE, acceptValidator, createPraxisFilesUploadI18nConfig, getPrimaryErrorItem, isErrorEnvelope, maxBulkSizeValidator, maxFileSizeValidator, maxFilesPerBulkValidator, providePraxisFilesUploadI18n, providePraxisFilesUploadMetadata, resolvePraxisFilesUploadText };
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, OnDestroy, NgZone, OnInit, OnChanges, AfterViewInit, EventEmitter, ViewContainerRef, ChangeDetectorRef, DestroyRef, Provider } from '@angular/core';
3
3
  import { FormControl, ValidatorFn, FormGroup, FormBuilder } from '@angular/forms';
4
- import { PraxisI18nDictionary, PraxisI18nConfig, PraxisI18nService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapability, AiCapabilityCatalog } from '@praxisui/core';
4
+ import { PraxisI18nDictionary, PraxisI18nConfig, PraxisI18nService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapability, AiCapabilityCatalog, ComponentAuthoringManifest } from '@praxisui/core';
5
5
  import { SettingsPanelService, SettingsValueProvider } from '@praxisui/settings-panel';
6
6
  import { HttpClient, HttpEvent, HttpHeaders } from '@angular/common/http';
7
7
  import { Observable, BehaviorSubject } from 'rxjs';
@@ -802,6 +802,39 @@ declare class PraxisFilesUploadConfigEditor implements OnInit, SettingsValueProv
802
802
  static ɵcmp: i0.ɵɵComponentDeclaration<PraxisFilesUploadConfigEditor, "praxis-files-upload-config-editor", never, {}, {}, never, never, true, never>;
803
803
  }
804
804
 
805
+ interface PraxisFilesUploadWidgetEditorInputs {
806
+ config?: FilesUploadConfig | null;
807
+ filesUploadId?: string;
808
+ componentInstanceId?: string;
809
+ baseUrl?: string;
810
+ displayMode?: 'full' | 'compact';
811
+ context?: Record<string, unknown> | null;
812
+ enableCustomization?: boolean;
813
+ [key: string]: unknown;
814
+ }
815
+ interface PraxisFilesUploadWidgetEditorValue {
816
+ inputs: PraxisFilesUploadWidgetEditorInputs;
817
+ }
818
+ declare class PraxisFilesUploadWidgetConfigEditor implements SettingsValueProvider, AfterViewInit, OnDestroy {
819
+ inputs: PraxisFilesUploadWidgetEditorInputs | null;
820
+ widgetKey?: string;
821
+ uploadEditor?: PraxisFilesUploadConfigEditor;
822
+ readonly isDirty$: BehaviorSubject<boolean>;
823
+ readonly isValid$: BehaviorSubject<boolean>;
824
+ readonly isBusy$: BehaviorSubject<boolean>;
825
+ private readonly subscription;
826
+ ngAfterViewInit(): void;
827
+ ngOnDestroy(): void;
828
+ getSettingsValue(): PraxisFilesUploadWidgetEditorValue;
829
+ onSave(): PraxisFilesUploadWidgetEditorValue;
830
+ reset(): void;
831
+ private initializeChildEditor;
832
+ private buildValue;
833
+ private normalizeEditorPatch;
834
+ static ɵfac: i0.ɵɵFactoryDeclaration<PraxisFilesUploadWidgetConfigEditor, never>;
835
+ static ɵcmp: i0.ɵɵComponentDeclaration<PraxisFilesUploadWidgetConfigEditor, "praxis-files-upload-widget-config-editor", never, { "inputs": { "alias": "inputs"; "required": false; }; "widgetKey": { "alias": "widgetKey"; "required": false; }; }, {}, never, never, true, never>;
836
+ }
837
+
805
838
  declare const FILES_UPLOAD_EN_US: {
806
839
  'praxis.filesUpload.editor.summary.ariaLabel': string;
807
840
  'praxis.filesUpload.editor.summary.title': string;
@@ -1465,5 +1498,7 @@ interface CapabilityCatalog extends AiCapabilityCatalog {
1465
1498
  }
1466
1499
  declare const FILES_UPLOAD_AI_CAPABILITIES: CapabilityCatalog;
1467
1500
 
1468
- export { BulkUploadResultStatus, ErrorCode, ErrorMapperService, FILES_UPLOAD_AI_CAPABILITIES, FILES_UPLOAD_EN_US, FILES_UPLOAD_ERROR_MESSAGES, FILES_UPLOAD_PT_BR, FILES_UPLOAD_TEXTS, FilesApiClient, PRAXIS_FILES_UPLOAD_COMPONENT_METADATA, PdxFilesUploadFieldComponent, PraxisFilesUpload, PraxisFilesUploadConfigEditor, PresignedUploaderService, ScanStatus, TRANSLATE_LIKE, acceptValidator, createPraxisFilesUploadI18nConfig, getPrimaryErrorItem, isErrorEnvelope, maxBulkSizeValidator, maxFileSizeValidator, maxFilesPerBulkValidator, providePraxisFilesUploadI18n, providePraxisFilesUploadMetadata, resolvePraxisFilesUploadText };
1469
- export type { ApiErrorEnvelope, ApiErrorItem, ApiResponseStatus, ApiSuccessEnvelope, BulkConfig, BulkUploadFile, BulkUploadFileResult, BulkUploadOptions, BulkUploadResponse, BulkUploadResponseData, Capability, CapabilityCatalog, CapabilityCategory, EffectiveUploadConfig, ErrorLike, ErrorResponse, FileMetadata, FileUploadOptions, FilesUploadBackendOptionsConfig, FilesUploadConfig, FilesUploadExecutionConfig, FilesUploadHeadersConfig, FilesUploadMessagesConfig, FilesUploadQuotaFeedbackConfig, FilesUploadRateLimitFeedbackConfig, FilesUploadReadinessEvent, FilesUploadReadySource, FilesUploadReadyState, FilesUploadStrategy, FilesUploadTexts, FilesUploadValidationConfig, FilesUploadVisualConfig, MappedError, Metadata, PendingFilesState, PraxisFilesUploadI18nOptions, PresignResponse, QuotasConfig, RateLimitConfig, RateLimitInfo, ServerMessages, TranslateLike, UploadOptions, UploadProgressEvent, UploadResponse, UploadResponseData, UploadStartEvent, ValueKind };
1501
+ declare const PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST: ComponentAuthoringManifest;
1502
+
1503
+ export { BulkUploadResultStatus, ErrorCode, ErrorMapperService, FILES_UPLOAD_AI_CAPABILITIES, FILES_UPLOAD_EN_US, FILES_UPLOAD_ERROR_MESSAGES, FILES_UPLOAD_PT_BR, FILES_UPLOAD_TEXTS, FilesApiClient, PRAXIS_FILES_UPLOAD_AUTHORING_MANIFEST, PRAXIS_FILES_UPLOAD_COMPONENT_METADATA, PdxFilesUploadFieldComponent, PraxisFilesUpload, PraxisFilesUploadConfigEditor, PraxisFilesUploadWidgetConfigEditor, PresignedUploaderService, ScanStatus, TRANSLATE_LIKE, acceptValidator, createPraxisFilesUploadI18nConfig, getPrimaryErrorItem, isErrorEnvelope, maxBulkSizeValidator, maxFileSizeValidator, maxFilesPerBulkValidator, providePraxisFilesUploadI18n, providePraxisFilesUploadMetadata, resolvePraxisFilesUploadText };
1504
+ export type { ApiErrorEnvelope, ApiErrorItem, ApiResponseStatus, ApiSuccessEnvelope, BulkConfig, BulkUploadFile, BulkUploadFileResult, BulkUploadOptions, BulkUploadResponse, BulkUploadResponseData, Capability, CapabilityCatalog, CapabilityCategory, EffectiveUploadConfig, ErrorLike, ErrorResponse, FileMetadata, FileUploadOptions, FilesUploadBackendOptionsConfig, FilesUploadConfig, FilesUploadExecutionConfig, FilesUploadHeadersConfig, FilesUploadMessagesConfig, FilesUploadQuotaFeedbackConfig, FilesUploadRateLimitFeedbackConfig, FilesUploadReadinessEvent, FilesUploadReadySource, FilesUploadReadyState, FilesUploadStrategy, FilesUploadTexts, FilesUploadValidationConfig, FilesUploadVisualConfig, MappedError, Metadata, PendingFilesState, PraxisFilesUploadI18nOptions, PraxisFilesUploadWidgetEditorInputs, PraxisFilesUploadWidgetEditorValue, PresignResponse, QuotasConfig, RateLimitConfig, RateLimitInfo, ServerMessages, TranslateLike, UploadOptions, UploadProgressEvent, UploadResponse, UploadResponseData, UploadStartEvent, ValueKind };
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/files-upload",
3
- "version": "8.0.0-beta.3",
3
+ "version": "8.0.0-beta.30",
4
4
  "description": "File upload components and services for Praxis UI with presigned and direct strategies, quotas and rate-limit handling.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/cdk": "^20.0.0",
8
8
  "@angular/core": "^20.0.0",
9
9
  "@angular/material": "^20.0.0",
10
- "@praxisui/core": "^8.0.0-beta.3",
11
- "@praxisui/dynamic-fields": "^8.0.0-beta.3",
12
- "@praxisui/settings-panel": "^8.0.0-beta.3"
10
+ "@praxisui/core": "^8.0.0-beta.30",
11
+ "@praxisui/dynamic-fields": "^8.0.0-beta.30",
12
+ "@praxisui/settings-panel": "^8.0.0-beta.30",
13
+ "@angular/forms": "^20.0.0",
14
+ "@angular/router": "^20.0.0",
15
+ "@praxisui/ai": "^8.0.0-beta.30",
16
+ "rxjs": "~7.8.0"
13
17
  },
14
18
  "dependencies": {
15
19
  "tslib": "^2.3.0"