@rangertechnologies/ngnxt
Version:
This library was used for creating dymanic UI based on the input JSON/data
627 lines • 198 kB
JavaScript
import { Component, EventEmitter, Input, Output, ViewChildren } from '@angular/core';
import { VERSION } from '../../../../environments/version';
import { PdfPropertiesComponent } from '../pdf-properties/pdf-properties.component';
import { CommonModule } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FormsModule } from '@angular/forms';
import { NxtDatatable } from '../../../components/datatable/datatable.component';
import { BookletComponent } from '../../booklet/booklet.component';
import { AppTemplatesComponent } from '../../builder/templates/templates.component';
import { NxtInput } from '../../../components/nxt-input/nxt-input.component';
import { ImageCropperComponent } from '../../../components/image-cropper/component/image-cropper.component';
import * as i0 from "@angular/core";
import * as i1 from "../../../services/pdf-designer.service";
import * as i2 from "@angular/common";
import * as i3 from "@angular/material/tooltip";
import * as i4 from "@angular/forms";
export class PdfDesignerComponent {
pdfDesignerService;
//@Output() elementButtonClicked = new EventEmitter<string>();
textareas;
pdfJSON;
bookletId;
isPreview = false;
templateMode = new EventEmitter();
pdfPreviewEmit = new EventEmitter();
field;
pdfElements = [];
elements = [];
bookId;
sections = {
basic: true,
advanced: true
};
elementsList = [];
elementDisabledArray;
version = VERSION.version;
pdf;
selectedElement = null;
isSelectTablePopup = false;
currentType;
addTable = true;
dots = Array(6);
draggedIndex = null;
templateSelected = false;
isImageEdit = false;
selectedImageElement;
transform = {
translateUnit: 'px',
scale: 1,
rotate: 0,
flipH: false,
flipV: false,
translateH: 0,
translateV: 0
};
loading = false;
canvasRotation = 0;
cropper;
cropperMaxHeight = 0;
cropperMaxWidth = 0;
cropperMinHeight = 0;
cropperMinWidth = 0;
cropperStaticWidth = 0;
cropperStaticHeight = 0;
aspectRatio = 4 / 3;
roundCropper = false;
isImageHover = false;
alignImage = 'center';
showTextSettings = false;
selectedColumn = null;
//subscription: any;
constructor(pdfDesignerService) {
this.pdfDesignerService = pdfDesignerService;
}
//AP-14JUN25 - Called after view initialization to auto-resize all textareas
ngAfterViewInit() {
this.resizeAllTextareas();
}
//AP-14JUN25 - Calculates contrast text color (black or white) based on background color
getContrastColor(bgColor = '#000000') {
// Convert hex to RGB
const color = bgColor.replace('#', '');
const r = parseInt(color.substring(0, 2), 16);
const g = parseInt(color.substring(2, 4), 16);
const b = parseInt(color.substring(4, 6), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128 ? '#000000' : '#ffffff';
}
//AP-14JUN25 - Automatically resizes all textareas based on their content
resizeAllTextareas() {
this.textareas.forEach((textareaRef) => {
const textarea = textareaRef.nativeElement;
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
});
}
ngOnInit() {
this.elementsList = [
{ "type": "text", "img": "TextArea", "label": "Text" },
{ "type": "input", "img": "Text", "label": "Input" },
{ "type": "date", "img": "Date", "label": "Date" },
{ "type": "image", "img": "Image", "label": "Image" },
{ "type": "Line", "img": "line", "label": "Line" },
{ "type": "Space", "img": "space", "label": "Space" },
{ "type": "Table", "img": "Table", "label": "Table" },
{ "type": "Pdf", "img": "Search", "label": "Book" },
];
this.elementDisabledArray = {
Table: [
'Pdf', 'Calendar', "Boolean", 'List', 'Table', 'Checkbox', 'Radio',
'Dropdown', 'RichTextArea', 'Number', 'Label', 'image',
'Email', 'Date', 'Time', 'DateTime', 'Line', 'Space'
]
};
if (!this.isPreview) {
this.pdfDesignerService.pdfElements$.subscribe(elements => {
setTimeout(() => {
this.pdfElements = elements.map(field => ({ ...field }));
}, 0);
});
}
if (this.pdfJSON && (this.pdfJSON !== '')) {
this.initializeForm();
}
else {
this.pdfDesignerService.newBook();
}
// AP-12MAR25 - Added to handle bookletId
if (this.bookletId) {
localStorage.setItem('unique_id', this.bookletId);
}
this.pdf = this.pdfDesignerService.getBook();
if (!this.isPreview) {
this.pdfDesignerService.selectedElement$.subscribe(index => {
const elements = this.pdfDesignerService.getElements();
if (index >= 0) {
this.selectedElement = elements[index];
}
// this.pdfElements = [...elements]
});
}
this.pdfDesignerService.addElementWithId();
}
isSectionOpen(section) {
return this.sections[section];
}
ngOnChanges(changes) {
if (changes['pdfJSON'] && changes['pdfJSON'].currentValue) {
if (this.pdfJSON && (this.pdfJSON !== '')) {
this.initializeForm();
}
else {
this.pdfDesignerService.newBook();
}
}
}
onTemplateSelected(event) {
this.pdfDesignerService.clearElements();
event.elements.forEach(el => this.pdfDesignerService.addElement(el));
// Emit template mode to parent
this.templateMode.emit(true);
}
initializeForm() {
if (!this.pdfJSON || !this.pdfJSON) {
console.warn("Invalid pdfJSON structure");
return;
}
this.pdfElements = [];
this.pdfDesignerService.clearElements();
this.pdfDesignerService.intializeBook(this.pdfJSON);
this.pdfElements = this.pdfDesignerService.getElements();
}
selectElement(index) {
this.selectedFieldIndex = index;
this.pdfDesignerService.setSelectedElement(index);
this.pdf = this.pdfDesignerService.getBook();
}
selectedFieldIndex = null;
selectHeading(event) {
this.pdfDesignerService.selectHeading(event);
this.pdf = this.pdfDesignerService.getBook();
}
// AP-17APR25 generateUiId
generateUiId() {
return this.pdfDesignerService.addElementWithId();
}
addElement(type) {
const unique_id = this.generateUiId();
this.currentType = type;
if (this.selectedElement?.type === 'Table' && this.addTable) {
this.isSelectTablePopup = true;
}
else {
const newElement = {
id: unique_id,
type,
margin: [0, 0, 0, 0],
fontSize: 14,
fontWeight: '400',
fontStyle: [],
alignItems: '',
nxtType: type,
referenceField: null,
question: null,
questionNumber: this.pdfDesignerService.getElements().length + 1,
fieldsMeta: [],
pdfReference: null,
pdfReferenceQuestions: null,
style: {
bold: false,
italic: false,
alignment: 'left',
fontSize: 14,
margin: [0, 0, 0, 0],
color: '#000000',
},
imageData: null,
imageSize: type === 'image' ? { width: 150, height: 150 } : null,
tableConfig: type === 'Table' ? {
isNosIndicator: false,
addInlineRecord: true,
isPagination: false,
actionButton: false,
isDeleteRow: true,
isEditRow: false,
searchBar: false,
isButtons: false,
} : null,
width: 100,
styleClass: unique_id,
};
this.pdfDesignerService.addElement(newElement);
this.pdfElements = this.pdfDesignerService.getElements();
this.addTable = true;
}
}
//AP-14JUN25 - Handles textarea input: auto-resizes and updates the field content
onTextAreaInput(event, field) {
const textarea = event.target;
// 1. Auto-resize logic
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
// 2. Trigger value update and PDF sync
this.onQuestionChange(textarea.value, field);
}
onQuestionChange(value, field) {
console.log(field);
let elementId;
const updatePdfQuestion = (element, value) => {
if (element.type === 'Pdf') {
const subElements = element.pdfReferenceQuestions?.[element.pdfReference] || [];
for (const subElement of subElements) {
const found = updatePdfQuestion(subElement, value); // recursive call
if (found)
return true; // stop once match is found
}
}
else {
if (element.id === field.id) {
element.value = value;
return true;
}
}
return false;
};
this.pdfElements.forEach((element, index) => {
const found = updatePdfQuestion(element, value);
if (found && elementId === undefined) {
elementId = index; // only set once on first match
}
});
if (elementId !== undefined) {
this.pdfDesignerService.elementUpdate(this.pdfElements[elementId]);
if (this.isPreview) {
this.pdfPreview(this.pdfDesignerService.downloadElement());
}
}
}
pdfPreview(pdf) {
this.pdfPreviewEmit.emit(pdf);
}
onFieldDateChange(event) {
console.log(event.value);
//this.pdfDesignerService.setSelectedTableElement(this.selectedFieldIndex, event);
}
removeElement(field, index) {
this.pdfDesignerService.removeElementComponent(field.id);
this.pdfElements = this.pdfDesignerService.getElements();
}
onDragStart(event, index) {
this.draggedIndex = index;
event.dataTransfer?.setData('text/plain', index.toString());
}
onDragOver(event, index) {
event.preventDefault();
}
onDrop(event, dropIndex) {
event.preventDefault();
if (this.draggedIndex === null || this.draggedIndex === dropIndex)
return;
const draggedItem = this.pdfElements[this.draggedIndex];
// Remove dragged item from old position and insert it in new position
this.pdfElements.splice(this.draggedIndex, 1);
this.pdfElements.splice(dropIndex, 0, draggedItem);
//AP-28MAR25 Update questionNumber dynamically based on new order
this.pdfElements.forEach((element, index) => {
element.questionNumber = index + 1;
});
// Reset dragged index
this.draggedIndex = null;
// Notify service about the update
this.pdfDesignerService.updateElementsOrder(this.pdfElements);
}
toggleBoolean(field) {
field.boolean = !field.boolean;
}
getFontStyles(field) {
const margin = field.margin || [0, 0, 0, 0]; // [left, top, right, bottom]
const fontStyles = field.fontStyle || [];
return {
'font-family': field.font || 'Helvetica Neue',
//'font-weight': field.fontWeight || '400',
'font-size': field.fontSize ? `${field.fontSize}px` : '14px',
'width': field.width ? `${field.width}%` : '100%',
//'text-align': field.textAlign || 'left',
'align-items': field.alignItems || '',
'border-radius': '5px',
'border-width': field.lineWidth ? `${field.lineWidth}px` : '1px',
'color': field.fontColor || '#000000',
'font-style': fontStyles.includes('italic') ? 'italic' : 'normal',
'font-weight': fontStyles.includes('bold') ? 'bold' : (field.fontWeight || '400'),
'margin': `${margin[0]}px ${margin[1]}px ${margin[2]}px ${margin[3]}px`,
'padding-top': field.paddingTop ? `${field.paddingTop}px` : '0px',
'padding-bottom': field.paddingBottom ? `${field.paddingBottom}px` : '10px',
};
}
getLineStyles(field) {
const styles = {
'font-family': field.font || 'Helvetica Neue',
'font-weight': field.fontWeight || '400',
'font-size': field.fontSize || '14px',
'width': field.width ? `${field.width}%` : '100%',
'text-align': field.textAlign || 'left',
'border-radius': '5px',
'border-width': field.lineWidth ? `${field.lineWidth}px` : '1px',
'border-style': field.lineStyle?.toLowerCase() || 'solid',
'color': field.fontColor || '#000000',
//'border-color': field.color || '#EFF8FF',
'margin-top': field.paddingTop ? `${field.paddingTop}px` : '0px',
'margin-bottom': field.paddingBottom ? `${field.paddingBottom}px` : '10px'
};
return styles;
}
// SKS13MAR25 table popup conformation based element add
onClose() {
this.isSelectTablePopup = false;
this.addTable = false;
this.addElement(this.currentType);
}
// SKS13MAR25 column element add inside a table
addOnTable() {
this.addTable = true;
this.isSelectTablePopup = false;
const elements = [...this.pdfDesignerService.getElements()];
// Find the element and update its columns
const index = elements.findIndex(el => el.id === this.selectedElement.id);
const rowNum = this.pdfElements[index].columns ? this.pdfElements[index].columns?.length : 0;
const unique_id = this.generateUiId();
const tableElement = {
label: `HEADER LABEL ${rowNum}`,
fieldName: `FIELD NAME${rowNum}`,
type: this.currentType,
id: unique_id
};
this.pdfDesignerService.addTableElement(tableElement, this.selectedFieldIndex);
this.pdfElements = this.pdfDesignerService.getElements();
// this.addTableData(unique_id, fieldName)
}
addTableData(fieldId, fieldName) {
// this.pdfDesignerService.addTablefieldData(tableElement);
}
columnSelected(event) {
// SKS19MAR25 table column update
if (!this.isPreview) {
this.pdfDesignerService.setSelectedTableElement(this.selectedFieldIndex, event);
}
}
removeColumn(event) {
// console.log(event)
this.pdfDesignerService.removeSelectedTableElement(this.selectedFieldIndex, event);
}
// SKS25MAR25 image add
async fileChangeEvent(field, event) {
const file = event.target.files[0];
if (!file)
return;
try {
const imageData = await this.readFileAsDataURL(file);
let elementId;
const updatePdfImage = (element) => {
if (element.type === 'Pdf') {
const subElements = element.pdfReferenceQuestions?.[element.pdfReference] || [];
for (const subElement of subElements) {
const found = updatePdfImage(subElement); // recursive call
if (found)
return true;
}
}
else {
if (element.id === field.id) {
element.imageData = imageData;
element.orgImageData = imageData;
if (!element.imageSize) {
element.imageSize = { width: 150, height: 150 };
}
return true;
}
}
return false;
};
this.pdfElements.forEach((element, index) => {
const found = updatePdfImage(element);
if (found && elementId === undefined) {
elementId = index; // remember top-level match
}
});
if (elementId !== undefined) {
this.pdfDesignerService.elementUpdate(this.pdfElements[elementId]);
if (this.isPreview) {
this.pdfPreview(this.pdfDesignerService.downloadElement());
}
}
}
catch (error) {
console.error("Error reading file:", error);
}
}
readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
}
// SKS2APR25 disabled element
isElementDisabled(elementType) {
if (this.selectedElement) {
const elements = this.elementDisabledArray[this.selectedElement.type];
if (elements) {
return elements.includes(elementType);
}
}
return false;
}
async onImageEdit(ques) {
this.isImageEdit = true;
this.selectedImageElement = ques;
console.log("onImageEdit");
}
async onImageDelete(ques) {
try {
ques.imageData = '';
ques.orgImageData = '';
// Initialize logo size if not already set
if (!ques.imageSize) {
ques.imageSize = { width: 150, height: 150 };
}
// await this.childEventCapture(ques.imageData, ques);
}
catch (error) {
console.error("Error reading file:", error);
}
}
// SKS25MAR25 image edit functions
flipHorizontal() {
this.transform = {
...this.transform,
flipH: !this.transform.flipH
};
}
flipVertical() {
this.transform = {
...this.transform,
flipV: !this.transform.flipV
};
}
resetImage() {
this.canvasRotation = 0;
this.cropper = undefined;
this.transform = {
translateUnit: 'px',
scale: 1,
rotate: 0,
flipH: false,
flipV: false,
translateH: 0,
translateV: 0
};
}
zoomOut() {
this.transform = {
...this.transform,
scale: this.transform.scale - .1
};
}
zoomIn() {
this.transform = {
...this.transform,
scale: this.transform.scale + .1
};
}
rotateLeft() {
this.loading = true;
setTimeout(() => {
this.canvasRotation--;
this.flipAfterRotate();
});
}
rotateRight() {
this.loading = true;
setTimeout(() => {
this.canvasRotation++;
this.flipAfterRotate();
});
}
moveLeft() {
this.transform = {
...this.transform,
translateH: this.transform.translateH - 1
};
}
moveRight() {
this.transform = {
...this.transform,
translateH: this.transform.translateH + 1
};
}
moveDown() {
this.transform = {
...this.transform,
translateV: this.transform.translateV + 1
};
}
moveUp() {
this.transform = {
...this.transform,
translateV: this.transform.translateV - 1
};
}
flipAfterRotate() {
const flippedH = this.transform.flipH;
const flippedV = this.transform.flipV;
this.transform = {
...this.transform,
flipH: flippedV,
flipV: flippedH,
translateH: 0,
translateV: 0
};
}
async imageCropped(event) {
try {
const base64 = await this.convertBlobToBase64(event.objectUrl);
this.selectedImageElement.imageData = base64;
// await this.childEventCapture(base64, this.selectedImageElement);
}
catch (error) {
console.error("Error in imageCropped:", error);
}
}
cropperReady(sourceImageDimensions) {
this.loading = false;
}
// SKS25MAR25 blob to base 64 converter
convertBlobToBase64(objectUrl) {
return fetch(objectUrl)
.then(response => response.blob())
.then(blob => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
});
}
closeModal() {
this.isImageEdit = false;
}
openTextSettings(field, event) {
this.selectedColumn = field;
this.showTextSettings = true;
}
closeTextSettings() {
this.showTextSettings = false;
}
//AP-14JUN25 - Toggles bold or italic style for the selected text block
toggleStyle(style) {
if (!this.selectedColumn)
return;
if (style === 'bold') {
this.selectedColumn.style.bold = !this.selectedColumn.style.bold;
}
else if (style === 'italic') {
this.selectedColumn.style.italics = !this.selectedColumn.style.italics;
}
this.updateContent();
}
//AP-14JUN25 - Sets text alignment (left, center, right) for the selected text block
setAlignment(alignment) {
if (!this.selectedColumn)
return;
this.selectedColumn.style.alignment = alignment;
this.updateContent();
}
//AP-14JUN25 - Updates the content in the PDF designer with the selected style changes
updateContent() {
this.pdfDesignerService.elementUpdate(this.selectedColumn);
}
getMarginStyle(margin) {
if (!margin || margin.length !== 4)
return '0';
return `${margin[1]}px ${margin[2]}px ${margin[3]}px ${margin[0]}px`;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PdfDesignerComponent, deps: [{ token: i1.PdfDesignerService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: PdfDesignerComponent, isStandalone: true, selector: "app-pdf-designer", inputs: { pdfJSON: "pdfJSON", bookletId: "bookletId", isPreview: "isPreview" }, outputs: { templateMode: "templateMode", pdfPreviewEmit: "pdfPreviewEmit" }, viewQueries: [{ propertyName: "textareas", predicate: ["autoTextarea"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- AP 22JAN25 - form preview and All form elements -->\n<!-- AP 25FEB25 - All elements update -->\n<div class=\"form-container\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"center-frame\" [ngClass]=\"{'isPreview': isPreview}\">\n <!-- Form Builder Section All Elements -->\n <div *ngIf=\"!isPreview\" class=\"form-builder\">\n\n <ng-container *ngFor=\"let element of elementsList\">\n <div class=\"element\" [class.disabled]=\"isElementDisabled(element.type)\"\n (click)=\"!isElementDisabled(element.type) && addElement(element.type)\"\n [draggable]=\"!isElementDisabled(element.type)\"\n [matTooltip]=\"isElementDisabled(element.type) ? 'This element is not supported when a ' + (selectedElement?.type || 'certain element') + ' is present' : null\">\n <img src=\"../assets/icons/{{ element.img }}.svg\" class=\"element-icon\">\n <div class=\"hover-label\">{{ element.label }}</div>\n <div class=\"drag-dots\">\n <div class=\"dot\" *ngFor=\"let dot of dots\"></div>\n </div>\n </div>\n </ng-container>\n\n <div class=\"template-section\">\n <app-templates (templateSelected)=\"onTemplateSelected($event)\"></app-templates>\n </div>\n\n <!-- SKS10MAR25 footer version show -->\n <div class=\"sticky-footer-version\">\n {{version}}\n </div>\n </div>\n <!-- AP-27MAR25 Remove CDK drag and drop replace draggable function -->\n <div class=\"form-preview\">\n <!-- AP-10MAR25 Heading -->\n <div *ngIf=\"!isPreview\" class=\"field-container\"\n style=\"width: 100%;background-color: #EFF8FF; border: 1px solid #E6F3FF;display: flex;justify-content: center;margin-bottom:10px\"\n (click)=\"selectHeading('Header')\">\n <div class=\"label-container\" style=\"padding: 10px;\">\n <div *ngIf=\"pdf\">\n <div *ngIf=\"pdf.title == ''\" style=\"color:#3f4a525c\">Heading</div>\n <div *ngIf=\"pdf.title !== ''\">{{pdf.title}}</div>\n </div>\n </div>\n </div>\n\n <ng-container *ngFor=\"let field of pdfElements; let i = index\" getProperties().elementProps>\n\n <!-- AP-19MAR25 Line Element -->\n <div *ngIf=\"field?.type === 'Line'\" class=\"line-field\" [ngClass]=\"{'isPreview': isPreview}\"\n (click)=\"selectElement(i)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div *ngIf=\"!isPreview\" class=\"line-element\">\n <div></div>\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n <hr class=\"custom-line\" style=\"display: inline-flex\" [ngStyle]=\"getLineStyles(field)\" />\n </div>\n\n <!--SKS25MAR25 Image Upload Element -->\n <div *ngIf=\"field?.type === 'image'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [class.highlight]=\"selectedFieldIndex === i && !isPreview\" draggable=\"true && !isPreview\"\n (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\" (drop)=\"onDrop($event, i)\"\n [ngStyle]=\"getFontStyles(field)\" (mouseenter)=\"isImageHover = true;\"\n (mouseleave)=\"isImageHover = false;\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <div>\n <div *ngIf=\"isImageHover\" style=\"display: flex; justify-content: end;\">\n <svg *ngIf=\"isPreview\" (click)=\"onImageEdit(field)\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"\n fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M11.1067 6.07174L9.92833 4.8934L2.16667 12.6551V13.8334H3.345L11.1067 6.07174ZM12.285 4.8934L13.4633 3.71507L12.285 2.53674L11.1067 3.71507L12.285 4.8934ZM4.035 15.5001H0.5V11.9642L11.6958 0.768403C11.8521 0.612177 12.064 0.524414 12.285 0.524414C12.506 0.524414 12.7179 0.612177 12.8742 0.768403L15.2317 3.1259C15.3879 3.28218 15.4757 3.4941 15.4757 3.71507C15.4757 3.93604 15.3879 4.14796 15.2317 4.30424L4.03583 15.5001H4.035Z\"\n fill=\"#6C757D\" />\n </svg>\n <svg *ngIf=\"isPreview\" (click)=\"onImageDelete(field)\" width=\"16\" height=\"16\"\n viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14 3.98726C11.78 3.76726 9.54667 3.65393 7.32 3.65393C6 3.65393 4.68 3.7206 3.36 3.85393L2 3.98726\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M5.6665 3.31362L5.81317 2.44028C5.91984 1.80695 5.99984 1.33362 7.1265 1.33362H8.87317C9.99984 1.33362 10.0865 1.83362 10.1865 2.44695L10.3332 3.31362\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M12.5667 6.09375L12.1334 12.8071C12.06 13.8537 12 14.6671 10.14 14.6671H5.86002C4.00002 14.6671 3.94002 13.8537 3.86668 12.8071L3.43335 6.09375\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M6.88647 11.0004H9.10647\" stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n <path d=\"M6.3335 8.33325H9.66683\" stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </div>\n <div class=\"logo-container\">\n <div class=\"logo-preview\" *ngIf=\"field.imageData\">\n <img [src]=\"field.imageData\" />\n </div>\n\n <div *ngIf=\"!field.imageData\" class=\"logo-upload-placeholder\">\n <label for=\"logo-upload-{{i}}\" class=\"logo-upload-label\">\n <img src=\"../assets/icons/Image.svg\" alt=\"Upload\" />\n <span>Upload Image</span>\n </label>\n <input type=\"file\" id=\"logo-upload-{{i}}\" accept=\"image/*\" (change)=\"fileChangeEvent(field, $event)\"\n style=\"display: none;\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Space Element -->\n <div *ngIf=\"field?.type === 'Space'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" style=\"height:93px\" [ngClass]=\"{'isPreview': isPreview}\">\n <div *ngIf=\"!isPreview\" class=\"field-content\">\n <div class=\"label-container\">\n <div class=\"top-right\" style=\"margin: -11px -11px 0 0;\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- TextArea -->\n <div *ngIf=\"field?.type === 'text'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <textarea class=\"custom-textarea\"\n [placeholder]=\"'Enter detailed text here...'\"\n [value]=\"field.value? field.value : ''\"\n (input)=\"onTextAreaInput($event, field)\"\n #autoTextarea\n [style.height.px]=\"isPreview ? 40 : null\"\n (click)=\"openTextSettings(field, $event)\"\n (ngModelChange)=\"updateContent()\" \n [ngStyle]=\"{\n 'font-size': field.style.fontSize\n ? field.style.fontSize + 'px'\n : '14px',\n 'font-weight': field.style.bold ? 'bold' : 'normal',\n 'font-style': field.style.italics ? 'italic' : 'normal',\n 'text-decoration': field.style.decoration ? 'decoration' : 'none',\n 'text-align': field.style.alignment || 'left',\n margin: field.style.margin ? getMarginStyle(field.style.margin) : '0',\n color: field.style.color || '#000',\n }\"></textarea>\n \n </div>\n </div>\n\n <!--AP-05JUN25 Text Settings Toolbar: Allows editing font size, bold/italic styles, alignment, and closing the toolbar for the selected column -->\n <div *ngIf=\"showTextSettings && isPreview && selectedColumn === field\" class=\"text-settings-toolbar\"\n [ngStyle]=\"{\n position: 'absolute',\n background: '#fff',\n border: '1px solid #ccc',\n padding: '6px 10px',\n 'border-radius': '4px',\n 'box-shadow': '0 2px 6px rgba(0, 0, 0, 0.1)',\n 'z-index': '65535',\n display: 'flex',\n 'align-items': 'center',\n gap: '8px'\n }\">\n \n <!-- Font Size -->\n <select [(ngModel)]=\"selectedColumn.style.fontSize\" class=\"toolbar-select\">\n <option [value]=\"12\">12pt</option>\n <option [value]=\"14\">14pt</option>\n <option [value]=\"16\">16pt</option>\n <option [value]=\"18\">18pt</option>\n <option [value]=\"24\">24pt</option>\n </select>\n\n <!-- Text Color -->\n <label title=\"Text Color\" class=\"color-label\">\n <span class=\"color-box\" [style.color]=\"selectedColumn.style.color || '#000'\" (click)=\"textColorInput.click()\">A</span>\n <input #textColorInput type=\"color\" [(ngModel)]=\"selectedColumn.style.color\" (change)=\"updateContent()\" class=\"color-picker-hidden\"/>\n </label>\n\n <!-- Bold -->\n <button (click)=\"toggleStyle('bold')\" [class.active]=\"selectedColumn?.style?.bold\" class=\"toolbar-btn\">B</button>\n \n <!-- Italic -->\n <button (click)=\"toggleStyle('italic')\" [class.active]=\"selectedColumn?.style?.italics\" class=\"toolbar-btn\"><i>I</i></button>\n\n <!-- Underline -->\n <!-- <button (click)=\"toggleStyle('underline')\" [class.active]=\"selectedColumn?.style?.underline\" class=\"toolbar-btn\"><u>U</u></button> -->\n\n \n <!-- Align Left -->\n <button (click)=\"setAlignment('left')\" [class.active]=\"selectedColumn?.style?.alignment === 'left'\" class=\"toolbar-btn\">\n <span class=\"material-icons\">format_align_left</span>\n </button>\n \n <!-- Align Center -->\n <button (click)=\"setAlignment('center')\" [class.active]=\"selectedColumn?.style?.alignment === 'center'\" class=\"toolbar-btn\">\n <span class=\"material-icons\">format_align_center</span>\n </button>\n \n <!-- Align Right -->\n <button (click)=\"setAlignment('right')\" [class.active]=\"selectedColumn?.style?.alignment === 'right'\" class=\"toolbar-btn\">\n <span class=\"material-icons\">format_align_right</span>\n </button>\n \n <!-- Close -->\n <button (click)=\"closeTextSettings()\" class=\"toolbar-btn\" style=\"margin-left: auto;\">\u2715</button>\n </div>\n \n </div>\n\n <!-- Input -->\n <div *ngIf=\"field?.type === 'input'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <!-- AP-04JUN25 - replace app-custom-input -->\n <nxt-input [type]=\"'text'\" [mode]=\"'edit'\" [value]=\"field.input\" [question]=\"\" [labelFont]=\"\"\n [label]=\"field?.questionText\" [labelColor]=\"\" [labelSize]=\"\" [inputValueSize]=\"\" [labelWeight]=\"\"\n [inputWeight]=\"\" [showLabel]=\"\" inputBorder=\"none\" svgHeight=\"20px\" svgWidth=\"20px\"\n [placeholder]=\"field.question ? field.question : 'Enter your input'\" [required]=\"\"\n inputBgColor=\"#F5F5F5\" [inputId]=\"\" [errorMessages]=\"{ required: 'This field is required' }\"\n [inputIconLeftSrc]=\"''\" (inputValue)=\"onQuestionChange($event, field)\">\n </nxt-input>\n </div>\n </div>\n </div>\n\n <!-- Date -->\n <div *ngIf=\"field?.type === 'date'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <!-- AP-04JUN25 - replace custom date picker -->\n <nxt-input [type]=\"'date'\" [mode]=\"'edit'\" [value]=\"field.value\" [question]=\"\" [labelFont]=\"\"\n [label]=\"field?.questionText\" [labelColor]=\"\" [labelSize]=\"\" [inputValueSize]=\"\" [labelWeight]=\"\"\n [inputWeight]=\"\" [showLabel]=\"\" inputBorder=\"none\" svgHeight=\"20px\" svgWidth=\"20px\"\n [placeholder]=\"field.question ? field.question : 'Enter your input'\" [required]=\"\"\n inputBgColor=\"#F5F5F5\" [inputId]=\"\" [errorMessages]=\"{ required: 'This field is required' }\"\n [inputIconLeftSrc]=\"''\" (inputValue)=\"onQuestionChange($event, field)\">\n </nxt-input>\n </div>\n </div>\n </div>\n\n <!-- Pdf -->\n <div *ngIf=\"field?.type === 'Pdf'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <!-- <lib-booklet [bookletJSON]=\"field.pdfReferenceQuestions\"></lib-booklet> -->\n <div *ngIf=\"field?.pdfReferenceQuestions\">\n <ng-container *ngFor=\"let field of field?.pdfReferenceQuestions[field?.pdfReference]; let i = index\">\n <!-- AP-19MAR25 Line Element -->\n <div *ngIf=\"field?.type === 'Line'\" class=\"line-field\">\n <hr class=\"custom-line\" style=\"display: inline-flex\" [ngStyle]=\"getLineStyles(field)\" />\n </div>\n\n <div *ngIf=\"field?.type === 'image'\" class=\"field-container\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div>\n <div class=\"logo-container\">\n <!-- Logo preview area -->\n <div class=\"logo-preview\" *ngIf=\"field.imageData\">\n <img [src]=\"field.imageData\" />\n </div>\n\n <!-- Upload button -->\n <div *ngIf=\"!field.imageData\" class=\"logo-upload-placeholder\">\n <label for=\"logo-upload-{{i}}\" class=\"logo-upload-label\">\n <img src=\"../assets/icons/Image.svg\" alt=\"Upload\" />\n <span>Upload Image</span>\n </label>\n <input type=\"file\" id=\"logo-upload-{{i}}\" accept=\"image/*\"\n (change)=\"fileChangeEvent(field, $event)\" style=\"display: none;\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div *ngIf=\"field?.type === 'Space'\" class=\"field-container\">\n <div class=\"field-wrapper\" style=\"height:93px\" [ngClass]=\"{'isPreview': isPreview}\">\n </div>\n </div>\n <div *ngIf=\"field?.type === 'text'\" class=\"field-container\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <textarea class=\"custom-textarea\" [placeholder]=\"'Enter detailed text here...'\"\n [style.height.px]=\"isPreview ? 40 : field.size || 100\"\n [value]=\"field.value ? field.value : ''\"(input)=\"onQuestionChange($event.target.value, field)\" ></textarea>\n </div>\n </div>\n </div>\n <!-- AP-14JUN25 Added nxt-input in pdf -->\n <div *ngIf=\"field?.type === 'input'\" class=\"field-container\">\n <div class=\"field-wrapper\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\"> \n <nxt-input [type]=\"'text'\" [mode]=\"'edit'\" [value]=\"field.value ? field.value : ''\"\n [placeholder]=\"'Enter your input'\" (input)=\"onQuestionChange($event.target.value, field)\">\n </nxt-input>\n </div>\n </div>\n </div>\n <!-- AP-06MAR25 -->\n <div *ngIf=\"field?.type === 'Table'\" class=\"field-container\">\n <div class=\"field-wrapper\" style=\"overflow: hidden;\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div class=\"nxt-table-container\">\n <nxt-datatable isButtons [question]=\"field\" from=\"formBuilder\"\n [mode]=\"isPreview ? 'view' : 'edit'\" [apiMeta]=\"field?.subText\"\n [tableConfig]=\"field.tableConfig\" tableId=\"\" direction=\"ltr\" tableWidth=\"auto\"\n (valueChange)=\"onQuestionChange($event, field)\"\n [data]=\"field.value?.data\"\n isEditable=true (columnSelected)=columnSelected($event) (removeColumn)=removeColumn($event)>\n </nxt-datatable>\n </div>\n </div>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Table -->\n <!-- AP-06MAR25 -->\n <div *ngIf=\"field?.type === 'Table'\" class=\"field-container\" (click)=\"selectElement(i)\"\n [ngStyle]=\"getFontStyles(field)\" [class.highlight]=\"selectedFieldIndex === i && !isPreview\"\n draggable=\"true && !isPreview\" (dragstart)=\"onDragStart($event, i)\" (dragover)=\"onDragOver($event, i)\"\n (drop)=\"onDrop($event, i)\">\n <div class=\"field-wrapper\" style=\"overflow: hidden;\" [ngClass]=\"{'isPreview': isPreview}\">\n <div class=\"field-content\">\n <div *ngIf=\"!isPreview\" class=\"label-container\">\n <div class=\"top-right\">\n <img src=\"../assets/icons/drag-dots.svg\" alt=\"Drag\" class=\"drag-dot\" />\n <img src=\"../assets/icons/Trash.svg\" (click)=\"removeElement(field, i)\" class=\"delete-icon\" />\n </div>\n </div>\n <div class=\"nxt-table-container\">\n <nxt-datatable isButtons [question]=\"field\" from=\"formBuilder\" [mode]=\"isPreview ? 'view' : 'edit'\"\n [apiMeta]=\"field?.subText\" [tableConfig]=\"field.tableConfig\" tableId=\"\" direction=\"ltr\"\n tableWidth=\"auto\" isEditable=true (columnSelected)=columnSelected($event)\n [data]=\"field.value?.data\"\n (valueChange)=\"onQuestionChange($event, field)\"\n (removeColumn)=removeColumn($event)>\n </nxt-datatable>\n </div>\n </div>\n </div>\n </div>\n\n </ng-container>\n </div>\n </div>\n <!-- SKS13MAR25 popup conformation box -->\n <div class=\"dialog-overlay\" *ngIf=\"isSelectTablePopup\">\n <div class=\"dialog-box\">\n <button class=\"close-btn-fb\" (click)=\"onClose()\">\u2715</button>\n <p>These element want to add a table</p>\n <div class=\"button-container-fb\">\n <button class=\"yes-btn-fb\" (click)=\"addOnTable()\">Yes</button>\n <button class=\"no-btn-fb\" (click)=\"onClose()\">No</button>\n </div>\n </div>\n </div>\n <app-pdf-properties *ngIf=\"!isPreview\"></app-pdf-properties>\n</div>\n<!--SKS25MAR25 Modal Overlay -->\n<div class=\"modal-overlay\" *ngIf=\"isImageEdit\">\n <div class=\"modal-content\">\n <span class=\"close-button\" (click)=\"closeModal()\">\u00D7</span>\n <!-- Image Editor -->\n <div *ngIf=\"selectedImageElement?.type === 'image'\">\n <image-cropper *ngIf=\"selectedImageElement.imageData\" [imageBase64]=\"selectedImageElement.orgImageData\"\n [disabled]=\"false\" [alignImage]=\"alignImage\" [roundCropper]=\"roundCropper\" [backgroundColor]=\"'white'\"\n imageAltText=\"Alternative image text\" [allowMoveImage]=\"false\" [hideResizeSquares]=\"false\"\n [canvasRotation]=\"canvasRotation\" [aspectRatio]=\"aspectRatio\" [containWithinAspectRatio]=\"false\"\n [maintainAspectRatio]=\"false\" [cropperStaticWidth]=\"cropperStaticWidth\"\n [cropperStaticHeight]=\"cropperStaticHeight\" [cropperMinWidth]=\"cropperMinWidth\"\n [cropperMinHeight]=\"cropperMinHeight\" [cropperMaxWidth]=\"cropperMaxWidth\"\n [cropperMaxHe