ngx-kit 7.3.2 → 7.4.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.
@@ -0,0 +1,349 @@
1
+ # ngx-kit/media-control
2
+
3
+ An Angular-native Audio/Video player: native `<audio>`/`<video>` fast path,
4
+ optional MSE and WASM decoder backends, DI-based decoder plugins, signals
5
+ throughout, SSR- and zoneless-safe.
6
+
7
+ > Migrating from `ngx-audio-control`? Nothing breaks — see
8
+ > [Migrating from `NgxAudioControl`](#migrating-from-ngxaudiocontrol).
9
+
10
+ ## 1. Installation
11
+
12
+ ```
13
+ npm install ngx-kit --save
14
+ ```
15
+
16
+ ## 2. Basic audio usage
17
+
18
+ ```ts
19
+ import { NgxMediaControl } from 'ngx-kit/media-control';
20
+
21
+ @Component({
22
+ imports: [NgxMediaControl],
23
+ template: `<ngx-media-control type="audio" [fileList]="files" [download]="true" />`,
24
+ })
25
+ export class PlayerComponent {
26
+ files = ['assets/a.mp3', 'assets/b.mp3'];
27
+ }
28
+ ```
29
+
30
+ ## 3. Basic video usage
31
+
32
+ ```html
33
+ <ngx-media-control type="video" [sources]="[{ src: 'assets/clip.mp4', poster: 'assets/poster.jpg' }]" />
34
+ ```
35
+
36
+ ## 4. Audio + video configuration
37
+
38
+ `type` is `'audio' | 'video' | 'both'`:
39
+
40
+ - `'audio'` — no video frame is rendered even if a source has a video track.
41
+ - `'video'` — video frame, fullscreen/PiP controls (when the browser supports them).
42
+ - `'both'` — video frame is rendered; degrades to audio-style controls when the active source has no video track.
43
+
44
+ ## 5. Playlist
45
+
46
+ Use `sources` (typed, supports metadata) instead of the legacy `fileList` (plain string URLs) when you need titles/artist/poster/codecs/download URLs:
47
+
48
+ ```ts
49
+ sources: NgxMediaSource[] = [
50
+ { src: 'a.mp3', title: 'Track A', artist: 'Someone' },
51
+ { src: 'b.mp3', title: 'Track B' },
52
+ ];
53
+ ```
54
+
55
+ ## 6. Controls (inputs)
56
+
57
+ | Input | Type | Default | Notes |
58
+ |---|---|---|---|
59
+ | `type` | `'audio' \| 'video' \| 'both'` | `'audio'` | new |
60
+ | `poster` | `string` | — | new, video only |
61
+ | `showList` | `boolean` | `true` | unchanged |
62
+ | `download` | `boolean` | `false` | unchanged |
63
+ | `showFileName` | `boolean` | `true` | unchanged |
64
+ | `showSpeed` | `boolean` | `true` | unchanged |
65
+ | `showVolume` | `boolean` | `true` | unchanged |
66
+ | `linear` | `boolean` | `false` | unchanged |
67
+ | `preload` | `'none' \| 'metadata' \| 'auto'` | `'metadata'` | unchanged |
68
+ | `fetchHeaders` | `HeadersInit` | — | see note below — NOT sent on plain native playback |
69
+ | `fileList` | `string[]` | — | unchanged, still works |
70
+ | `sources` | `NgxMediaSource[]` | — | new, preferred |
71
+
72
+ Outputs: `trackChange` (fires with the active `NgxMediaSource`), `error` (fires with a human-readable message; inspect `NgxMediaError.category` if you're subscribing programmatically via the facade in your own wrapper).
73
+
74
+ > **`fetchHeaders` platform limitation.** A native `<audio src>`/`<video src>`
75
+ > fetch cannot carry arbitrary request headers — that's a browser limitation,
76
+ > not something this library papers over with a hidden `fetch()` + `Blob`
77
+ > step (which would break streaming/range-requests for anything but small
78
+ > clips). `fetchHeaders` is applied as the default `requestHeaders` for any
79
+ > resolved source that doesn't specify its own, and only actually takes
80
+ > effect for sources that end up on a decoder/MSE backend — both of which
81
+ > fetch through `NGX_MEDIA_REQUEST_HANDLER` and can carry headers. For a
82
+ > plain natively-playable URL, set headers via a signed/short-lived URL or a
83
+ > `NGX_MEDIA_REQUEST_HANDLER`-based backend instead.
84
+
85
+ ## 7. Media sources
86
+
87
+ ```ts
88
+ export interface NgxMediaSource {
89
+ src: string | Blob | File | MediaStream;
90
+ type?: string;
91
+ codecs?: string;
92
+ title?: string;
93
+ artist?: string;
94
+ album?: string;
95
+ poster?: string;
96
+ downloadUrl?: string;
97
+ downloadFileName?: string;
98
+ requestHeaders?: HeadersInit;
99
+ }
100
+ ```
101
+
102
+ Playback source and download source are independent (`downloadUrl`) — the
103
+ playback URL is not assumed to be publicly downloadable or vice versa.
104
+
105
+ ## 8. Browser support
106
+
107
+ Chrome, Edge, Firefox, Safari (desktop + iOS), Android WebView. Every
108
+ optional API (`MediaSource`, `AudioContext`, `mediaSession`,
109
+ `requestPictureInPicture`, `requestFullscreen`) is feature-detected at the
110
+ call site (`utils/capabilities.ts`) before use; the UI hides the
111
+ corresponding control when unsupported instead of showing a broken button.
112
+
113
+ ## 9. Native playback
114
+
115
+ The default `MediaEngineFactory` always tries a native `<audio>`/`<video>`
116
+ element first via `HTMLMediaElement.canPlayType()` combined with an explicit
117
+ `type`/`codecs` you supply (never trusting the file extension alone — see
118
+ `utils/capabilities.ts`). This is the fast path for MP3/AAC/WAV/OGG/Opus/WebM/MP4/etc.
119
+ whenever the browser can decode them itself.
120
+
121
+ ## 10. Decoder architecture
122
+
123
+ ```
124
+ Source
125
+ │
126
+ ▼
127
+ Capability detection (canPlayType + type/codecs)
128
+ │
129
+ ├─ native? ───────────► NativeMediaEngine (<audio>/<video>)
130
+ │
131
+ └─ not native ─► NgxMediaDecoderRegistry.findDecoder()
132
+ │
133
+ ├─ found ─► decoder.createEngine() → WASM / MSE / custom engine
134
+ └─ none ─► NgxMediaError('UNSUPPORTED_FORMAT')
135
+ ```
136
+
137
+ The UI (`NgxMediaControl`) never touches an engine directly — it only calls
138
+ `NgxMediaFacade`, which asks `NgxMediaEngineFactory` for a native engine and
139
+ actually attempts `load()` before ever consulting the decoder registry.
140
+ `canPlayType()`/extension/mime are only ever a hint, not a guarantee — a
141
+ mislabeled or fake extension, or a container the browser opens but can't
142
+ actually decode, only shows up as a genuine failure once `load()` is
143
+ attempted, so the decoder registry is tried on a real failure, not on a
144
+ pessimistic upfront guess. Swapping the engine implementation never touches
145
+ the component.
146
+
147
+ ## 11. WASM decoders
148
+
149
+ **Is there an npm package you can install today to try this?** Yes —
150
+ [`@ffmpeg/ffmpeg`](https://www.npmjs.com/package/@ffmpeg/ffmpeg) is the
151
+ general-purpose answer (handles 3GP, WMA, AMR, FLV, and effectively
152
+ anything FFmpeg reads). For narrower needs there are also small pure-WASM
153
+ decoders like `opus-decoder`/`mpg123-decoder` if you specifically only need
154
+ one codec and want a much smaller download than FFmpeg's ~25MB core.
155
+
156
+ ```
157
+ npm install @ffmpeg/ffmpeg @ffmpeg/util @ffmpeg/core
158
+ ```
159
+
160
+ A complete, copy-pasteable decoder built on it is in
161
+ `examples/ffmpeg-transcode-decoder.example.ts` (outside the library's own
162
+ source tree on purpose, so the library itself never depends on `@ffmpeg/*`
163
+ — see below). It transcodes the unsupported source into `wav`/`mp4`
164
+ in-memory and hands the result to `NativeMediaEngine`, reusing all of the
165
+ native engine's playback/seek/event handling instead of reimplementing it.
166
+ Copy that file into your app (e.g. `src/app/decoders/`) and register it:
167
+
168
+ ```ts
169
+ import { provideNgxMediaDecoder } from 'ngx-kit/media-control';
170
+ import { FfmpegTranscodeDecoder } from './decoders/ffmpeg-transcode-decoder.example';
171
+
172
+ bootstrapApplication(AppComponent, {
173
+ providers: [provideNgxMediaDecoder(FfmpegTranscodeDecoder)],
174
+ });
175
+ ```
176
+
177
+
178
+
179
+ `NgxWasmDecoderBase` (in `decoders/wasm-decoder.base.ts`) is the harness: it
180
+ lazily `import()`s your codec module (and optionally runs it in a `Worker`)
181
+ on first real use, so nothing WASM-related ever enters the default bundle.
182
+ **No real codec ships with this library** — bundling e.g. FFmpeg/WASM into
183
+ the core would defeat the "lightweight native fast path" requirement for
184
+ every consumer, including those who never need it. You extend the base class
185
+ with your own `loadModule()`/`buildEngine()`; `DecodedBufferEngine` is
186
+ provided as a ready-made `NgxMediaEngine` for decoders that decode a full
187
+ clip into an `AudioBuffer` and play it via Web Audio.
188
+
189
+ ## 12. Custom decoder development
190
+
191
+ ```ts
192
+ import { NgxMediaDecoder, NgxMediaSource, provideNgxMediaDecoder } from 'ngx-kit/media-control';
193
+
194
+ @Injectable()
195
+ export class My3gpDecoder implements NgxMediaDecoder {
196
+ readonly id = '3gp';
197
+ readonly formats = ['3gp'];
198
+ readonly priority = 0;
199
+
200
+ canDecode(source: NgxMediaSource) {
201
+ return source.type === 'video/3gpp';
202
+ }
203
+
204
+ async createEngine(source: NgxMediaSource) {
205
+ // return any NgxMediaEngine — e.g. extend NgxWasmDecoderBase, or build
206
+ // one by hand around MSE/Web Audio.
207
+ }
208
+
209
+ destroy() {}
210
+ }
211
+ ```
212
+
213
+ ## 13. Angular DI registration
214
+
215
+ ```ts
216
+ bootstrapApplication(AppComponent, {
217
+ providers: [
218
+ provideNgxMedia(), // optional global config
219
+ provideNgxMediaDecoder(My3gpDecoder),
220
+ // provideNgxMediaRequestHandler({ request: (source, signal) => myFetch(source, signal) }),
221
+ ],
222
+ });
223
+ ```
224
+
225
+ `provideNgxMediaDecoder` registers into the `NGX_MEDIA_DECODER` multi
226
+ token; register as many as you like, `priority` (higher first) breaks ties
227
+ when more than one decoder claims the same source.
228
+
229
+ ## 14. Performance
230
+
231
+ - No polling: every state field is driven by a real media event (`timeupdate`, `progress`, `durationchange`, ...).
232
+ - All native listeners are attached inside `NgZone.runOutsideAngular` via the facade; the only thing that can trigger a render is a signal write, which OnPush + signals coalesce naturally — high-frequency events don't cause excess change detection.
233
+ - `AudioContext`/Web Audio graph is created lazily, only if a consumer calls into it.
234
+ - WASM/MSE code paths are behind dynamic `import()`.
235
+
236
+ ## 15. SSR
237
+
238
+ Every browser API access is guarded by `isBrowser()`
239
+ (`utils/capabilities.ts`). `NgxMediaEngineFactory.create()` throws a typed
240
+ `NgxMediaError('BROWSER_UNSUPPORTED', ...)` rather than crashing if somehow
241
+ invoked outside a browser context.
242
+
243
+ ## 16. Zoneless Angular
244
+
245
+ The facade and engines are 100% signal-driven; nothing relies on Zone.js
246
+ patches. `NgZone.runOutsideAngular` is used defensively for zone-based apps
247
+ but is a no-op wrapper under zoneless change detection.
248
+
249
+ ## 17. Web Audio
250
+
251
+ Opt-in only, via `facade.getWebAudioGraph()` inside your own extension —
252
+ gain/analyser nodes are created lazily on first access. Never bypasses
253
+ autoplay policy; `resumeOnUserGesture()` must be called from a real user
254
+ gesture handler.
255
+
256
+ ## 18. MSE
257
+
258
+ `engines/mse-media-engine.ts` is a real, working single-`SourceBuffer`
259
+ progressive-fetch engine, reached only via explicit opt-in
260
+ (`NgxMediaEngineFactory.createMse()`, typically from inside a decoder). It is
261
+ not an ABR/manifest (DASH/HLS) player — that's a decoder plugin's job, built
262
+ on top of this engine.
263
+
264
+ ## 19. Media Session
265
+
266
+ Wired automatically (`facade/media-session.ts`) whenever `navigator.mediaSession`
267
+ exists: play/pause/prev/next/seek actions and `title`/`artist`/`album`/`poster`
268
+ metadata. No-ops everywhere the API is absent.
269
+
270
+ ## 20. Fullscreen
271
+
272
+ Shown automatically for `type="video"`/`"both"` when `document.fullscreenEnabled`
273
+ is true. Double-clicking the video also toggles fullscreen.
274
+
275
+ ## 21. Picture-in-Picture
276
+
277
+ Shown automatically when `document.pictureInPictureEnabled` /
278
+ `HTMLVideoElement.prototype.requestPictureInPicture` exist.
279
+
280
+ ## 22. Error handling
281
+
282
+ ```ts
283
+ export type NgxMediaErrorCategory =
284
+ | 'NETWORK' | 'UNSUPPORTED_FORMAT' | 'DECODER' | 'ABORTED'
285
+ | 'AUTOPLAY_BLOCKED' | 'MEDIA' | 'PERMISSION' | 'BROWSER_UNSUPPORTED' | 'UNKNOWN';
286
+ ```
287
+
288
+ `NgxMediaError` carries a `category` and human-readable `message`; the
289
+ component's `error` output emits the message, and `NgxMediaError.cause`
290
+ keeps the original DOM/browser error for logging without leaking it into the
291
+ template.
292
+
293
+ ## 23. Security limitations
294
+
295
+ Client-side code cannot prevent someone from downloading a file the browser
296
+ can already fetch and play. This library does not implement URL obfuscation
297
+ and call it security. For genuine protection, use short-lived signed URLs
298
+ and/or authenticated requests — `NGX_MEDIA_REQUEST_HANDLER` lets you plug in
299
+ your own authenticated fetcher without coupling the core to `HttpClient`.
300
+
301
+ ## 24. Examples
302
+
303
+ See `demo/media-control/` for a working audio + video showcase, including
304
+ the exact custom-decoder registration snippet from §12.
305
+
306
+ ---
307
+
308
+ ## Seeking support on your server
309
+
310
+
311
+ ### IIS `web.config`
312
+ ```xml
313
+ <system.webServer>
314
+ <httpProtocol>
315
+ <customHeaders>
316
+ <add name="Accept-Ranges" value="bytes" />
317
+ </customHeaders>
318
+ </httpProtocol>
319
+ </system.webServer>
320
+ ```
321
+
322
+ ### Apache `.htaccess`
323
+ ```
324
+ <IfModule mod_headers.c>
325
+ Header set Accept-Ranges bytes
326
+ </IfModule>
327
+ ```
328
+
329
+ ### Nginx `nginx.conf`
330
+ ```
331
+ http {
332
+ server {
333
+ location / {
334
+ add_header Accept-Ranges bytes;
335
+ }
336
+ }
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Migrating from `NgxAudioControl`
343
+
344
+ Nothing is required. `<ngx-audio-control [fileList]="...">` keeps working —
345
+ it's now a thin wrapper around `<ngx-media-control type="audio">` with the
346
+ exact same inputs/defaults. When you're ready, switch the selector to
347
+ `ngx-media-control` and add `type="audio"` to opt into the richer `sources`
348
+ input, `trackChange`/`error` outputs, and future decoder support.
349
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-kit",
3
- "version": "7.3.2",
3
+ "version": "7.4.0",
4
4
  "description": "Angular input components",
5
5
  "author": "Mohammadreza Samani <mr.samani1368@gmail.com>",
6
6
  "license": "MIT",
@@ -97,6 +97,10 @@
97
97
  "types": "./types/ngx-kit-infinite-scroll.d.ts",
98
98
  "default": "./fesm2022/ngx-kit-infinite-scroll.mjs"
99
99
  },
100
+ "./media-control": {
101
+ "types": "./types/ngx-kit-media-control.d.ts",
102
+ "default": "./fesm2022/ngx-kit-media-control.mjs"
103
+ },
100
104
  "./menu": {
101
105
  "types": "./types/ngx-kit-menu.d.ts",
102
106
  "default": "./fesm2022/ngx-kit-menu.mjs"
@@ -330,7 +330,7 @@ declare class NgxCalendarComponent extends NgxDatePickerBase implements OnInit {
330
330
  eventChange: EventEmitter<CalendarEventChange>;
331
331
  eventsChange: EventEmitter<MsEvents<Date>[]>;
332
332
  selected?: Date;
333
- anchorDate: Date;
333
+ anchorDate: _angular_core.WritableSignal<Date | undefined>;
334
334
  eventViewItems: {
335
335
  event: MsEventViewer;
336
336
  start: Date;
@@ -232,10 +232,9 @@ declare class Dialog {
232
232
  declare const DIALOG_DATA: InjectionToken<any>;
233
233
  declare const DIALOG_REF: InjectionToken<NgxDialogRef<any>>;
234
234
  /** The resolved config for the dialog currently being rendered. */
235
- declare const NGX_DIALOG_DEFAULT_CONFIG: NgxDialogConfig<any>;
236
235
  declare const NGX_DIALOG_CONFIG: InjectionToken<NgxDialogConfig<any>>;
237
236
  /** The arbitrary content component passed to `open()`, projected via `*ngComponentOutlet`. */
238
237
  declare const DIALOG_CONTENT: InjectionToken<Type<any>>;
239
238
 
240
- export { DIALOG_CONTENT, DIALOG_DATA, DIALOG_REF, Dialog, NGX_DIALOG_CONFIG, NGX_DIALOG_DEFAULT_CONFIG, NgxDialogBody, NgxDialogComponent, NgxDialogConfig, NgxDialogFooter, NgxDialogHeader, NgxDialogModule, NgxDialogService, provideNgxDialog };
239
+ export { DIALOG_CONTENT, DIALOG_DATA, DIALOG_REF, Dialog, NGX_DIALOG_CONFIG, NgxDialogBody, NgxDialogComponent, NgxDialogConfig, NgxDialogFooter, NgxDialogHeader, NgxDialogModule, NgxDialogService, provideNgxDialog };
241
240
  export type { NgxDialogFooterAlign, NgxDialogFooterConfig, NgxDialogHeaderConfig, NgxDialogSize };
@@ -77,7 +77,7 @@ interface NgxDropzoneFileLike {
77
77
  }
78
78
 
79
79
  type NgxDropzoneUploadMode = 'all' | 'single';
80
- declare class NgxDropzoneComponent implements OnDestroy {
80
+ declare class NgxDropzone implements OnDestroy {
81
81
  /** الگوی accept مثل input[type=file]: "image/*", ".pdf,.docx" و ... */
82
82
  accept: _angular_core.InputSignal<string>;
83
83
  multiple: _angular_core.InputSignal<boolean>;
@@ -157,8 +157,8 @@ declare class NgxDropzoneComponent implements OnDestroy {
157
157
  private finishUpload;
158
158
  private updateFile;
159
159
  private emitCurrentFiles;
160
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxDropzoneComponent, never>;
161
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<NgxDropzoneComponent, "ngx-dropzone", never, { "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "maxFileSize": { "alias": "maxFileSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showFileList": { "alias": "showFileList"; "required": false; "isSignal": true; }; "uploadUrl": { "alias": "uploadUrl"; "required": false; "isSignal": true; }; "uploadAlias": { "alias": "uploadAlias"; "required": false; "isSignal": true; }; "autoUpload": { "alias": "autoUpload"; "required": false; "isSignal": true; }; "maxParallel": { "alias": "maxParallel"; "required": false; "isSignal": true; }; "uploadMode": { "alias": "uploadMode"; "required": false; "isSignal": true; }; "uploadData": { "alias": "uploadData"; "required": false; "isSignal": true; }; "uploadHeaders": { "alias": "uploadHeaders"; "required": false; "isSignal": true; }; "uploadWithCredentials": { "alias": "uploadWithCredentials"; "required": false; "isSignal": true; }; }, { "filesAdded": "filesAdded"; "filesRejected": "filesRejected"; "filesChange": "filesChange"; "uploadStarted": "uploadStarted"; "uploadProgress": "uploadProgress"; "uploadSuccess": "uploadSuccess"; "uploadError": "uploadError"; }, never, ["[dropzone-icon]", "*"], true, never>;
160
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxDropzone, never>;
161
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NgxDropzone, "ngx-dropzone", never, { "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "maxFileSize": { "alias": "maxFileSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showFileList": { "alias": "showFileList"; "required": false; "isSignal": true; }; "uploadUrl": { "alias": "uploadUrl"; "required": false; "isSignal": true; }; "uploadAlias": { "alias": "uploadAlias"; "required": false; "isSignal": true; }; "autoUpload": { "alias": "autoUpload"; "required": false; "isSignal": true; }; "maxParallel": { "alias": "maxParallel"; "required": false; "isSignal": true; }; "uploadMode": { "alias": "uploadMode"; "required": false; "isSignal": true; }; "uploadData": { "alias": "uploadData"; "required": false; "isSignal": true; }; "uploadHeaders": { "alias": "uploadHeaders"; "required": false; "isSignal": true; }; "uploadWithCredentials": { "alias": "uploadWithCredentials"; "required": false; "isSignal": true; }; }, { "filesAdded": "filesAdded"; "filesRejected": "filesRejected"; "filesChange": "filesChange"; "uploadStarted": "uploadStarted"; "uploadProgress": "uploadProgress"; "uploadSuccess": "uploadSuccess"; "uploadError": "uploadError"; }, never, ["[dropzone-icon]", "*"], true, never>;
162
162
  }
163
163
 
164
164
  declare class NgxDropzoneHttpUploadService implements NgxDropzoneUploadService {
@@ -191,5 +191,5 @@ declare function formatBytes(bytes: number): string;
191
191
  /** استخراج فایل‌های واقعی از یک DataTransfer، شامل پیمایش بازگشتیِ پوشه‌ها (وقتی مرورگر پشتیبانی کنه) */
192
192
  declare function extractFilesFromDataTransfer(dataTransfer: DataTransfer): Promise<File[]>;
193
193
 
194
- export { NGX_DROPZONE_UPLOAD_SERVICE, NgxDropzoneComponent, NgxDropzoneHttpUploadService, extractFilesFromDataTransfer, formatBytes, matchesAccept };
194
+ export { NGX_DROPZONE_UPLOAD_SERVICE, NgxDropzone, NgxDropzoneHttpUploadService, extractFilesFromDataTransfer, formatBytes, matchesAccept };
195
195
  export type { NgxDropzoneFile, NgxDropzoneFileLike, NgxDropzoneFileStatus, NgxDropzoneRejectedFile, NgxDropzoneRejectionReason, NgxDropzoneUploadEvent, NgxDropzoneUploadMode, NgxDropzoneUploadProgressEvent, NgxDropzoneUploadRequest, NgxDropzoneUploadService };
@@ -9,6 +9,7 @@ interface NgxCropRect {
9
9
  height: number;
10
10
  }
11
11
  type NgxImageFilterPreset = 'none' | 'grayscale' | 'sepia' | 'invert' | 'cartoon';
12
+ type NgxImageFileType = 'image/jpeg' | 'image/png' | 'image/webp';
12
13
  interface NgxImageEditorAdjustments {
13
14
  /** همه ۱۰۰ یعنی بدون تغییر؛ بازه‌ی معقول ۰ تا ۲۰۰ */
14
15
  brightness: number;
@@ -19,10 +20,12 @@ interface NgxImageEditorAdjustments {
19
20
  filter: NgxImageFilterPreset;
20
21
  }
21
22
  interface NgxImageEditorResult {
23
+ file: File;
22
24
  blob: Blob;
23
25
  dataUrl: string;
24
26
  width: number;
25
27
  height: number;
28
+ type: NgxImageFileType;
26
29
  }
27
30
 
28
31
  interface NgxPointerDragDelta {
@@ -75,33 +78,13 @@ declare class NgxImageEditorLocalization {
75
78
  }
76
79
  declare const DEFAULT_NGX_IMAGE_EDITOR_LOCALIZATION: NgxImageEditorLocalization;
77
80
 
78
- /**
79
- * ویرایشگر تصویر: کراپ + چرخش + روشنایی/کنتراست/اشباع + فیلترهای آماده
80
- * (سیاه‌وسفید، سپیا، معکوس، کارتونی) + فشرده‌سازی خروجی. کاملاً روی Canvas
81
- * خام پیاده شده، بدون هیچ وابستگی خارجی‌ای.
82
- *
83
- * نکته‌ی مهمِ معماری: تمام تعامل زنده (چرخوندن، جابه‌جا/ریسایز کراپ‌باکس،
84
- * پیش‌نمایش فیلترها حتی کارتونی) روی یه کپیِ کوچیک‌شده از تصویر انجام می‌شه
85
- * (createWorkingCopy)، نه روی تصویر اصلی که ممکنه چند مگاپیکسل باشه — وگرنه
86
- * درگ‌کردن اسلایدرها/گوشه‌های کراپ رو کاربر حس تاخیر می‌کرد. فقط لحظه‌ی
87
- * save()، رندر نهایی از روی تصویر اصلیِ کامل انجام می‌شه.
88
- *
89
- * اندازه‌ی صحنه (stage) با panelSize کنترل می‌شه: عرض همیشه ۱۰۰٪ عرضِ
90
- * والدِ کامپوننته (با ResizeObserver اندازه‌گیری می‌شه، پس با تغییر سایز
91
- * صفحه/layout هم زنده هماهنگ می‌مونه)، ارتفاع دقیقاً همون چیزیه که کاربر با
92
- * panelSize می‌ده. تصویر همیشه با یک مقیاسِ «contain» واقعی (هم بر اساس
93
- * عرض، هم بر اساس ارتفاع — هرکدوم تنگ‌تره) جا داده می‌شه، پس کلِ تصویر
94
- * همیشه در محدوده‌ی همین باکس دیده می‌شه، صرف‌نظر از اندازه‌ی واقعیِ فایل.
95
- * سقفِ مقیاس هم ۱ هست، یعنی تصاویر کوچیک بزرگ‌نمایی نمی‌شن (که باعثِ محو
96
- * شدن‌شون می‌شد).
97
- */
98
- declare class NgxImageEditorComponent {
99
- source: _angular_core.InputSignal<string | Blob | File | null>;
81
+ declare class NgxImageEditor {
82
+ source: _angular_core.InputSignal<string | File | Blob | null>;
100
83
  /** قفل نسبتِ کراپ (مثلاً ۱ برای آواتار مربعی)؛ undefined یعنی آزاد */
101
84
  aspectRatio: _angular_core.InputSignal<number | undefined>;
102
85
  outputMaxWidth: _angular_core.InputSignal<number | undefined>;
103
86
  outputMaxHeight: _angular_core.InputSignal<number | undefined>;
104
- outputType: _angular_core.InputSignal<"image/jpeg" | "image/png" | "image/webp">;
87
+ outputType: _angular_core.InputSignal<NgxImageFileType>;
105
88
  /**
106
89
  * کدوم فیلترها نشون داده بشن (و به چه ترتیبی). اگه ندید، هر ۵ تا نشون داده
107
90
  * می‌شن. آرایه‌ی خالی یعنی کلِ بخشِ فیلتر مخفی بشه.
@@ -161,8 +144,8 @@ declare class NgxImageEditorComponent {
161
144
  protected onResizeDragMove(corner: Corner, delta: NgxPointerDragDelta): void;
162
145
  protected cancel(): void;
163
146
  save(): Promise<void>;
164
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxImageEditorComponent, never>;
165
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<NgxImageEditorComponent, "ngx-image-editor", never, { "source": { "alias": "source"; "required": false; "isSignal": true; }; "aspectRatio": { "alias": "aspectRatio"; "required": false; "isSignal": true; }; "outputMaxWidth": { "alias": "outputMaxWidth"; "required": false; "isSignal": true; }; "outputMaxHeight": { "alias": "outputMaxHeight"; "required": false; "isSignal": true; }; "outputType": { "alias": "outputType"; "required": false; "isSignal": true; }; "filters": { "alias": "filters"; "required": false; "isSignal": true; }; "quality": { "alias": "quality"; "required": false; "isSignal": true; }; "localize": { "alias": "localize"; "required": false; "isSignal": true; }; }, { "saved": "saved"; "cancelled": "cancelled"; }, never, never, true, never>;
147
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxImageEditor, never>;
148
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NgxImageEditor, "ngx-image-editor", never, { "source": { "alias": "source"; "required": false; "isSignal": true; }; "aspectRatio": { "alias": "aspectRatio"; "required": false; "isSignal": true; }; "outputMaxWidth": { "alias": "outputMaxWidth"; "required": false; "isSignal": true; }; "outputMaxHeight": { "alias": "outputMaxHeight"; "required": false; "isSignal": true; }; "outputType": { "alias": "outputType"; "required": false; "isSignal": true; }; "filters": { "alias": "filters"; "required": false; "isSignal": true; }; "quality": { "alias": "quality"; "required": false; "isSignal": true; }; "localize": { "alias": "localize"; "required": false; "isSignal": true; }; }, { "saved": "saved"; "cancelled": "cancelled"; }, never, never, true, never>;
166
149
  }
167
150
 
168
151
  /** یه File/Blob/data-URL/URL رو به یک HTMLImageElement بارگذاری‌شده تبدیل می‌کنه */
@@ -216,5 +199,5 @@ declare function applyCartoonEffect(ctx: CanvasRenderingContext2D, width: number
216
199
  edgeThreshold?: number;
217
200
  }): void;
218
201
 
219
- export { DEFAULT_NGX_IMAGE_EDITOR_LOCALIZATION, NGX_IMAGE_EDITOR_LOCALIZATION, NgxImageEditorComponent, NgxImageEditorLocalization, NgxPointerDragDirective, applyCartoonEffect, canvasToBlob, createWorkingCopy, fitWithin, loadImage, renderComposite, renderRotated };
220
- export type { NgxCropRect, NgxImageEditorAdjustments, NgxImageEditorResult, NgxImageFilterPreset, NgxPointerDragDelta };
202
+ export { DEFAULT_NGX_IMAGE_EDITOR_LOCALIZATION, NGX_IMAGE_EDITOR_LOCALIZATION, NgxImageEditor, NgxImageEditorLocalization, NgxPointerDragDirective, applyCartoonEffect, canvasToBlob, createWorkingCopy, fitWithin, loadImage, renderComposite, renderRotated };
203
+ export type { NgxCropRect, NgxImageEditorAdjustments, NgxImageEditorResult, NgxImageFileType, NgxImageFilterPreset, NgxPointerDragDelta };