@acorex/core 21.0.2-next.4 → 21.0.2-next.40

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.
Files changed (32) hide show
  1. package/fesm2022/acorex-core-components.mjs +3 -3
  2. package/fesm2022/acorex-core-config.mjs +3 -3
  3. package/fesm2022/acorex-core-date-time.mjs +105 -91
  4. package/fesm2022/acorex-core-date-time.mjs.map +1 -1
  5. package/fesm2022/acorex-core-events.mjs +3 -3
  6. package/fesm2022/acorex-core-file.mjs +667 -92
  7. package/fesm2022/acorex-core-file.mjs.map +1 -1
  8. package/fesm2022/acorex-core-format.mjs +19 -19
  9. package/fesm2022/acorex-core-full-screen.mjs +4 -4
  10. package/fesm2022/acorex-core-full-screen.mjs.map +1 -1
  11. package/fesm2022/acorex-core-icon.mjs +3 -3
  12. package/fesm2022/acorex-core-image.mjs +3 -3
  13. package/fesm2022/acorex-core-locale.mjs +30 -13
  14. package/fesm2022/acorex-core-locale.mjs.map +1 -1
  15. package/fesm2022/acorex-core-network.mjs +4 -4
  16. package/fesm2022/acorex-core-network.mjs.map +1 -1
  17. package/fesm2022/acorex-core-pipes.mjs +3 -3
  18. package/fesm2022/acorex-core-platform.mjs +4 -4
  19. package/fesm2022/acorex-core-platform.mjs.map +1 -1
  20. package/fesm2022/acorex-core-storage.mjs +9 -9
  21. package/fesm2022/acorex-core-translation.mjs +68 -24
  22. package/fesm2022/acorex-core-translation.mjs.map +1 -1
  23. package/fesm2022/acorex-core-utils.mjs +3 -78
  24. package/fesm2022/acorex-core-utils.mjs.map +1 -1
  25. package/fesm2022/acorex-core-validation.mjs +40 -40
  26. package/fesm2022/acorex-core-z-index.mjs +3 -3
  27. package/package.json +3 -2
  28. package/types/acorex-core-date-time.d.ts +24 -20
  29. package/types/acorex-core-file.d.ts +268 -40
  30. package/types/acorex-core-locale.d.ts +5 -0
  31. package/types/acorex-core-translation.d.ts +8 -1
  32. package/types/acorex-core-utils.d.ts +0 -22
@@ -1,5 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, NgModule, inject, DOCUMENT, PLATFORM_ID } from '@angular/core';
2
+ import { Injectable, InjectionToken, inject, NgModule, DOCUMENT, isDevMode } from '@angular/core';
3
+ import * as i2 from '@acorex/core/validation';
4
+ import { AXValidationService, AXValidationRegistryService, AXValidationModule } from '@acorex/core/validation';
3
5
  import * as i1 from '@acorex/core/format';
4
6
  import { AXFormatModule } from '@acorex/core/format';
5
7
 
@@ -29,24 +31,585 @@ class AXFileSizeFormatter {
29
31
  const i = Math.floor(Math.log(size) / Math.log(k));
30
32
  return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
31
33
  }
32
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileSizeFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
33
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileSizeFormatter }); }
34
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileSizeFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
35
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileSizeFormatter }); }
34
36
  }
35
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileSizeFormatter, decorators: [{
37
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileSizeFormatter, decorators: [{
36
38
  type: Injectable
37
39
  }] });
38
40
 
