ngx-kit 5.0.0 → 6.0.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 (68) hide show
  1. package/README.md +3 -2
  2. package/angle-selector/README.md +34 -0
  3. package/box-shadow/README.md +33 -0
  4. package/color-picker/README.md +52 -0
  5. package/data-table/README.md +120 -0
  6. package/date-picker/README.md +74 -0
  7. package/dialog/README.md +94 -169
  8. package/drawer-menu/README.md +76 -0
  9. package/dropzone/README.md +59 -0
  10. package/fesm2022/ngx-kit-angle-selector.mjs +2 -2
  11. package/fesm2022/ngx-kit-angle-selector.mjs.map +1 -1
  12. package/fesm2022/ngx-kit-box-shadow.mjs +28 -27
  13. package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
  14. package/fesm2022/ngx-kit-color-picker.mjs +173 -43
  15. package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
  16. package/fesm2022/ngx-kit-data-table.mjs +9 -2
  17. package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
  18. package/fesm2022/ngx-kit-date-picker.mjs +120 -86
  19. package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
  20. package/fesm2022/ngx-kit-dialog.mjs +38 -7
  21. package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
  22. package/fesm2022/ngx-kit-drawer-menu.mjs +190 -0
  23. package/fesm2022/ngx-kit-drawer-menu.mjs.map +1 -0
  24. package/fesm2022/ngx-kit-dropzone.mjs +272 -0
  25. package/fesm2022/ngx-kit-dropzone.mjs.map +1 -0
  26. package/fesm2022/ngx-kit-form-field.mjs +87 -0
  27. package/fesm2022/ngx-kit-form-field.mjs.map +1 -0
  28. package/fesm2022/ngx-kit-gallery.mjs +419 -0
  29. package/fesm2022/ngx-kit-gallery.mjs.map +1 -0
  30. package/fesm2022/ngx-kit-gradient-picker.mjs +12 -4
  31. package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
  32. package/fesm2022/ngx-kit-image-editor.mjs +501 -0
  33. package/fesm2022/ngx-kit-image-editor.mjs.map +1 -0
  34. package/fesm2022/ngx-kit-menu.mjs +9 -4
  35. package/fesm2022/ngx-kit-menu.mjs.map +1 -1
  36. package/fesm2022/ngx-kit-message.mjs +50 -24
  37. package/fesm2022/ngx-kit-message.mjs.map +1 -1
  38. package/fesm2022/ngx-kit-notify.mjs +1 -4
  39. package/fesm2022/ngx-kit-notify.mjs.map +1 -1
  40. package/fesm2022/ngx-kit-shared.mjs +126 -83
  41. package/fesm2022/ngx-kit-shared.mjs.map +1 -1
  42. package/fesm2022/ngx-kit-time-picker.mjs +562 -0
  43. package/fesm2022/ngx-kit-time-picker.mjs.map +1 -0
  44. package/fesm2022/ngx-kit.mjs.map +1 -1
  45. package/gallery/README.md +71 -0
  46. package/gradient-picker/README.md +41 -0
  47. package/image-editor/README.md +44 -0
  48. package/menu/README.md +85 -0
  49. package/message/README.md +70 -0
  50. package/notify/README.md +56 -0
  51. package/package.json +25 -1
  52. package/shared/README.md +117 -0
  53. package/styles/all.css +27 -0
  54. package/styles/image-viewer.css +12 -0
  55. package/types/ngx-kit-box-shadow.d.ts +1 -2
  56. package/types/ngx-kit-data-table.d.ts +1 -0
  57. package/types/ngx-kit-date-picker.d.ts +1 -1
  58. package/types/ngx-kit-dialog.d.ts +1 -0
  59. package/types/ngx-kit-drawer-menu.d.ts +101 -0
  60. package/types/ngx-kit-dropzone.d.ts +84 -0
  61. package/types/ngx-kit-form-field.d.ts +31 -0
  62. package/types/ngx-kit-gallery.d.ts +168 -0
  63. package/types/ngx-kit-image-editor.d.ts +175 -0
  64. package/types/ngx-kit-menu.d.ts +1 -0
  65. package/types/ngx-kit-message.d.ts +13 -3
  66. package/types/ngx-kit-notify.d.ts +0 -1
  67. package/types/ngx-kit-shared.d.ts +44 -13
  68. package/types/ngx-kit-time-picker.d.ts +150 -0
