ngx-kit 6.0.1 → 6.1.0
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/angle-selector/README.md +11 -11
- package/box-shadow/README.md +10 -10
- package/color-picker/README.md +20 -23
- package/data-table/README.md +34 -41
- package/date-picker/README.md +21 -26
- package/dialog/README.md +31 -33
- package/drawer-menu/README.md +51 -50
- package/dropzone/README.md +395 -32
- package/fesm2022/ngx-kit-box-shadow.mjs +2 -2
- package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
- package/fesm2022/ngx-kit-color-picker.mjs +2 -2
- package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-data-table.mjs +68 -26
- package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
- package/fesm2022/ngx-kit-dropzone.mjs +274 -29
- package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
- package/fesm2022/ngx-kit-form-field.mjs +129 -64
- package/fesm2022/ngx-kit-form-field.mjs.map +1 -1
- package/fesm2022/ngx-kit-gallery.mjs +2 -2
- package/fesm2022/ngx-kit-gallery.mjs.map +1 -1
- package/fesm2022/ngx-kit-gradient-picker.mjs +2 -2
- package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-image-editor.mjs +33 -10
- package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
- package/fesm2022/ngx-kit-message.mjs +5 -0
- package/fesm2022/ngx-kit-message.mjs.map +1 -1
- package/fesm2022/ngx-kit-time-picker.mjs +17 -4
- package/fesm2022/ngx-kit-time-picker.mjs.map +1 -1
- package/gallery/README.md +29 -29
- package/gradient-picker/README.md +16 -16
- package/image-editor/README.md +19 -19
- package/menu/README.md +27 -27
- package/message/README.md +34 -34
- package/notify/README.md +25 -23
- package/package.json +1 -1
- package/shared/README.md +32 -32
- package/time-picker/README.md +95 -0
- package/types/ngx-kit-data-table.d.ts +25 -5
- package/types/ngx-kit-dropzone.d.ts +129 -18
- package/types/ngx-kit-form-field.d.ts +65 -27
- package/types/ngx-kit-image-editor.d.ts +23 -3
- package/types/ngx-kit-time-picker.d.ts +19 -14
package/drawer-menu/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# ngx-kit/drawer-menu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A side drawer menu with 7 different opening/closing effects (from simple to 3D/jelly), 3 behaviors for the page content (overlay/push/reveal), and a responsive default open/closed state based on screen size.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install ngx-kit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Basic usage
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
14
|
<ngx-drawer-menu
|
|
@@ -18,59 +18,60 @@ npm install ngx-kit
|
|
|
18
18
|
[width]="280"
|
|
19
19
|
[openOnDesktop]="true"
|
|
20
20
|
[openOnMobile]="false"
|
|
21
|
-
[(open)]="isOpen"
|
|
21
|
+
[(open)]="isOpen"
|
|
22
|
+
>
|
|
22
23
|
<div drawerContent>
|
|
23
|
-
<!--
|
|
24
|
-
<a routerLink="/home"
|
|
25
|
-
<a routerLink="/settings"
|
|
24
|
+
<!-- menu content -->
|
|
25
|
+
<a routerLink="/home">Home</a>
|
|
26
|
+
<a routerLink="/settings">Settings</a>
|
|
26
27
|
</div>
|
|
27
28
|
|
|
28
|
-
<!--
|
|
29
|
+
<!-- main page content (default, no select) -->
|
|
29
30
|
<router-outlet />
|
|
30
31
|
</ngx-drawer-menu>
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
## API
|
|
34
35
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
| `side`
|
|
38
|
-
| `effect`
|
|
39
|
-
| `contentBehavior`
|
|
40
|
-
| `width`
|
|
41
|
-
| `swipeEnabled`
|
|
42
|
-
| `swipeEdgeSize`
|
|
43
|
-
| `backdropClose`
|
|
44
|
-
| `curtainStrips`
|
|
45
|
-
| `open`
|
|
46
|
-
| `respondToViewport`
|
|
47
|
-
| `openOnDesktop` / `openOnMobile` | `boolean`
|
|
48
|
-
| `mobileBreakpoint`
|
|
49
|
-
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
| `slide`
|
|
55
|
-
| `curtain`
|
|
56
|
-
| `jelly`
|
|
57
|
-
| `pull`
|
|
58
|
-
| `rotate3d` |
|
|
59
|
-
| `flip3d`
|
|
60
|
-
| `scale`
|
|
61
|
-
|
|
62
|
-
###
|
|
63
|
-
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
| `overlay` |
|
|
67
|
-
| `push`
|
|
68
|
-
| `reveal`
|
|
69
|
-
|
|
70
|
-
##
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
36
|
+
| Input | Type | Default | Description |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `side` | `'start' \| 'end'` | `'start'` | Logical — automatically flips under RTL |
|
|
39
|
+
| `effect` | `'slide' \| 'curtain' \| 'jelly' \| 'pull' \| 'rotate3d' \| 'flip3d' \| 'scale'` | `'slide'` | The panel's own open/close effect (described below) |
|
|
40
|
+
| `contentBehavior` | `'overlay' \| 'push' \| 'reveal'` | `'overlay'` | How the main page content behaves |
|
|
41
|
+
| `width` | `number` | `280` | Panel width in pixels |
|
|
42
|
+
| `swipeEnabled` | `boolean` | `true` | Open by swiping from the screen edge |
|
|
43
|
+
| `swipeEdgeSize` | `number` | `24` | Width of the invisible edge zone that a swipe can start from |
|
|
44
|
+
| `backdropClose` | `boolean` | `true` | Close on backdrop click (or dimming the content in push/reveal) |
|
|
45
|
+
| `curtainStrips` | `number` | `7` | Only for `effect="curtain"` |
|
|
46
|
+
| `open` | `boolean` (model, two-way) | `false` | Controllable via `[(open)]` |
|
|
47
|
+
| `respondToViewport` | `boolean` | `true` | Crossing `mobileBreakpoint` **live** (not just on first load) syncs `open` with `openOnDesktop`/`openOnMobile` |
|
|
48
|
+
| `openOnDesktop` / `openOnMobile` | `boolean` | `true` / `false` | |
|
|
49
|
+
| `mobileBreakpoint` | `number` | `768` | |
|
|
50
|
+
|
|
51
|
+
### Effects (`effect`)
|
|
52
|
+
|
|
53
|
+
| Value | Description |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `slide` | Classic simple slide |
|
|
56
|
+
| `curtain` | Several same-colored vertical strips that collapse with a staggered delay, revealing the content |
|
|
57
|
+
| `jelly` | A multi-stage spring oscillation that settles down (inspired by the [Jelly Slide Menu](https://dribbble.com/shots/2307371) effect) |
|
|
58
|
+
| `pull` | A single rubber-band stretch |
|
|
59
|
+
| `rotate3d` | A 3D rotation around a hinge, like a door opening |
|
|
60
|
+
| `flip3d` | Flips with depth on the Z axis, like a book cover |
|
|
61
|
+
| `scale` | Zooms in from the center with a fade |
|
|
62
|
+
|
|
63
|
+
### Content behavior (`contentBehavior`)
|
|
64
|
+
|
|
65
|
+
| Value | Description |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `overlay` | Content stays in place, the drawer floats over it (with a backdrop) |
|
|
68
|
+
| `push` | Content is pushed aside by the drawer's width |
|
|
69
|
+
| `reveal` | Content shrinks/recedes slightly, as if the drawer is revealed behind it (the iOS effect) |
|
|
70
|
+
|
|
71
|
+
## Architecture note: embedding inside a bounded area
|
|
72
|
+
|
|
73
|
+
By default, the drawer takes up the full height of the nearest positioned ancestor (which is what you want for a full app shell). If you want to embed it inside a bounded card/area (not the whole page), just give that container `position: relative` and a defined height — the drawer will automatically confine itself to that box (thanks to the `perspective` it uses for 3D effects, which per the CSS spec creates a containing block for `position:fixed` descendants).
|
|
74
|
+
|
|
75
|
+
## Dark mode and RTL
|
|
76
|
+
|
|
77
|
+
Supported automatically; drag math (which, unlike CSS logical properties, needs the real physical direction) uses the shared `DirectionService`, so it works correctly even with a live `dir` change.
|
package/dropzone/README.md
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
# ngx-kit/dropzone
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A standalone Angular dropzone with file validation, previews, recursive folder support, keyboard accessibility, and a production-ready upload pipeline.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The upload layer is intentionally decoupled from the UI. The component only knows about `NgxDropzoneUploadService`, so applications can use the default Angular `HttpClient` implementation or replace it with Axios, `fetch`, tus, S3 multipart, or any custom transport.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Drag & drop and file-picker selection
|
|
10
|
+
- MIME, wildcard MIME and extension validation
|
|
11
|
+
- Maximum file size and maximum file count
|
|
12
|
+
- Recursive folder traversal when the browser exposes `webkitGetAsEntry`
|
|
13
|
+
- Image previews with `URL.revokeObjectURL` cleanup
|
|
14
|
+
- Keyboard accessible dropzone
|
|
15
|
+
- Signal-based Angular component with `OnPush`
|
|
16
|
+
- Automatic or manual uploads
|
|
17
|
+
- Configurable multipart field alias
|
|
18
|
+
- Configurable upload URL
|
|
19
|
+
- Configurable parallel upload limit
|
|
20
|
+
- Per-file upload progress
|
|
21
|
+
- Per-file success/error state
|
|
22
|
+
- Retry failed uploads
|
|
23
|
+
- Cancel an active upload by removing the file
|
|
24
|
+
- Optional multipart fields, headers and credentials
|
|
25
|
+
- Pluggable upload adapter through an `InjectionToken`
|
|
10
26
|
|
|
11
|
-
##
|
|
27
|
+
## Basic selection
|
|
12
28
|
|
|
13
29
|
```html
|
|
14
30
|
<ngx-dropzone
|
|
@@ -18,42 +34,389 @@ npm install ngx-kit
|
|
|
18
34
|
[maxFiles]="10"
|
|
19
35
|
(filesAdded)="onFilesAdded($event)"
|
|
20
36
|
(filesRejected)="onFilesRejected($event)">
|
|
21
|
-
|
|
37
|
+
Drop files here or click to browse
|
|
22
38
|
</ngx-dropzone>
|
|
23
39
|
```
|
|
24
40
|
|
|
25
|
-
##
|
|
41
|
+
## Automatic upload
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
When `autoUpload` is enabled, every newly accepted file is queued immediately.
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<ngx-dropzone
|
|
47
|
+
[accept]="'image/*,.pdf'"
|
|
48
|
+
[uploadUrl]="'/api/files/upload'"
|
|
49
|
+
[uploadAlias]="'file'"
|
|
50
|
+
[autoUpload]="true"
|
|
51
|
+
[maxParallel]="3">
|
|
52
|
+
Drop files here or click to browse
|
|
53
|
+
</ngx-dropzone>
|
|
54
|
+
```
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
| --------------- | ------------------------------------------------------- | --------------------------------------------- |
|
|
38
|
-
| `filesAdded` | `File[]` | فایلهای تازهپذیرفتهشده |
|
|
39
|
-
| `filesRejected` | `{ file: File; reason: 'type' \| 'size' \| 'count' }[]` | فایلهای ردشده و دلیلش |
|
|
40
|
-
| `filesChange` | `File[]` | کل لیستِ فعلی، بعد از هر تغییر (اضافه یا حذف) |
|
|
56
|
+
For example, the browser sends a multipart request equivalent to:
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
```text
|
|
59
|
+
POST /api/files/upload
|
|
60
|
+
Content-Type: multipart/form-data
|
|
61
|
+
|
|
62
|
+
file=<selected file>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`maxParallel="3"` means at most three upload requests are active at the same time. Additional files remain in an internal FIFO queue.
|
|
66
|
+
|
|
67
|
+
## Manual upload: one "Upload all" button
|
|
68
|
+
|
|
69
|
+
This is the default manual mode.
|
|
43
70
|
|
|
44
71
|
```html
|
|
45
|
-
<ngx-dropzone
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
<ngx-dropzone
|
|
73
|
+
[uploadUrl]="'/api/files/upload'"
|
|
74
|
+
[uploadAlias]="'files'"
|
|
75
|
+
[autoUpload]="false"
|
|
76
|
+
[uploadMode]="'all'"
|
|
77
|
+
[maxParallel]="4"></ngx-dropzone>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The component renders an **Upload all** button. Clicking it queues every pending file. `maxParallel` still controls the number of concurrent requests.
|
|
81
|
+
|
|
82
|
+
## Manual upload: one button per file
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<ngx-dropzone
|
|
86
|
+
[uploadUrl]="'/api/files/upload'"
|
|
87
|
+
[uploadAlias]="'file'"
|
|
88
|
+
[autoUpload]="false"
|
|
89
|
+
[uploadMode]="'single'"
|
|
90
|
+
[maxParallel]="2"></ngx-dropzone>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Each pending row gets its own **Upload** button. The user can decide which files enter the upload queue.
|
|
94
|
+
|
|
95
|
+
## Extra multipart fields
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<ngx-dropzone
|
|
99
|
+
[uploadUrl]="'/api/files/upload'"
|
|
100
|
+
[uploadAlias]="'file'"
|
|
101
|
+
[uploadData]="{
|
|
102
|
+
tenantId: 'abc',
|
|
103
|
+
folderId: 123,
|
|
104
|
+
isPublic: false
|
|
105
|
+
}"></ngx-dropzone>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Every request contains the selected file plus those multipart fields.
|
|
109
|
+
|
|
110
|
+
## Headers and credentials
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<ngx-dropzone
|
|
114
|
+
[uploadUrl]="'https://api.example.com/files'"
|
|
115
|
+
[uploadHeaders]="{
|
|
116
|
+
'X-Tenant-Id': 'abc',
|
|
117
|
+
'X-Upload-Source': 'ngx-dropzone'
|
|
118
|
+
}"
|
|
119
|
+
[uploadWithCredentials]="true"></ngx-dropzone>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Authentication headers that are already added by an Angular `HttpInterceptor` do not need to be supplied here.
|
|
123
|
+
|
|
124
|
+
## Upload lifecycle
|
|
125
|
+
|
|
126
|
+
Each file has one of these states:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
pending -> uploading -> uploaded
|
|
130
|
+
\
|
|
131
|
+
-> error -> retry -> uploading
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Removing a file while it is uploading unsubscribes from the upload observable. With the default `HttpClient` adapter this cancels the underlying HTTP request.
|
|
135
|
+
|
|
136
|
+
Every row displays:
|
|
137
|
+
|
|
138
|
+
- current status
|
|
139
|
+
- upload percentage
|
|
140
|
+
- progress bar
|
|
141
|
+
- upload/retry action when appropriate
|
|
142
|
+
- remove action
|
|
143
|
+
|
|
144
|
+
## Outputs
|
|
145
|
+
|
|
146
|
+
| Output | Payload | Description |
|
|
147
|
+
| ---------------- | -------------------------------- | ----------------------------- |
|
|
148
|
+
| `filesAdded` | `File[]` | Newly accepted files |
|
|
149
|
+
| `filesRejected` | `NgxDropzoneRejectedFile[]` | Files rejected by validation |
|
|
150
|
+
| `filesChange` | `File[]` | Current selected files |
|
|
151
|
+
| `uploadStarted` | `NgxDropzoneFile` | A file started uploading |
|
|
152
|
+
| `uploadProgress` | `NgxDropzoneUploadProgressEvent` | Progress update |
|
|
153
|
+
| `uploadSuccess` | `NgxDropzoneFile` | Upload completed successfully |
|
|
154
|
+
| `uploadError` | `{ item, error }` | Upload failed |
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<ngx-dropzone
|
|
160
|
+
[uploadUrl]="'/api/files'"
|
|
161
|
+
[autoUpload]="true"
|
|
162
|
+
[maxParallel]="3"
|
|
163
|
+
(uploadProgress)="onUploadProgress($event)"
|
|
164
|
+
(uploadSuccess)="onUploadSuccess($event)"
|
|
165
|
+
(uploadError)="onUploadError($event)"></ngx-dropzone>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Upload contracts
|
|
169
|
+
|
|
170
|
+
The upload boundary is defined by:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
NgxDropzoneUploadService;
|
|
174
|
+
NgxDropzoneUploadRequest;
|
|
175
|
+
NgxDropzoneUploadEvent;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The service returns an `Observable`. It emits progress events and finally a response event.
|
|
179
|
+
|
|
180
|
+
This is important: **the component does not depend on `HttpClient`**.
|
|
181
|
+
|
|
182
|
+
## Default Angular HttpClient adapter
|
|
183
|
+
|
|
184
|
+
The library ships with `NgxDropzoneHttpUploadService`.
|
|
185
|
+
|
|
186
|
+
It uses:
|
|
187
|
+
|
|
188
|
+
- `HttpClient.post`
|
|
189
|
+
- `FormData`
|
|
190
|
+
- `reportProgress: true`
|
|
191
|
+
- `observe: 'events'`
|
|
192
|
+
|
|
193
|
+
The default adapter is connected through:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
NGX_DROPZONE_UPLOAD_SERVICE;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The token has a default factory, so normal applications do not need to configure anything.
|
|
200
|
+
|
|
201
|
+
Make sure your application provides Angular's HTTP client:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
205
|
+
|
|
206
|
+
export const appConfig = {
|
|
207
|
+
providers: [provideHttpClient()],
|
|
208
|
+
};
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Replacing HttpClient with Axios
|
|
212
|
+
|
|
213
|
+
You can completely replace the transport without changing the dropzone component.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { Injectable } from '@angular/core';
|
|
217
|
+
import { Observable } from 'rxjs';
|
|
218
|
+
import axios from 'axios';
|
|
219
|
+
|
|
220
|
+
import {
|
|
221
|
+
NgxDropzoneUploadEvent,
|
|
222
|
+
NgxDropzoneUploadRequest,
|
|
223
|
+
NgxDropzoneUploadService,
|
|
224
|
+
} from 'ngx-kit';
|
|
225
|
+
|
|
226
|
+
@Injectable()
|
|
227
|
+
export class AxiosDropzoneUploadService implements NgxDropzoneUploadService {
|
|
228
|
+
upload<TResponse = unknown>(
|
|
229
|
+
request: NgxDropzoneUploadRequest,
|
|
230
|
+
): Observable<NgxDropzoneUploadEvent<TResponse>> {
|
|
231
|
+
return new Observable((subscriber) => {
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
|
|
234
|
+
const form = new FormData();
|
|
235
|
+
form.append(request.alias, request.file, request.file.name);
|
|
236
|
+
|
|
237
|
+
for (const [key, value] of Object.entries(request.data ?? {})) {
|
|
238
|
+
form.append(key, value instanceof Blob ? value : String(value));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
axios
|
|
242
|
+
.post<TResponse>(request.url, form, {
|
|
243
|
+
headers: request.headers,
|
|
244
|
+
withCredentials: request.withCredentials,
|
|
245
|
+
signal: controller.signal,
|
|
246
|
+
onUploadProgress: (event) => {
|
|
247
|
+
const total = event.total;
|
|
248
|
+
subscriber.next({
|
|
249
|
+
type: 'progress',
|
|
250
|
+
loaded: event.loaded,
|
|
251
|
+
total,
|
|
252
|
+
percent: total ? Math.min(100, Math.round((event.loaded / total) * 100)) : 0,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
.then((response) => {
|
|
257
|
+
subscriber.next({
|
|
258
|
+
type: 'response',
|
|
259
|
+
response: response.data,
|
|
260
|
+
status: response.status,
|
|
261
|
+
});
|
|
262
|
+
subscriber.complete();
|
|
263
|
+
})
|
|
264
|
+
.catch((error) => subscriber.error(error));
|
|
265
|
+
|
|
266
|
+
return () => controller.abort();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then override the token:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { NGX_DROPZONE_UPLOAD_SERVICE } from 'ngx-kit';
|
|
276
|
+
|
|
277
|
+
export const appConfig = {
|
|
278
|
+
providers: [
|
|
279
|
+
{
|
|
280
|
+
provide: NGX_DROPZONE_UPLOAD_SERVICE,
|
|
281
|
+
useClass: AxiosDropzoneUploadService,
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
This keeps the dropzone UI independent from the HTTP library.
|
|
288
|
+
|
|
289
|
+
## Custom adapter at component level
|
|
290
|
+
|
|
291
|
+
You can also replace the adapter for a specific dropzone:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
@Component({
|
|
295
|
+
// ...
|
|
296
|
+
providers: [
|
|
297
|
+
{
|
|
298
|
+
provide: NGX_DROPZONE_UPLOAD_SERVICE,
|
|
299
|
+
useClass: AxiosDropzoneUploadService,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
})
|
|
303
|
+
export class MyUploaderComponent {}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This is useful when one part of an application uses a different upload backend.
|
|
307
|
+
|
|
308
|
+
## Configuration API
|
|
309
|
+
|
|
310
|
+
| Input | Type | Default | Description |
|
|
311
|
+
| ----------------------- | ------------------------------------ | --------- | ---------------------------------- |
|
|
312
|
+
| `accept` | `string` | `''` | MIME/extension filters |
|
|
313
|
+
| `multiple` | `boolean` | `true` | Allow multiple files |
|
|
314
|
+
| `maxFileSize` | `number` | unlimited | Maximum size per file in bytes |
|
|
315
|
+
| `maxFiles` | `number` | unlimited | Maximum number of files |
|
|
316
|
+
| `disabled` | `boolean` | `false` | Disable interaction |
|
|
317
|
+
| `showFileList` | `boolean` | `true` | Show selected files |
|
|
318
|
+
| `uploadUrl` | `string` | `''` | Upload endpoint |
|
|
319
|
+
| `uploadAlias` | `string` | `'file'` | Multipart file field name |
|
|
320
|
+
| `autoUpload` | `boolean` | `false` | Automatically queue accepted files |
|
|
321
|
+
| `maxParallel` | `number` | `3` | Maximum simultaneous uploads |
|
|
322
|
+
| `uploadMode` | `'all' \| 'single'` | `'all'` | Manual upload UI behavior |
|
|
323
|
+
| `uploadData` | `Record<string, ...>` | `{}` | Extra multipart fields |
|
|
324
|
+
| `uploadHeaders` | `Record<string, string \| string[]>` | `{}` | Extra request headers |
|
|
325
|
+
| `uploadWithCredentials` | `boolean` | `false` | Send credentials |
|
|
326
|
+
|
|
327
|
+
## Tests
|
|
328
|
+
|
|
329
|
+
The upload implementation is covered at two levels.
|
|
330
|
+
|
|
331
|
+
### HTTP adapter tests
|
|
332
|
+
|
|
333
|
+
`ngx-dropzone-upload.spec.ts` verifies:
|
|
334
|
+
|
|
335
|
+
- multipart file alias
|
|
336
|
+
- extra form data
|
|
337
|
+
- POST request
|
|
338
|
+
- upload progress calculation
|
|
339
|
+
- final response event
|
|
340
|
+
|
|
341
|
+
### Component upload tests
|
|
342
|
+
|
|
343
|
+
`dropzone-upload.spec.ts` verifies:
|
|
344
|
+
|
|
345
|
+
- automatic upload
|
|
346
|
+
- `maxParallel` concurrency
|
|
347
|
+
- queued uploads starting after a slot is released
|
|
348
|
+
- per-file manual upload
|
|
349
|
+
- progress propagation
|
|
350
|
+
- failed upload state
|
|
351
|
+
- retry UI
|
|
352
|
+
|
|
353
|
+
The existing `file-matching.spec.ts` continues to cover file type/extension matching and byte formatting.
|
|
354
|
+
|
|
355
|
+
## Design decisions
|
|
356
|
+
|
|
357
|
+
### Why an `InjectionToken`?
|
|
358
|
+
|
|
359
|
+
The dropzone should not be coupled to a transport library.
|
|
360
|
+
|
|
361
|
+
Bad:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
class Dropzone {
|
|
365
|
+
constructor(private http: HttpClient) {}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Better:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
class Dropzone {
|
|
373
|
+
constructor(
|
|
374
|
+
@Inject(NGX_DROPZONE_UPLOAD_SERVICE)
|
|
375
|
+
private uploader: NgxDropzoneUploadService,
|
|
376
|
+
) {}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Now the UI depends on a small capability (`upload`) instead of Angular `HttpClient`.
|
|
381
|
+
|
|
382
|
+
### Why an Observable?
|
|
383
|
+
|
|
384
|
+
An upload is a stream of events rather than a single value:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
progress -> progress -> progress -> response
|
|
49
388
|
```
|
|
50
389
|
|
|
51
|
-
|
|
390
|
+
It also gives custom adapters a clean cancellation mechanism: unsubscribing from the observable cancels the transport.
|
|
391
|
+
|
|
392
|
+
### Why `maxParallel` is handled by the component
|
|
393
|
+
|
|
394
|
+
The upload adapter is responsible for **one file**.
|
|
395
|
+
|
|
396
|
+
The dropzone is responsible for **orchestration**:
|
|
397
|
+
|
|
398
|
+
```text
|
|
399
|
+
Files
|
|
400
|
+
|
|
|
401
|
+
+-- pending queue
|
|
402
|
+
|
|
|
403
|
+
+-- upload #1
|
|
404
|
+
+-- upload #2
|
|
405
|
+
+-- upload #3
|
|
406
|
+
|
|
|
407
|
+
+-- pending queue...
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
This keeps the adapter simple and lets every transport share the same concurrency behavior.
|
|
411
|
+
|
|
412
|
+
## Existing behavior retained
|
|
413
|
+
|
|
414
|
+
The upload feature is layered on top of the existing selection implementation. File acceptance, maximum size/count validation, recursive directory traversal, keyboard interaction, previews, and preview URL cleanup remain part of the component.
|
|
415
|
+
|
|
416
|
+
## Accessibility
|
|
52
417
|
|
|
53
|
-
|
|
54
|
-
- درگکردن یه **پوشه** هم پشتیبانی میشه (`webkitGetAsEntry` + پیمایشِ بازگشتی) — مرورگرهایی که پشتیبانی نکنن، به گرفتنِ فایلهای مستقیم fallback میکنن.
|
|
55
|
-
- object URLهای پیشنمایش موقعِ حذفِ فایل یا destroy شدنِ کامپوننت، خودکار `revokeObjectURL` میشن (بدون نشتی حافظه).
|
|
418
|
+
The dropzone remains keyboard accessible through Enter/Space. Upload progress uses the ARIA `progressbar` role and exposes the current percentage.
|
|
56
419
|
|
|
57
|
-
##
|
|
420
|
+
## Dark mode and RTL
|
|
58
421
|
|
|
59
|
-
|
|
422
|
+
The existing CSS uses logical properties and `light-dark()`, so RTL and dark/light themes continue to work automatically.
|