41
+ /** Human-readable file size (binary units, e.g. `41.5 MB`). */
42
+ function formatFileSizeBytes(bytes) {
43
+ if (!Number.isFinite(bytes) || bytes < 0) {
44
+ return '0 B';
45
+ }
46
+ if (bytes === 0) {
47
+ return '0 B';
48
+ }
49
+ const k = 1024;
50
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
51
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
52
+ const value = bytes / Math.pow(k, i);
53
+ if (i === 0) {
54
+ return `${bytes} B`;
55
+ }
56
+ const digits = value >= 100 ? 0 : value >= 10 ? 1 : 2;
57
+ return `${value.toFixed(digits)} ${units[i]}`;
58
+ }
59
+
60
+ /** Resolves clipboard/plain text from a copy result. */
61
+ function resolveFileCopyText(result) {
62
+ if (result == null) {
63
+ return null;
64
+ }
65
+ if (typeof result === 'string') {
66
+ const trimmed = result.trim();
67
+ return trimmed.length > 0 ? trimmed : null;
68
+ }
69
+ const trimmed = result.text?.trim();
70
+ return trimmed && trimmed.length > 0 ? trimmed : null;
71
+ }
72
+
73
+ /** Contributes {@link AXFileType} entries (multi provider). */
74
+ class AXFileTypeInfoProvider {
75
+ }
76
+ const AX_FILE_TYPE_INFO_PROVIDER = new InjectionToken('AX_FILE_TYPE_INFO_PROVIDER');
77
+ function provideFileTypeInfoProvider(provider) {
78
+ return {
79
+ provide: AX_FILE_TYPE_INFO_PROVIDER,
80
+ useClass: provider,
81
+ multi: true,
82
+ };
83
+ }
84
+
85
+ /** Merges type-level validations with extension overrides (extension wins per field). */
86
+ function mergeFileValidations(base, override) {
87
+ if (!override) {
88
+ return { ...base };
89
+ }
90
+ return {
91
+ mimeTypes: override.mimeTypes ?? base.mimeTypes,
92
+ minSize: override.minSize ?? base.minSize,
93
+ maxSize: override.maxSize ?? base.maxSize,
94
+ rules: override.rules ?? base.rules,
95
+ };
96
+ }
97
+ /** Resolved validations for a type + optional extension. */
98
+ function resolveFileValidations(type, extension) {
99
+ return mergeFileValidations(type.validations ?? {}, extension?.validations);
100
+ }
101
+ /** True when no mime/size/rules are defined on the type. */
102
+ function isEmptyFileValidations(validations) {
103
+ if (!validations) {
104
+ return true;
105
+ }
106
+ return ((validations.mimeTypes?.length ?? 0) === 0 &&
107
+ validations.minSize == null &&
108
+ validations.maxSize == null &&
109
+ (validations.rules?.length ?? 0) === 0);
110
+ }
111
+ /**
112
+ * Extension-only mode: empty type `validations` and a non-empty `extensions` list.
113
+ * Files must match a listed extension; rules come from that extension entry.
114
+ */
115
+ function isExtensionsOnlyType(type) {
116
+ return isEmptyFileValidations(type.validations) && (type.extensions?.length ?? 0) > 0;
117
+ }
118
+ /** File extension from name (lowercase, no dot). */
119
+ function getFileExtension(fileName) {
120
+ const parts = fileName.trim().split('.');
121
+ return parts.length > 1 ? (parts.pop()?.toLowerCase() ?? '') : '';
122
+ }
123
+ /** Matching extension entry for a file within a type, if any. */
124
+ function findFileTypeExtension(file, type) {
125
+ const ext = getFileExtension(file.name);
126
+ if (!ext || !type.extensions?.length) {
127
+ return undefined;
128
+ }
129
+ return type.extensions.find((e) => e.name.toLowerCase() === ext);
130
+ }
131
+
132
+ /** Maps {@link AXFileValidations} to executable validation entries. */
133
+ function fileValidationsToRules(validations) {
134
+ const rules = [];
135
+ if (validations.mimeTypes?.length) {
136
+ rules.push({
137
+ rule: 'fileMimeType',
138
+ options: { allowed: validations.mimeTypes },
139
+ });
140
+ }
141
+ if (validations.minSize != null) {
142
+ rules.push({
143
+ rule: 'fileMinSize',
144
+ options: { minSize: validations.minSize },
145
+ });
146
+ }
147
+ if (validations.maxSize != null) {
148
+ rules.push({
149
+ rule: 'fileMaxSize',
150
+ options: { maxSize: validations.maxSize },
151
+ });
152
+ }
153
+ if (validations.rules?.length) {
154
+ rules.push(...validations.rules);
155
+ }
156
+ return rules;
157
+ }
158
+ /** Failed validation results; empty array means the file is valid. */
159
+ async function validateFileWithValidations(validation, file, validations) {
160
+ const entries = fileValidationsToRules(validations);
161
+ const errors = [];
162
+ for (const { rule, options } of entries) {
163
+ const result = await validation.validate(rule, file, options);
164
+ if (!result.result) {
165
+ errors.push(result);
166
+ }
167
+ }
168
+ return errors;
169
+ }
170
+ /** Validates a file against a type; extension rules override when the file name matches. */
171
+ async function validateFileAgainstType(validation, file, type, extension) {
172
+ const matchedExtension = extension ?? findFileTypeExtension(file, type);
173
+ if (isExtensionsOnlyType(type) && !matchedExtension) {
174
+ const ext = getFileExtension(file.name);
175
+ return [
176
+ {
177
+ rule: 'fileExtension',
178
+ result: false,
179
+ message: ext ? `Extension ".${ext}" is not allowed.` : 'File must have an allowed extension.',
180
+ value: ext,
181
+ },
182
+ ];
183
+ }
184
+ const resolved = resolveFileValidations(type, matchedExtension);
185
+ return validateFileWithValidations(validation, file, resolved);
186
+ }
187
+
188
+ class AXFileTypeRegistryService {
189
+ constructor() {
190
+ this.validation = inject(AXValidationService);
191
+ this.providers = inject(AX_FILE_TYPE_INFO_PROVIDER, { optional: true }) ?? [];
192
+ this.registeredTypes = [];
193
+ this.cache = null;
194
+ }
195
+ /** Registers or replaces catalog entries (e.g. from feature bootstrap). */
196
+ registerTypes(types) {
197
+ for (const type of types) {
198
+ const index = this.registeredTypes.findIndex((t) => t.name === type.name);
199
+ if (index >= 0) {
200
+ this.registeredTypes[index] = type;
201
+ }
202
+ else {
203
+ this.registeredTypes.push(type);
204
+ }
205
+ }
206
+ this.invalidateCache();
207
+ }
208
+ invalidateCache() {
209
+ this.cache = null;
210
+ }
211
+ async resolveTypes() {
212
+ if (this.cache) {
213
+ return this.cache;
214
+ }
215
+ const byName = new Map();
216
+ if (this.providers.length) {
217
+ const batches = await Promise.all(this.providers.map((p) => p.items()));
218
+ for (const batch of batches) {
219
+ for (const type of batch) {
220
+ if (!byName.has(type.name)) {
221
+ byName.set(type.name, type);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ for (const type of this.registeredTypes) {
227
+ byName.set(type.name, type);
228
+ }
229
+ this.cache = [...byName.values()];
230
+ return this.cache;
231
+ }
232
+ async getTypes() {
233
+ return this.resolveTypes();
234
+ }
235
+ async get(name) {
236
+ const types = await this.resolveTypes();
237
+ return types.find((t) => t.name === name);
238
+ }
239
+ async accept(typeNames) {
240
+ const names = typeNames ? (Array.isArray(typeNames) ? typeNames : [typeNames]) : undefined;
241
+ const types = await this.resolveTypes();
242
+ const selected = names?.length ? types.filter((t) => names.includes(t.name)) : types;
243
+ if (names?.length && selected.length === 0) {
244
+ return '';
245
+ }
246
+ const parts = new Set();
247
+ for (const type of selected) {
248
+ type.validations?.mimeTypes?.forEach((m) => parts.add(m));
249
+ type.extensions?.forEach((ext) => {
250
+ ext.validations?.mimeTypes?.forEach((m) => parts.add(m));
251
+ parts.add(`.${ext.name.replace(/^\./, '')}`);
252
+ });
253
+ }
254
+ return [...parts].join(',');
255
+ }
256
+ async matchType(file) {
257
+ const types = await this.resolveTypes();
258
+ const mime = file.type.trim().toLowerCase();
259
+ return types.find((type) => {
260
+ const patterns = [
261
+ ...(type.validations?.mimeTypes ?? []),
262
+ ...(type.extensions?.flatMap((e) => e.validations?.mimeTypes ?? []) ?? []),
263
+ ];
264
+ if (!patterns.length) {
265
+ return false;
266
+ }
267
+ return patterns.some((pattern) => this.matchesMime(mime, pattern.trim().toLowerCase()));
268
+ });
269
+ }
270
+ async validate(file, typeName) {
271
+ const type = await this.get(typeName);
272
+ if (!type) {
273
+ return [
274
+ {
275
+ rule: 'fileType',
276
+ result: false,
277
+ message: `Unknown file type: ${typeName}`,
278
+ value: typeName,
279
+ },
280
+ ];
281
+ }
282
+ const extension = findFileTypeExtension(file, type);
283
+ return validateFileAgainstType(this.validation, file, type, extension);
284
+ }
285
+ async validateAgainstTypes(file, typeNames) {
286
+ const names = Array.isArray(typeNames) ? typeNames : [typeNames];
287
+ let lastErrors = [];
288
+ for (const name of names) {
289
+ const errors = await this.validate(file, name);
290
+ if (errors.length === 0) {
291
+ return [];
292
+ }
293
+ lastErrors = errors;
294
+ }
295
+ return lastErrors;
296
+ }
297
+ async validateMany(files, typeNames) {
298
+ const accepted = [];
299
+ const rejected = [];
300
+ for (const file of files) {
301
+ const errors = await this.validateAgainstTypes(file, typeNames);
302
+ if (errors.length === 0) {
303
+ accepted.push(file);
304
+ }
305
+ else {
306
+ rejected.push({ file, errors });
307
+ }
308
+ }
309
+ return { accepted, rejected };
310
+ }
311
+ matchesMime(mime, pattern) {
312
+ if (!pattern || pattern === '*/*' || pattern === '*') {
313
+ return true;
314
+ }
315
+ if (pattern.endsWith('/*')) {
316
+ const category = pattern.slice(0, -2);
317
+ return mime.startsWith(`${category}/`) || (mime === '' && category === 'application');
318
+ }
319
+ return mime === pattern;
320
+ }
321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileTypeRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
322
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileTypeRegistryService, providedIn: 'root' }); }
323
+ }
324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileTypeRegistryService, decorators: [{
325
+ type: Injectable,
326
+ args: [{ providedIn: 'root' }]
327
+ }] });
328
+
329
+ const MB$4 = 1024 * 1024;
330
+ const AX_AUDIO_FILE_TYPE = {
331
+ name: 'audio',
332
+ title: 'Audio',
333
+ icon: 'fa-light fa-music ax-text-amber-500',
334
+ validations: {
335
+ mimeTypes: ['audio/*'],
336
+ minSize: 1,
337
+ maxSize: 50 * MB$4,
338
+ },
339
+ extensions: [
340
+ { name: 'mp3', title: 'MP3' },
341
+ { name: 'wav', title: 'WAV', validations: { maxSize: 30 * MB$4 } },
342
+ { name: 'ogg', title: 'OGG' },
343
+ { name: 'm4a', title: 'M4A' },
344
+ ],
345
+ };
346
+ class AXAudioFileTypeProvider extends AXFileTypeInfoProvider {
347
+ items() {
348
+ return Promise.resolve([AX_AUDIO_FILE_TYPE]);
349
+ }
350
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXAudioFileTypeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
351
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXAudioFileTypeProvider }); }
352
+ }
353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXAudioFileTypeProvider, decorators: [{
354
+ type: Injectable
355
+ }] });
356
+
357
+ const MB$3 = 1024 * 1024;
358
+ /** Extension-only: no type `validations` — only listed extensions are allowed. */
359
+ const AX_DOCUMENT_FILE_TYPE = {
360
+ name: 'document',
361
+ title: 'Document',
362
+ icon: 'fa-light fa-file-lines ax-text-red-500',
363
+ extensions: [{ name: 'pdf', title: 'PDF', validations: { mimeTypes: ['application/pdf'], maxSize: 5 * MB$3 } }],
364
+ };
365
+ class AXDocumentFileTypeProvider extends AXFileTypeInfoProvider {
366
+ items() {
367
+ return Promise.resolve([AX_DOCUMENT_FILE_TYPE]);
368
+ }
369
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXDocumentFileTypeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
370
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXDocumentFileTypeProvider }); }
371
+ }
372
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXDocumentFileTypeProvider, decorators: [{
373
+ type: Injectable
374
+ }] });
375
+
376
+ const MB$2 = 1024 * 1024;
377
+ const AX_FILE_FILE_TYPE = {
378
+ name: 'file',
379
+ title: 'File',
380
+ icon: 'fa-light fa-file ax-text-neutral-500',
381
+ validations: {
382
+ mimeTypes: ['application/*', 'text/*'],
383
+ minSize: 1,
384
+ maxSize: 100 * MB$2,
385
+ },
386
+ extensions: [
387
+ { name: 'pdf', title: 'PDF', validations: { mimeTypes: ['application/pdf'], maxSize: 25 * MB$2 } },
388
+ { name: 'doc', title: 'Word' },
389
+ { name: 'docx', title: 'Word' },
390
+ { name: 'txt', title: 'Text', validations: { mimeTypes: ['text/plain'], maxSize: 5 * MB$2 } },
391
+ { name: 'zip', title: 'ZIP', validations: { maxSize: 50 * MB$2 } },
392
+ ],
393
+ };
394
+ class AXFileFileTypeProvider extends AXFileTypeInfoProvider {
395
+ items() {
396
+ return Promise.resolve([AX_FILE_FILE_TYPE]);
397
+ }
398
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileFileTypeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
399
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileFileTypeProvider }); }
400
+ }
401
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileFileTypeProvider, decorators: [{
402
+ type: Injectable
403
+ }] });
404
+
405
+ const MB$1 = 1024 * 1024;
406
+ const AX_IMAGE_FILE_TYPE = {
407
+ name: 'image',
408
+ title: 'Image',
409
+ icon: 'fa-light fa-image ax-text-purple-500',
410
+ validations: {
411
+ mimeTypes: ['image/*'],
412
+ minSize: 1,
413
+ maxSize: 100 * MB$1,
414
+ },
415
+ extensions: [
416
+ { name: 'jpg', title: 'JPEG' },
417
+ { name: 'jpeg', title: 'JPEG', validations: { maxSize: 5 * MB$1 } },
418
+ { name: 'png', title: 'PNG' },
419
+ { name: 'gif', title: 'GIF' },
420
+ { name: 'webp', title: 'WebP' },
421
+ { name: 'svg', title: 'SVG', validations: { maxSize: 2 * MB$1 } },
422
+ ],
423
+ };
424
+ class AXImageFileTypeProvider extends AXFileTypeInfoProvider {
425
+ items() {
426
+ return Promise.resolve([AX_IMAGE_FILE_TYPE]);
427
+ }
428
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXImageFileTypeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
429
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXImageFileTypeProvider }); }
430
+ }
431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXImageFileTypeProvider, decorators: [{
432
+ type: Injectable
433
+ }] });
434
+
435
+ const MB = 1024 * 1024;
436
+ const AX_VIDEO_FILE_TYPE = {
437
+ name: 'video',
438
+ title: 'Video',
439
+ icon: 'fa-light fa-video ax-text-blue-500',
440
+ validations: {
441
+ mimeTypes: ['video/*'],
442
+ minSize: 1,
443
+ maxSize: 500 * MB,
444
+ },
445
+ extensions: [
446
+ { name: 'mp4', title: 'MP4' },
447
+ { name: 'webm', title: 'WebM', validations: { maxSize: 200 * MB } },
448
+ { name: 'ogg', title: 'OGG' },
449
+ ],
450
+ };
451
+ class AXVideoFileTypeProvider extends AXFileTypeInfoProvider {
452
+ items() {
453
+ return Promise.resolve([AX_VIDEO_FILE_TYPE]);
454
+ }
455
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXVideoFileTypeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
456
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXVideoFileTypeProvider }); }
457
+ }
458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXVideoFileTypeProvider, decorators: [{
459
+ type: Injectable
460
+ }] });
461
+
462
+ /** Built-in file type provider classes (one logical type per provider). */
463
+ const AX_BUILTIN_FILE_TYPE_PROVIDERS = [
464
+ AXImageFileTypeProvider,
465
+ AXVideoFileTypeProvider,
466
+ AXAudioFileTypeProvider,
467
+ AXFileFileTypeProvider,
468
+ AXDocumentFileTypeProvider,
469
+ ];
470
+ /** Registers all built-in file type providers (image, video, audio, file, document). */
471
+ function provideBuiltinFileTypeProviders() {
472
+ return AX_BUILTIN_FILE_TYPE_PROVIDERS.map((provider) => provideFileTypeInfoProvider(provider));
473
+ }
474
+ /** @deprecated Use {@link provideBuiltinFileTypeProviders}. */
475
+ const provideDefaultFileTypeProviders = provideBuiltinFileTypeProviders;
476
+
477
+ class AXFileMimeTypeValidationRule {
478
+ get name() {
479
+ return 'fileMimeType';
480
+ }
481
+ async validate(value, options) {
482
+ const mime = (value instanceof File || value instanceof Blob ? value.type : '').trim().toLowerCase();
483
+ const allowed = options.allowed ?? [];
484
+ const valid = allowed.length === 0 ||
485
+ allowed.some((pattern) => this.matchesMime(mime, pattern.trim().toLowerCase()));
486
+ return {
487
+ rule: this.name,
488
+ result: valid,
489
+ message: valid ? '' : (options.message ?? 'File type is not allowed.'),
490
+ value: mime,
491
+ };
492
+ }
493
+ matchesMime(mime, pattern) {
494
+ if (!pattern || pattern === '*/*' || pattern === '*') {
495
+ return true;
496
+ }
497
+ if (pattern.endsWith('/*')) {
498
+ const category = pattern.slice(0, -2);
499
+ return mime.startsWith(`${category}/`) || (mime === '' && category === 'application');
500
+ }
501
+ return mime === pattern;
502
+ }
503
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMimeTypeValidationRule, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
504
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMimeTypeValidationRule }); }
505
+ }
506
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMimeTypeValidationRule, decorators: [{
507
+ type: Injectable
508
+ }] });
509
+
510
+ class AXFileMinSizeValidationRule {
511
+ get name() {
512
+ return 'fileMinSize';
513
+ }
514
+ async validate(value, options) {
515
+ const size = value instanceof File || value instanceof Blob ? value.size : 0;
516
+ const valid = size >= options.minSize;
517
+ const limitLabel = formatFileSizeBytes(options.minSize);
518
+ return {
519
+ rule: this.name,
520
+ result: valid,
521
+ message: valid
522
+ ? ''
523
+ : (options.message ?? `File is too small. Minimum size is ${limitLabel}.`),
524
+ value: size,
525
+ minSize: options.minSize,
526
+ };
527
+ }
528
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMinSizeValidationRule, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
529
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMinSizeValidationRule }); }
530
+ }
531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMinSizeValidationRule, decorators: [{
532
+ type: Injectable
533
+ }] });
534
+
535
+ class AXFileMaxSizeValidationRule {
536
+ get name() {
537
+ return 'fileMaxSize';
538
+ }
539
+ async validate(value, options) {
540
+ const size = value instanceof File || value instanceof Blob ? value.size : 0;
541
+ const valid = size <= options.maxSize;
542
+ const limitLabel = formatFileSizeBytes(options.maxSize);
543
+ return {
544
+ rule: this.name,
545
+ result: valid,
546
+ message: valid
547
+ ? ''
548
+ : (options.message ??
549
+ `File is too large. Maximum size is ${limitLabel}.`),
550
+ value: size,
551
+ maxSize: options.maxSize,
552
+ };
553
+ }
554
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMaxSizeValidationRule, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
555
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMaxSizeValidationRule }); }
556
+ }
557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileMaxSizeValidationRule, decorators: [{
558
+ type: Injectable
559
+ }] });
560
+
561
+ /** Validation rule classes to register with {@link AXValidationModule}. */
562
+ const AX_FILE_VALIDATION_RULES = [
563
+ AXFileMimeTypeValidationRule,
564
+ AXFileMinSizeValidationRule,
565
+ AXFileMaxSizeValidationRule,
566
+ ];
567
+ /** Registers file validation rules with {@link AXValidationRegistryService}. */
568
+ function provideFileValidationRules() {
569
+ return {
570
+ provide: 'AXValidationModuleFactory',
571
+ useFactory: (registry) => () => {
572
+ registry.register(...AX_FILE_VALIDATION_RULES);
573
+ },
574
+ deps: [AXValidationRegistryService],
575
+ multi: true,
576
+ };
577
+ }
578
+ /**
579
+ * Registers validation rules and optional built-in file type providers.
580
+ * {@link AXFileTypeRegistryService} and {@link AXFileService} are `providedIn: 'root'`.
581
+ */
582
+ function provideFileTypeSystem() {
583
+ return [provideFileValidationRules(), ...provideBuiltinFileTypeProviders()];
584
+ }
585
+
39
586
  class AXFileModule {
40
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
41
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: AXFileModule, imports: [i1.AXFormatModule] }); }
42
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileModule, providers: [AXFileSizeFormatter], imports: [AXFormatModule.forChild({ formatters: [AXFileSizeFormatter] })] }); }
587
+ static forRoot(config) {
588
+ const includeDefaults = config?.includeDefaultFileTypes !== false;
589
+ return {
590
+ ngModule: AXFileModule,
591
+ providers: [
592
+ provideFileValidationRules(),
593
+ ...(includeDefaults ? provideDefaultFileTypeProviders() : []),
594
+ ...(config?.fileTypeProviders ?? []),
595
+ ],
596
+ };
597
+ }
598
+ static forChild(config) {
599
+ return AXFileModule.forRoot(config);
600
+ }
601
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
602
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXFileModule, imports: [i1.AXFormatModule, i2.AXValidationModule] }); }
603
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileModule, providers: [AXFileSizeFormatter], imports: [AXFormatModule.forChild({ formatters: [AXFileSizeFormatter] }),
604
+ AXValidationModule.forChild({ rules: [...AX_FILE_VALIDATION_RULES] })] }); }
43
605
  }
