Files
documenso/packages/lib/universal/field-renderer/render-dropdown-field.ts
2025-11-08 23:40:03 +11:00

193 lines
4.6 KiB
TypeScript

import { FieldType } from '@prisma/client';
import Konva from 'konva';
import { DEFAULT_STANDARD_FONT_SIZE } from '../../constants/pdf';
import type { TDropdownFieldMeta } from '../../types/field-meta';
import {
createFieldHoverInteraction,
konvaTextFill,
konvaTextFontFamily,
upsertFieldGroup,
upsertFieldRect,
} from './field-generic-items';
import { calculateFieldPosition } from './field-renderer';
import type { FieldToRender, RenderFieldElementOptions } from './field-renderer';
type CalculateDropdownPositionOptions = {
fieldWidth: number;
fieldHeight: number;
};
/**
* Calculate the position of a field item such as Checkbox, Radio.
*/
const calculateDropdownPosition = (options: CalculateDropdownPositionOptions) => {
const { fieldWidth, fieldHeight } = options;
const fieldPadding = 8;
const arrowSize = 12;
const textHeight = fieldHeight - fieldPadding * 2;
const textWidth = fieldWidth - fieldPadding * 2;
const textX = fieldPadding;
const textY = fieldPadding;
const arrowX = fieldWidth - arrowSize - fieldPadding;
const arrowY = fieldHeight / 2 - arrowSize / 4;
return {
arrowX,
arrowY,
arrowSize,
textX,
textY,
textWidth,
textHeight,
};
};
export const renderDropdownFieldElement = (
field: FieldToRender,
options: RenderFieldElementOptions,
) => {
const { pageWidth, pageHeight, pageLayer, mode, translations, color } = options;
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
const dropdownMeta: TDropdownFieldMeta | null = (field.fieldMeta as TDropdownFieldMeta) || null;
let selectedValue = translations?.[FieldType.DROPDOWN] || 'Select Option';
const isFirstRender = !pageLayer.findOne(`#${field.renderId}`);
// Clear previous children to re-render fresh.
const fieldGroup = upsertFieldGroup(field, options);
fieldGroup.removeChildren();
fieldGroup.off('transform');
const fieldRect = upsertFieldRect(field, options);
fieldGroup.add(fieldRect);
if (isFirstRender) {
pageLayer.add(fieldGroup);
}
const fontSize = dropdownMeta?.fontSize || DEFAULT_STANDARD_FONT_SIZE;
// Don't show any labels when exporting.
if (mode === 'export') {
selectedValue = '';
}
// Render the default value if readonly.
if (
dropdownMeta?.readOnly &&
dropdownMeta.defaultValue &&
dropdownMeta.values &&
dropdownMeta.values.some((value) => value.value === dropdownMeta.defaultValue)
) {
selectedValue = dropdownMeta.defaultValue;
}
if (field.inserted) {
selectedValue = field.customText;
}
const { arrowX, arrowY, arrowSize, textX, textY, textWidth, textHeight } =
calculateDropdownPosition({
fieldWidth,
fieldHeight,
});
// Selected value text
const selectedText = new Konva.Text({
name: 'dropdown-selected-text',
x: textX,
y: textY,
width: textWidth,
height: textHeight,
text: selectedValue,
fontSize,
fontFamily: konvaTextFontFamily,
fill: konvaTextFill,
verticalAlign: 'middle',
});
const arrow = new Konva.Line({
name: 'dropdown-arrow',
x: arrowX,
y: arrowY,
points: [0, 0, arrowSize / 2, arrowSize / 2, arrowSize, 0],
stroke: '#6B7280',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round',
closed: false,
visible: mode !== 'export',
});
fieldGroup.add(selectedText);
if (!field.inserted || mode === 'export') {
fieldGroup.add(arrow);
}
fieldGroup.on('transform', () => {
const groupScaleX = fieldGroup.scaleX();
const groupScaleY = fieldGroup.scaleY();
const fieldRect = fieldGroup.findOne('.field-rect');
const text = fieldGroup.findOne('.dropdown-selected-text');
const arrow = fieldGroup.findOne('.dropdown-arrow');
if (!fieldRect || !text || !arrow) {
return;
}
const rectWidth = fieldRect.width() * groupScaleX;
const rectHeight = fieldRect.height() * groupScaleY;
const { arrowX, arrowY, textX, textY, textWidth, textHeight } = calculateDropdownPosition({
fieldWidth: rectWidth,
fieldHeight: rectHeight,
});
arrow.setAttrs({
x: arrowX,
y: arrowY,
scaleX: 1,
scaleY: 1,
});
text.setAttrs({
scaleX: 1,
scaleY: 1,
x: textX,
y: textY,
width: textWidth,
height: textHeight,
});
fieldRect.setAttrs({
width: rectWidth,
height: rectHeight,
});
fieldGroup.scale({
x: 1,
y: 1,
});
pageLayer.batchDraw();
});
if (color !== 'readOnly' && mode !== 'export') {
createFieldHoverInteraction({ fieldGroup, fieldRect, options });
}
return {
fieldGroup,
isFirstRender,
};
};