@acorex/cdk 21.0.2-next.21 → 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.
- package/{types/acorex-cdk-common.d.ts → common/index.d.ts} +7 -9
- package/fesm2022/acorex-cdk-accordion.mjs +24 -24
- package/fesm2022/acorex-cdk-accordion.mjs.map +1 -1
- package/fesm2022/acorex-cdk-carousel.mjs +3 -3
- package/fesm2022/acorex-cdk-carousel.mjs.map +1 -1
- package/fesm2022/acorex-cdk-clipboard.mjs +8 -8
- package/fesm2022/acorex-cdk-clipboard.mjs.map +1 -1
- package/fesm2022/acorex-cdk-common.mjs +106 -161
- package/fesm2022/acorex-cdk-common.mjs.map +1 -1
- package/fesm2022/acorex-cdk-dom.mjs +4 -4
- package/fesm2022/acorex-cdk-dom.mjs.map +1 -1
- package/fesm2022/acorex-cdk-double-click.mjs +4 -4
- package/fesm2022/acorex-cdk-double-click.mjs.map +1 -1
- package/fesm2022/acorex-cdk-drag-drop.mjs +22 -22
- package/fesm2022/acorex-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/acorex-cdk-drawer.mjs +13 -13
- package/fesm2022/acorex-cdk-drawer.mjs.map +1 -1
- package/fesm2022/acorex-cdk-focus-trap.mjs +3 -3
- package/fesm2022/acorex-cdk-focus-trap.mjs.map +1 -1
- package/fesm2022/acorex-cdk-full-screen.mjs +4 -4
- package/fesm2022/acorex-cdk-full-screen.mjs.map +1 -1
- package/fesm2022/acorex-cdk-input-mask.mjs +5 -11
- package/fesm2022/acorex-cdk-input-mask.mjs.map +1 -1
- package/fesm2022/acorex-cdk-list-navigation.mjs +13 -13
- package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
- package/fesm2022/acorex-cdk-outline.mjs +57 -68
- package/fesm2022/acorex-cdk-outline.mjs.map +1 -1
- package/fesm2022/acorex-cdk-overlay.mjs +3 -16
- package/fesm2022/acorex-cdk-overlay.mjs.map +1 -1
- package/fesm2022/acorex-cdk-pan-view.mjs +4 -4
- package/fesm2022/acorex-cdk-pan-view.mjs.map +1 -1
- package/fesm2022/acorex-cdk-qrcode.mjs.map +1 -1
- package/fesm2022/acorex-cdk-resizable.mjs +37 -14
- package/fesm2022/acorex-cdk-resizable.mjs.map +1 -1
- package/fesm2022/acorex-cdk-selection.mjs +13 -13
- package/fesm2022/acorex-cdk-selection.mjs.map +1 -1
- package/fesm2022/acorex-cdk-sliding-item.mjs +3 -3
- package/fesm2022/acorex-cdk-sliding-item.mjs.map +1 -1
- package/fesm2022/acorex-cdk-sticky.mjs +3 -3
- package/fesm2022/acorex-cdk-sticky.mjs.map +1 -1
- package/fesm2022/acorex-cdk-uploader.mjs +277 -171
- package/fesm2022/acorex-cdk-uploader.mjs.map +1 -1
- package/fesm2022/acorex-cdk-virtual-scroll.mjs +11 -11
- package/fesm2022/acorex-cdk-virtual-scroll.mjs.map +1 -1
- package/fesm2022/acorex-cdk-wysiwyg.mjs.map +1 -1
- package/fesm2022/acorex-cdk-z-index.mjs +3 -3
- package/fesm2022/acorex-cdk-z-index.mjs.map +1 -1
- package/fesm2022/acorex-cdk.mjs.map +1 -1
- package/{types/acorex-cdk-focus-trap.d.ts → focus-trap/index.d.ts} +1 -1
- package/{types/acorex-cdk-input-mask.d.ts → input-mask/index.d.ts} +0 -1
- package/{types/acorex-cdk-outline.d.ts → outline/index.d.ts} +0 -1
- package/{types/acorex-cdk-overlay.d.ts → overlay/index.d.ts} +0 -1
- package/package.json +31 -31
- package/{types/acorex-cdk-resizable.d.ts → resizable/index.d.ts} +2 -0
- package/{types/acorex-cdk-uploader.d.ts → uploader/index.d.ts} +119 -110
- /package/{types/acorex-cdk-accordion.d.ts → accordion/index.d.ts} +0 -0
- /package/{types/acorex-cdk-carousel.d.ts → carousel/index.d.ts} +0 -0
- /package/{types/acorex-cdk-clipboard.d.ts → clipboard/index.d.ts} +0 -0
- /package/{types/acorex-cdk-dom.d.ts → dom/index.d.ts} +0 -0
- /package/{types/acorex-cdk-double-click.d.ts → double-click/index.d.ts} +0 -0
- /package/{types/acorex-cdk-drag-drop.d.ts → drag-drop/index.d.ts} +0 -0
- /package/{types/acorex-cdk-drawer.d.ts → drawer/index.d.ts} +0 -0
- /package/{types/acorex-cdk-full-screen.d.ts → full-screen/index.d.ts} +0 -0
- /package/{types/acorex-cdk.d.ts → index.d.ts} +0 -0
- /package/{types/acorex-cdk-list-navigation.d.ts → list-navigation/index.d.ts} +0 -0
- /package/{types/acorex-cdk-pan-view.d.ts → pan-view/index.d.ts} +0 -0
- /package/{types/acorex-cdk-qrcode.d.ts → qrcode/index.d.ts} +0 -0
- /package/{types/acorex-cdk-selection.d.ts → selection/index.d.ts} +0 -0
- /package/{types/acorex-cdk-sliding-item.d.ts → sliding-item/index.d.ts} +0 -0
- /package/{types/acorex-cdk-sticky.d.ts → sticky/index.d.ts} +0 -0
- /package/{types/acorex-cdk-virtual-scroll.d.ts → virtual-scroll/index.d.ts} +0 -0
- /package/{types/acorex-cdk-wysiwyg.d.ts → wysiwyg/index.d.ts} +0 -0
- /package/{types/acorex-cdk-z-index.d.ts → z-index/index.d.ts} +0 -0
|
@@ -7,6 +7,27 @@ import { AXFileService } from '@acorex/core/file';
|
|
|
7
7
|
import { sumBy } from 'lodash-es';
|
|
8
8
|
import { Subject, BehaviorSubject, map } from 'rxjs';
|
|
9
9
|
|
|
10
|
+
/** DOM marker so browse handles can find a zone across content projection. */
|
|
11
|
+
const AX_UPLOADER_ZONE_HOST = Symbol('AX_UPLOADER_ZONE_HOST');
|
|
12
|
+
function bindUploaderZoneHost(element, zone) {
|
|
13
|
+
element[AX_UPLOADER_ZONE_HOST] = zone;
|
|
14
|
+
}
|
|
15
|
+
function unbindUploaderZoneHost(element) {
|
|
16
|
+
delete element[AX_UPLOADER_ZONE_HOST];
|
|
17
|
+
}
|
|
18
|
+
/** Walk ancestors to resolve the zone (works with projected browse handles). */
|
|
19
|
+
function findUploaderZoneFromDom(start) {
|
|
20
|
+
let el = start;
|
|
21
|
+
while (el) {
|
|
22
|
+
const zone = el[AX_UPLOADER_ZONE_HOST];
|
|
23
|
+
if (zone) {
|
|
24
|
+
return zone;
|
|
25
|
+
}
|
|
26
|
+
el = el.parentElement;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
class AXUploadRequest {
|
|
11
32
|
get name() {
|
|
12
33
|
return this.file.name;
|
|
@@ -106,76 +127,165 @@ class AXUploadRequest {
|
|
|
106
127
|
}
|
|
107
128
|
|
|
108
129
|
/**
|
|
109
|
-
*
|
|
130
|
+
* File upload queue and validation via {@link AXFileService}.
|
|
131
|
+
* Storage is implemented by subscribing to upload/resolve/delete events.
|
|
110
132
|
* @category Services
|
|
111
133
|
*/
|
|
112
134
|
class AXUploaderService {
|
|
113
135
|
constructor() {
|
|
114
|
-
/**
|
|
115
|
-
* Translation service for localized text.
|
|
116
|
-
* @ignore
|
|
117
|
-
*/
|
|
118
|
-
this.translateService = inject(AXTranslationService);
|
|
119
|
-
/**
|
|
120
|
-
* File service for file operations.
|
|
121
|
-
* @ignore
|
|
122
|
-
*/
|
|
123
136
|
this.fileService = inject(AXFileService);
|
|
124
|
-
/**
|
|
125
|
-
* Behavior subject for managing upload requests.
|
|
126
|
-
* @ignore
|
|
127
|
-
*/
|
|
128
137
|
this._files$ = new BehaviorSubject([]);
|
|
129
|
-
/**
|
|
130
|
-
* Gets the files behavior subject for observing upload requests.
|
|
131
|
-
*/
|
|
132
138
|
this.files = this._files$.asObservable();
|
|
133
|
-
/**
|
|
134
|
-
* Subject for file upload start events.
|
|
135
|
-
*/
|
|
136
139
|
this.onFileUploadStart = new Subject();
|
|
137
|
-
/**
|
|
138
|
-
* Subject for file upload complete events.
|
|
139
|
-
*/
|
|
140
140
|
this.onFileUploadComplete = new Subject();
|
|
141
|
-
/**
|
|
142
|
-
* Subject for all files upload complete events.
|
|
143
|
-
*/
|
|
144
141
|
this.onFilesUploadComplete = new Subject();
|
|
145
|
-
/**
|
|
146
|
-
* Subject for file upload canceled events.
|
|
147
|
-
*/
|
|
148
142
|
this.onFileUploadCanceled = new Subject();
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
/**
|
|
154
|
-
|
|
155
|
-
*/
|
|
143
|
+
/** Subscribe to perform uploads (e.g. HTTP, IndexedDB). */
|
|
144
|
+
this.onUpload = new Subject();
|
|
145
|
+
/** Subscribe to resolve a stored reference to a playback URL. */
|
|
146
|
+
this.onResolveUrl = new Subject();
|
|
147
|
+
/** Subscribe to delete stored media. */
|
|
148
|
+
this.onDeleteMedia = new Subject();
|
|
156
149
|
this.totalEstimateTime = this._files$.pipe(map((files) => sumBy(files, (file) => (file.status() === 'inprogress' ? file.estimateTime() : 0))));
|
|
157
150
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
isAnyDetermined() {
|
|
152
|
+
return this._files$.value.some((file) => file.isDetermined());
|
|
153
|
+
}
|
|
154
|
+
validateFiles(files, fileType) {
|
|
155
|
+
return this.fileService.validateMany(files, fileType);
|
|
156
|
+
}
|
|
157
|
+
async getAcceptAttribute(fileType) {
|
|
158
|
+
return this.fileService.getAcceptAttribute(fileType);
|
|
159
|
+
}
|
|
160
|
+
upload(options) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
if (!this.hasUploadHandler()) {
|
|
163
|
+
reject(new Error('No upload handler subscribed to AXUploaderService.onUpload. Provide a service that handles upload events.'));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.onUpload.next({
|
|
167
|
+
component: this,
|
|
168
|
+
options,
|
|
169
|
+
resolve,
|
|
170
|
+
reject,
|
|
171
|
+
isUserInteraction: false,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
resolveUrl(reference, signal) {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
if (!this.hasResolveUrlHandler()) {
|
|
178
|
+
reject(new Error('No handler subscribed to AXUploaderService.onResolveUrl. Provide a service that handles resolve events.'));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.onResolveUrl.next({
|
|
182
|
+
component: this,
|
|
183
|
+
reference,
|
|
184
|
+
signal,
|
|
185
|
+
resolve,
|
|
186
|
+
reject,
|
|
187
|
+
isUserInteraction: false,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async resolvePlaybackUrl(reference, signal) {
|
|
192
|
+
const direct = reference.url?.trim();
|
|
193
|
+
if (direct && !reference.mediaId) {
|
|
194
|
+
return direct;
|
|
195
|
+
}
|
|
196
|
+
if (direct && reference.mediaId) {
|
|
197
|
+
try {
|
|
198
|
+
return await this.resolveUrl(reference, signal);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return direct;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (reference.mediaId) {
|
|
205
|
+
return this.resolveUrl(reference, signal);
|
|
206
|
+
}
|
|
207
|
+
if (direct) {
|
|
208
|
+
return direct;
|
|
209
|
+
}
|
|
210
|
+
throw new Error('Upload reference has no url or mediaId');
|
|
211
|
+
}
|
|
212
|
+
deleteMedia(reference) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
if (!this.hasDeleteMediaHandler()) {
|
|
215
|
+
reject(new Error('No handler subscribed to AXUploaderService.onDeleteMedia. Provide a service that handles delete events.'));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.onDeleteMedia.next({
|
|
219
|
+
component: this,
|
|
220
|
+
reference,
|
|
221
|
+
resolve,
|
|
222
|
+
reject,
|
|
223
|
+
isUserInteraction: false,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
hasUploadHandler() {
|
|
228
|
+
return this.onUpload.observers.length > 0;
|
|
229
|
+
}
|
|
230
|
+
hasResolveUrlHandler() {
|
|
231
|
+
return this.onResolveUrl.observers.length > 0;
|
|
232
|
+
}
|
|
233
|
+
hasDeleteMediaHandler() {
|
|
234
|
+
return this.onDeleteMedia.observers.length > 0;
|
|
235
|
+
}
|
|
236
|
+
async add(files, options) {
|
|
237
|
+
const list = Array.from(files).map((f) => new AXUploadRequest(f));
|
|
238
|
+
await this.applyFileTypeValidation(list, options?.fileType);
|
|
239
|
+
const newFiles = [...this._files$.value, ...list];
|
|
240
|
+
this._files$.next(newFiles);
|
|
241
|
+
void this.startUpload();
|
|
242
|
+
return list;
|
|
164
243
|
}
|
|
165
244
|
/**
|
|
166
|
-
*
|
|
167
|
-
* @private
|
|
245
|
+
* Opens the file dialog and returns validated files without enqueueing uploads.
|
|
168
246
|
*/
|
|
247
|
+
async chooseFiles(options = {}) {
|
|
248
|
+
try {
|
|
249
|
+
if (!options.fileType) {
|
|
250
|
+
console.warn('AXUploaderService.chooseFiles: fileType is required for validated file selection.');
|
|
251
|
+
return { accepted: [], rejected: [] };
|
|
252
|
+
}
|
|
253
|
+
const accept = options.accept ?? (await this.fileService.getAcceptAttribute(options.fileType));
|
|
254
|
+
return this.fileService.chooseValidated({
|
|
255
|
+
fileType: options.fileType,
|
|
256
|
+
multiple: options.multiple ?? false,
|
|
257
|
+
accept,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error('File choose failed:', error);
|
|
262
|
+
return { accepted: [], rejected: [] };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async browse(options = { multiple: false }) {
|
|
266
|
+
const { accepted } = await this.chooseFiles(options);
|
|
267
|
+
if (!accepted.length) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
return this.add(accepted, { fileType: options.fileType });
|
|
271
|
+
}
|
|
272
|
+
async cancelAll() {
|
|
273
|
+
await Promise.all(this._files$.value.filter((c) => c.status() !== 'completed').map((c) => c.cancel()));
|
|
274
|
+
}
|
|
275
|
+
clearAll() {
|
|
276
|
+
const remainingFiles = this._files$.value.filter((c) => c.status() === 'inprogress');
|
|
277
|
+
this._files$.next(remainingFiles);
|
|
278
|
+
}
|
|
279
|
+
remove(item) {
|
|
280
|
+
const updatedFiles = this._files$.value.filter((c) => c !== item);
|
|
281
|
+
this._files$.next(updatedFiles);
|
|
282
|
+
}
|
|
169
283
|
async startUpload() {
|
|
170
284
|
const newFiles = this._files$.value.filter((c) => c.status() === 'new');
|
|
171
285
|
for (const file of newFiles) {
|
|
172
286
|
await this.bindEvents(file);
|
|
173
287
|
}
|
|
174
288
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Binds event handlers to an upload request.
|
|
177
|
-
* @private
|
|
178
|
-
*/
|
|
179
289
|
async bindEvents(c) {
|
|
180
290
|
c.onStart.subscribe(() => {
|
|
181
291
|
this.onFileUploadStart.next({
|
|
@@ -208,62 +318,22 @@ class AXUploaderService {
|
|
|
208
318
|
});
|
|
209
319
|
});
|
|
210
320
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
* @returns Promise that resolves to an array of upload requests
|
|
215
|
-
*/
|
|
216
|
-
async browse(options = { multiple: false }) {
|
|
217
|
-
try {
|
|
218
|
-
const files = await this.fileService.choose({ multiple: options?.multiple || false, accept: options.accept });
|
|
219
|
-
if (files.length) {
|
|
220
|
-
return this.add(files);
|
|
221
|
-
}
|
|
222
|
-
return [];
|
|
321
|
+
async applyFileTypeValidation(requests, fileType) {
|
|
322
|
+
if (!fileType) {
|
|
323
|
+
return requests;
|
|
223
324
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
325
|
+
for (const request of requests) {
|
|
326
|
+
const errors = await this.fileService.validate(request.file, fileType);
|
|
327
|
+
if (errors.length > 0) {
|
|
328
|
+
request.error(errors[0]?.message ?? 'Invalid file');
|
|
329
|
+
}
|
|
228
330
|
}
|
|
331
|
+
return requests;
|
|
229
332
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
* @param files - Files to add to the upload queue
|
|
233
|
-
* @returns Promise that resolves to an array of upload requests
|
|
234
|
-
*/
|
|
235
|
-
async add(files) {
|
|
236
|
-
const list = Array.from(files).map((f) => this.convertFileToRequest(f));
|
|
237
|
-
const newFiles = [...this._files$.value, ...list];
|
|
238
|
-
this._files$.next(newFiles);
|
|
239
|
-
this.startUpload();
|
|
240
|
-
return list;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Cancels all pending and in-progress uploads.
|
|
244
|
-
*/
|
|
245
|
-
async cancelAll() {
|
|
246
|
-
await Promise.all(this._files$.value.filter((c) => c.status() !== 'completed').map((c) => c.cancel()));
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Clears all completed uploads from the queue.
|
|
250
|
-
*/
|
|
251
|
-
clearAll() {
|
|
252
|
-
const remainingFiles = this._files$.value.filter((c) => c.status() === 'inprogress');
|
|
253
|
-
this._files$.next(remainingFiles);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Removes a specific upload request from the queue.
|
|
257
|
-
* @param item - The upload request to remove
|
|
258
|
-
*/
|
|
259
|
-
remove(item) {
|
|
260
|
-
const updatedFiles = this._files$.value.filter((c) => c !== item);
|
|
261
|
-
this._files$.next(updatedFiles);
|
|
262
|
-
}
|
|
263
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
264
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
|
|
333
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
334
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
|
|
265
335
|
}
|
|
266
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, decorators: [{
|
|
267
337
|
type: Injectable,
|
|
268
338
|
args: [{ providedIn: 'root' }]
|
|
269
339
|
}] });
|
|
@@ -298,6 +368,11 @@ class AXUploaderZoneDirective {
|
|
|
298
368
|
* @defaultValue null
|
|
299
369
|
*/
|
|
300
370
|
this.accept = input(null, ...(ngDevMode ? [{ debugName: "accept" }] : []));
|
|
371
|
+
/**
|
|
372
|
+
* Logical file type name from {@link AXFileService} (e.g. `conversation-image`).
|
|
373
|
+
* Required for browse handles and validated selection.
|
|
374
|
+
*/
|
|
375
|
+
this.fileType = input(null, ...(ngDevMode ? [{ debugName: "fileType" }] : []));
|
|
301
376
|
/**
|
|
302
377
|
* Custom template for the drag overlay. If provided, this will be used instead of the default overlay.
|
|
303
378
|
*/
|
|
@@ -405,6 +480,7 @@ class AXUploaderZoneDirective {
|
|
|
405
480
|
this.animationEndHandler = null;
|
|
406
481
|
this.element = this.elementRef.nativeElement;
|
|
407
482
|
this.element.style.position = 'relative';
|
|
483
|
+
bindUploaderZoneHost(this.element, this);
|
|
408
484
|
//
|
|
409
485
|
this.uploadService.onFileUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
|
|
410
486
|
this.onFileUploadComplete.emit(c);
|
|
@@ -430,6 +506,7 @@ class AXUploaderZoneDirective {
|
|
|
430
506
|
* Cleans up event listeners when the directive is destroyed.
|
|
431
507
|
*/
|
|
432
508
|
ngOnDestroy() {
|
|
509
|
+
unbindUploaderZoneHost(this.element);
|
|
433
510
|
this.element.removeEventListener('click', this.browser.bind(this));
|
|
434
511
|
this.element.removeEventListener('dragenter', this.handleDragEnter.bind(this));
|
|
435
512
|
this.element.removeEventListener('drop', this.handleOnDrop.bind(this));
|
|
@@ -467,16 +544,25 @@ class AXUploaderZoneDirective {
|
|
|
467
544
|
// Reset drag depth on drop
|
|
468
545
|
this.dragDepth.set(0);
|
|
469
546
|
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
this.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
547
|
+
let files = Array.from(event.dataTransfer.files);
|
|
548
|
+
let rejected;
|
|
549
|
+
const catalog = this.fileType();
|
|
550
|
+
if (catalog) {
|
|
551
|
+
const result = await this.uploadService.validateFiles(files, catalog);
|
|
552
|
+
files = result.accepted;
|
|
553
|
+
rejected = result.rejected;
|
|
554
|
+
}
|
|
555
|
+
this.fileChange.emit({ event, files, rejected });
|
|
556
|
+
if (!this.disableBrowse()) {
|
|
557
|
+
const requests = await this.uploadService.add(files, { fileType: catalog ?? undefined });
|
|
558
|
+
if (requests.length > 0) {
|
|
559
|
+
this.onChanged.emit({
|
|
560
|
+
component: this,
|
|
561
|
+
requests,
|
|
562
|
+
isUserInteraction: true,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
480
566
|
}
|
|
481
567
|
this.removeZone();
|
|
482
568
|
this.cdr.detectChanges();
|
|
@@ -640,32 +726,60 @@ class AXUploaderZoneDirective {
|
|
|
640
726
|
* @returns Promise that resolves when files are processed
|
|
641
727
|
*/
|
|
642
728
|
async browser() {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
729
|
+
const catalog = this.fileType();
|
|
730
|
+
let selected = [];
|
|
731
|
+
let rejected;
|
|
732
|
+
if (catalog) {
|
|
733
|
+
const accept = this.accept() ?? (await this.uploadService.getAcceptAttribute(catalog));
|
|
734
|
+
const result = await this.uploadService.chooseFiles({
|
|
735
|
+
fileType: catalog,
|
|
736
|
+
multiple: this.multiple(),
|
|
737
|
+
accept,
|
|
738
|
+
});
|
|
739
|
+
selected = result.accepted;
|
|
740
|
+
rejected = result.rejected;
|
|
741
|
+
}
|
|
742
|
+
else if (!this.disableBrowse()) {
|
|
743
|
+
const requests = await this.uploadService.browse({
|
|
744
|
+
accept: this.accept() ?? undefined,
|
|
745
|
+
multiple: this.multiple(),
|
|
656
746
|
});
|
|
657
|
-
|
|
747
|
+
selected = requests.map((r) => r.file);
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
console.warn('AXUploaderZone.browser: fileType is required when disableBrowse is enabled (use axUploaderBrowseHandle).');
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (selected.length === 0 && !(rejected?.length ?? 0)) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const syntheticEvent = new Event('change');
|
|
757
|
+
const target = Object.create(EventTarget.prototype);
|
|
758
|
+
target.files = selected;
|
|
759
|
+
Object.defineProperty(syntheticEvent, 'target', { value: target, writable: false });
|
|
760
|
+
this.fileChange.emit({
|
|
761
|
+
event: syntheticEvent,
|
|
762
|
+
files: selected,
|
|
763
|
+
rejected,
|
|
764
|
+
});
|
|
765
|
+
if (this.disableBrowse()) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const requests = catalog
|
|
769
|
+
? await this.uploadService.add(selected, { fileType: catalog })
|
|
770
|
+
: await this.uploadService.add(selected);
|
|
771
|
+
if (requests.length > 0) {
|
|
658
772
|
this.onChanged.emit({
|
|
659
773
|
component: this,
|
|
660
|
-
requests
|
|
774
|
+
requests,
|
|
661
775
|
isUserInteraction: true,
|
|
662
776
|
});
|
|
663
777
|
}
|
|
664
778
|
}
|
|
665
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
666
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
779
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
780
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.3", type: AXUploaderZoneDirective, isStandalone: true, selector: "[axUploaderZone]", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, fileType: { classPropertyName: "fileType", publicName: "fileType", isSignal: true, isRequired: false, transformFunction: null }, overlayTemplate: { classPropertyName: "overlayTemplate", publicName: "overlayTemplate", isSignal: true, isRequired: false, transformFunction: null }, disableBrowse: { classPropertyName: "disableBrowse", publicName: "disableBrowse", isSignal: true, isRequired: false, transformFunction: null }, disableDragDrop: { classPropertyName: "disableDragDrop", publicName: "disableDragDrop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", onChanged: "onChanged", dragEnter: "dragEnter", dragLeave: "dragLeave", dragOver: "dragOver", onFileUploadComplete: "onFileUploadComplete", onFilesUploadComplete: "onFilesUploadComplete" }, host: { classAttribute: "ax-drop-zone" }, providers: [AXUnsubscriber], ngImport: i0 }); }
|
|
667
781
|
}
|
|
668
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
782
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
|
|
669
783
|
type: Directive,
|
|
670
784
|
args: [{
|
|
671
785
|
selector: '[axUploaderZone]',
|
|
@@ -674,66 +788,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
674
788
|
class: 'ax-drop-zone',
|
|
675
789
|
},
|
|
676
790
|
}]
|
|
677
|
-
}], ctorParameters: () => []
|
|
791
|
+
}], ctorParameters: () => [] });
|
|
678
792
|
|
|
679
793
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
794
|
+
* Triggers the nearest {@link AXUploaderZoneDirective} file dialog on click.
|
|
795
|
+
* Resolves the zone via DI when possible, otherwise walks the DOM (supports content projection).
|
|
682
796
|
* @category Directives
|
|
683
797
|
*/
|
|
684
798
|
class AXUploaderBrowseDirective {
|
|
685
799
|
constructor() {
|
|
686
|
-
|
|
687
|
-
* The uploader zone directive instance.
|
|
688
|
-
* @ignore
|
|
689
|
-
*/
|
|
690
|
-
this.uploaderZone = inject(AXUploaderZoneDirective);
|
|
691
|
-
/**
|
|
692
|
-
* The element reference for the directive host.
|
|
693
|
-
* @ignore
|
|
694
|
-
*/
|
|
695
|
-
this.elementRef = inject((ElementRef));
|
|
696
|
-
/**
|
|
697
|
-
* Platform ID for browser detection.
|
|
698
|
-
* @ignore
|
|
699
|
-
*/
|
|
800
|
+
this.elementRef = inject(ElementRef);
|
|
700
801
|
this.platformID = inject(PLATFORM_ID);
|
|
802
|
+
/** When browse + zone share the same host element. */
|
|
803
|
+
this.zoneFromInjector = inject(AXUploaderZoneDirective, { optional: true, self: true });
|
|
804
|
+
this.onClick = () => {
|
|
805
|
+
void this.handleClick();
|
|
806
|
+
};
|
|
701
807
|
}
|
|
702
|
-
/**
|
|
703
|
-
* Initializes the directive by adding click event listener and data attribute.
|
|
704
|
-
*/
|
|
705
808
|
ngOnInit() {
|
|
706
|
-
if (isPlatformBrowser(this.platformID)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
809
|
+
if (!isPlatformBrowser(this.platformID)) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const element = this.elementRef.nativeElement;
|
|
813
|
+
element.addEventListener('click', this.onClick);
|
|
814
|
+
if (element.dataset) {
|
|
815
|
+
element.dataset['axUploaderBrowseHandle'] = 'true';
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
element.setAttribute('data-ax-uploader-browse-handle', 'true');
|
|
716
819
|
}
|
|
717
820
|
}
|
|
718
|
-
/**
|
|
719
|
-
* Cleans up the directive by removing event listeners.
|
|
720
|
-
*/
|
|
721
821
|
ngOnDestroy() {
|
|
722
|
-
if (isPlatformBrowser(this.platformID)
|
|
723
|
-
this.elementRef.nativeElement.removeEventListener('click', this.
|
|
822
|
+
if (isPlatformBrowser(this.platformID)) {
|
|
823
|
+
this.elementRef.nativeElement.removeEventListener('click', this.onClick);
|
|
724
824
|
}
|
|
725
825
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
826
|
+
resolveZone() {
|
|
827
|
+
return (this.zoneFromInjector ??
|
|
828
|
+
findUploaderZoneFromDom(this.elementRef.nativeElement) ??
|
|
829
|
+
undefined);
|
|
830
|
+
}
|
|
730
831
|
async handleClick() {
|
|
731
|
-
|
|
832
|
+
const zone = this.resolveZone();
|
|
833
|
+
if (!zone) {
|
|
834
|
+
console.warn('[axUploaderBrowseHandle] No axUploaderZone found. Place axUploaderBrowseHandle inside an axUploaderZone.');
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
await zone.browser();
|
|
732
838
|
}
|
|
733
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
734
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
839
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
840
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.3", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
|
|
735
841
|
}
|
|
736
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
842
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
|
|
737
843
|
type: Directive,
|
|
738
844
|
args: [{ selector: '[axUploaderBrowseHandle]' }]
|
|
739
845
|
}] });
|