44
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileModule, decorators: [{
606
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileModule, decorators: [{
45
607
  type: NgModule,
46
608
  args: [{
47
- imports: [AXFormatModule.forChild({ formatters: [AXFileSizeFormatter] })],
48
- exports: [],
49
- declarations: [],
609
+ imports: [
610
+ AXFormatModule.forChild({ formatters: [AXFileSizeFormatter] }),
611
+ AXValidationModule.forChild({ rules: [...AX_FILE_VALIDATION_RULES] }),
612
+ ],
50
613
  providers: [AXFileSizeFormatter],
51
614
  }]
52
615
  }] });
@@ -54,112 +617,124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
54
617
  class AXFileService {
55
618
  constructor() {
56
619
  this.document = inject(DOCUMENT);
57
- this.platformID = inject(PLATFORM_ID);
58
- /**
59
- * Gets the size of a file, blob, or base64 string.
60
- *
61
- * @param file - The file, blob, or base64 string to get size for
62
- * @returns number | false - The size in bytes or false if not a base64 string
63
- */
64
- this.blobToBase64 = (blob) => {
65
- if (blob instanceof Blob) {
66
- return new Promise((resolve, reject) => {
67
- const reader = new FileReader();
68
- reader.onerror = reject;
69
- reader.onload = () => {
70
- resolve(reader.result);
71
- };
72
- reader.readAsDataURL(blob);
73
- });
74
- }
75
- else {
76
- return Promise.reject('input is not blob');
77
- }
78
- };
620
+ this.fileTypes = inject(AXFileTypeRegistryService);
621
+ }
622
+ async getFileTypes() {
623
+ return this.fileTypes.getTypes();
624
+ }
625
+ async getFileType(name) {
626
+ return this.fileTypes.get(name);
627
+ }
628
+ async getAcceptAttribute(fileType) {
629
+ return this.fileTypes.accept(fileType);
630
+ }
631
+ async matchFileType(file) {
632
+ return this.fileTypes.matchType(file);
633
+ }
634
+ validate(file, fileType) {
635
+ return this.fileTypes.validateAgainstTypes(file, fileType);
636
+ }
637
+ validateMany(files, fileType) {
638
+ return this.fileTypes.validateMany(files, fileType);
79
639
  }
80
640
  /**
81
- * Opens a file selection dialog and returns the selected files.
82
- *
83
- * @param options - Optional configuration for file selection
84
- * @param options.accept - File types to accept (e.g., 'image/*', '.pdf')
85
- * @param options.multiple - Whether to allow multiple file selection
86
- * @returns Promise<File[]> - Promise that resolves with the selected files
641
+ * Opens a file selection dialog.
642
+ * With `fileType`, returns files that pass catalog validation (`accept` defaults from the catalog).
643
+ * Without `fileType`, returns all selected files (legacy `accept` / `multiple` only).
87
644
  */
88
- choose(options) {
645
+ async chooseValidated(options) {
646
+ const resolved = await this.resolveChooseOptions(options);
647
+ const files = await this.openFileDialog(resolved);
648
+ if (files.length === 0) {
649
+ return { accepted: [], rejected: [] };
650
+ }
651
+ if (options.fileType) {
652
+ return this.validateMany(files, options.fileType);
653
+ }
654
+ return { accepted: files, rejected: [] };
655
+ }
656
+ async choose(options) {
657
+ const { accepted } = await this.chooseValidated(options);
658
+ return accepted;
659
+ }
660
+ blobToBase64(blob) {
661
+ if (!(blob instanceof Blob)) {
662
+ return Promise.reject(new Error('input is not blob'));
663
+ }
89
664
  return new Promise((resolve, reject) => {
90
- options = Object.assign({ multiple: false }, options);
91
- const input = this.document.createElement('input');
92
- input.style.display = 'none';
93
- this.document.body.appendChild(input);
94
- input.type = 'file';
95
- input.accept = options.accept || '';
96
- input.multiple = options.multiple || false;
97
- //
98
- const onError = (e) => {
99
- reject(e);
100
- this.document.body.removeChild(input);
101
- input.remove();
102
- input.removeEventListener('change', onChange);
103
- input.removeEventListener('error', onError);
104
- };
105
- //
106
- const onChange = () => {
107
- resolve(Array.from(input.files || []));
108
- this.document.body.removeChild(input);
109
- input.remove();
110
- input.removeEventListener('change', onChange);
111
- input.removeEventListener('error', onError);
112
- };
113
- //
114
- input.addEventListener('change', onChange);
115
- input.addEventListener('error', onError);
116
- input.click();
665
+ const reader = new FileReader();
666
+ reader.onerror = () => reject(reader.error);
667
+ reader.onload = () => resolve(reader.result);
668
+ reader.readAsDataURL(blob);
117
669
  });
118
670
  }
119
- /**
120
- * Gets the size of a file, blob, or base64 string.
121
- *
122
- * @param file - The file, blob, or base64 string to get size for
123
- * @returns number | false - The size in bytes or false if not a base64 string
124
- */
125
671
  getSize(file) {
126
672
  if (this.isBase64(file)) {
127
673
  return this.getBase64Size(file);
128
674
  }
129
675
  return false;
130
676
  }
131
- /**
132
- * Checks if a string is a valid base64 string.
133
- *
134
- * @param base64 - The string to check
135
- * @returns boolean - True if the string is a valid base64 string
136
- */
137
677
  isBase64(base64) {
138
678
  const regx = /[^:]\w+\/[\w-+\d.]+(?=;|,)/;
139
679
  return regx.test(base64);
140
680
  }
141
- /**
142
- * Gets the size of a base64 string in bytes.
143
- *
144
- * @param base64 - The base64 string to get size for
145
- * @returns number - The size in bytes
146
- */
147
681
  getBase64Size(base64) {
148
682
  return atob(base64.substring(base64.indexOf(',') + 1)).length;
149
683
  }
150
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
151
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileService, providedIn: 'root' }); }
684
+ async resolveChooseOptions(options) {
685
+ const multiple = options.multiple ?? false;
686
+ if (options.fileType) {
687
+ const accept = options.accept ?? (await this.getAcceptAttribute(options.fileType));
688
+ if (isDevMode() && !accept) {
689
+ const name = Array.isArray(options.fileType) ? options.fileType.join(', ') : options.fileType;
690
+ console.warn(`[AXFileService] No accept filter for file type "${name}". ` +
691
+ 'Register a file type provider (e.g. provideConversationFileCatalog).');
692
+ }
693
+ return { accept, multiple };
694
+ }
695
+ return { accept: options.accept ?? '', multiple };
696
+ }
697
+ openFileDialog(options) {
698
+ return new Promise((resolve, reject) => {
699
+ const input = this.document.createElement('input');
700
+ input.style.display = 'none';
701
+ this.document.body.appendChild(input);
702
+ input.type = 'file';
703
+ input.accept = options.accept;
704
+ input.multiple = options.multiple;
705
+ const cleanup = () => {
706
+ input.removeEventListener('change', onChange);
707
+ input.removeEventListener('error', onError);
708
+ if (input.parentNode) {
709
+ this.document.body.removeChild(input);
710
+ }
711
+ input.remove();
712
+ };
713
+ const onError = (e) => {
714
+ cleanup();
715
+ reject(e);
716
+ };
717
+ const onChange = () => {
718
+ const files = Array.from(input.files ?? []);
719
+ cleanup();
720
+ resolve(files);
721
+ };
722
+ input.addEventListener('change', onChange);
723
+ input.addEventListener('error', onError);
724
+ input.click();
725
+ });
726
+ }
727
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
728
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileService, providedIn: 'root' }); }
152
729
  }
