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.
Files changed (42) hide show
  1. package/angle-selector/README.md +11 -11
  2. package/box-shadow/README.md +10 -10
  3. package/color-picker/README.md +20 -23
  4. package/data-table/README.md +34 -41
  5. package/date-picker/README.md +21 -26
  6. package/dialog/README.md +31 -33
  7. package/drawer-menu/README.md +51 -50
  8. package/dropzone/README.md +395 -32
  9. package/fesm2022/ngx-kit-box-shadow.mjs +2 -2
  10. package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
  11. package/fesm2022/ngx-kit-color-picker.mjs +2 -2
  12. package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
  13. package/fesm2022/ngx-kit-data-table.mjs +68 -26
  14. package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
  15. package/fesm2022/ngx-kit-dropzone.mjs +274 -29
  16. package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
  17. package/fesm2022/ngx-kit-form-field.mjs +129 -64
  18. package/fesm2022/ngx-kit-form-field.mjs.map +1 -1
  19. package/fesm2022/ngx-kit-gallery.mjs +2 -2
  20. package/fesm2022/ngx-kit-gallery.mjs.map +1 -1
  21. package/fesm2022/ngx-kit-gradient-picker.mjs +2 -2
  22. package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
  23. package/fesm2022/ngx-kit-image-editor.mjs +33 -10
  24. package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
  25. package/fesm2022/ngx-kit-message.mjs +5 -0
  26. package/fesm2022/ngx-kit-message.mjs.map +1 -1
  27. package/fesm2022/ngx-kit-time-picker.mjs +17 -4
  28. package/fesm2022/ngx-kit-time-picker.mjs.map +1 -1
  29. package/gallery/README.md +29 -29
  30. package/gradient-picker/README.md +16 -16
  31. package/image-editor/README.md +19 -19
  32. package/menu/README.md +27 -27
  33. package/message/README.md +34 -34
  34. package/notify/README.md +25 -23
  35. package/package.json +1 -1
  36. package/shared/README.md +32 -32
  37. package/time-picker/README.md +95 -0
  38. package/types/ngx-kit-data-table.d.ts +25 -5
  39. package/types/ngx-kit-dropzone.d.ts +129 -18
  40. package/types/ngx-kit-form-field.d.ts +65 -27
  41. package/types/ngx-kit-image-editor.d.ts +23 -3
  42. package/types/ngx-kit-time-picker.d.ts +19 -14
@@ -1,14 +1,14 @@
1
1
  # ngx-kit/drawer-menu
2
2
 
3
- درور منو (side drawer) با ۷ جلوه‌ی بازوبسته‌شدنِ متفاوت (از ساده تا سه‌بعدی/ژله‌ای)، ۳ رفتار برای محتوای صفحه (overlay/push/reveal)، و پیش‌فرضِ باز/بسته‌بودنِ واکنش‌گرا نسبت به اندازه‌ی صفحه.
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">خانه</a>
25
- <a routerLink="/settings">تنظیمات</a>
24
+ <!-- menu content -->
25
+ <a routerLink="/home">Home</a>
26
+ <a routerLink="/settings">Settings</a>
26
27
  </div>
27
28
 
