@fovestta2/web-angular 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/fv-dropdown/fv-dropdown.component.mjs +12 -4
- package/esm2022/lib/fv-image-selector/fv-image-selector.component.mjs +32 -19
- package/esm2022/lib/fv-name-code/fv-name-code.component.mjs +14 -4
- package/fesm2022/fovestta2-web-angular.mjs +55 -24
- package/fesm2022/fovestta2-web-angular.mjs.map +1 -1
- package/lib/fv-image-selector/fv-image-selector.component.d.ts +2 -0
- package/package.json +2 -2
|
@@ -696,6 +696,9 @@ class FvDropdownComponent {
|
|
|
696
696
|
// Search Control Changes
|
|
697
697
|
this.searchControl.valueChanges.subscribe(term => {
|
|
698
698
|
this.filterOptions(term || '');
|
|
699
|
+
if (!this.disabled && !this.isOpen) {
|
|
700
|
+
this.isOpen = true;
|
|
701
|
+
}
|
|
699
702
|
});
|
|
700
703
|
// Validate initial value
|
|
701
704
|
this.validateValue(this.control.value);
|
|
@@ -737,7 +740,12 @@ class FvDropdownComponent {
|
|
|
737
740
|
}
|
|
738
741
|
}
|
|
739
742
|
filterOptions(term) {
|
|
740
|
-
|
|
743
|
+
let actualTerm = term;
|
|
744
|
+
const selected = this.findOption(this.control?.value);
|
|
745
|
+
if (selected && term === selected.label) {
|
|
746
|
+
actualTerm = '';
|
|
747
|
+
}
|
|
748
|
+
this.filteredOptions = this.generateViewItems(this.options, actualTerm);
|
|
741
749
|
// Adjust highlight index if needed (reset if search changed)
|
|
742
750
|
if (this.filteredOptions.length === 1 && !this.filteredOptions[0].isGroupLabel) {
|
|
743
751
|
this.highlightedIndex = 0;
|
|
@@ -969,11 +977,11 @@ class FvDropdownComponent {
|
|
|
969
977
|
return viewItem.value === this.control?.value;
|
|
970
978
|
}
|
|
971
979
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDropdownComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
972
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDropdownComponent, isStandalone: true, selector: "fv-dropdown", inputs: { label: "label", placeholder: "placeholder", options: "options", schema: "schema", control: "control", disabled: "disabled" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, host: { listeners: { "keydown": "onKeyDown($event)", "document:click": "onClickOutside($event)" } }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage && control?.touched\"\r\n [class.fv-dropdown-disabled]=\"disabled\" [class.fv-dropdown-open]=\"isOpen\"\r\n (click)=\"onContainerClick($event)\">\r\n <input #searchInput type=\"text\" class=\"fv-dropdown-input\" [formControl]=\"searchControl\"\r\n [placeholder]=\"placeholder\" (focus)=\"onInputFocus()\" (click)=\"$event.stopPropagation()\"\r\n autocomplete=\"off\">\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\" (click)=\"toggleDropdown()\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <ng-container *ngFor=\"let option of filteredOptions; let i = index\">\r\n <div *ngIf=\"option.isGroupLabel\" class=\"fv-dropdown-group-label\">\r\n {{ option.label }}\r\n </div>\r\n <div *ngIf=\"!option.isGroupLabel\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\"\r\n [class.fv-dropdown-option-highlighted]=\"i === highlightedIndex\"\r\n [class.child-option]=\"option.isChild\" (mouseenter)=\"highlightedIndex = i\"\r\n (
|
|
980
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDropdownComponent, isStandalone: true, selector: "fv-dropdown", inputs: { label: "label", placeholder: "placeholder", options: "options", schema: "schema", control: "control", disabled: "disabled" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, host: { listeners: { "keydown": "onKeyDown($event)", "document:click": "onClickOutside($event)" } }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage && control?.touched\"\r\n [class.fv-dropdown-disabled]=\"disabled\" [class.fv-dropdown-open]=\"isOpen\"\r\n (click)=\"onContainerClick($event)\">\r\n <input #searchInput type=\"text\" class=\"fv-dropdown-input\" [formControl]=\"searchControl\"\r\n [placeholder]=\"placeholder\" (focus)=\"onInputFocus()\" (click)=\"$event.stopPropagation(); onInputFocus()\"\r\n autocomplete=\"off\">\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\" (click)=\"toggleDropdown()\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <ng-container *ngFor=\"let option of filteredOptions; let i = index\">\r\n <div *ngIf=\"option.isGroupLabel\" class=\"fv-dropdown-group-label\">\r\n {{ option.label }}\r\n </div>\r\n <div *ngIf=\"!option.isGroupLabel\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\"\r\n [class.fv-dropdown-option-highlighted]=\"i === highlightedIndex\"\r\n [class.child-option]=\"option.isChild\" (mouseenter)=\"highlightedIndex = i\"\r\n (mousedown)=\"selectOption(option); $event.preventDefault()\">\r\n {{ option.label }}\r\n </div>\r\n </ng-container>\r\n\r\n <div *ngIf=\"filteredOptions.length === 0\" class=\"fv-dropdown-option fv-no-results\">\r\n No options found\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-dropdown-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/icon?family=Material+Icons\";@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif}.material-icons{font-family:Material Icons!important}.fv-dropdown-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-dropdown-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-dropdown-wrapper{position:relative}.fv-dropdown{padding:5px 10px;border:1px solid #8CBBA8;border-radius:8px;background-color:#fff;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:border-color .2s;outline:none;font-size:14px;font-weight:400;color:#1f2b41;box-sizing:border-box;height:34px}.fv-dropdown:hover:not(.fv-dropdown-disabled){border-color:#3498db}.fv-dropdown:focus-within:not(.fv-dropdown-disabled){border-color:#3498db;box-shadow:0 0 0 3px #3498db1a}.fv-dropdown-error{border-color:#dc3545!important}.fv-dropdown-disabled{background-color:#ecf0f1;opacity:.6;cursor:not-allowed}.fv-dropdown-open{border-color:#3498db}.fv-dropdown-selected{font-size:14px;font-weight:400;color:#1f2b41;flex:1}.fv-dropdown-placeholder{color:#999}.fv-dropdown-arrow{font-size:10px;color:#666;margin-left:8px;margin-right:5px;transition:transform .2s}.arrow-up{transform:rotate(180deg)}.fv-dropdown-options{position:absolute;top:100%;left:0;right:0;margin-top:4px;background-color:#fff;border:1px solid #cccccc;border-radius:4px;max-height:300px;overflow-y:auto;z-index:1000;box-shadow:0 4px 6px #0000001a}.fv-dropdown-option{padding:12px 16px;font-size:14px;color:#151d48;cursor:pointer;transition:background-color .2s;border-bottom:1px solid #eeeeee}.fv-dropdown-option:last-child{border-bottom:none}.fv-dropdown-option:hover,.fv-dropdown-option-highlighted{background-color:#f8f9fa}.fv-dropdown-option-selected{background-color:#e7f3ff;color:#007bff;font-weight:600}.fv-dropdown-error-message{margin-top:4px;font-size:12px;color:#dc3545}.fv-dropdown-input{border:none!important;outline:none!important;box-shadow:none!important;background:transparent;flex:1;width:100%;min-width:0;appearance:none;-webkit-appearance:none;font-size:14px;color:#1f2b41;padding:0;margin:0;cursor:text;z-index:2;position:relative;pointer-events:auto;height:100%}.fv-dropdown-input::placeholder{color:#999}.fv-no-results{color:#999;font-style:italic;cursor:default;text-align:center}.fv-dropdown-group-label{font-weight:600;color:#151d48;padding:8px 16px;background-color:#f9f9f9;cursor:default;pointer-events:none;font-size:14px;letter-spacing:normal;border-bottom:1px solid #eee}.fv-dropdown-option.child-option{padding-left:32px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
|
|
973
981
|
}
|
|
974
982
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDropdownComponent, decorators: [{
|
|
975
983
|
type: Component,
|
|
976
|
-
args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-dropdown', template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage && control?.touched\"\r\n [class.fv-dropdown-disabled]=\"disabled\" [class.fv-dropdown-open]=\"isOpen\"\r\n (click)=\"onContainerClick($event)\">\r\n <input #searchInput type=\"text\" class=\"fv-dropdown-input\" [formControl]=\"searchControl\"\r\n [placeholder]=\"placeholder\" (focus)=\"onInputFocus()\" (click)=\"$event.stopPropagation()\"\r\n autocomplete=\"off\">\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\" (click)=\"toggleDropdown()\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <ng-container *ngFor=\"let option of filteredOptions; let i = index\">\r\n <div *ngIf=\"option.isGroupLabel\" class=\"fv-dropdown-group-label\">\r\n {{ option.label }}\r\n </div>\r\n <div *ngIf=\"!option.isGroupLabel\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\"\r\n [class.fv-dropdown-option-highlighted]=\"i === highlightedIndex\"\r\n [class.child-option]=\"option.isChild\" (mouseenter)=\"highlightedIndex = i\"\r\n (
|
|
984
|
+
args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-dropdown', template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage && control?.touched\"\r\n [class.fv-dropdown-disabled]=\"disabled\" [class.fv-dropdown-open]=\"isOpen\"\r\n (click)=\"onContainerClick($event)\">\r\n <input #searchInput type=\"text\" class=\"fv-dropdown-input\" [formControl]=\"searchControl\"\r\n [placeholder]=\"placeholder\" (focus)=\"onInputFocus()\" (click)=\"$event.stopPropagation(); onInputFocus()\"\r\n autocomplete=\"off\">\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\" (click)=\"toggleDropdown()\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <ng-container *ngFor=\"let option of filteredOptions; let i = index\">\r\n <div *ngIf=\"option.isGroupLabel\" class=\"fv-dropdown-group-label\">\r\n {{ option.label }}\r\n </div>\r\n <div *ngIf=\"!option.isGroupLabel\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\"\r\n [class.fv-dropdown-option-highlighted]=\"i === highlightedIndex\"\r\n [class.child-option]=\"option.isChild\" (mouseenter)=\"highlightedIndex = i\"\r\n (mousedown)=\"selectOption(option); $event.preventDefault()\">\r\n {{ option.label }}\r\n </div>\r\n </ng-container>\r\n\r\n <div *ngIf=\"filteredOptions.length === 0\" class=\"fv-dropdown-option fv-no-results\">\r\n No options found\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-dropdown-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/icon?family=Material+Icons\";@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif}.material-icons{font-family:Material Icons!important}.fv-dropdown-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-dropdown-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-dropdown-wrapper{position:relative}.fv-dropdown{padding:5px 10px;border:1px solid #8CBBA8;border-radius:8px;background-color:#fff;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:border-color .2s;outline:none;font-size:14px;font-weight:400;color:#1f2b41;box-sizing:border-box;height:34px}.fv-dropdown:hover:not(.fv-dropdown-disabled){border-color:#3498db}.fv-dropdown:focus-within:not(.fv-dropdown-disabled){border-color:#3498db;box-shadow:0 0 0 3px #3498db1a}.fv-dropdown-error{border-color:#dc3545!important}.fv-dropdown-disabled{background-color:#ecf0f1;opacity:.6;cursor:not-allowed}.fv-dropdown-open{border-color:#3498db}.fv-dropdown-selected{font-size:14px;font-weight:400;color:#1f2b41;flex:1}.fv-dropdown-placeholder{color:#999}.fv-dropdown-arrow{font-size:10px;color:#666;margin-left:8px;margin-right:5px;transition:transform .2s}.arrow-up{transform:rotate(180deg)}.fv-dropdown-options{position:absolute;top:100%;left:0;right:0;margin-top:4px;background-color:#fff;border:1px solid #cccccc;border-radius:4px;max-height:300px;overflow-y:auto;z-index:1000;box-shadow:0 4px 6px #0000001a}.fv-dropdown-option{padding:12px 16px;font-size:14px;color:#151d48;cursor:pointer;transition:background-color .2s;border-bottom:1px solid #eeeeee}.fv-dropdown-option:last-child{border-bottom:none}.fv-dropdown-option:hover,.fv-dropdown-option-highlighted{background-color:#f8f9fa}.fv-dropdown-option-selected{background-color:#e7f3ff;color:#007bff;font-weight:600}.fv-dropdown-error-message{margin-top:4px;font-size:12px;color:#dc3545}.fv-dropdown-input{border:none!important;outline:none!important;box-shadow:none!important;background:transparent;flex:1;width:100%;min-width:0;appearance:none;-webkit-appearance:none;font-size:14px;color:#1f2b41;padding:0;margin:0;cursor:text;z-index:2;position:relative;pointer-events:auto;height:100%}.fv-dropdown-input::placeholder{color:#999}.fv-no-results{color:#999;font-style:italic;cursor:default;text-align:center}.fv-dropdown-group-label{font-weight:600;color:#151d48;padding:8px 16px;background-color:#f9f9f9;cursor:default;pointer-events:none;font-size:14px;letter-spacing:normal;border-bottom:1px solid #eee}.fv-dropdown-option.child-option{padding-left:32px}\n"] }]
|
|
977
985
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { label: [{
|
|
978
986
|
type: Input
|
|
979
987
|
}], placeholder: [{
|
|
@@ -1232,9 +1240,12 @@ class FvImageSelectorComponent {
|
|
|
1232
1240
|
editImageSrc = null;
|
|
1233
1241
|
editImageName = '';
|
|
1234
1242
|
scale = 1;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1243
|
+
fitScale = 1; // auto-calculated scale to fit image in the crop circle
|
|
1244
|
+
minScale = 0.1;
|
|
1245
|
+
maxScale = 5;
|
|
1246
|
+
step = 0.05;
|
|
1247
|
+
// The crop circle display diameter in the modal (matches CSS .fv-crop-mask radius = 150px)
|
|
1248
|
+
CROP_DISPLAY_SIZE = 300;
|
|
1238
1249
|
panX = 0;
|
|
1239
1250
|
panY = 0;
|
|
1240
1251
|
isDragging = false;
|
|
@@ -1304,9 +1315,18 @@ class FvImageSelectorComponent {
|
|
|
1304
1315
|
}
|
|
1305
1316
|
const reader = new FileReader();
|
|
1306
1317
|
reader.onload = (e) => {
|
|
1307
|
-
|
|
1318
|
+
const src = e.target.result;
|
|
1319
|
+
this.editImageSrc = src;
|
|
1308
1320
|
this.editImageName = file.name;
|
|
1309
|
-
|
|
1321
|
+
// Load image to get natural dimensions before opening editor
|
|
1322
|
+
const tempImg = new Image();
|
|
1323
|
+
tempImg.onload = () => {
|
|
1324
|
+
// Calculate scale to fit entire image inside the crop circle
|
|
1325
|
+
const fitS = Math.min(this.CROP_DISPLAY_SIZE / tempImg.naturalWidth, this.CROP_DISPLAY_SIZE / tempImg.naturalHeight);
|
|
1326
|
+
this.fitScale = fitS;
|
|
1327
|
+
this.openEditor();
|
|
1328
|
+
};
|
|
1329
|
+
tempImg.src = src;
|
|
1310
1330
|
input.value = '';
|
|
1311
1331
|
};
|
|
1312
1332
|
reader.readAsDataURL(file);
|
|
@@ -1314,7 +1334,7 @@ class FvImageSelectorComponent {
|
|
|
1314
1334
|
}
|
|
1315
1335
|
openEditor() {
|
|
1316
1336
|
this.isEditorOpen = true;
|
|
1317
|
-
this.scale =
|
|
1337
|
+
this.scale = this.fitScale;
|
|
1318
1338
|
this.panX = 0;
|
|
1319
1339
|
this.panY = 0;
|
|
1320
1340
|
}
|
|
@@ -1351,7 +1371,7 @@ class FvImageSelectorComponent {
|
|
|
1351
1371
|
this.scale = Math.max(this.scale - this.step, this.minScale);
|
|
1352
1372
|
}
|
|
1353
1373
|
resetEditor() {
|
|
1354
|
-
this.scale =
|
|
1374
|
+
this.scale = this.fitScale;
|
|
1355
1375
|
this.panX = 0;
|
|
1356
1376
|
this.panY = 0;
|
|
1357
1377
|
}
|
|
@@ -1363,31 +1383,32 @@ class FvImageSelectorComponent {
|
|
|
1363
1383
|
const ctx = canvas.getContext('2d');
|
|
1364
1384
|
if (!ctx)
|
|
1365
1385
|
return;
|
|
1366
|
-
// Output size
|
|
1367
|
-
const size =
|
|
1386
|
+
// Output size matches the crop circle size (300x300)
|
|
1387
|
+
const size = this.CROP_DISPLAY_SIZE;
|
|
1368
1388
|
canvas.width = size;
|
|
1369
1389
|
canvas.height = size;
|
|
1390
|
+
// Clip to circle for clean avatar output
|
|
1391
|
+
ctx.beginPath();
|
|
1392
|
+
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
|
|
1393
|
+
ctx.clip();
|
|
1370
1394
|
// Fill background
|
|
1371
1395
|
ctx.fillStyle = '#ffffff';
|
|
1372
1396
|
ctx.fillRect(0, 0, size, size);
|
|
1373
|
-
//
|
|
1397
|
+
// The crop container in the DOM is 100% wide but crop mask circle is CROP_DISPLAY_SIZE px diameter.
|
|
1398
|
+
// panX/panY are in display pixels (relative to the container center).
|
|
1399
|
+
// We draw: center canvas, apply pan (already in canvas-equivalent pixels since display size == output size), scale, then center image.
|
|
1374
1400
|
ctx.translate(size / 2, size / 2);
|
|
1375
1401
|
ctx.translate(this.panX, this.panY);
|
|
1376
1402
|
ctx.scale(this.scale, this.scale);
|
|
1377
1403
|
ctx.translate(-img.naturalWidth / 2, -img.naturalHeight / 2);
|
|
1378
1404
|
ctx.drawImage(img, 0, 0);
|
|
1379
1405
|
const dataUrl = canvas.toDataURL('image/png');
|
|
1380
|
-
// Convert base64 to File object to mimic file selection
|
|
1381
|
-
// We'll use the original name but .png
|
|
1382
|
-
// This 'file' is mocked, but 'url' is what is displayed.
|
|
1383
|
-
// In real app we might want to upload the blob.
|
|
1384
|
-
// For ImageInfo we store the dataURL as url.
|
|
1385
1406
|
const imageInfo = {
|
|
1386
1407
|
file: new File([this.dataURLtoBlob(dataUrl)], this.editImageName, { type: 'image/png' }),
|
|
1387
1408
|
url: this.sanitizer.bypassSecurityTrustUrl(dataUrl),
|
|
1388
1409
|
width: size,
|
|
1389
1410
|
height: size,
|
|
1390
|
-
size: 0
|
|
1411
|
+
size: 0
|
|
1391
1412
|
};
|
|
1392
1413
|
this.selectedImage = imageInfo;
|
|
1393
1414
|
if (this.control) {
|
|
@@ -1443,11 +1464,11 @@ class FvImageSelectorComponent {
|
|
|
1443
1464
|
return errorMessages[this.errorMessage] || this.errorMessage;
|
|
1444
1465
|
}
|
|
1445
1466
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvImageSelectorComponent, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
1446
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvImageSelectorComponent, isStandalone: true, selector: "fv-image-selector", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", maxSize: "maxSize" }, outputs: { valueChange: "valueChange", blur: "blur" }, host: { listeners: { "document:mousemove": "onDrag($event)", "document:mouseup": "endDrag()" } }, viewQueries: [{ propertyName: "imageInput", first: true, predicate: ["imageInput"], descendants: true }, { propertyName: "editorImage", first: true, predicate: ["editorImage"], descendants: true }], ngImport: i0, template: "<div class=\"fv-image-selector-container\" *ngIf=\"control\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <div class=\"fv-image-upload-box\" [class.fv-disabled]=\"disabled\" [class.fv-error]=\"errorMessage && control?.touched\">\r\n <div class=\"fv-image-main-section\" (click)=\"openImageDialog()\">\r\n <div class=\"fv-preview-mini\" *ngIf=\"selectedImage\">\r\n <img [src]=\"selectedImage.url\" alt=\"Preview\" />\r\n </div>\r\n <span class=\"fv-upload-text\">{{ selectedImage ? selectedImage.file.name : placeholder }}</span>\r\n </div>\r\n <div class=\"fv-upload-icon-section\">\r\n <ng-container *ngIf=\"!selectedImage\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\" (click)=\"openImageDialog()\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </ng-container>\r\n <ng-container *ngIf=\"selectedImage\">\r\n <div class=\"fv-action-icons\">\r\n <span class=\"material-icons edit-icon\" (click)=\"openImageDialog()\" title=\"Edit\">edit</span>\r\n <span class=\"material-icons delete-icon\" (click)=\"removeImage()\" title=\"Delete\">delete</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Modal -->\r\n <div *ngIf=\"isEditorOpen\" class=\"fv-modal-overlay\">\r\n <div class=\"fv-modal-content\">\r\n <div class=\"fv-modal-header\">\r\n <h3>Adjust Photo</h3>\r\n <button type=\"button\" class=\"close-icon\" (click)=\"closeEditor()\">\u00D7</button>\r\n </div>\r\n <div class=\"fv-modal-body\">\r\n <div class=\"fv-crop-container\" (mousedown)=\"startDrag($event)\">\r\n <img #editorImage [src]=\"editImageSrc\" class=\"fv-crop-image\"\r\n [style.transform]=\"'translate(' + panX + 'px, ' + panY + 'px) scale(' + scale + ')'\"\r\n draggable=\"false\" />\r\n <div class=\"fv-crop-mask\"></div>\r\n </div>\r\n\r\n <div class=\"fv-zoom-controls\">\r\n <span class=\"zoom-label\">Zoom</span>\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomOut()\">-</button>\r\n <input type=\"range\" [min]=\"minScale\" [max]=\"maxScale\" [step]=\"step\" [value]=\"scale\"\r\n (input)=\"onSliderChange($event)\" class=\"zoom-slider\">\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomIn()\">+</button>\r\n <span class=\"zoom-value\">{{ (scale * 100) | number:'1.0-0' }}%</span>\r\n </div>\r\n\r\n <p class=\"fv-hint-text\">Drag the image to reposition. Use the zoom control to fill the circle.</p>\r\n </div>\r\n\r\n <div class=\"fv-modal-footer\">\r\n <button type=\"button\" class=\"btn-reset\" (click)=\"resetEditor()\">Reset</button>\r\n <div class=\"footer-actions\">\r\n <button type=\"button\" class=\"btn-cancel\" (click)=\"closeEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn-apply\" (click)=\"applyCrop()\">Apply</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif;box-sizing:border-box}.fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:20px;width:fit-content;max-width:100%}.fv-image-selector-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700;margin-left:4px}.fv-image-upload-box{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:default;overflow:hidden;width:100%;min-width:160px;height:34px;transition:all .2s ease}.fv-image-upload-box:hover:not(.fv-disabled){border-color:#8cbba8}.fv-disabled{cursor:not-allowed!important;background-color:#f9f9f9;opacity:.7}.fv-error{border-color:#dc3545!important}.fv-image-main-section{flex:1;display:flex;align-items:center;padding:0 12px;height:100%;cursor:pointer;overflow:hidden;gap:8px}.fv-preview-mini{width:24px;height:24px;border-radius:4px;overflow:hidden;flex-shrink:0;border:1px solid #eee}.fv-preview-mini img{width:100%;height:100%;object-fit:cover}.fv-upload-text{font-size:14px;color:#1f2b41;text-align:left;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fv-upload-icon-section{background:#f4f5f7;padding:0 12px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px;cursor:pointer}.fv-action-icons{display:flex;align-items:center;gap:8px}.material-icons{font-size:20px}.edit-icon{color:#3498db;cursor:pointer}.delete-icon{color:#e74c3c;cursor:pointer}.edit-icon:hover,.delete-icon:hover{opacity:.8}.fv-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0009;display:flex;align-items:center;justify-content:center;z-index:9999}.fv-modal-content{background:#fff;width:650px;max-width:90vw;border-radius:8px;overflow:hidden;box-shadow:0 10px 30px #0003;display:flex;flex-direction:column}.fv-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;border-bottom:1px solid #f0f0f0}.fv-modal-header h3{margin:0;font-size:18px;font-weight:600;color:#1f2b41}.close-icon{background:none;border:none;font-size:24px;line-height:1;cursor:pointer;color:#888}.fv-modal-body{padding:24px;display:flex;flex-direction:column;align-items:center;background:#fff}.fv-crop-container{width:100%;height:350px;background:#333;position:relative;overflow:hidden;margin-bottom:24px;border-radius:4px;cursor:move;cursor:grab;display:flex;align-items:center;justify-content:center}.fv-crop-container:active{cursor:grabbing}.fv-crop-image{max-width:none;max-height:none;-webkit-user-select:none;user-select:none;pointer-events:none;transform-origin:center center}.fv-crop-mask{position:absolute;inset:0;background:radial-gradient(circle at center,transparent 150px,rgba(0,0,0,.6) 151px);pointer-events:none}.fv-zoom-controls{display:flex;align-items:center;gap:16px;width:100%;max-width:500px;margin-bottom:12px}.zoom-label{font-size:14px;font-weight:500;color:#333;min-width:40px}.zoom-btn{width:32px;height:32px;border-radius:50%;background:#e6f0ff;color:#006aff;border:none;font-weight:700;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding-bottom:2px}.zoom-btn:hover{background:#d0e1ff}.zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:4px;background:#ddd;border-radius:2px;outline:none}.zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:#006aff;cursor:pointer;box-shadow:0 0 0 4px #006aff33}.zoom-value{font-size:14px;color:#666;min-width:40px;text-align:right}.fv-hint-text{font-size:13px;color:#7f8c8d;margin:0;text-align:center}.fv-modal-footer{padding:16px 24px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;background:#fff}.footer-actions{display:flex;gap:12px}.btn-reset,.btn-cancel,.btn-apply{padding:8px 20px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-reset{background:#f5f5f5;border:1px solid #ddd;color:#333}.btn-reset:hover{background:#eee}.btn-cancel{background:#fff;border:1px solid #ddd;color:#333}.btn-cancel:hover{border-color:#bbb}.btn-apply{background:#006aff;border:1px solid #006aff;color:#fff}.btn-apply:hover{background:#0056cc;border-color:#0056cc}.fv-image-selector-error-message{margin-top:6px;font-size:12px;color:#e74c3c;display:flex;align-items:center;gap:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
1467
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvImageSelectorComponent, isStandalone: true, selector: "fv-image-selector", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", maxSize: "maxSize" }, outputs: { valueChange: "valueChange", blur: "blur" }, host: { listeners: { "document:mousemove": "onDrag($event)", "document:mouseup": "endDrag()" } }, viewQueries: [{ propertyName: "imageInput", first: true, predicate: ["imageInput"], descendants: true }, { propertyName: "editorImage", first: true, predicate: ["editorImage"], descendants: true }], ngImport: i0, template: "<div class=\"fv-image-selector-container\" *ngIf=\"control\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <!-- Avatar circle -->\r\n <div class=\"fv-avatar-wrapper\" [class.fv-disabled]=\"disabled\" [class.fv-error]=\"errorMessage && control?.touched\">\r\n <div class=\"fv-avatar-circle\" (click)=\"!disabled && openImageDialog()\">\r\n <ng-container *ngIf=\"selectedImage; else emptyAvatar\">\r\n <img [src]=\"selectedImage.url\" alt=\"Profile Photo\" class=\"fv-avatar-img\" />\r\n <div class=\"fv-avatar-overlay\">\r\n <svg viewBox=\"0 0 24 24\" class=\"fv-overlay-icon\">\r\n <path d=\"M12 20h9\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\r\n <path d=\"M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4L16.5 3.5z\" stroke=\"white\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" />\r\n </svg>\r\n </div>\r\n </ng-container>\r\n <ng-template #emptyAvatar>\r\n <div class=\"fv-avatar-placeholder\">\r\n <svg viewBox=\"0 0 24 24\" class=\"fv-camera-icon\">\r\n <path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"\r\n stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" />\r\n <circle cx=\"12\" cy=\"13\" r=\"4\" stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\" />\r\n </svg>\r\n <span class=\"fv-avatar-hint\">Upload Photo</span>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Action buttons below the circle -->\r\n <div class=\"fv-avatar-actions\" *ngIf=\"selectedImage && !disabled\">\r\n <button type=\"button\" class=\"fv-avatar-btn fv-btn-edit\" (click)=\"openImageDialog()\" title=\"Change Photo\">\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\">\r\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" stroke=\"currentColor\"\r\n stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n <path d=\"M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" stroke=\"currentColor\"\r\n stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n Change\r\n </button>\r\n <button type=\"button\" class=\"fv-avatar-btn fv-btn-remove\" (click)=\"removeImage()\" title=\"Remove Photo\">\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\">\r\n <polyline points=\"3 6 5 6 21 6\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" stroke=\"currentColor\" stroke-width=\"2\"\r\n fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n Remove\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Modal -->\r\n <div *ngIf=\"isEditorOpen\" class=\"fv-modal-overlay\">\r\n <div class=\"fv-modal-content\">\r\n <div class=\"fv-modal-header\">\r\n <h3>Adjust Photo</h3>\r\n <button type=\"button\" class=\"close-icon\" (click)=\"closeEditor()\">\u00D7</button>\r\n </div>\r\n <div class=\"fv-modal-body\">\r\n <div class=\"fv-crop-container\" (mousedown)=\"startDrag($event)\">\r\n <img #editorImage [src]=\"editImageSrc\" class=\"fv-crop-image\"\r\n [style.transform]=\"'translate(' + panX + 'px, ' + panY + 'px) scale(' + scale + ')'\"\r\n draggable=\"false\" />\r\n <div class=\"fv-crop-mask\"></div>\r\n </div>\r\n\r\n <div class=\"fv-zoom-controls\">\r\n <span class=\"zoom-label\">Zoom</span>\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomOut()\">-</button>\r\n <input type=\"range\" [min]=\"minScale\" [max]=\"maxScale\" [step]=\"step\" [value]=\"scale\"\r\n (input)=\"onSliderChange($event)\" class=\"zoom-slider\">\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomIn()\">+</button>\r\n <span class=\"zoom-value\">{{ (scale * 100) | number:'1.0-0' }}%</span>\r\n </div>\r\n\r\n <p class=\"fv-hint-text\">Drag the image to reposition. Use the zoom control to fill the circle.</p>\r\n </div>\r\n\r\n <div class=\"fv-modal-footer\">\r\n <button type=\"button\" class=\"btn-reset\" (click)=\"resetEditor()\">Reset</button>\r\n <div class=\"footer-actions\">\r\n <button type=\"button\" class=\"btn-cancel\" (click)=\"closeEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn-apply\" (click)=\"applyCrop()\">Apply</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif;box-sizing:border-box}.fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:20px;width:fit-content;max-width:100%}.fv-image-selector-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700;margin-left:4px}.fv-avatar-wrapper{display:flex;flex-direction:column;align-items:flex-start;gap:10px}.fv-avatar-wrapper.fv-error .fv-avatar-circle{border-color:#dc3545!important}.fv-avatar-wrapper.fv-disabled .fv-avatar-circle{opacity:.6;cursor:not-allowed}.fv-avatar-circle{width:100px;height:100px;border-radius:50%;overflow:hidden;border:2px solid #dcdcdc;cursor:pointer;position:relative;background:#f4f5f7;transition:border-color .2s ease;flex-shrink:0}.fv-avatar-circle:hover:not(.fv-disabled){border-color:#8cbba8}.fv-avatar-img{width:100%;height:100%;object-fit:cover;display:block}.fv-avatar-overlay{position:absolute;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease}.fv-avatar-circle:hover .fv-avatar-overlay{opacity:1}.fv-overlay-icon{width:24px;height:24px}.fv-avatar-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#aaa}.fv-camera-icon{width:36px;height:36px}.fv-avatar-hint{font-size:10px;font-weight:500;color:#aaa;text-align:center;line-height:1.2}.fv-avatar-actions{display:flex;gap:8px}.fv-avatar-btn{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid #dcdcdc;transition:all .2s ease;font-family:Poppins,sans-serif}.fv-btn-edit{background:#fff;color:#3498db;border-color:#3498db}.fv-btn-edit:hover{background:#e8f4fc}.fv-btn-remove{background:#fff;color:#e74c3c;border-color:#e74c3c}.fv-btn-remove:hover{background:#fdecea}.fv-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0009;display:flex;align-items:center;justify-content:center;z-index:9999}.fv-modal-content{background:#fff;width:650px;max-width:90vw;border-radius:8px;overflow:hidden;box-shadow:0 10px 30px #0003;display:flex;flex-direction:column}.fv-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;border-bottom:1px solid #f0f0f0}.fv-modal-header h3{margin:0;font-size:18px;font-weight:600;color:#1f2b41}.close-icon{background:none;border:none;font-size:24px;line-height:1;cursor:pointer;color:#888}.fv-modal-body{padding:24px;display:flex;flex-direction:column;align-items:center;background:#fff}.fv-crop-container{width:300px;height:300px;background:#333;position:relative;overflow:hidden;margin:0 auto 24px;border-radius:50%;cursor:move;cursor:grab;display:flex;align-items:center;justify-content:center;border:2px solid #555}.fv-crop-container:active{cursor:grabbing}.fv-crop-image{max-width:none;max-height:none;-webkit-user-select:none;user-select:none;pointer-events:none;transform-origin:center center}.fv-crop-mask{display:none}.fv-zoom-controls{display:flex;align-items:center;gap:16px;width:100%;max-width:500px;margin-bottom:12px}.zoom-label{font-size:14px;font-weight:500;color:#333;min-width:40px}.zoom-btn{width:32px;height:32px;border-radius:50%;background:#e6f0ff;color:#006aff;border:none;font-weight:700;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding-bottom:2px}.zoom-btn:hover{background:#d0e1ff}.zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:4px;background:#ddd;border-radius:2px;outline:none}.zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:#006aff;cursor:pointer;box-shadow:0 0 0 4px #006aff33}.zoom-value{font-size:14px;color:#666;min-width:40px;text-align:right}.fv-hint-text{font-size:13px;color:#7f8c8d;margin:0;text-align:center}.fv-modal-footer{padding:16px 24px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;background:#fff}.footer-actions{display:flex;gap:12px}.btn-reset,.btn-cancel,.btn-apply{padding:8px 20px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-reset{background:#f5f5f5;border:1px solid #ddd;color:#333}.btn-reset:hover{background:#eee}.btn-cancel{background:#fff;border:1px solid #ddd;color:#333}.btn-cancel:hover{border-color:#bbb}.btn-apply{background:#006aff;border:1px solid #006aff;color:#fff}.btn-apply:hover{background:#0056cc;border-color:#0056cc}.fv-image-selector-error-message{margin-top:6px;font-size:12px;color:#e74c3c;display:flex;align-items:center;gap:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
1447
1468
|
}
|
|
1448
1469
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvImageSelectorComponent, decorators: [{
|
|
1449
1470
|
type: Component,
|
|
1450
|
-
args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-image-selector', template: "<div class=\"fv-image-selector-container\" *ngIf=\"control\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <div class=\"fv-image-upload-box\" [class.fv-disabled]=\"disabled\" [class.fv-error]=\"errorMessage && control?.touched\">\r\n <div class=\"fv-image-main-section\" (click)=\"openImageDialog()\">\r\n <div class=\"fv-preview-mini\" *ngIf=\"selectedImage\">\r\n <img [src]=\"selectedImage.url\" alt=\"Preview\" />\r\n </div>\r\n <span class=\"fv-upload-text\">{{ selectedImage ? selectedImage.file.name : placeholder }}</span>\r\n </div>\r\n <div class=\"fv-upload-icon-section\">\r\n <ng-container *ngIf=\"!selectedImage\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\" (click)=\"openImageDialog()\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </ng-container>\r\n <ng-container *ngIf=\"selectedImage\">\r\n <div class=\"fv-action-icons\">\r\n <span class=\"material-icons edit-icon\" (click)=\"openImageDialog()\" title=\"Edit\">edit</span>\r\n <span class=\"material-icons delete-icon\" (click)=\"removeImage()\" title=\"Delete\">delete</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Modal -->\r\n <div *ngIf=\"isEditorOpen\" class=\"fv-modal-overlay\">\r\n <div class=\"fv-modal-content\">\r\n <div class=\"fv-modal-header\">\r\n <h3>Adjust Photo</h3>\r\n <button type=\"button\" class=\"close-icon\" (click)=\"closeEditor()\">\u00D7</button>\r\n </div>\r\n <div class=\"fv-modal-body\">\r\n <div class=\"fv-crop-container\" (mousedown)=\"startDrag($event)\">\r\n <img #editorImage [src]=\"editImageSrc\" class=\"fv-crop-image\"\r\n [style.transform]=\"'translate(' + panX + 'px, ' + panY + 'px) scale(' + scale + ')'\"\r\n draggable=\"false\" />\r\n <div class=\"fv-crop-mask\"></div>\r\n </div>\r\n\r\n <div class=\"fv-zoom-controls\">\r\n <span class=\"zoom-label\">Zoom</span>\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomOut()\">-</button>\r\n <input type=\"range\" [min]=\"minScale\" [max]=\"maxScale\" [step]=\"step\" [value]=\"scale\"\r\n (input)=\"onSliderChange($event)\" class=\"zoom-slider\">\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomIn()\">+</button>\r\n <span class=\"zoom-value\">{{ (scale * 100) | number:'1.0-0' }}%</span>\r\n </div>\r\n\r\n <p class=\"fv-hint-text\">Drag the image to reposition. Use the zoom control to fill the circle.</p>\r\n </div>\r\n\r\n <div class=\"fv-modal-footer\">\r\n <button type=\"button\" class=\"btn-reset\" (click)=\"resetEditor()\">Reset</button>\r\n <div class=\"footer-actions\">\r\n <button type=\"button\" class=\"btn-cancel\" (click)=\"closeEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn-apply\" (click)=\"applyCrop()\">Apply</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif;box-sizing:border-box}.fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:20px;width:fit-content;max-width:100%}.fv-image-selector-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700;margin-left:4px}.fv-image-upload-box{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:default;overflow:hidden;width:100%;min-width:160px;height:34px;transition:all .2s ease}.fv-image-upload-box:hover:not(.fv-disabled){border-color:#8cbba8}.fv-disabled{cursor:not-allowed!important;background-color:#f9f9f9;opacity:.7}.fv-error{border-color:#dc3545!important}.fv-image-main-section{flex:1;display:flex;align-items:center;padding:0 12px;height:100%;cursor:pointer;overflow:hidden;gap:8px}.fv-preview-mini{width:24px;height:24px;border-radius:4px;overflow:hidden;flex-shrink:0;border:1px solid #eee}.fv-preview-mini img{width:100%;height:100%;object-fit:cover}.fv-upload-text{font-size:14px;color:#1f2b41;text-align:left;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fv-upload-icon-section{background:#f4f5f7;padding:0 12px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px;cursor:pointer}.fv-action-icons{display:flex;align-items:center;gap:8px}.material-icons{font-size:20px}.edit-icon{color:#3498db;cursor:pointer}.delete-icon{color:#e74c3c;cursor:pointer}.edit-icon:hover,.delete-icon:hover{opacity:.8}.fv-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0009;display:flex;align-items:center;justify-content:center;z-index:9999}.fv-modal-content{background:#fff;width:650px;max-width:90vw;border-radius:8px;overflow:hidden;box-shadow:0 10px 30px #0003;display:flex;flex-direction:column}.fv-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;border-bottom:1px solid #f0f0f0}.fv-modal-header h3{margin:0;font-size:18px;font-weight:600;color:#1f2b41}.close-icon{background:none;border:none;font-size:24px;line-height:1;cursor:pointer;color:#888}.fv-modal-body{padding:24px;display:flex;flex-direction:column;align-items:center;background:#fff}.fv-crop-container{width:100%;height:350px;background:#333;position:relative;overflow:hidden;margin-bottom:24px;border-radius:4px;cursor:move;cursor:grab;display:flex;align-items:center;justify-content:center}.fv-crop-container:active{cursor:grabbing}.fv-crop-image{max-width:none;max-height:none;-webkit-user-select:none;user-select:none;pointer-events:none;transform-origin:center center}.fv-crop-mask{position:absolute;inset:0;background:radial-gradient(circle at center,transparent 150px,rgba(0,0,0,.6) 151px);pointer-events:none}.fv-zoom-controls{display:flex;align-items:center;gap:16px;width:100%;max-width:500px;margin-bottom:12px}.zoom-label{font-size:14px;font-weight:500;color:#333;min-width:40px}.zoom-btn{width:32px;height:32px;border-radius:50%;background:#e6f0ff;color:#006aff;border:none;font-weight:700;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding-bottom:2px}.zoom-btn:hover{background:#d0e1ff}.zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:4px;background:#ddd;border-radius:2px;outline:none}.zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:#006aff;cursor:pointer;box-shadow:0 0 0 4px #006aff33}.zoom-value{font-size:14px;color:#666;min-width:40px;text-align:right}.fv-hint-text{font-size:13px;color:#7f8c8d;margin:0;text-align:center}.fv-modal-footer{padding:16px 24px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;background:#fff}.footer-actions{display:flex;gap:12px}.btn-reset,.btn-cancel,.btn-apply{padding:8px 20px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-reset{background:#f5f5f5;border:1px solid #ddd;color:#333}.btn-reset:hover{background:#eee}.btn-cancel{background:#fff;border:1px solid #ddd;color:#333}.btn-cancel:hover{border-color:#bbb}.btn-apply{background:#006aff;border:1px solid #006aff;color:#fff}.btn-apply:hover{background:#0056cc;border-color:#0056cc}.fv-image-selector-error-message{margin-top:6px;font-size:12px;color:#e74c3c;display:flex;align-items:center;gap:4px}\n"] }]
|
|
1471
|
+
args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-image-selector', template: "<div class=\"fv-image-selector-container\" *ngIf=\"control\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <!-- Avatar circle -->\r\n <div class=\"fv-avatar-wrapper\" [class.fv-disabled]=\"disabled\" [class.fv-error]=\"errorMessage && control?.touched\">\r\n <div class=\"fv-avatar-circle\" (click)=\"!disabled && openImageDialog()\">\r\n <ng-container *ngIf=\"selectedImage; else emptyAvatar\">\r\n <img [src]=\"selectedImage.url\" alt=\"Profile Photo\" class=\"fv-avatar-img\" />\r\n <div class=\"fv-avatar-overlay\">\r\n <svg viewBox=\"0 0 24 24\" class=\"fv-overlay-icon\">\r\n <path d=\"M12 20h9\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\r\n <path d=\"M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4L16.5 3.5z\" stroke=\"white\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" />\r\n </svg>\r\n </div>\r\n </ng-container>\r\n <ng-template #emptyAvatar>\r\n <div class=\"fv-avatar-placeholder\">\r\n <svg viewBox=\"0 0 24 24\" class=\"fv-camera-icon\">\r\n <path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"\r\n stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" />\r\n <circle cx=\"12\" cy=\"13\" r=\"4\" stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\" />\r\n </svg>\r\n <span class=\"fv-avatar-hint\">Upload Photo</span>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Action buttons below the circle -->\r\n <div class=\"fv-avatar-actions\" *ngIf=\"selectedImage && !disabled\">\r\n <button type=\"button\" class=\"fv-avatar-btn fv-btn-edit\" (click)=\"openImageDialog()\" title=\"Change Photo\">\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\">\r\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" stroke=\"currentColor\"\r\n stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n <path d=\"M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" stroke=\"currentColor\"\r\n stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n Change\r\n </button>\r\n <button type=\"button\" class=\"fv-avatar-btn fv-btn-remove\" (click)=\"removeImage()\" title=\"Remove Photo\">\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\">\r\n <polyline points=\"3 6 5 6 21 6\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" stroke=\"currentColor\" stroke-width=\"2\"\r\n fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n Remove\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Modal -->\r\n <div *ngIf=\"isEditorOpen\" class=\"fv-modal-overlay\">\r\n <div class=\"fv-modal-content\">\r\n <div class=\"fv-modal-header\">\r\n <h3>Adjust Photo</h3>\r\n <button type=\"button\" class=\"close-icon\" (click)=\"closeEditor()\">\u00D7</button>\r\n </div>\r\n <div class=\"fv-modal-body\">\r\n <div class=\"fv-crop-container\" (mousedown)=\"startDrag($event)\">\r\n <img #editorImage [src]=\"editImageSrc\" class=\"fv-crop-image\"\r\n [style.transform]=\"'translate(' + panX + 'px, ' + panY + 'px) scale(' + scale + ')'\"\r\n draggable=\"false\" />\r\n <div class=\"fv-crop-mask\"></div>\r\n </div>\r\n\r\n <div class=\"fv-zoom-controls\">\r\n <span class=\"zoom-label\">Zoom</span>\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomOut()\">-</button>\r\n <input type=\"range\" [min]=\"minScale\" [max]=\"maxScale\" [step]=\"step\" [value]=\"scale\"\r\n (input)=\"onSliderChange($event)\" class=\"zoom-slider\">\r\n <button type=\"button\" class=\"zoom-btn\" (click)=\"zoomIn()\">+</button>\r\n <span class=\"zoom-value\">{{ (scale * 100) | number:'1.0-0' }}%</span>\r\n </div>\r\n\r\n <p class=\"fv-hint-text\">Drag the image to reposition. Use the zoom control to fill the circle.</p>\r\n </div>\r\n\r\n <div class=\"fv-modal-footer\">\r\n <button type=\"button\" class=\"btn-reset\" (click)=\"resetEditor()\">Reset</button>\r\n <div class=\"footer-actions\">\r\n <button type=\"button\" class=\"btn-cancel\" (click)=\"closeEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn-apply\" (click)=\"applyCrop()\">Apply</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";*{font-family:Poppins,sans-serif;box-sizing:border-box}.fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:20px;width:fit-content;max-width:100%}.fv-image-selector-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700;margin-left:4px}.fv-avatar-wrapper{display:flex;flex-direction:column;align-items:flex-start;gap:10px}.fv-avatar-wrapper.fv-error .fv-avatar-circle{border-color:#dc3545!important}.fv-avatar-wrapper.fv-disabled .fv-avatar-circle{opacity:.6;cursor:not-allowed}.fv-avatar-circle{width:100px;height:100px;border-radius:50%;overflow:hidden;border:2px solid #dcdcdc;cursor:pointer;position:relative;background:#f4f5f7;transition:border-color .2s ease;flex-shrink:0}.fv-avatar-circle:hover:not(.fv-disabled){border-color:#8cbba8}.fv-avatar-img{width:100%;height:100%;object-fit:cover;display:block}.fv-avatar-overlay{position:absolute;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease}.fv-avatar-circle:hover .fv-avatar-overlay{opacity:1}.fv-overlay-icon{width:24px;height:24px}.fv-avatar-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#aaa}.fv-camera-icon{width:36px;height:36px}.fv-avatar-hint{font-size:10px;font-weight:500;color:#aaa;text-align:center;line-height:1.2}.fv-avatar-actions{display:flex;gap:8px}.fv-avatar-btn{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid #dcdcdc;transition:all .2s ease;font-family:Poppins,sans-serif}.fv-btn-edit{background:#fff;color:#3498db;border-color:#3498db}.fv-btn-edit:hover{background:#e8f4fc}.fv-btn-remove{background:#fff;color:#e74c3c;border-color:#e74c3c}.fv-btn-remove:hover{background:#fdecea}.fv-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0009;display:flex;align-items:center;justify-content:center;z-index:9999}.fv-modal-content{background:#fff;width:650px;max-width:90vw;border-radius:8px;overflow:hidden;box-shadow:0 10px 30px #0003;display:flex;flex-direction:column}.fv-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;border-bottom:1px solid #f0f0f0}.fv-modal-header h3{margin:0;font-size:18px;font-weight:600;color:#1f2b41}.close-icon{background:none;border:none;font-size:24px;line-height:1;cursor:pointer;color:#888}.fv-modal-body{padding:24px;display:flex;flex-direction:column;align-items:center;background:#fff}.fv-crop-container{width:300px;height:300px;background:#333;position:relative;overflow:hidden;margin:0 auto 24px;border-radius:50%;cursor:move;cursor:grab;display:flex;align-items:center;justify-content:center;border:2px solid #555}.fv-crop-container:active{cursor:grabbing}.fv-crop-image{max-width:none;max-height:none;-webkit-user-select:none;user-select:none;pointer-events:none;transform-origin:center center}.fv-crop-mask{display:none}.fv-zoom-controls{display:flex;align-items:center;gap:16px;width:100%;max-width:500px;margin-bottom:12px}.zoom-label{font-size:14px;font-weight:500;color:#333;min-width:40px}.zoom-btn{width:32px;height:32px;border-radius:50%;background:#e6f0ff;color:#006aff;border:none;font-weight:700;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding-bottom:2px}.zoom-btn:hover{background:#d0e1ff}.zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:4px;background:#ddd;border-radius:2px;outline:none}.zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:#006aff;cursor:pointer;box-shadow:0 0 0 4px #006aff33}.zoom-value{font-size:14px;color:#666;min-width:40px;text-align:right}.fv-hint-text{font-size:13px;color:#7f8c8d;margin:0;text-align:center}.fv-modal-footer{padding:16px 24px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;background:#fff}.footer-actions{display:flex;gap:12px}.btn-reset,.btn-cancel,.btn-apply{padding:8px 20px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-reset{background:#f5f5f5;border:1px solid #ddd;color:#333}.btn-reset:hover{background:#eee}.btn-cancel{background:#fff;border:1px solid #ddd;color:#333}.btn-cancel:hover{border-color:#bbb}.btn-apply{background:#006aff;border:1px solid #006aff;color:#fff}.btn-apply:hover{background:#0056cc;border-color:#0056cc}.fv-image-selector-error-message{margin-top:6px;font-size:12px;color:#e74c3c;display:flex;align-items:center;gap:4px}\n"] }]
|
|
1451
1472
|
}], ctorParameters: () => [{ type: i1$1.DomSanitizer }], propDecorators: { label: [{
|
|
1452
1473
|
type: Input
|
|
1453
1474
|
}], placeholder: [{
|
|
@@ -1649,7 +1670,7 @@ class FvNameCodeComponent {
|
|
|
1649
1670
|
value = null;
|
|
1650
1671
|
ngOnChanges(changes) {
|
|
1651
1672
|
if (changes['options']) {
|
|
1652
|
-
this.
|
|
1673
|
+
this.filterOptions(this.searchControl?.value || '');
|
|
1653
1674
|
if (this.value !== null && this.value !== undefined) {
|
|
1654
1675
|
this.writeValue(this.value);
|
|
1655
1676
|
}
|
|
@@ -1659,6 +1680,9 @@ class FvNameCodeComponent {
|
|
|
1659
1680
|
this.filteredOptions = this.options;
|
|
1660
1681
|
this.searchControl.valueChanges.subscribe(term => {
|
|
1661
1682
|
this.filterOptions(term || '');
|
|
1683
|
+
if (!this.disabled && !this.isOpen) {
|
|
1684
|
+
this.isOpen = true;
|
|
1685
|
+
}
|
|
1662
1686
|
});
|
|
1663
1687
|
if (this.control) {
|
|
1664
1688
|
if (this.control.value !== null && this.control.value !== undefined) {
|
|
@@ -1678,6 +1702,11 @@ class FvNameCodeComponent {
|
|
|
1678
1702
|
this.filteredOptions = this.options;
|
|
1679
1703
|
return;
|
|
1680
1704
|
}
|
|
1705
|
+
const selectedOption = this.options.find(o => o.value === this.value);
|
|
1706
|
+
if (selectedOption && term === `${selectedOption.code} - ${selectedOption.name}`) {
|
|
1707
|
+
this.filteredOptions = this.options;
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1681
1710
|
const lowerTerm = term.toLowerCase();
|
|
1682
1711
|
this.filteredOptions = this.options.filter(opt => opt.code.toLowerCase().includes(lowerTerm) ||
|
|
1683
1712
|
opt.name.toLowerCase().includes(lowerTerm));
|
|
@@ -1870,6 +1899,7 @@ class FvNameCodeComponent {
|
|
|
1870
1899
|
[placeholder]="(searchControl.value || isOpen) ? '' : (placeholder || 'Search by Code or Name')"
|
|
1871
1900
|
[formControl]="searchControl"
|
|
1872
1901
|
(focus)="openDropdown()"
|
|
1902
|
+
(click)="openDropdown()"
|
|
1873
1903
|
(blur)="onBlur()"
|
|
1874
1904
|
[class.error]="isInvalid()"
|
|
1875
1905
|
/>
|
|
@@ -1880,7 +1910,7 @@ class FvNameCodeComponent {
|
|
|
1880
1910
|
class="dropdown-item"
|
|
1881
1911
|
*ngFor="let option of filteredOptions; let i = index"
|
|
1882
1912
|
[class.highlighted]="i === highlightedIndex"
|
|
1883
|
-
(
|
|
1913
|
+
(mousedown)="selectOption(option); $event.preventDefault()"
|
|
1884
1914
|
>
|
|
1885
1915
|
<span class="code">{{ option.code }}</span>
|
|
1886
1916
|
<span class="name">{{ option.name }}</span>
|
|
@@ -1912,6 +1942,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
1912
1942
|
[placeholder]="(searchControl.value || isOpen) ? '' : (placeholder || 'Search by Code or Name')"
|
|
1913
1943
|
[formControl]="searchControl"
|
|
1914
1944
|
(focus)="openDropdown()"
|
|
1945
|
+
(click)="openDropdown()"
|
|
1915
1946
|
(blur)="onBlur()"
|
|
1916
1947
|
[class.error]="isInvalid()"
|
|
1917
1948
|
/>
|
|
@@ -1922,7 +1953,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
1922
1953
|
class="dropdown-item"
|
|
1923
1954
|
*ngFor="let option of filteredOptions; let i = index"
|
|
1924
1955
|
[class.highlighted]="i === highlightedIndex"
|
|
1925
|
-
(
|
|
1956
|
+
(mousedown)="selectOption(option); $event.preventDefault()"
|
|
1926
1957
|
>
|
|
1927
1958
|
<span class="code">{{ option.code }}</span>
|
|
1928
1959
|
<span class="name">{{ option.name }}</span>
|