@@ -0,0 +1,272 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, signal, viewChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+
4
+ /**
5
+ * چک می‌کنه یه فایل با pattern رشته‌ای accept (مثل input[type=file])
6
+ * مطابقت داره یا نه. پشتیبانی می‌شه از:
7
+ * - MIME کامل: "image/png"
8
+ * - MIME با wildcard: "image/*"
9
+ * - پسوند فایل: ".png", ".jpg"
10
+ * - لیست کاما-جدا از موارد بالا: "image/*,.pdf,.docx"
11
+ */
12
+ function matchesAccept(file, accept) {
13
+ if (!accept || accept.trim() === '')
14
+ return true;
15
+ const patterns = accept
16
+ .split(',')
17
+ .map((p) => p.trim().toLowerCase())
18
+ .filter(Boolean);
19
+ if (!patterns.length)
20
+ return true;
21
+ const fileName = file.name.toLowerCase();
22
+ const fileType = (file.type || '').toLowerCase();
23
+ return patterns.some((pattern) => {
24
+ if (pattern.startsWith('.')) {
25
+ return fileName.endsWith(pattern);
26
+ }
27
+ if (pattern.endsWith('/*')) {
28
+ const prefix = pattern.slice(0, -1); // "image/"
29
+ return fileType.startsWith(prefix);
30
+ }
31
+ return fileType === pattern;
32
+ });
33
+ }
34
+ /** تبدیل بایت به یه رشته‌ی خوانا (KB/MB/GB) */
35
+ function formatBytes(bytes) {
36
+ if (bytes === 0)
37
+ return '0 B';
38
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
39
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
40
+ const value = bytes / Math.pow(1024, i);
41
+ return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
42
+ }
43
+ /** استخراج فایل‌های واقعی از یک DataTransfer، شامل پیمایش بازگشتیِ پوشه‌ها (وقتی مرورگر پشتیبانی کنه) */
44
+ async function extractFilesFromDataTransfer(dataTransfer) {
45
+ const items = Array.from(dataTransfer.items || []);
46
+ const hasEntrySupport = items.length > 0 && typeof items[0].webkitGetAsEntry === 'function';
47
+ if (!hasEntrySupport) {
48
+ return Array.from(dataTransfer.files || []);
49
+ }
50
+ const entries = items
51
+ .map((item) => item.webkitGetAsEntry?.())
52
+ .filter((e) => !!e);
53
+ if (!entries.length) {
54
+ return Array.from(dataTransfer.files || []);
55
+ }
56
+ const files = [];
57
+ await Promise.all(entries.map((entry) => walkEntry(entry, files)));
58
+ return files;
59
+ }
60
+ function walkEntry(entry, out) {
61
+ return new Promise((resolve) => {
62
+ if (entry.isFile) {
63
+ entry.file((file) => {
64
+ out.push(file);
65
+ resolve();
66
+ }, () => resolve());
67
+ }
68
+ else if (entry.isDirectory) {
69
+ const reader = entry.createReader();
70
+ const readAll = [];
71
+ const readBatch = () => {
72
+ reader.readEntries(async (batch) => {
73
+ if (!batch.length) {
74
+ await Promise.all(readAll.map((e) => walkEntry(e, out)));
75
+ resolve();
76
+ return;
77
+ }
78
+ readAll.push(...batch);
79
+ readBatch();
80
+ }, () => resolve());
81
+ };
82
+ readBatch();
83
+ }
84
+ else {
85
+ resolve();
86
+ }
87
+ });
88
+ }
89
+
90
+ let uidCounter = 0;
91
+ function nextId() {
92
+ return `ngx-dz-${Date.now().toString(36)}-${uidCounter++}`;
93
+ }
94
+ /**
95
+ * ناحیه‌ی درگ‌اند‌دراپِ فایل — کاملاً مستقل (بدون وابستگی به هیچ lib
96
+ * دیگه‌ای، حتی shared)، signal-based و OnPush.
97
+ *
98
+ * ویژگی‌های مهمی که معمولاً پیاده‌سازی‌های ساده‌ی dropzone ندارن:
99
+ * - شمارنده‌ی dragenter/dragleave: چون dragleave برای فرزندهای داخل
100
+ * dropzone هم شلیک می‌شه، پیاده‌سازی‌های ساده موقع درگ‌کردن روی متن/آیکون
101
+ * داخل ناحیه، حالت "درگ" رو اشتباهی خاموش می‌کنن (چشمک‌زدن). این‌جا با
102
+ * شمارنده حل شده.
103
+ * - پیمایش بازگشتیِ پوشه‌ها (وقتی کاربر یه پوشه رو درگ کنه، نه فقط فایل).
104
+ * - قابل‌دسترسی با کیبورد (focusable + Enter/Space).
105
+ * - پاک‌سازی object URL پیش‌نمایش‌ها روی حذف/destroy (وگرنه نشتی حافظه‌ست).
106
+ */
107
+ class NgxDropzoneComponent {
108
+ /** الگوی accept مثل input[type=file]: "image/*", ".pdf,.docx" و ... */
109
+ accept = input('', /* @ts-ignore */
110
+ ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
111
+ multiple = input(true, /* @ts-ignore */
112
+ ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
113
+ /** حداکثر حجم هر فایل به بایت؛ undefined یعنی بدون محدودیت */
114
+ maxFileSize = input(undefined, /* @ts-ignore */
115
+ ...(ngDevMode ? [{ debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
116
+ /** حداکثر تعداد فایل مجاز؛ undefined یعنی بدون محدودیت */
117
+ maxFiles = input(undefined, /* @ts-ignore */
118
+ ...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
119
+ disabled = input(false, /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
121
+ /** نمایش لیست/پیش‌نمایش فایل‌های انتخاب‌شده داخل خودِ کامپوننت */
122
+ showFileList = input(true, /* @ts-ignore */
123
+ ...(ngDevMode ? [{ debugName: "showFileList" }] : /* istanbul ignore next */ []));
124
+ filesAdded = output();
125
+ filesRejected = output();
126
+ /** کل لیست فعلی (بعد از هر تغییر) — برای binding راحت‌تر */
127
+ filesChange = output();
128
+ files = signal([], /* @ts-ignore */
129
+ ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
130
+ isDragging = signal(false, /* @ts-ignore */
131
+ ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
132
+ dragCounter = 0;
133
+ fileInput = viewChild('fileInput', /* @ts-ignore */
134
+ ...(ngDevMode ? [{ debugName: "fileInput" }] : /* istanbul ignore next */ []));
135
+ hint = computed(() => {
136
+ const parts = [];
137
+ const accept = this.accept();
138
+ if (accept)
139
+ parts.push(accept);
140
+ const max = this.maxFileSize();
141
+ if (max)
142
+ parts.push(`حداکثر ${formatBytes(max)}`);
143
+ return parts.join(' · ');
144
+ }, /* @ts-ignore */
145
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
146
+ formatSize(bytes) {
147
+ return formatBytes(bytes);
148
+ }
149
+ ngOnDestroy() {
150
+ // همه‌ی object URLهای پیش‌نمایش باید revoke بشن، وگرنه تا وقتی تب باز
151
+ // باشه، بلاب‌های تصویر توی حافظه‌ی مرورگر می‌مونن.
152
+ for (const f of this.files()) {
153
+ if (f.previewUrl)
154
+ URL.revokeObjectURL(f.previewUrl);
155
+ }
156
+ }
157
+ onDragEnter(ev) {
158
+ ev.preventDefault();
159
+ if (this.disabled())
160
+ return;
161
+ this.dragCounter++;
162
+ this.isDragging.set(true);
163
+ }
164
+ onDragOver(ev) {
165
+ // بدون preventDefault روی dragover، رویداد drop اصلاً شلیک نمی‌شه.
166
+ ev.preventDefault();
167
+ if (ev.dataTransfer)
168
+ ev.dataTransfer.dropEffect = this.disabled() ? 'none' : 'copy';
169
+ }
170
+ onDragLeave(ev) {
171
+ ev.preventDefault();
172
+ if (this.disabled())
173
+ return;
174
+ this.dragCounter = Math.max(0, this.dragCounter - 1);
175
+ if (this.dragCounter === 0)
176
+ this.isDragging.set(false);
177
+ }
178
+ async onDrop(ev) {
179
+ ev.preventDefault();
180
+ this.dragCounter = 0;
181
+ this.isDragging.set(false);
182
+ if (this.disabled() || !ev.dataTransfer)
183
+ return;
184
+ const files = await extractFilesFromDataTransfer(ev.dataTransfer);
185
+ this.handleIncomingFiles(files);
186
+ }
187
+ onBrowseClick() {
188
+ if (this.disabled())
189
+ return;
190
+ this.fileInput()?.nativeElement.click();
191
+ }
192
+ onKeydown(ev) {
193
+ if (ev.key === 'Enter' || ev.key === ' ') {
194
+ ev.preventDefault();
195
+ this.onBrowseClick();
196
+ }
197
+ }
198
+ onFileInputChange(ev) {
199
+ const input = ev.target;
200
+ const files = Array.from(input.files ?? []);
201
+ this.handleIncomingFiles(files);
202
+ // اجازه بده همون فایل دوباره انتخاب بشه (input تغییر نمی‌کنه وگرنه)
203
+ input.value = '';
204
+ }
205
+ removeFile(id) {
206
+ const target = this.files().find((f) => f.id === id);
207
+ if (target?.previewUrl)
208
+ URL.revokeObjectURL(target.previewUrl);
209
+ this.files.update((list) => list.filter((f) => f.id !== id));
210
+ this.emitCurrentFiles();
211
+ }
212
+ handleIncomingFiles(incoming) {
213
+ if (!incoming.length)
214
+ return;
215
+ const accepted = [];
216
+ const rejected = [];
217
+ const existingCount = this.files().filter((f) => f.status === 'accepted').length;
218
+ const maxFiles = this.maxFiles();
219
+ const maxSize = this.maxFileSize();
220
+ const isMultiple = this.multiple();
221
+ const pool = isMultiple ? incoming : incoming.slice(0, 1);
222
+ for (const file of pool) {
223
+ let reason;
224
+ if (!matchesAccept(file, this.accept())) {
225
+ reason = 'type';
226
+ }
227
+ else if (maxSize != null && file.size > maxSize) {
228
+ reason = 'size';
229
+ }
230
+ else if (maxFiles != null && existingCount + accepted.length >= maxFiles) {
231
+ reason = 'count';
232
+ }
233
+ if (reason) {
234
+ rejected.push({ file, reason });
235
+ continue;
236
+ }
237
+ const isImage = file.type.startsWith('image/');
238
+ accepted.push({
239
+ id: nextId(),
240
+ file,
241
+ previewUrl: isImage ? URL.createObjectURL(file) : undefined,
242
+ status: 'accepted',
243
+ });
244
+ }
245
+ if (accepted.length) {
246
+ this.files.update((list) => (isMultiple ? [...list, ...accepted] : accepted));
247
+ this.filesAdded.emit(accepted.map((a) => a.file));
248
+ this.emitCurrentFiles();
249
+ }
250
+ if (rejected.length) {
251
+ this.filesRejected.emit(rejected);
252
+ }
253
+ }
254
+ emitCurrentFiles() {
255
+ this.filesChange.emit(this.files().map((f) => f.file));
256
+ }
257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxDropzoneComponent, isStandalone: true, selector: "ngx-dropzone", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesAdded: "filesAdded", filesRejected: "filesRejected", filesChange: "filesChange" }, host: { classAttribute: "ngx-dropzone-host" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"ngx-dropzone\"\n [class.dragging]=\"isDragging()\"\n [class.disabled]=\"disabled()\"\n [class.has-files]=\"files().length > 0\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"onBrowseClick()\"\n (keydown)=\"onKeydown($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n #fileInput\n type=\"file\"\n class=\"ngx-dropzone-input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onFileInputChange($event)\" />\n\n <div class=\"ngx-dropzone-icon-wrap\">\n <ng-content select=\"[dropzone-icon]\">\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </ng-content>\n </div>\n\n <div class=\"ngx-dropzone-text\">\n <ng-content>\n <strong>\u0641\u0627\u06CC\u0644\u200C\u0647\u0627 \u0631\u0627 \u0627\u06CC\u0646\u062C\u0627 \u0631\u0647\u0627 \u06A9\u0646\u06CC\u062F</strong>\n <span>\u06CC\u0627 \u0628\u0631\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F</span>\n </ng-content>\n </div>\n\n @if (hint()) {\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\n }\n</div>\n\n@if (showFileList() && files().length) {\n <ul class=\"ngx-dropzone-list\">\n @for (item of files(); track item.id) {\n <li class=\"ngx-dropzone-item\">\n <div class=\"ngx-dropzone-thumb-wrap\">\n @if (item.previewUrl) {\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\n } @else {\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\n }\n </div>\n <div class=\"ngx-dropzone-meta\">\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n <span class=\"ngx-dropzone-status\" aria-hidden=\"true\">\u2713</span>\n <button\n type=\"button\"\n class=\"ngx-dropzone-remove\"\n [attr.aria-label]=\"'\u062D\u0630\u0641 ' + item.file.name\"\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
259
+ }
260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropzoneComponent, decorators: [{
261
+ type: Component,
262
+ args: [{ selector: 'ngx-dropzone', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
263
+ class: 'ngx-dropzone-host',
264
+ }, template: "<div\n class=\"ngx-dropzone\"\n [class.dragging]=\"isDragging()\"\n [class.disabled]=\"disabled()\"\n [class.has-files]=\"files().length > 0\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"onBrowseClick()\"\n (keydown)=\"onKeydown($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n #fileInput\n type=\"file\"\n class=\"ngx-dropzone-input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onFileInputChange($event)\" />\n\n <div class=\"ngx-dropzone-icon-wrap\">\n <ng-content select=\"[dropzone-icon]\">\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </ng-content>\n </div>\n\n <div class=\"ngx-dropzone-text\">\n <ng-content>\n <strong>\u0641\u0627\u06CC\u0644\u200C\u0647\u0627 \u0631\u0627 \u0627\u06CC\u0646\u062C\u0627 \u0631\u0647\u0627 \u06A9\u0646\u06CC\u062F</strong>\n <span>\u06CC\u0627 \u0628\u0631\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F</span>\n </ng-content>\n </div>\n\n @if (hint()) {\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\n }\n</div>\n\n@if (showFileList() && files().length) {\n <ul class=\"ngx-dropzone-list\">\n @for (item of files(); track item.id) {\n <li class=\"ngx-dropzone-item\">\n <div class=\"ngx-dropzone-thumb-wrap\">\n @if (item.previewUrl) {\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\n } @else {\n <span class=\"ngx-dropzone-file-icon\">\uD83D\uDCC4</span>\n }\n </div>\n <div class=\"ngx-dropzone-meta\">\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n <span class=\"ngx-dropzone-status\" aria-hidden=\"true\">\u2713</span>\n <button\n type=\"button\"\n class=\"ngx-dropzone-remove\"\n [attr.aria-label]=\"'\u062D\u0630\u0641 ' + item.file.name\"\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.ngx-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;padding:2.25rem 1.5rem;border-radius:16px;cursor:pointer;text-align:center;overflow:hidden;background:light-dark(#fafbfc,#1a1a1e);border:2px dashed light-dark(#d6dae1,#35353c);transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease}.ngx-dropzone:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,light-dark(rgba(37,99,235,.06),rgba(59,130,246,.1)),transparent 65%);opacity:0;transition:opacity .2s ease;pointer-events:none}.ngx-dropzone:hover:not(.disabled){border-color:light-dark(#93c5fd,#3b82f6);box-shadow:0 4px 18px light-dark(rgba(37,99,235,.08),rgba(0,0,0,.35))}.ngx-dropzone:hover:not(.disabled):before{opacity:1}.ngx-dropzone:focus-visible{outline:2px solid light-dark(#2563eb,#60a5fa);outline-offset:3px}.ngx-dropzone.dragging{border-color:light-dark(#2563eb,#60a5fa);border-style:solid;background:light-dark(#eff6ff,#16213a);transform:scale(1.015);box-shadow:0 8px 28px light-dark(rgba(37,99,235,.18),rgba(59,130,246,.25))}.ngx-dropzone.dragging:before{opacity:1}.ngx-dropzone.dragging .ngx-dropzone-icon-wrap{transform:translateY(-4px) scale(1.08)}.ngx-dropzone.disabled{cursor:not-allowed;opacity:.55}.ngx-dropzone.has-files{padding-block:1.5rem}.ngx-dropzone-input{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;opacity:0}.ngx-dropzone-icon-wrap{display:flex;align-items:center;justify-content:center;inline-size:56px;block-size:56px;border-radius:50%;background:light-dark(#eef2ff,#232433);color:light-dark(#4f46e5,#818cf8);transition:transform .22s cubic-bezier(.34,1.56,.64,1);z-index:1}.ngx-dropzone-text{z-index:1;display:flex;flex-direction:column;gap:.15rem}.ngx-dropzone-text strong{font-size:.92rem;font-weight:600;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-text span{font-size:.8rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-hint{z-index:1;font-size:.72rem;color:light-dark(#9ca3af,#6b7280);background:light-dark(#f3f4f6,#232327);padding:.15rem .6rem;border-radius:999px}.ngx-dropzone-list{list-style:none;margin:.85rem 0 0;padding:0;display:flex;flex-direction:column;gap:.45rem}.ngx-dropzone-item{display:flex;align-items:center;gap:.7rem;padding:.5rem .6rem;border-radius:10px;background:light-dark(#ffffff,#1f1f24);border:1px solid light-dark(#eef0f3,#2d2d33);transition:transform .16s ease,box-shadow .16s ease;animation:ngx-dropzone-item-in .22s cubic-bezier(.22,.9,.32,1) both}.ngx-dropzone-item:hover{box-shadow:0 2px 10px light-dark(rgba(0,0,0,.06),rgba(0,0,0,.4))}@keyframes ngx-dropzone-item-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.ngx-dropzone-thumb-wrap{flex-shrink:0;inline-size:38px;block-size:38px;border-radius:8px;overflow:hidden;background:light-dark(#f4f4f5,#29292f);display:flex;align-items:center;justify-content:center}.ngx-dropzone-thumb{inline-size:100%;block-size:100%;object-fit:cover}.ngx-dropzone-file-icon{font-size:1.1rem}.ngx-dropzone-meta{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:.1rem}.ngx-dropzone-name{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:light-dark(#1f2937,#e4e4e7)}.ngx-dropzone-size{font-size:.7rem;color:light-dark(#9ca3af,#71717a)}.ngx-dropzone-status{flex-shrink:0;inline-size:20px;block-size:20px;border-radius:50%;background:light-dark(#dcfce7,#14532d);color:light-dark(#16a34a,#4ade80);font-size:.7rem;display:flex;align-items:center;justify-content:center}.ngx-dropzone-remove{flex-shrink:0;appearance:none;border:none;background:transparent;cursor:pointer;color:light-dark(#9ca3af,#71717a);font-size:.8rem;line-height:1;padding:.3rem;border-radius:6px;transition:background-color .14s ease,color .14s ease}.ngx-dropzone-remove:hover{color:light-dark(#dc2626,#f87171);background:light-dark(#fee2e2,#3f1d1d)}\n"] }]
265
+ }], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], filesAdded: [{ type: i0.Output, args: ["filesAdded"] }], filesRejected: [{ type: i0.Output, args: ["filesRejected"] }], filesChange: [{ type: i0.Output, args: ["filesChange"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
266
+
267
+ /**
268
+ * Generated bundle index. Do not edit.
269
+ */
270
+
271
+ export { NgxDropzoneComponent, extractFilesFromDataTransfer, formatBytes, matchesAccept };
272
+ //# sourceMappingURL=ngx-kit-dropzone.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-kit-dropzone.mjs","sources":["../../../projects/libs/dropzone/utils/file-matching.ts","../../../projects/libs/dropzone/components/dropzone.component.ts","../../../projects/libs/dropzone/components/dropzone.component.html","../../../projects/libs/dropzone/ngx-kit-dropzone.ts"],"sourcesContent":["/**\n * چک می‌کنه یه فایل با pattern رشته‌ای accept (مثل input[type=file])\n * مطابقت داره یا نه. پشتیبانی می‌شه از:\n * - MIME کامل: \"image/png\"\n * - MIME با wildcard: \"image/*\"\n * - پسوند فایل: \".png\", \".jpg\"\n * - لیست کاما-جدا از موارد بالا: \"image/*,.pdf,.docx\"\n */\nexport function matchesAccept(file: File, accept?: string): boolean {\n if (!accept || accept.trim() === '') return true;\n\n const patterns = accept\n .split(',')\n .map((p) => p.trim().toLowerCase())\n .filter(Boolean);\n\n if (!patterns.length) return true;\n\n const fileName = file.name.toLowerCase();\n const fileType = (file.type || '').toLowerCase();\n\n return patterns.some((pattern) => {\n if (pattern.startsWith('.')) {\n return fileName.endsWith(pattern);\n }\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"image/\"\n return fileType.startsWith(prefix);\n }\n return fileType === pattern;\n });\n}\n\n/** تبدیل بایت به یه رشته‌ی خوانا (KB/MB/GB) */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n const value = bytes / Math.pow(1024, i);\n return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;\n}\n\n/** استخراج فایل‌های واقعی از یک DataTransfer، شامل پیمایش بازگشتیِ پوشه‌ها (وقتی مرورگر پشتیبانی کنه) */\nexport async function extractFilesFromDataTransfer(dataTransfer: DataTransfer): Promise<File[]> {\n const items = Array.from(dataTransfer.items || []);\n const hasEntrySupport = items.length > 0 && typeof items[0].webkitGetAsEntry === 'function';\n\n if (!hasEntrySupport) {\n return Array.from(dataTransfer.files || []);\n }\n\n const entries = items\n .map((item) => item.webkitGetAsEntry?.())\n .filter((e): e is FileSystemEntry => !!e);\n\n if (!entries.length) {\n return Array.from(dataTransfer.files || []);\n }\n\n const files: File[] = [];\n await Promise.all(entries.map((entry) => walkEntry(entry, files)));\n return files;\n}\n\nfunction walkEntry(entry: FileSystemEntry, out: File[]): Promise<void> {\n return new Promise((resolve) => {\n if (entry.isFile) {\n (entry as FileSystemFileEntry).file(\n (file) => {\n out.push(file);\n resolve();\n },\n () => resolve(),\n );\n } else if (entry.isDirectory) {\n const reader = (entry as FileSystemDirectoryEntry).createReader();\n const readAll: FileSystemEntry[] = [];\n const readBatch = () => {\n reader.readEntries(\n async (batch) => {\n if (!batch.length) {\n await Promise.all(readAll.map((e) => walkEntry(e, out)));\n resolve();\n return;\n }\n readAll.push(...batch);\n readBatch();\n },\n () => resolve(),\n );\n };\n readBatch();\n } else {\n resolve();\n }\n });\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n computed,\n effect,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport {\n NgxDropzoneFile,\n NgxDropzoneRejectedFile,\n NgxDropzoneRejectionReason,\n} from '../contracts/dropzone-file';\nimport { extractFilesFromDataTransfer, formatBytes, matchesAccept } from '../utils/file-matching';\n\nlet uidCounter = 0;\nfunction nextId(): string {\n return `ngx-dz-${Date.now().toString(36)}-${uidCounter++}`;\n}\n\n/**\n * ناحیه‌ی درگ‌اند‌دراپِ فایل — کاملاً مستقل (بدون وابستگی به هیچ lib\n * دیگه‌ای، حتی shared)، signal-based و OnPush.\n *\n * ویژگی‌های مهمی که معمولاً پیاده‌سازی‌های ساده‌ی dropzone ندارن:\n * - شمارنده‌ی dragenter/dragleave: چون dragleave برای فرزندهای داخل\n * dropzone هم شلیک می‌شه، پیاده‌سازی‌های ساده موقع درگ‌کردن روی متن/آیکون\n * داخل ناحیه، حالت \"درگ\" رو اشتباهی خاموش می‌کنن (چشمک‌زدن). این‌جا با\n * شمارنده حل شده.\n * - پیمایش بازگشتیِ پوشه‌ها (وقتی کاربر یه پوشه رو درگ کنه، نه فقط فایل).\n * - قابل‌دسترسی با کیبورد (focusable + Enter/Space).\n * - پاک‌سازی object URL پیش‌نمایش‌ها روی حذف/destroy (وگرنه نشتی حافظه‌ست).\n */\n@Component({\n selector: 'ngx-dropzone',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './dropzone.component.html',\n styleUrl: './dropzone.component.scss',\n host: {\n class: 'ngx-dropzone-host',\n },\n})\nexport class NgxDropzoneComponent implements OnDestroy {\n /** الگوی accept مثل input[type=file]: \"image/*\", \".pdf,.docx\" و ... */\n accept = input<string>('');\n multiple = input<boolean>(true);\n /** حداکثر حجم هر فایل به بایت؛ undefined یعنی بدون محدودیت */\n maxFileSize = input<number | undefined>(undefined);\n /** حداکثر تعداد فایل مجاز؛ undefined یعنی بدون محدودیت */\n maxFiles = input<number | undefined>(undefined);\n disabled = input<boolean>(false);\n /** نمایش لیست/پیش‌نمایش فایل‌های انتخاب‌شده داخل خودِ کامپوننت */\n showFileList = input<boolean>(true);\n\n filesAdded = output<File[]>();\n filesRejected = output<NgxDropzoneRejectedFile[]>();\n /** کل لیست فعلی (بعد از هر تغییر) — برای binding راحت‌تر */\n filesChange = output<File[]>();\n\n protected readonly files = signal<NgxDropzoneFile[]>([]);\n protected readonly isDragging = signal(false);\n private dragCounter = 0;\n\n private readonly fileInput = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n protected readonly hint = computed(() => {\n const parts: string[] = [];\n const accept = this.accept();\n if (accept) parts.push(accept);\n const max = this.maxFileSize();\n if (max) parts.push(`حداکثر ${formatBytes(max)}`);\n return parts.join(' · ');\n });\n\n protected formatSize(bytes: number): string {\n return formatBytes(bytes);\n }\n\n ngOnDestroy(): void {\n // همه‌ی object URLهای پیش‌نمایش باید revoke بشن، وگرنه تا وقتی تب باز\n // باشه، بلاب‌های تصویر توی حافظه‌ی مرورگر می‌مونن.\n for (const f of this.files()) {\n if (f.previewUrl) URL.revokeObjectURL(f.previewUrl);\n }\n }\n\n protected onDragEnter(ev: DragEvent): void {\n ev.preventDefault();\n if (this.disabled()) return;\n this.dragCounter++;\n this.isDragging.set(true);\n }\n\n protected onDragOver(ev: DragEvent): void {\n // بدون preventDefault روی dragover، رویداد drop اصلاً شلیک نمی‌شه.\n ev.preventDefault();\n if (ev.dataTransfer) ev.dataTransfer.dropEffect = this.disabled() ? 'none' : 'copy';\n }\n\n protected onDragLeave(ev: DragEvent): void {\n ev.preventDefault();\n if (this.disabled()) return;\n this.dragCounter = Math.max(0, this.dragCounter - 1);\n if (this.dragCounter === 0) this.isDragging.set(false);\n }\n\n protected async onDrop(ev: DragEvent): Promise<void> {\n ev.preventDefault();\n this.dragCounter = 0;\n this.isDragging.set(false);\n if (this.disabled() || !ev.dataTransfer) return;\n\n const files = await extractFilesFromDataTransfer(ev.dataTransfer);\n this.handleIncomingFiles(files);\n }\n\n protected onBrowseClick(): void {\n if (this.disabled()) return;\n this.fileInput()?.nativeElement.click();\n }\n\n protected onKeydown(ev: KeyboardEvent): void {\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n this.onBrowseClick();\n }\n }\n\n protected onFileInputChange(ev: Event): void {\n const input = ev.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n this.handleIncomingFiles(files);\n // اجازه بده همون فایل دوباره انتخاب بشه (input تغییر نمی‌کنه وگرنه)\n input.value = '';\n }\n\n protected removeFile(id: string): void {\n const target = this.files().find((f) => f.id === id);\n if (target?.previewUrl) URL.revokeObjectURL(target.previewUrl);\n this.files.update((list) => list.filter((f) => f.id !== id));\n this.emitCurrentFiles();\n }\n\n private handleIncomingFiles(incoming: File[]): void {\n if (!incoming.length) return;\n\n const accepted: NgxDropzoneFile[] = [];\n const rejected: NgxDropzoneRejectedFile[] = [];\n\n const existingCount = this.files().filter((f) => f.status === 'accepted').length;\n const maxFiles = this.maxFiles();\n const maxSize = this.maxFileSize();\n const isMultiple = this.multiple();\n\n const pool = isMultiple ? incoming : incoming.slice(0, 1);\n\n for (const file of pool) {\n let reason: NgxDropzoneRejectionReason | undefined;\n if (!matchesAccept(file, this.accept())) {\n reason = 'type';\n } else if (maxSize != null && file.size > maxSize) {\n reason = 'size';\n } else if (maxFiles != null && existingCount + accepted.length >= maxFiles) {\n reason = 'count';\n }\n\n if (reason) {\n rejected.push({ file, reason });\n continue;\n }\n\n const isImage = file.type.startsWith('image/');\n accepted.push({\n id: nextId(),\n file,\n previewUrl: isImage ? URL.createObjectURL(file) : undefined,\n status: 'accepted',\n });\n }\n\n if (accepted.length) {\n this.files.update((list) => (isMultiple ? [...list, ...accepted] : accepted));\n this.filesAdded.emit(accepted.map((a) => a.file));\n this.emitCurrentFiles();\n }\n if (rejected.length) {\n this.filesRejected.emit(rejected);\n }\n }\n\n private emitCurrentFiles(): void {\n this.filesChange.emit(this.files().map((f) => f.file));\n }\n}\n","<div\n class=\"ngx-dropzone\"\n [class.dragging]=\"isDragging()\"\n [class.disabled]=\"disabled()\"\n [class.has-files]=\"files().length > 0\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"onBrowseClick()\"\n (keydown)=\"onKeydown($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n #fileInput\n type=\"file\"\n class=\"ngx-dropzone-input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onFileInputChange($event)\" />\n\n <div class=\"ngx-dropzone-icon-wrap\">\n <ng-content select=\"[dropzone-icon]\">\n <svg class=\"ngx-dropzone-icon\" viewBox=\"0 0 24 24\" fill=\"none\" width=\"30\" height=\"30\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </ng-content>\n </div>\n\n <div class=\"ngx-dropzone-text\">\n <ng-content>\n <strong>فایل‌ها را اینجا رها کنید</strong>\n <span>یا برای انتخاب کلیک کنید</span>\n </ng-content>\n </div>\n\n @if (hint()) {\n <div class=\"ngx-dropzone-hint\">{{ hint() }}</div>\n }\n</div>\n\n@if (showFileList() && files().length) {\n <ul class=\"ngx-dropzone-list\">\n @for (item of files(); track item.id) {\n <li class=\"ngx-dropzone-item\">\n <div class=\"ngx-dropzone-thumb-wrap\">\n @if (item.previewUrl) {\n <img class=\"ngx-dropzone-thumb\" [src]=\"item.previewUrl\" [alt]=\"item.file.name\" />\n } @else {\n <span class=\"ngx-dropzone-file-icon\">📄</span>\n }\n </div>\n <div class=\"ngx-dropzone-meta\">\n <span class=\"ngx-dropzone-name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n <span class=\"ngx-dropzone-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n <span class=\"ngx-dropzone-status\" aria-hidden=\"true\">✓</span>\n <button\n type=\"button\"\n class=\"ngx-dropzone-remove\"\n [attr.aria-label]=\"'حذف ' + item.file.name\"\n (click)=\"removeFile(item.id); $event.stopPropagation()\">\n ✕\n </button>\n </li>\n }\n </ul>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import * as i0 from '@angular/core';
2
+ import { model, input, output, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+
4
+ // form-field.component.ts
5
+ let nextId = 0;
6
+ class NgxFormField {
7
+ // ─────────────────────────────────────────────────────────
8
+ // Two-way binding
9
+ // ─────────────────────────────────────────────────────────
10
+ value = model('', /* @ts-ignore */
11
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
12
+ // ─────────────────────────────────────────────────────────
13
+ // Required inputs
14
+ // ─────────────────────────────────────────────────────────
15
+ label = input.required(/* @ts-ignore */
16
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
17
+ // ─────────────────────────────────────────────────────────
18
+ // Optional inputs (Signal inputs)
19
+ // ─────────────────────────────────────────────────────────
20
+ type = input('text', /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
22
+ placeholder = input('', /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
24
+ required = input(false, /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
26
+ disabled = input(false, /* @ts-ignore */
27
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
28
+ readonly = input(false, /* @ts-ignore */
29
+ ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
30
+ float = input(false, /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "float" }] : /* istanbul ignore next */ []));
32
+ name = input(null, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
34
+ autocomplete = input(null, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
36
+ rows = input(3, /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
38
+ hint = input('', /* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
40
+ errorMessages = input([], /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "errorMessages" }] : /* istanbul ignore next */ []));
42
+ dir = input('', /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
44
+ id = input(`mx-field-${++nextId}`, /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
46
+ // ─────────────────────────────────────────────────────────
47
+ // Outputs
48
+ // ─────────────────────────────────────────────────────────
49
+ focused = output();
50
+ blurred = output();
51
+ // ─────────────────────────────────────────────────────────
52
+ // Derived state (Computed signal)
53
+ // ─────────────────────────────────────────────────────────
54
+ hasValue = computed(() => {
55
+ const v = this.value();
56
+ return v !== null && v !== undefined && v !== '';
57
+ }, /* @ts-ignore */
58
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
59
+ // ─────────────────────────────────────────────────────────
60
+ // Event handlers
61
+ // ─────────────────────────────────────────────────────────
62
+ onInput(event) {
63
+ const target = event.target;
64
+ this.value.set(target.value);
65
+ }
66
+ onFocus() {
67
+ this.focused.emit();
68
+ }
69
+ onBlur() {
70
+ this.blurred.emit();
71
+ }
72
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
73
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxFormField, isStandalone: true, selector: "ngx-form-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, float: { classPropertyName: "float", publicName: "float", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", focused: "focused", blurred: "blurred" }, host: { properties: { "attr.dir": "dir() || null" } }, ngImport: i0, template: "<!-- form-field.component.html -->\n<div class=\"mx-form-field\" [class.float]=\"float()\">\n @if (type() === 'textarea') {\n <textarea\n [id]=\"id()\"\n [rows]=\"rows()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"></textarea>\n } @else {\n <input\n [id]=\"id()\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\" />\n }\n\n <div class=\"mx-form-field_before\"></div>\n <label class=\"mx-form-field_label\" [attr.for]=\"id()\">\n <span>{{ label() }}</span>\n </label>\n <div class=\"mx-form-field_after\"></div>\n</div>\n\n@if (hint()) {\n <div class=\"mx-form-field_hint\">{{ hint() }}</div>\n}\n\n@if (errorMessages().length > 0) {\n <div class=\"mx-form-field_error-list\" role=\"alert\">\n @for (msg of errorMessages(); track msg) {\n <div>{{ msg }}</div>\n }\n </div>\n}\n", styles: [":host{display:inline-flex;flex-direction:column;max-width:100%;font-family:Inter,Segoe UI,Roboto,Arial,sans-serif}.mx-form-field{--mx-field-height: 44px;--mx-field-radius: 6px;--mx-field-border-width: 1px;--mx-field-margin: 5px;--mx-field-border-color: light-dark(#737373, #8f8f8f);--mx-field-hover-color: light-dark(#3f3f3f, #d0d0d0);--mx-field-active-color: light-dark(#007ffe, #4da3ff);--mx-field-invalid-color: light-dark(#d60000, #ff4d5a);--mx-field-disabled-color: light-dark(#bdbdbd, #555555);--mx-field-label-color: light-dark(#555555, #c7c7c7);--mx-field-text-color: light-dark(#1a1a1a, #f5f5f5);--mx-field-placeholder-color: light-dark(#888888, #999999);--mx-field-focus-ring: light-dark(rgb(0 127 254 / 12%), rgb(77 163 255 / 18%));--mx-field-error-ring: light-dark(rgb(214 0 0 / 10%), rgb(255 77 90 / 15%));position:relative;display:inline-flex;align-items:stretch;width:220px;max-width:100%;height:var(--mx-field-height);margin-top:var(--mx-field-margin);margin-bottom:var(--mx-field-margin);color:var(--mx-field-text-color);isolation:isolate}.mx-form-field_before,.mx-form-field_label,.mx-form-field_after{border:var(--mx-field-border-width) solid var(--mx-field-border-color);pointer-events:none;transition:border-color .15s ease}.mx-form-field_before{flex:0 0 14px;border-right:0;border-radius:var(--mx-field-radius) 0 0 var(--mx-field-radius)}.mx-form-field_label{flex:0 0 auto;min-width:0;border-left:0;border-right:0;color:var(--mx-field-label-color);white-space:nowrap}.mx-form-field_label span{display:block;padding:calc((var(--mx-field-height) - 1em) / 2) 6px;color:inherit;font-size:14px;font-weight:400;line-height:1;opacity:.72;transform-origin:left center;transition:transform .15s cubic-bezier(.2,0,0,1),opacity .15s ease,color .15s ease}.mx-form-field_after{flex:1 1 auto;min-width:14px;border-left:0;border-radius:0 var(--mx-field-radius) var(--mx-field-radius) 0}.mx-form-field>input,.mx-form-field>textarea,.mx-form-field>select{position:absolute;inset:0;min-width:0;width:100%;height:100%;margin:0;padding:0 16px;border:0;outline:0;background:transparent;color:var(--mx-field-text-color);font:inherit;font-size:14px;text-align:start;caret-color:var(--mx-field-active-color);z-index:2}.mx-form-field>input::placeholder,.mx-form-field>textarea::placeholder{color:var(--mx-field-placeholder-color);opacity:0;transition:opacity .12s ease}.mx-form-field>input:focus::placeholder,.mx-form-field>textarea:focus::placeholder{opacity:.8}.mx-form-field>input:focus~.mx-form-field_label span,.mx-form-field>input.has-value~.mx-form-field_label span,.mx-form-field>textarea:focus~.mx-form-field_label span,.mx-form-field>textarea.has-value~.mx-form-field_label span,.mx-form-field.float .mx-form-field_label span{transform:translateY(-50%) scale(.86);opacity:1}.mx-form-field>input:focus~.mx-form-field_label,.mx-form-field>input.has-value~.mx-form-field_label,.mx-form-field>textarea:focus~.mx-form-field_label,.mx-form-field>textarea.has-value~.mx-form-field_label,.mx-form-field.float .mx-form-field_label{border-top-width:0}.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_before,.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_label,.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_after{border-color:var(--mx-field-hover-color)}.mx-form-field:focus-within .mx-form-field_before,.mx-form-field:focus-within .mx-form-field_label,.mx-form-field:focus-within .mx-form-field_after{border-color:var(--mx-field-active-color)!important}.mx-form-field:focus-within .mx-form-field_label span{color:var(--mx-field-active-color)}.mx-form-field>input:invalid~.mx-form-field_before,.mx-form-field>input:invalid~.mx-form-field_label,.mx-form-field>input:invalid~.mx-form-field_after,.mx-form-field>textarea:invalid~.mx-form-field_before,.mx-form-field>textarea:invalid~.mx-form-field_label,.mx-form-field>textarea:invalid~.mx-form-field_after{border-color:var(--mx-field-invalid-color)}.mx-form-field:has(input:invalid:focus):after,.mx-form-field:has(textarea:invalid:focus):after{border-color:var(--mx-field-error-ring)}.mx-form-field:has(input:invalid:focus) .mx-form-field_label span,.mx-form-field:has(textarea:invalid:focus) .mx-form-field_label span{color:var(--mx-field-invalid-color)}.mx-form-field:has(input:disabled),.mx-form-field:has(textarea:disabled),.mx-form-field:has(select:disabled){opacity:.62;cursor:not-allowed}.mx-form-field:has(input:disabled) .mx-form-field_before,.mx-form-field:has(input:disabled) .mx-form-field_label,.mx-form-field:has(input:disabled) .mx-form-field_after,.mx-form-field:has(textarea:disabled) .mx-form-field_before,.mx-form-field:has(textarea:disabled) .mx-form-field_label,.mx-form-field:has(textarea:disabled) .mx-form-field_after,.mx-form-field:has(select:disabled) .mx-form-field_before,.mx-form-field:has(select:disabled) .mx-form-field_label,.mx-form-field:has(select:disabled) .mx-form-field_after{border-color:var(--mx-field-disabled-color)}.mx-form-field:has(input:read-only:not(:disabled)){--mx-field-border-color: light-dark(#9a9a9a, #686868)}.mx-form-field input:-webkit-autofill,.mx-form-field input:-webkit-autofill:hover,.mx-form-field input:-webkit-autofill:focus{-webkit-text-fill-color:var(--mx-field-text-color);caret-color:var(--mx-field-text-color);transition:background-color 99999s ease-in-out 0s}.mx-form-field_hint{font-size:.8rem;color:var(--mx-field-hint-color, light-dark(#3598fb, #0366d0))}.mx-form-field_error-list{font-size:.8rem;color:var(--mx-field-error-list-color, light-dark(#d60000, #ff4d5a))}:host([dir=rtl]) .mx-form-field_before{flex:0 0 14px;border-right:var(--mx-field-border-width) solid var(--mx-field-border-color);border-left:0;border-radius:0 var(--mx-field-radius) var(--mx-field-radius) 0}:host([dir=rtl]) .mx-form-field_after{border-left:var(--mx-field-border-width) solid var(--mx-field-border-color);border-right:0;border-radius:var(--mx-field-radius) 0 0 var(--mx-field-radius)}:host([dir=rtl]) .mx-form-field_label span{transform-origin:right center}:host([dir=rtl]) .mx-form-field>input:focus~:host([dir=rtl]) .mx-form-field_label span,:host([dir=rtl]) .mx-form-field>input.has-value~:host([dir=rtl]) .mx-form-field_label span,:host([dir=rtl]) .mx-form-field>textarea:focus~:host([dir=rtl]) .mx-form-field_label span{transform:translateY(-50%) scale(.86)}@media(prefers-reduced-motion:reduce){.mx-form-field *,.mx-form-field:after{transition:none!important}}@media(forced-colors:active){.mx-form-field_before,.mx-form-field_label,.mx-form-field_after{border-color:CanvasText}.mx-form-field:focus-within .mx-form-field_before,.mx-form-field:focus-within .mx-form-field_label,.mx-form-field:focus-within .mx-form-field_after{border-color:Highlight}.mx-form-field_label span{color:CanvasText}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
74
+ }
75
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxFormField, decorators: [{
76
+ type: Component,
77
+ args: [{ selector: 'ngx-form-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
78
+ '[attr.dir]': 'dir() || null',
79
+ }, template: "<!-- form-field.component.html -->\n<div class=\"mx-form-field\" [class.float]=\"float()\">\n @if (type() === 'textarea') {\n <textarea\n [id]=\"id()\"\n [rows]=\"rows()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"></textarea>\n } @else {\n <input\n [id]=\"id()\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\" />\n }\n\n <div class=\"mx-form-field_before\"></div>\n <label class=\"mx-form-field_label\" [attr.for]=\"id()\">\n <span>{{ label() }}</span>\n </label>\n <div class=\"mx-form-field_after\"></div>\n</div>\n\n@if (hint()) {\n <div class=\"mx-form-field_hint\">{{ hint() }}</div>\n}\n\n@if (errorMessages().length > 0) {\n <div class=\"mx-form-field_error-list\" role=\"alert\">\n @for (msg of errorMessages(); track msg) {\n <div>{{ msg }}</div>\n }\n </div>\n}\n", styles: [":host{display:inline-flex;flex-direction:column;max-width:100%;font-family:Inter,Segoe UI,Roboto,Arial,sans-serif}.mx-form-field{--mx-field-height: 44px;--mx-field-radius: 6px;--mx-field-border-width: 1px;--mx-field-margin: 5px;--mx-field-border-color: light-dark(#737373, #8f8f8f);--mx-field-hover-color: light-dark(#3f3f3f, #d0d0d0);--mx-field-active-color: light-dark(#007ffe, #4da3ff);--mx-field-invalid-color: light-dark(#d60000, #ff4d5a);--mx-field-disabled-color: light-dark(#bdbdbd, #555555);--mx-field-label-color: light-dark(#555555, #c7c7c7);--mx-field-text-color: light-dark(#1a1a1a, #f5f5f5);--mx-field-placeholder-color: light-dark(#888888, #999999);--mx-field-focus-ring: light-dark(rgb(0 127 254 / 12%), rgb(77 163 255 / 18%));--mx-field-error-ring: light-dark(rgb(214 0 0 / 10%), rgb(255 77 90 / 15%));position:relative;display:inline-flex;align-items:stretch;width:220px;max-width:100%;height:var(--mx-field-height);margin-top:var(--mx-field-margin);margin-bottom:var(--mx-field-margin);color:var(--mx-field-text-color);isolation:isolate}.mx-form-field_before,.mx-form-field_label,.mx-form-field_after{border:var(--mx-field-border-width) solid var(--mx-field-border-color);pointer-events:none;transition:border-color .15s ease}.mx-form-field_before{flex:0 0 14px;border-right:0;border-radius:var(--mx-field-radius) 0 0 var(--mx-field-radius)}.mx-form-field_label{flex:0 0 auto;min-width:0;border-left:0;border-right:0;color:var(--mx-field-label-color);white-space:nowrap}.mx-form-field_label span{display:block;padding:calc((var(--mx-field-height) - 1em) / 2) 6px;color:inherit;font-size:14px;font-weight:400;line-height:1;opacity:.72;transform-origin:left center;transition:transform .15s cubic-bezier(.2,0,0,1),opacity .15s ease,color .15s ease}.mx-form-field_after{flex:1 1 auto;min-width:14px;border-left:0;border-radius:0 var(--mx-field-radius) var(--mx-field-radius) 0}.mx-form-field>input,.mx-form-field>textarea,.mx-form-field>select{position:absolute;inset:0;min-width:0;width:100%;height:100%;margin:0;padding:0 16px;border:0;outline:0;background:transparent;color:var(--mx-field-text-color);font:inherit;font-size:14px;text-align:start;caret-color:var(--mx-field-active-color);z-index:2}.mx-form-field>input::placeholder,.mx-form-field>textarea::placeholder{color:var(--mx-field-placeholder-color);opacity:0;transition:opacity .12s ease}.mx-form-field>input:focus::placeholder,.mx-form-field>textarea:focus::placeholder{opacity:.8}.mx-form-field>input:focus~.mx-form-field_label span,.mx-form-field>input.has-value~.mx-form-field_label span,.mx-form-field>textarea:focus~.mx-form-field_label span,.mx-form-field>textarea.has-value~.mx-form-field_label span,.mx-form-field.float .mx-form-field_label span{transform:translateY(-50%) scale(.86);opacity:1}.mx-form-field>input:focus~.mx-form-field_label,.mx-form-field>input.has-value~.mx-form-field_label,.mx-form-field>textarea:focus~.mx-form-field_label,.mx-form-field>textarea.has-value~.mx-form-field_label,.mx-form-field.float .mx-form-field_label{border-top-width:0}.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_before,.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_label,.mx-form-field:hover:not(:has(input:disabled)):not(:has(textarea:disabled)):not(:has(select:disabled)) .mx-form-field_after{border-color:var(--mx-field-hover-color)}.mx-form-field:focus-within .mx-form-field_before,.mx-form-field:focus-within .mx-form-field_label,.mx-form-field:focus-within .mx-form-field_after{border-color:var(--mx-field-active-color)!important}.mx-form-field:focus-within .mx-form-field_label span{color:var(--mx-field-active-color)}.mx-form-field>input:invalid~.mx-form-field_before,.mx-form-field>input:invalid~.mx-form-field_label,.mx-form-field>input:invalid~.mx-form-field_after,.mx-form-field>textarea:invalid~.mx-form-field_before,.mx-form-field>textarea:invalid~.mx-form-field_label,.mx-form-field>textarea:invalid~.mx-form-field_after{border-color:var(--mx-field-invalid-color)}.mx-form-field:has(input:invalid:focus):after,.mx-form-field:has(textarea:invalid:focus):after{border-color:var(--mx-field-error-ring)}.mx-form-field:has(input:invalid:focus) .mx-form-field_label span,.mx-form-field:has(textarea:invalid:focus) .mx-form-field_label span{color:var(--mx-field-invalid-color)}.mx-form-field:has(input:disabled),.mx-form-field:has(textarea:disabled),.mx-form-field:has(select:disabled){opacity:.62;cursor:not-allowed}.mx-form-field:has(input:disabled) .mx-form-field_before,.mx-form-field:has(input:disabled) .mx-form-field_label,.mx-form-field:has(input:disabled) .mx-form-field_after,.mx-form-field:has(textarea:disabled) .mx-form-field_before,.mx-form-field:has(textarea:disabled) .mx-form-field_label,.mx-form-field:has(textarea:disabled) .mx-form-field_after,.mx-form-field:has(select:disabled) .mx-form-field_before,.mx-form-field:has(select:disabled) .mx-form-field_label,.mx-form-field:has(select:disabled) .mx-form-field_after{border-color:var(--mx-field-disabled-color)}.mx-form-field:has(input:read-only:not(:disabled)){--mx-field-border-color: light-dark(#9a9a9a, #686868)}.mx-form-field input:-webkit-autofill,.mx-form-field input:-webkit-autofill:hover,.mx-form-field input:-webkit-autofill:focus{-webkit-text-fill-color:var(--mx-field-text-color);caret-color:var(--mx-field-text-color);transition:background-color 99999s ease-in-out 0s}.mx-form-field_hint{font-size:.8rem;color:var(--mx-field-hint-color, light-dark(#3598fb, #0366d0))}.mx-form-field_error-list{font-size:.8rem;color:var(--mx-field-error-list-color, light-dark(#d60000, #ff4d5a))}:host([dir=rtl]) .mx-form-field_before{flex:0 0 14px;border-right:var(--mx-field-border-width) solid var(--mx-field-border-color);border-left:0;border-radius:0 var(--mx-field-radius) var(--mx-field-radius) 0}:host([dir=rtl]) .mx-form-field_after{border-left:var(--mx-field-border-width) solid var(--mx-field-border-color);border-right:0;border-radius:var(--mx-field-radius) 0 0 var(--mx-field-radius)}:host([dir=rtl]) .mx-form-field_label span{transform-origin:right center}:host([dir=rtl]) .mx-form-field>input:focus~:host([dir=rtl]) .mx-form-field_label span,:host([dir=rtl]) .mx-form-field>input.has-value~:host([dir=rtl]) .mx-form-field_label span,:host([dir=rtl]) .mx-form-field>textarea:focus~:host([dir=rtl]) .mx-form-field_label span{transform:translateY(-50%) scale(.86)}@media(prefers-reduced-motion:reduce){.mx-form-field *,.mx-form-field:after{transition:none!important}}@media(forced-colors:active){.mx-form-field_before,.mx-form-field_label,.mx-form-field_after{border-color:CanvasText}.mx-form-field:focus-within .mx-form-field_before,.mx-form-field:focus-within .mx-form-field_label,.mx-form-field:focus-within .mx-form-field_after{border-color:Highlight}.mx-form-field_label span{color:CanvasText}}\n"] }]
80
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], float: [{ type: i0.Input, args: [{ isSignal: true, alias: "float", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], focused: [{ type: i0.Output, args: ["focused"] }], blurred: [{ type: i0.Output, args: ["blurred"] }] } });
81
+
82
+ /**
83
+ * Generated bundle index. Do not edit.
84
+ */
85
+
86
+ export { NgxFormField };
87
+ //# sourceMappingURL=ngx-kit-form-field.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-kit-form-field.mjs","sources":["../../../projects/libs/form-field/components/form-field.ts","../../../projects/libs/form-field/components/form-field.html","../../../projects/libs/form-field/ngx-kit-form-field.ts"],"sourcesContent":["// form-field.component.ts\nimport { Component, ChangeDetectionStrategy, input, output, model, computed } from '@angular/core';\nimport { NgxFormFieldType } from '../types/field-type';\n\nlet nextId = 0;\n\n@Component({\n selector: 'ngx-form-field',\n standalone: true,\n templateUrl: './form-field.html',\n styleUrl: './form-field.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[attr.dir]': 'dir() || null',\n },\n})\nexport class NgxFormField {\n // ─────────────────────────────────────────────────────────\n // Two-way binding\n // ─────────────────────────────────────────────────────────\n readonly value = model<string | number>('');\n\n // ─────────────────────────────────────────────────────────\n // Required inputs\n // ─────────────────────────────────────────────────────────\n readonly label = input.required<string>();\n\n // ─────────────────────────────────────────────────────────\n // Optional inputs (Signal inputs)\n // ─────────────────────────────────────────────────────────\n readonly type = input<NgxFormFieldType>('text');\n readonly placeholder = input<string>('');\n readonly required = input<boolean>(false);\n readonly disabled = input<boolean>(false);\n readonly readonly = input<boolean>(false);\n readonly float = input<boolean>(false);\n readonly name = input<string | null>(null);\n readonly autocomplete = input<string | null>(null);\n readonly rows = input<number>(3);\n readonly hint = input<string>('');\n readonly errorMessages = input<string[]>([]);\n readonly dir = input<'' | 'ltr' | 'rtl'>('');\n readonly id = input<string>(`mx-field-${++nextId}`);\n\n // ─────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────\n readonly focused = output<void>();\n readonly blurred = output<void>();\n\n // ─────────────────────────────────────────────────────────\n // Derived state (Computed signal)\n // ─────────────────────────────────────────────────────────\n readonly hasValue = computed(() => {\n const v = this.value();\n return v !== null && v !== undefined && v !== '';\n });\n\n // ─────────────────────────────────────────────────────────\n // Event handlers\n // ─────────────────────────────────────────────────────────\n onInput(event: Event): void {\n const target = event.target as HTMLInputElement | HTMLTextAreaElement;\n this.value.set(target.value);\n }\n\n onFocus(): void {\n this.focused.emit();\n }\n\n onBlur(): void {\n this.blurred.emit();\n }\n}\n","<!-- form-field.component.html -->\n<div class=\"mx-form-field\" [class.float]=\"float()\">\n @if (type() === 'textarea') {\n <textarea\n [id]=\"id()\"\n [rows]=\"rows()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"></textarea>\n } @else {\n <input\n [id]=\"id()\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.name]=\"name()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-label]=\"label()\"\n [class.has-value]=\"hasValue()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\" />\n }\n\n <div class=\"mx-form-field_before\"></div>\n <label class=\"mx-form-field_label\" [attr.for]=\"id()\">\n <span>{{ label() }}</span>\n </label>\n <div class=\"mx-form-field_after\"></div>\n</div>\n\n@if (hint()) {\n <div class=\"mx-form-field_hint\">{{ hint() }}</div>\n}\n\n@if (errorMessages().length > 0) {\n <div class=\"mx-form-field_error-list\" role=\"alert\">\n @for (msg of errorMessages(); track msg) {\n <div>{{ msg }}</div>\n }\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;AAAA;AAIA,IAAI,MAAM,GAAG,CAAC;MAYD,YAAY,CAAA;;;;IAId,KAAK,GAAG,KAAK,CAAkB,EAAE;8EAAC;;;;IAKlC,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAU;;;;IAKhC,IAAI,GAAG,KAAK,CAAmB,MAAM;6EAAC;IACtC,WAAW,GAAG,KAAK,CAAS,EAAE;oFAAC;IAC/B,QAAQ,GAAG,KAAK,CAAU,KAAK;iFAAC;IAChC,QAAQ,GAAG,KAAK,CAAU,KAAK;iFAAC;IAChC,QAAQ,GAAG,KAAK,CAAU,KAAK;iFAAC;IAChC,KAAK,GAAG,KAAK,CAAU,KAAK;8EAAC;IAC7B,IAAI,GAAG,KAAK,CAAgB,IAAI;6EAAC;IACjC,YAAY,GAAG,KAAK,CAAgB,IAAI;qFAAC;IACzC,IAAI,GAAG,KAAK,CAAS,CAAC;6EAAC;IACvB,IAAI,GAAG,KAAK,CAAS,EAAE;6EAAC;IACxB,aAAa,GAAG,KAAK,CAAW,EAAE;sFAAC;IACnC,GAAG,GAAG,KAAK,CAAqB,EAAE;4EAAC;AACnC,IAAA,EAAE,GAAG,KAAK,CAAS,CAAA,SAAA,EAAY,EAAE,MAAM,CAAA,CAAE;2EAAC;;;;IAK1C,OAAO,GAAG,MAAM,EAAQ;IACxB,OAAO,GAAG,MAAM,EAAQ;;;;AAKxB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAK;AAChC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;QACtB,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;IAClD,CAAC;iFAAC;;;;AAKF,IAAA,OAAO,CAAC,KAAY,EAAA;AAClB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAgD;QACrE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;IAC9B;IAEA,OAAO,GAAA;AACL,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;IACrB;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;IACrB;uGAxDW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,8hEChBzB,siDAsDA,EAAA,MAAA,EAAA,CAAA,kwNAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FDtCa,YAAY,EAAA,UAAA,EAAA,CAAA;kBAVxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,cACd,IAAI,EAAA,eAAA,EAGC,uBAAuB,CAAC,MAAM,EAAA,IAAA,EACzC;AACJ,wBAAA,YAAY,EAAE,eAAe;AAC9B,qBAAA,EAAA,QAAA,EAAA,siDAAA,EAAA,MAAA,EAAA,CAAA,kwNAAA,CAAA,EAAA;;;AEdH;;AAEG;;;;"}