28
- <!-- محتوای اصلیِ صفحه (پیش‌فرض، بدون select) -->
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` | `'start' \| 'end'` | `'start'` | منطقی — زیر RTL خودکار برعکس می‌شه |
38
- | `effect` | `'slide' \| 'curtain' \| 'jelly' \| 'pull' \| 'rotate3d' \| 'flip3d' \| 'scale'` | `'slide'` | جلوه‌ی بازوبسته‌شدنِ خودِ پنل (پایین‌تر توضیح داده شده) |
39
- | `contentBehavior` | `'overlay' \| 'push' \| 'reveal'` | `'overlay'` | رفتارِ محتوای اصلیِ صفحه |
40
- | `width` | `number` | `280` | عرضِ پنل به پیکسل |
41
- | `swipeEnabled` | `boolean` | `true` | باز کردن با کشیدن از لبه‌ی صفحه |
42
- | `swipeEdgeSize` | `number` | `24` | پهنای ناحیه‌ی نامرئیِ لبه که کشیدن ازش باز می‌کنه |
43
- | `backdropClose` | `boolean` | `true` | بستن با کلیک روی بک‌دراپ (یا تیره‌شدنِ محتوا در push/reveal) |
44
- | `curtainStrips` | `number` | `7` | فقط برای `effect="curtain"` |
45
- | `open` | `boolean` (model، دوطرفه) | `false` | با `[(open)]` قابل‌کنترله |
46
- | `respondToViewport` | `boolean` | `true` | رد شدن از `mobileBreakpoint` به‌صورت **زنده** (نه فقط بار اول) `open` رو با `openOnDesktop`/`openOnMobile` هماهنگ می‌کنه |
47
- | `openOnDesktop` / `openOnMobile` | `boolean` | `true` / `false` | |
48
- | `mobileBreakpoint` | `number` | `768` | |
49
-
50
- ### جلوه‌ها (`effect`)
51
-
52
- | مقدار | توضیح |
53
- | ---------- | ---------------------------------------------------------------------------------------------------------------- |
54
- | `slide` | اسلایدِ ساده‌ی کلاسیک |
55
- | `curtain` | چند نوارِ عمودیِ هم‌رنگِ پنل که با تأخیرِ پلکانی جمع می‌شن و محتوا رو نمایان می‌کنن |
56
- | `jelly` | نوسانِ فنریِ چندمرحله‌ای که فروکش می‌کنه (الهام از افکتِ [Jelly Slide Menu](https://dribbble.com/shots/2307371)) |
57
- | `pull` | کشیدگیِ لاستیکیِ تک‌ضربه‌ای (rubber-band) |
58
- | `rotate3d` | چرخشِ سه‌بعدیِ دورِ لولا، مثلِ بازشدنِ یه در |
59
- | `flip3d` | ورق‌خوردن با عمق روی محورِ Z، مثلِ جلدِ یه کتاب |
60
- | `scale` | بزرگ‌شدن از مرکز همراه با fade |
61
-
62
- ### رفتارِ محتوا (`contentBehavior`)
63
-
64
- | مقدار | توضیح |
65
- | --------- | ---------------------------------------------------------------------- |
66
- | `overlay` | محتوا سرجاش می‌مونه، درور روش شناور می‌شه (با بک‌دراپ) |
67
- | `push` | محتوا به‌اندازه‌ی عرضِ درور هل داده می‌شه (کنار می‌ره) |
68
- | `reveal` | محتوا کمی کوچیک/عقب می‌ره، انگار درور از زیرش نمایان می‌شه (افکتِ iOS) |
69
-
70
- ## نکته‌ی معماری: امبد کردن داخلِ یه ناحیه‌ی محدود
71
-
72
- پیش‌فرض، درور کل ارتفاعِ نزدیک‌ترین والدِ positioned رو می‌گیره (برای shell سراسریِ اپ همینو می‌خواید). اگه می‌خواید داخلِ یه کارت/ناحیه‌ی محدود (نه کل صفحه) امبدش کنید، کافیه اون container یه `position: relative` و ارتفاعِ مشخص داشته باشه — درور خودکار به همون باکس محدود می‌مونه (به لطفِ `perspective`ای که برای جلوه‌های سه‌بعدی استفاده می‌کنه و طبقِ اسپکِ CSS، containing-block برای فرزندهای `position:fixed` می‌سازه).
73
-
74
- ## دارک‌مود و RTL
75
-
76
- خودکار پشتیبانی می‌شه؛ محاسباتِ درگ (که برخلافِ CSS logical properties، جهتِ فیزیکیِ واقعی لازم دارن) از `DirectionService` مشترک استفاده می‌کنن، پس با تغییرِ زنده‌ی `dir` هم درست کار می‌کنن.
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.
@@ -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
- ```bash
8
- npm install ngx-kit
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
- ## API
41
+ ## Automatic upload
26
42
 
27
- | ورودی | نوع | پیش‌فرض | توضیح |
28
- | -------------- | --------------- | ------------ | ---------------------------------------------------------------------- |
29
- | `accept` | `string` | `''` | مثل `input[type=file]`: `"image/*"`, `".pdf,.docx"`, یا ترکیبی با کاما |
30
- | `multiple` | `boolean` | `true` | اجازه‌ی انتخاب چند فایل |
31
- | `maxFileSize` | `number` (بایت) | بدون محدودیت | حداکثر حجمِ هر فایل |
32
- | `maxFiles` | `number` | بدون محدودیت | حداکثر تعدادِ فایل مجاز |
33
- | `disabled` | `boolean` | `false` | |
34
- | `showFileList` | `boolean` | `true` | نمایشِ لیست/پیش‌نمایشِ فایل‌های انتخاب‌شده داخل خودِ کامپوننت |
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
- <svg dropzone-icon>...</svg>
47
- متن دلخواه شما
48
- </ngx-dropzone>
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
- - شمارنده‌ی enter/leave برای جلوگیری از چشمک‌زدنِ حالتِ «درگ» موقعِ عبور از روی فرزندهای داخلِ ناحیه.
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
- ## دارک‌مود و RTL
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.