@acorex/cdk 21.0.2-next.31 → 21.0.2-next.33
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/fesm2022/acorex-cdk-accordion.mjs +38 -38
- package/fesm2022/acorex-cdk-accordion.mjs.map +1 -1
- package/fesm2022/acorex-cdk-carousel.mjs +4 -4
- 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 +171 -116
- 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 +6 -6
- package/fesm2022/acorex-cdk-double-click.mjs.map +1 -1
- package/fesm2022/acorex-cdk-drag-drop.mjs +71 -71
- package/fesm2022/acorex-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/acorex-cdk-drawer.mjs +27 -27
- package/fesm2022/acorex-cdk-drawer.mjs.map +1 -1
- package/fesm2022/acorex-cdk-focus-trap.mjs +4 -4
- package/fesm2022/acorex-cdk-focus-trap.mjs.map +1 -1
- package/fesm2022/acorex-cdk-full-screen.mjs +5 -5
- package/fesm2022/acorex-cdk-full-screen.mjs.map +1 -1
- package/fesm2022/acorex-cdk-input-mask.mjs +20 -14
- package/fesm2022/acorex-cdk-input-mask.mjs.map +1 -1
- package/fesm2022/acorex-cdk-list-navigation.mjs +19 -19
- package/fesm2022/acorex-cdk-list-navigation.mjs.map +1 -1
- package/fesm2022/acorex-cdk-outline.mjs +79 -68
- package/fesm2022/acorex-cdk-outline.mjs.map +1 -1
- package/fesm2022/acorex-cdk-overlay.mjs +16 -3
- package/fesm2022/acorex-cdk-overlay.mjs.map +1 -1
- package/fesm2022/acorex-cdk-pan-view.mjs +22 -22
- 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 +14 -13
- package/fesm2022/acorex-cdk-resizable.mjs.map +1 -1
- package/fesm2022/acorex-cdk-selection.mjs +23 -23
- package/fesm2022/acorex-cdk-selection.mjs.map +1 -1
- package/fesm2022/acorex-cdk-sliding-item.mjs +11 -11
- 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 +298 -188
- package/fesm2022/acorex-cdk-uploader.mjs.map +1 -1
- package/fesm2022/acorex-cdk-virtual-scroll.mjs +18 -18
- package/fesm2022/acorex-cdk-virtual-scroll.mjs.map +1 -1
- package/fesm2022/acorex-cdk-wysiwyg.mjs +1 -1
- package/fesm2022/acorex-cdk-wysiwyg.mjs.map +1 -1
- package/fesm2022/acorex-cdk-z-index.mjs +4 -4
- package/fesm2022/acorex-cdk-z-index.mjs.map +1 -1
- package/fesm2022/acorex-cdk.mjs.map +1 -1
- package/package.json +32 -31
- package/{common/index.d.ts → types/acorex-cdk-common.d.ts} +9 -7
- package/{focus-trap/index.d.ts → types/acorex-cdk-focus-trap.d.ts} +1 -1
- package/{input-mask/index.d.ts → types/acorex-cdk-input-mask.d.ts} +1 -0
- package/{outline/index.d.ts → types/acorex-cdk-outline.d.ts} +1 -0
- package/{overlay/index.d.ts → types/acorex-cdk-overlay.d.ts} +1 -0
- package/{uploader/index.d.ts → types/acorex-cdk-uploader.d.ts} +120 -110
- /package/{accordion/index.d.ts → types/acorex-cdk-accordion.d.ts} +0 -0
- /package/{carousel/index.d.ts → types/acorex-cdk-carousel.d.ts} +0 -0
- /package/{clipboard/index.d.ts → types/acorex-cdk-clipboard.d.ts} +0 -0
- /package/{dom/index.d.ts → types/acorex-cdk-dom.d.ts} +0 -0
- /package/{double-click/index.d.ts → types/acorex-cdk-double-click.d.ts} +0 -0
- /package/{drag-drop/index.d.ts → types/acorex-cdk-drag-drop.d.ts} +0 -0
- /package/{drawer/index.d.ts → types/acorex-cdk-drawer.d.ts} +0 -0
- /package/{full-screen/index.d.ts → types/acorex-cdk-full-screen.d.ts} +0 -0
- /package/{list-navigation/index.d.ts → types/acorex-cdk-list-navigation.d.ts} +0 -0
- /package/{pan-view/index.d.ts → types/acorex-cdk-pan-view.d.ts} +0 -0
- /package/{qrcode/index.d.ts → types/acorex-cdk-qrcode.d.ts} +0 -0
- /package/{resizable/index.d.ts → types/acorex-cdk-resizable.d.ts} +0 -0
- /package/{selection/index.d.ts → types/acorex-cdk-selection.d.ts} +0 -0
- /package/{sliding-item/index.d.ts → types/acorex-cdk-sliding-item.d.ts} +0 -0
- /package/{sticky/index.d.ts → types/acorex-cdk-sticky.d.ts} +0 -0
- /package/{virtual-scroll/index.d.ts → types/acorex-cdk-virtual-scroll.d.ts} +0 -0
- /package/{wysiwyg/index.d.ts → types/acorex-cdk-wysiwyg.d.ts} +0 -0
- /package/{z-index/index.d.ts → types/acorex-cdk-z-index.d.ts} +0 -0
- /package/{index.d.ts → types/acorex-cdk.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;
|
|
@@ -29,16 +50,16 @@ class AXUploadRequest {
|
|
|
29
50
|
}
|
|
30
51
|
constructor(uploadFile) {
|
|
31
52
|
this.uploadFile = uploadFile;
|
|
32
|
-
this._progress = signal(0, ...(ngDevMode ? [{ debugName: "_progress" }] : []));
|
|
33
|
-
this.progress = computed(() => this._progress(), ...(ngDevMode ? [{ debugName: "progress" }] : []));
|
|
34
|
-
this._estimateTime = signal(0, ...(ngDevMode ? [{ debugName: "_estimateTime" }] : []));
|
|
35
|
-
this.estimateTime = computed(() => this._estimateTime(), ...(ngDevMode ? [{ debugName: "estimateTime" }] : []));
|
|
36
|
-
this._status = signal('new', ...(ngDevMode ? [{ debugName: "_status" }] : []));
|
|
37
|
-
this.status = computed(() => this._status(), ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
38
|
-
this._message = signal(null, ...(ngDevMode ? [{ debugName: "_message" }] : []));
|
|
39
|
-
this.message = computed(() => this._message(), ...(ngDevMode ? [{ debugName: "message" }] : []));
|
|
40
|
-
this._isDetermined = signal(false, ...(ngDevMode ? [{ debugName: "_isDetermined" }] : []));
|
|
41
|
-
this.isDetermined = computed(() => this._isDetermined(), ...(ngDevMode ? [{ debugName: "isDetermined" }] : []));
|
|
53
|
+
this._progress = signal(0, ...(ngDevMode ? [{ debugName: "_progress" }] : /* istanbul ignore next */ []));
|
|
54
|
+
this.progress = computed(() => this._progress(), ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
|
|
55
|
+
this._estimateTime = signal(0, ...(ngDevMode ? [{ debugName: "_estimateTime" }] : /* istanbul ignore next */ []));
|
|
56
|
+
this.estimateTime = computed(() => this._estimateTime(), ...(ngDevMode ? [{ debugName: "estimateTime" }] : /* istanbul ignore next */ []));
|
|
57
|
+
this._status = signal('new', ...(ngDevMode ? [{ debugName: "_status" }] : /* istanbul ignore next */ []));
|
|
58
|
+
this.status = computed(() => this._status(), ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
59
|
+
this._message = signal(null, ...(ngDevMode ? [{ debugName: "_message" }] : /* istanbul ignore next */ []));
|
|
60
|
+
this.message = computed(() => this._message(), ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
61
|
+
this._isDetermined = signal(false, ...(ngDevMode ? [{ debugName: "_isDetermined" }] : /* istanbul ignore next */ []));
|
|
62
|
+
this.isDetermined = computed(() => this._isDetermined(), ...(ngDevMode ? [{ debugName: "isDetermined" }] : /* istanbul ignore next */ []));
|
|
42
63
|
this.bytesTransferred = 0;
|
|
43
64
|
this.onCancel = new Subject();
|
|
44
65
|
this.onStart = new Subject();
|
|
@@ -106,76 +127,169 @@ 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
|
-
*
|
|
245
|
+
* Opens the file dialog and returns selected files without enqueueing uploads.
|
|
246
|
+
* With `fileType`, applies catalog validation; otherwise uses legacy accept-only selection.
|
|
168
247
|
*/
|
|
248
|
+
async chooseFiles(options = {}) {
|
|
249
|
+
try {
|
|
250
|
+
if (options.fileType) {
|
|
251
|
+
const accept = options.accept ?? (await this.fileService.getAcceptAttribute(options.fileType));
|
|
252
|
+
return this.fileService.chooseValidated({
|
|
253
|
+
fileType: options.fileType,
|
|
254
|
+
multiple: options.multiple ?? false,
|
|
255
|
+
accept,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const accepted = await this.fileService.choose({
|
|
259
|
+
accept: options.accept,
|
|
260
|
+
multiple: options.multiple ?? false,
|
|
261
|
+
});
|
|
262
|
+
return { accepted, rejected: [] };
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error('File choose failed:', error);
|
|
266
|
+
return { accepted: [], rejected: [] };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async browse(options = { multiple: false }) {
|
|
270
|
+
const { accepted } = await this.chooseFiles(options);
|
|
271
|
+
if (!accepted.length) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
return this.add(accepted, { fileType: options.fileType });
|
|
275
|
+
}
|
|
276
|
+
async cancelAll() {
|
|
277
|
+
await Promise.all(this._files$.value.filter((c) => c.status() !== 'completed').map((c) => c.cancel()));
|
|
278
|
+
}
|
|
279
|
+
clearAll() {
|
|
280
|
+
const remainingFiles = this._files$.value.filter((c) => c.status() === 'inprogress');
|
|
281
|
+
this._files$.next(remainingFiles);
|
|
282
|
+
}
|
|
283
|
+
remove(item) {
|
|
284
|
+
const updatedFiles = this._files$.value.filter((c) => c !== item);
|
|
285
|
+
this._files$.next(updatedFiles);
|
|
286
|
+
}
|
|
169
287
|
async startUpload() {
|
|
170
288
|
const newFiles = this._files$.value.filter((c) => c.status() === 'new');
|
|
171
289
|
for (const file of newFiles) {
|
|
172
290
|
await this.bindEvents(file);
|
|
173
291
|
}
|
|
174
292
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Binds event handlers to an upload request.
|
|
177
|
-
* @private
|
|
178
|
-
*/
|
|
179
293
|
async bindEvents(c) {
|
|
180
294
|
c.onStart.subscribe(() => {
|
|
181
295
|
this.onFileUploadStart.next({
|
|
@@ -208,62 +322,22 @@ class AXUploaderService {
|
|
|
208
322
|
});
|
|
209
323
|
});
|
|
210
324
|
}
|
|
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 [];
|
|
325
|
+
async applyFileTypeValidation(requests, fileType) {
|
|
326
|
+
if (!fileType) {
|
|
327
|
+
return requests;
|
|
223
328
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
329
|
+
for (const request of requests) {
|
|
330
|
+
const errors = await this.fileService.validate(request.file, fileType);
|
|
331
|
+
if (errors.length > 0) {
|
|
332
|
+
request.error(errors[0]?.message ?? 'Invalid file');
|
|
333
|
+
}
|
|
228
334
|
}
|
|
335
|
+
return requests;
|
|
229
336
|
}
|
|
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: "20.3.3", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
264
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
|
|
337
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
338
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderService, providedIn: 'root' }); }
|
|
265
339
|
}
|
|
266
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
340
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderService, decorators: [{
|
|
267
341
|
type: Injectable,
|
|
268
342
|
args: [{ providedIn: 'root' }]
|
|
269
343
|
}] });
|
|
@@ -292,26 +366,31 @@ class AXUploaderZoneDirective {
|
|
|
292
366
|
* Whether multiple files can be selected.
|
|
293
367
|
* @defaultValue true
|
|
294
368
|
*/
|
|
295
|
-
this.multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
369
|
+
this.multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
296
370
|
/**
|
|
297
371
|
* File types that are accepted for upload.
|
|
298
372
|
* @defaultValue null
|
|
299
373
|
*/
|
|
300
|
-
this.accept = input(null, ...(ngDevMode ? [{ debugName: "accept" }] : []));
|
|
374
|
+
this.accept = input(null, ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
375
|
+
/**
|
|
376
|
+
* Logical file type name from {@link AXFileService} (e.g. `conversation-image`).
|
|
377
|
+
* Required for browse handles and validated selection.
|
|
378
|
+
*/
|
|
379
|
+
this.fileType = input(null, ...(ngDevMode ? [{ debugName: "fileType" }] : /* istanbul ignore next */ []));
|
|
301
380
|
/**
|
|
302
381
|
* Custom template for the drag overlay. If provided, this will be used instead of the default overlay.
|
|
303
382
|
*/
|
|
304
|
-
this.overlayTemplate = input(...(ngDevMode ? [undefined, { debugName: "overlayTemplate" }] : []));
|
|
383
|
+
this.overlayTemplate = input(...(ngDevMode ? [undefined, { debugName: "overlayTemplate" }] : /* istanbul ignore next */ []));
|
|
305
384
|
/**
|
|
306
385
|
* Whether browsing files by clicking on the container is disabled.
|
|
307
386
|
* @defaultValue false
|
|
308
387
|
*/
|
|
309
|
-
this.disableBrowse = input(false, ...(ngDevMode ? [{ debugName: "disableBrowse" }] : []));
|
|
388
|
+
this.disableBrowse = input(false, ...(ngDevMode ? [{ debugName: "disableBrowse" }] : /* istanbul ignore next */ []));
|
|
310
389
|
/**
|
|
311
390
|
* Whether drag and drop functionality is disabled.
|
|
312
391
|
* @defaultValue false
|
|
313
392
|
*/
|
|
314
|
-
this.disableDragDrop = input(false, ...(ngDevMode ? [{ debugName: "disableDragDrop" }] : []));
|
|
393
|
+
this.disableDragDrop = input(false, ...(ngDevMode ? [{ debugName: "disableDragDrop" }] : /* istanbul ignore next */ []));
|
|
315
394
|
/**
|
|
316
395
|
* Change detector reference.
|
|
317
396
|
* @ignore
|
|
@@ -391,13 +470,13 @@ class AXUploaderZoneDirective {
|
|
|
391
470
|
* Flag to track if dragOver has been emitted for the current drag session.
|
|
392
471
|
* @ignore
|
|
393
472
|
*/
|
|
394
|
-
this.dragOverEmitted = signal(false, ...(ngDevMode ? [{ debugName: "dragOverEmitted" }] : []));
|
|
473
|
+
this.dragOverEmitted = signal(false, ...(ngDevMode ? [{ debugName: "dragOverEmitted" }] : /* istanbul ignore next */ []));
|
|
395
474
|
/**
|
|
396
475
|
* Counter to track drag enter/leave depth to prevent false dragleave events.
|
|
397
476
|
* When moving from parent to child, dragleave fires even though we're still inside.
|
|
398
477
|
* @ignore
|
|
399
478
|
*/
|
|
400
|
-
this.dragDepth = signal(0, ...(ngDevMode ? [{ debugName: "dragDepth" }] : []));
|
|
479
|
+
this.dragDepth = signal(0, ...(ngDevMode ? [{ debugName: "dragDepth" }] : /* istanbul ignore next */ []));
|
|
401
480
|
/**
|
|
402
481
|
* Animation end handler for cleanup.
|
|
403
482
|
* @ignore
|
|
@@ -405,6 +484,7 @@ class AXUploaderZoneDirective {
|
|
|
405
484
|
this.animationEndHandler = null;
|
|
406
485
|
this.element = this.elementRef.nativeElement;
|
|
407
486
|
this.element.style.position = 'relative';
|
|
487
|
+
bindUploaderZoneHost(this.element, this);
|
|
408
488
|
//
|
|
409
489
|
this.uploadService.onFileUploadComplete.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
|
|
410
490
|
this.onFileUploadComplete.emit(c);
|
|
@@ -430,6 +510,7 @@ class AXUploaderZoneDirective {
|
|
|
430
510
|
* Cleans up event listeners when the directive is destroyed.
|
|
431
511
|
*/
|
|
432
512
|
ngOnDestroy() {
|
|
513
|
+
unbindUploaderZoneHost(this.element);
|
|
433
514
|
this.element.removeEventListener('click', this.browser.bind(this));
|
|
434
515
|
this.element.removeEventListener('dragenter', this.handleDragEnter.bind(this));
|
|
435
516
|
this.element.removeEventListener('drop', this.handleOnDrop.bind(this));
|
|
@@ -467,16 +548,25 @@ class AXUploaderZoneDirective {
|
|
|
467
548
|
// Reset drag depth on drop
|
|
468
549
|
this.dragDepth.set(0);
|
|
469
550
|
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
this.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
551
|
+
let files = Array.from(event.dataTransfer.files);
|
|
552
|
+
let rejected;
|
|
553
|
+
const catalog = this.fileType();
|
|
554
|
+
if (catalog) {
|
|
555
|
+
const result = await this.uploadService.validateFiles(files, catalog);
|
|
556
|
+
files = result.accepted;
|
|
557
|
+
rejected = result.rejected;
|
|
558
|
+
}
|
|
559
|
+
this.fileChange.emit({ event, files, rejected });
|
|
560
|
+
if (!this.disableBrowse()) {
|
|
561
|
+
const requests = await this.uploadService.add(files, { fileType: catalog ?? undefined });
|
|
562
|
+
if (requests.length > 0) {
|
|
563
|
+
this.onChanged.emit({
|
|
564
|
+
component: this,
|
|
565
|
+
requests,
|
|
566
|
+
isUserInteraction: true,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
480
570
|
}
|
|
481
571
|
this.removeZone();
|
|
482
572
|
this.cdr.detectChanges();
|
|
@@ -640,32 +730,60 @@ class AXUploaderZoneDirective {
|
|
|
640
730
|
* @returns Promise that resolves when files are processed
|
|
641
731
|
*/
|
|
642
732
|
async browser() {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
733
|
+
const catalog = this.fileType();
|
|
734
|
+
let selected = [];
|
|
735
|
+
let rejected;
|
|
736
|
+
if (catalog) {
|
|
737
|
+
const accept = this.accept() ?? (await this.uploadService.getAcceptAttribute(catalog));
|
|
738
|
+
const result = await this.uploadService.chooseFiles({
|
|
739
|
+
fileType: catalog,
|
|
740
|
+
multiple: this.multiple(),
|
|
741
|
+
accept,
|
|
742
|
+
});
|
|
743
|
+
selected = result.accepted;
|
|
744
|
+
rejected = result.rejected;
|
|
745
|
+
}
|
|
746
|
+
else if (!this.disableBrowse()) {
|
|
747
|
+
const requests = await this.uploadService.browse({
|
|
748
|
+
accept: this.accept() ?? undefined,
|
|
749
|
+
multiple: this.multiple(),
|
|
656
750
|
});
|
|
657
|
-
|
|
751
|
+
selected = requests.map((r) => r.file);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
console.warn('AXUploaderZone.browser: fileType is required when disableBrowse is enabled (use axUploaderBrowseHandle).');
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (selected.length === 0 && !(rejected?.length ?? 0)) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const syntheticEvent = new Event('change');
|
|
761
|
+
const target = Object.create(EventTarget.prototype);
|
|
762
|
+
target.files = selected;
|
|
763
|
+
Object.defineProperty(syntheticEvent, 'target', { value: target, writable: false });
|
|
764
|
+
this.fileChange.emit({
|
|
765
|
+
event: syntheticEvent,
|
|
766
|
+
files: selected,
|
|
767
|
+
rejected,
|
|
768
|
+
});
|
|
769
|
+
if (this.disableBrowse()) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const requests = catalog
|
|
773
|
+
? await this.uploadService.add(selected, { fileType: catalog })
|
|
774
|
+
: await this.uploadService.add(selected);
|
|
775
|
+
if (requests.length > 0) {
|
|
658
776
|
this.onChanged.emit({
|
|
659
777
|
component: this,
|
|
660
|
-
requests
|
|
778
|
+
requests,
|
|
661
779
|
isUserInteraction: true,
|
|
662
780
|
});
|
|
663
781
|
}
|
|
664
782
|
}
|
|
665
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
666
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
783
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
784
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", 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
785
|
}
|
|
668
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
786
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderZoneDirective, decorators: [{
|
|
669
787
|
type: Directive,
|
|
670
788
|
args: [{
|
|
671
789
|
selector: '[axUploaderZone]',
|
|
@@ -674,66 +792,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
674
792
|
class: 'ax-drop-zone',
|
|
675
793
|
},
|
|
676
794
|
}]
|
|
677
|
-
}], ctorParameters: () => [] });
|
|
795
|
+
}], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], fileType: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileType", required: false }] }], overlayTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "overlayTemplate", required: false }] }], disableBrowse: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableBrowse", required: false }] }], disableDragDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableDragDrop", required: false }] }], fileChange: [{ type: i0.Output, args: ["fileChange"] }], onChanged: [{ type: i0.Output, args: ["onChanged"] }], dragEnter: [{ type: i0.Output, args: ["dragEnter"] }], dragLeave: [{ type: i0.Output, args: ["dragLeave"] }], dragOver: [{ type: i0.Output, args: ["dragOver"] }], onFileUploadComplete: [{ type: i0.Output, args: ["onFileUploadComplete"] }], onFilesUploadComplete: [{ type: i0.Output, args: ["onFilesUploadComplete"] }] } });
|
|
678
796
|
|
|
679
797
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
798
|
+
* Triggers the nearest {@link AXUploaderZoneDirective} file dialog on click.
|
|
799
|
+
* Resolves the zone via DI when possible, otherwise walks the DOM (supports content projection).
|
|
682
800
|
* @category Directives
|
|
683
801
|
*/
|
|
684
802
|
class AXUploaderBrowseDirective {
|
|
685
803
|
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
|
-
*/
|
|
804
|
+
this.elementRef = inject(ElementRef);
|
|
700
805
|
this.platformID = inject(PLATFORM_ID);
|
|
806
|
+
/** When browse + zone share the same host element. */
|
|
807
|
+
this.zoneFromInjector = inject(AXUploaderZoneDirective, { optional: true, self: true });
|
|
808
|
+
this.onClick = () => {
|
|
809
|
+
void this.handleClick();
|
|
810
|
+
};
|
|
701
811
|
}
|
|
702
|
-
/**
|
|
703
|
-
* Initializes the directive by adding click event listener and data attribute.
|
|
704
|
-
*/
|
|
705
812
|
ngOnInit() {
|
|
706
|
-
if (isPlatformBrowser(this.platformID)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
813
|
+
if (!isPlatformBrowser(this.platformID)) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const element = this.elementRef.nativeElement;
|
|
817
|
+
element.addEventListener('click', this.onClick);
|
|
818
|
+
if (element.dataset) {
|
|
819
|
+
element.dataset['axUploaderBrowseHandle'] = 'true';
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
element.setAttribute('data-ax-uploader-browse-handle', 'true');
|
|
716
823
|
}
|
|
717
824
|
}
|
|
718
|
-
/**
|
|
719
|
-
* Cleans up the directive by removing event listeners.
|
|
720
|
-
*/
|
|
721
825
|
ngOnDestroy() {
|
|
722
|
-
if (isPlatformBrowser(this.platformID)
|
|
723
|
-
this.elementRef.nativeElement.removeEventListener('click', this.
|
|
826
|
+
if (isPlatformBrowser(this.platformID)) {
|
|
827
|
+
this.elementRef.nativeElement.removeEventListener('click', this.onClick);
|
|
724
828
|
}
|
|
725
829
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
830
|
+
resolveZone() {
|
|
831
|
+
return (this.zoneFromInjector ??
|
|
832
|
+
findUploaderZoneFromDom(this.elementRef.nativeElement) ??
|
|
833
|
+
undefined);
|
|
834
|
+
}
|
|
730
835
|
async handleClick() {
|
|
731
|
-
|
|
836
|
+
const zone = this.resolveZone();
|
|
837
|
+
if (!zone) {
|
|
838
|
+
console.warn('[axUploaderBrowseHandle] No axUploaderZone found. Place axUploaderBrowseHandle inside an axUploaderZone.');
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
await zone.browser();
|
|
732
842
|
}
|
|
733
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
734
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
843
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderBrowseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
844
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXUploaderBrowseDirective, isStandalone: true, selector: "[axUploaderBrowseHandle]", ngImport: i0 }); }
|
|
735
845
|
}
|
|
736
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
846
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXUploaderBrowseDirective, decorators: [{
|
|
737
847
|
type: Directive,
|
|
738
848
|
args: [{ selector: '[axUploaderBrowseHandle]' }]
|
|
739
849
|
}] });
|