@acorex/core 21.0.2-next.22 → 21.0.2-next.23

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