153
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXFileService, decorators: [{
730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXFileService, decorators: [{
154
731
  type: Injectable,
155
- args: [{
156
- providedIn: 'root',
157
- }]
732
+ args: [{ providedIn: 'root' }]
158
733
  }] });
159
734
 
160
735
  /**
161
736
  * Generated bundle index. Do not edit.
162
737
  */
163
738
 
164
- export { AXFileModule, AXFileService, AXFileSizeFormatter };
739
+ export { AXAudioFileTypeProvider, AXDocumentFileTypeProvider, AXFileFileTypeProvider, AXFileMaxSizeValidationRule, AXFileMimeTypeValidationRule, AXFileMinSizeValidationRule, AXFileModule, AXFileService, AXFileSizeFormatter, AXFileTypeInfoProvider, AXFileTypeRegistryService, AXImageFileTypeProvider, AXVideoFileTypeProvider, AX_AUDIO_FILE_TYPE, AX_BUILTIN_FILE_TYPE_PROVIDERS, AX_DOCUMENT_FILE_TYPE, AX_FILE_FILE_TYPE, AX_FILE_TYPE_INFO_PROVIDER, AX_FILE_VALIDATION_RULES, AX_IMAGE_FILE_TYPE, AX_VIDEO_FILE_TYPE, fileValidationsToRules, findFileTypeExtension, formatFileSizeBytes, getFileExtension, isEmptyFileValidations, isExtensionsOnlyType, mergeFileValidations, provideBuiltinFileTypeProviders, provideDefaultFileTypeProviders, provideFileTypeInfoProvider, provideFileTypeSystem, provideFileValidationRules, resolveFileCopyText, resolveFileValidations, validateFileAgainstType, validateFileWithValidations };
165
740
  //# sourceMappingURL=acorex-core-file.mjs.map