UNPKG

@pdfme/schemas

Version:

TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!

205 lines (172 loc) 6.32 kB
import type * as CSS from 'csstype'; import { propPanel as parentPropPanel } from '../text/propPanel.js'; import { Plugin, PropPanelWidgetProps, SchemaForUI } from '@pdfme/common'; import text from '../text/index.js'; import { TextSchema } from '../text/types.js'; import { ChevronDown } from 'lucide'; import { createSvgStr } from '../utils.js'; const selectIcon = createSvgStr(ChevronDown); interface Select extends TextSchema { options: string[]; } const addOptions = (props: PropPanelWidgetProps) => { const { rootElement, changeSchemas, activeSchema, i18n } = props; rootElement.style.width = '100%'; const selectSchema = activeSchema as SchemaForUI & Select; const currentOptions = selectSchema.options ? [...selectSchema.options] : []; const inputStyle = { width: '100%', padding: '6.25px 11px', border: '1px solid #ccc', borderRadius: '4px', }; const buttonStyle = { border: 'none', borderRadius: '4px', cursor: 'pointer' }; const updateSchemas = () => { changeSchemas([ { key: 'options', value: currentOptions, schemaId: activeSchema.id }, { key: 'content', value: currentOptions[0] || '', schemaId: activeSchema.id }, ]); }; const formContainer = document.createElement('div'); Object.assign(formContainer.style, { width: '100%', display: 'flex', alignItems: 'center', marginBottom: '10px', }); const input = document.createElement('input'); input.type = 'text'; input.placeholder = i18n('schemas.select.optionPlaceholder'); Object.assign(input.style, inputStyle, { marginRight: '10px' }); const addButton = document.createElement('button'); addButton.textContent = '+'; Object.assign(addButton.style, buttonStyle, { width: '25px', height: '25px', padding: '4px 8px', }); addButton.addEventListener('click', () => { const newValue = input.value.trim(); if (newValue) { currentOptions.push(newValue); updateSchemas(); renderOptions(); input.value = ''; } }); formContainer.appendChild(input); formContainer.appendChild(addButton); const optionsList = document.createElement('ul'); Object.assign(optionsList.style, { listStyle: 'none', padding: '0' }); const renderOptions = () => { optionsList.innerHTML = ''; currentOptions.forEach((option, index) => { const li = document.createElement('li'); Object.assign(li.style, { display: 'flex', alignItems: 'center', marginBottom: '5px' }); const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.value = option; Object.assign(optionInput.style, inputStyle, { marginRight: '10px' }); optionInput.addEventListener('change', () => { currentOptions[index] = optionInput.value; updateSchemas(); }); const removeButton = document.createElement('button'); removeButton.textContent = 'x'; Object.assign(removeButton.style, buttonStyle, { padding: '4px 8px' }); removeButton.addEventListener('click', () => { currentOptions.splice(index, 1); updateSchemas(); renderOptions(); }); li.appendChild(optionInput); li.appendChild(removeButton); optionsList.appendChild(li); }); }; rootElement.appendChild(formContainer); rootElement.appendChild(optionsList); renderOptions(); }; const schema: Plugin<Select> = { ui: async (arg) => { const { schema, value, onChange, rootElement, mode } = arg; await text.ui(Object.assign(arg, { mode: 'viewer' })); if (mode !== 'viewer' && !(mode === 'form' && schema.readOnly)) { const buttonWidth = 30; const selectButton = document.createElement('button'); selectButton.innerHTML = selectIcon; const selectButtonStyle: CSS.Properties = { position: 'absolute', zIndex: -1, right: `-${buttonWidth}px`, top: '0', padding: '0', margin: '0', cursor: 'pointer', height: `${buttonWidth}px`, width: `${buttonWidth}px`, }; Object.assign(selectButton.style, selectButtonStyle); rootElement.appendChild(selectButton); const selectElement = document.createElement('select'); const selectElementStyle: CSS.Properties = { opacity: '0', position: 'absolute', width: `calc(100% + ${buttonWidth}px)`, height: '100%', top: '0', left: '0', appearance: 'initial', }; Object.assign(selectElement.style, selectElementStyle); selectElement.value = value; selectElement.addEventListener('change', (e) => { if (onChange && e.target instanceof HTMLSelectElement) { if (onChange) onChange({ key: 'content', value: e.target.value }); } }); // Ensure schema.options is an array before mapping const options = Array.isArray(schema.options) ? schema.options : []; selectElement.innerHTML = options .map( (option) => `<option value="${option}" ${option === value ? 'selected' : ''}>${option}</option>`, ) .join(''); rootElement.appendChild(selectElement); } }, pdf: text.pdf, propPanel: { ...text.propPanel, widgets: { ...parentPropPanel.widgets, addOptions }, schema: (propPanelProps: Omit<PropPanelWidgetProps, 'rootElement'>) => { if (typeof parentPropPanel.schema !== 'function') { throw Error('Oops, is text schema no longer a function?'); } // Safely call the parent schema function with proper type checking const parentSchema = parentPropPanel.schema(propPanelProps); // Create a type-safe return object return { ...parentSchema, '-------': { type: 'void', widget: 'Divider' }, optionsContainer: { title: (propPanelProps as PropPanelWidgetProps).i18n('schemas.select.options'), type: 'string', widget: 'Card', span: 24, properties: { options: { widget: 'addOptions', span: 24 } }, }, }; }, defaultSchema: { ...(text.propPanel.defaultSchema as TextSchema), type: 'select', content: 'option1', options: ['option1', 'option2'], }, }, icon: selectIcon, }; export default schema;