@pdfme/schemas 4.1.0 → 4.1.1-dev.2
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/dist/cjs/src/constants.js +1 -1
- package/dist/cjs/src/constants.js.map +1 -1
- package/dist/cjs/src/graphics/image.js +3 -1
- package/dist/cjs/src/graphics/image.js.map +1 -1
- package/dist/cjs/src/index.js +11 -9
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/multiVariableText/helper.js +19 -0
- package/dist/cjs/src/multiVariableText/helper.js.map +1 -0
- package/dist/cjs/src/multiVariableText/index.js +8 -0
- package/dist/cjs/src/multiVariableText/index.js.map +1 -0
- package/dist/cjs/src/multiVariableText/pdfRender.js +16 -0
- package/dist/cjs/src/multiVariableText/pdfRender.js.map +1 -0
- package/dist/cjs/src/multiVariableText/propPanel.js +128 -0
- package/dist/cjs/src/multiVariableText/propPanel.js.map +1 -0
- package/dist/cjs/src/multiVariableText/types.js +3 -0
- package/dist/cjs/src/multiVariableText/types.js.map +1 -0
- package/dist/cjs/src/multiVariableText/uiRender.js +133 -0
- package/dist/cjs/src/multiVariableText/uiRender.js.map +1 -0
- package/dist/cjs/src/tables/tableHelper.js +2 -2
- package/dist/cjs/src/tables/tableHelper.js.map +1 -1
- package/dist/cjs/src/text/extraFormatter.js +1 -1
- package/dist/cjs/src/text/propPanel.js +1 -1
- package/dist/cjs/src/text/uiRender.js +122 -103
- package/dist/cjs/src/text/uiRender.js.map +1 -1
- package/dist/esm/src/constants.js +1 -1
- package/dist/esm/src/constants.js.map +1 -1
- package/dist/esm/src/graphics/image.js +3 -1
- package/dist/esm/src/graphics/image.js.map +1 -1
- package/dist/esm/src/index.js +2 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/multiVariableText/helper.js +15 -0
- package/dist/esm/src/multiVariableText/helper.js.map +1 -0
- package/dist/esm/src/multiVariableText/index.js +6 -0
- package/dist/esm/src/multiVariableText/index.js.map +1 -0
- package/dist/esm/src/multiVariableText/pdfRender.js +12 -0
- package/dist/esm/src/multiVariableText/pdfRender.js.map +1 -0
- package/dist/esm/src/multiVariableText/propPanel.js +125 -0
- package/dist/esm/src/multiVariableText/propPanel.js.map +1 -0
- package/dist/esm/src/multiVariableText/types.js +2 -0
- package/dist/esm/src/multiVariableText/types.js.map +1 -0
- package/dist/esm/src/multiVariableText/uiRender.js +129 -0
- package/dist/esm/src/multiVariableText/uiRender.js.map +1 -0
- package/dist/esm/src/tables/tableHelper.js +2 -2
- package/dist/esm/src/tables/tableHelper.js.map +1 -1
- package/dist/esm/src/text/extraFormatter.js +1 -1
- package/dist/esm/src/text/propPanel.js +1 -1
- package/dist/esm/src/text/uiRender.js +118 -101
- package/dist/esm/src/text/uiRender.js.map +1 -1
- package/dist/types/src/constants.d.ts +1 -1
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/multiVariableText/helper.d.ts +1 -0
- package/dist/types/src/multiVariableText/index.d.ts +4 -0
- package/dist/types/src/multiVariableText/pdfRender.d.ts +3 -0
- package/dist/types/src/multiVariableText/propPanel.d.ts +3 -0
- package/dist/types/src/multiVariableText/types.d.ts +5 -0
- package/dist/types/src/multiVariableText/uiRender.d.ts +3 -0
- package/dist/types/src/shapes/rectAndEllipse.d.ts +2 -0
- package/dist/types/src/text/uiRender.d.ts +6 -0
- package/package.json +1 -1
- package/src/constants.ts +1 -1
- package/src/graphics/image.ts +2 -1
- package/src/index.ts +2 -0
- package/src/multiVariableText/helper.ts +18 -0
- package/src/multiVariableText/index.ts +8 -0
- package/src/multiVariableText/pdfRender.ts +16 -0
- package/src/multiVariableText/propPanel.ts +139 -0
- package/src/multiVariableText/types.ts +6 -0
- package/src/multiVariableText/uiRender.ts +161 -0
- package/src/tables/tableHelper.ts +2 -2
- package/src/text/extraFormatter.ts +1 -1
- package/src/text/propPanel.ts +1 -1
- package/src/text/uiRender.ts +150 -118
@@ -0,0 +1,18 @@
|
|
1
|
+
export const substituteVariables = (text: string, variablesIn: string | Record<string, string>): string => {
|
2
|
+
if (!text || !variablesIn) {
|
3
|
+
return text;
|
4
|
+
}
|
5
|
+
|
6
|
+
let substitutedText = text;
|
7
|
+
|
8
|
+
const variables: Record<string, string> = (typeof variablesIn === "string") ? JSON.parse(variablesIn) || {} : variablesIn;
|
9
|
+
|
10
|
+
Object.keys(variables).forEach((variableName) => {
|
11
|
+
// handle special characters in variable name
|
12
|
+
const variableForRegex = variableName.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
13
|
+
const regex = new RegExp('{' + variableForRegex + '}', 'g');
|
14
|
+
substitutedText = substitutedText.replace(regex, variables[variableName]);
|
15
|
+
});
|
16
|
+
|
17
|
+
return substitutedText;
|
18
|
+
};
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { Plugin } from '@pdfme/common';
|
2
|
+
import { pdfRender } from './pdfRender.js';
|
3
|
+
import { propPanel } from './propPanel.js';
|
4
|
+
import { uiRender } from './uiRender.js';
|
5
|
+
import type { MultiVariableTextSchema } from './types';
|
6
|
+
|
7
|
+
const schema: Plugin<MultiVariableTextSchema> = { pdf: pdfRender, ui: uiRender, propPanel, uninterruptedEditMode: true };
|
8
|
+
export default schema;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { PDFRenderProps } from '@pdfme/common';
|
2
|
+
import { MultiVariableTextSchema } from './types';
|
3
|
+
import { pdfRender as parentPdfRender } from '../text/pdfRender';
|
4
|
+
import { substituteVariables } from './helper';
|
5
|
+
|
6
|
+
export const pdfRender = async (arg: PDFRenderProps<MultiVariableTextSchema>) => {
|
7
|
+
const { value, schema, ...rest } = arg;
|
8
|
+
|
9
|
+
const renderArgs = {
|
10
|
+
value: substituteVariables(schema.text || '', value),
|
11
|
+
schema,
|
12
|
+
...rest,
|
13
|
+
};
|
14
|
+
|
15
|
+
await parentPdfRender(renderArgs);
|
16
|
+
};
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import { propPanel as parentPropPanel } from '../text/propPanel';
|
2
|
+
import { PropPanel, PropPanelWidgetProps } from '@pdfme/common';
|
3
|
+
import { MultiVariableTextSchema } from './types';
|
4
|
+
|
5
|
+
const mapDynamicVariables = (props: PropPanelWidgetProps) => {
|
6
|
+
const { rootElement, changeSchemas, activeSchema, i18n, options } = props;
|
7
|
+
|
8
|
+
const mvtSchema = (activeSchema as any);
|
9
|
+
const text = mvtSchema.text || '';
|
10
|
+
const variables = JSON.parse(mvtSchema.content) || {};
|
11
|
+
const variablesChanged = updateVariablesFromText(text, variables);
|
12
|
+
const varNames = Object.keys(variables);
|
13
|
+
|
14
|
+
if (variablesChanged) {
|
15
|
+
changeSchemas([
|
16
|
+
{ key: 'content', value: JSON.stringify(variables), schemaId: activeSchema.id },
|
17
|
+
{ key: 'variables', value: varNames, schemaId: activeSchema.id }
|
18
|
+
]);
|
19
|
+
}
|
20
|
+
|
21
|
+
const placeholderRowEl = document.getElementById('placeholder-dynamic-var')?.closest('.ant-form-item') as HTMLElement;
|
22
|
+
if (!placeholderRowEl) {
|
23
|
+
throw new Error('Failed to find Ant form placeholder row to create dynamic variables inputs.');
|
24
|
+
}
|
25
|
+
placeholderRowEl.style.display = 'none';
|
26
|
+
|
27
|
+
// The wrapping form element has a display:flex which limits the width of the form fields, removing.
|
28
|
+
(rootElement.parentElement as HTMLElement).style.display = 'block';
|
29
|
+
|
30
|
+
if (varNames.length > 0) {
|
31
|
+
for (let variableName of varNames) {
|
32
|
+
const varRow = placeholderRowEl.cloneNode(true) as HTMLElement;
|
33
|
+
|
34
|
+
const textarea = varRow.querySelector('textarea') as HTMLTextAreaElement;
|
35
|
+
textarea.id = 'dynamic-var-' + variableName;
|
36
|
+
textarea.value = variables[variableName];
|
37
|
+
textarea.addEventListener('change', (e: Event) => {
|
38
|
+
variables[variableName] = (e.target as HTMLTextAreaElement).value;
|
39
|
+
changeSchemas([{ key: 'content', value: JSON.stringify(variables), schemaId: activeSchema.id }]);
|
40
|
+
});
|
41
|
+
|
42
|
+
const label = varRow.querySelector('label') as HTMLLabelElement
|
43
|
+
label.innerText = variableName;
|
44
|
+
|
45
|
+
varRow.style.display = 'block';
|
46
|
+
rootElement.appendChild(varRow);
|
47
|
+
}
|
48
|
+
} else {
|
49
|
+
const para = document.createElement('p');
|
50
|
+
para.innerHTML = i18n('schemas.mvt.typingInstructions')
|
51
|
+
+ ` <code style="color:${options?.theme?.token?.colorPrimary || "#168fe3"}; font-weight:bold;">{`
|
52
|
+
+ i18n('schemas.mvt.sampleField')
|
53
|
+
+ '}</code>';
|
54
|
+
rootElement.appendChild(para);
|
55
|
+
}
|
56
|
+
};
|
57
|
+
|
58
|
+
export const propPanel: PropPanel<MultiVariableTextSchema> = {
|
59
|
+
schema: (propPanelProps: Omit<PropPanelWidgetProps, 'rootElement'>) => {
|
60
|
+
if (typeof parentPropPanel.schema !== 'function') {
|
61
|
+
throw Error('Oops, is text schema no longer a function?');
|
62
|
+
}
|
63
|
+
return {
|
64
|
+
...parentPropPanel.schema(propPanelProps),
|
65
|
+
'-------': { type: 'void', widget: 'Divider' },
|
66
|
+
dynamicVarContainer: {
|
67
|
+
title: propPanelProps.i18n('schemas.mvt.variablesSampleData'),
|
68
|
+
type: 'string',
|
69
|
+
widget: 'Card',
|
70
|
+
span: 24,
|
71
|
+
properties: {
|
72
|
+
dynamicVariables: {
|
73
|
+
type: 'object',
|
74
|
+
widget: 'mapDynamicVariables',
|
75
|
+
bind: false,
|
76
|
+
span: 24
|
77
|
+
},
|
78
|
+
placeholderDynamicVar: {
|
79
|
+
title: 'Placeholder Dynamic Variable',
|
80
|
+
type: 'string',
|
81
|
+
format: 'textarea',
|
82
|
+
props: {
|
83
|
+
id: 'placeholder-dynamic-var',
|
84
|
+
autoSize: {
|
85
|
+
minRows: 2,
|
86
|
+
maxRows: 5,
|
87
|
+
},
|
88
|
+
},
|
89
|
+
span: 24,
|
90
|
+
},
|
91
|
+
}
|
92
|
+
},
|
93
|
+
|
94
|
+
};
|
95
|
+
},
|
96
|
+
widgets: { ...parentPropPanel.widgets, mapDynamicVariables },
|
97
|
+
defaultSchema: {
|
98
|
+
...parentPropPanel.defaultSchema,
|
99
|
+
type: 'multiVariableText',
|
100
|
+
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-type"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>',
|
101
|
+
text: 'Type something...',
|
102
|
+
content: '{}',
|
103
|
+
variables: [],
|
104
|
+
},
|
105
|
+
};
|
106
|
+
|
107
|
+
|
108
|
+
const updateVariablesFromText = (text: string, variables: any): boolean => {
|
109
|
+
const regex = /\{([^{}]+)}/g;
|
110
|
+
const matches = text.match(regex);
|
111
|
+
let changed = false;
|
112
|
+
|
113
|
+
if (matches) {
|
114
|
+
// Add any new variables
|
115
|
+
for (const match of matches) {
|
116
|
+
const variableName = match.replace('{', '').replace('}', '');
|
117
|
+
if (!variables[variableName]) {
|
118
|
+
// NOTE: We upper case the variable name as the default value
|
119
|
+
variables[variableName] = variableName.toUpperCase();
|
120
|
+
changed = true;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
// Remove any that no longer exist
|
124
|
+
Object.keys(variables).forEach((variableName) => {
|
125
|
+
if (!matches.includes('{' + variableName + '}')) {
|
126
|
+
delete variables[variableName];
|
127
|
+
changed = true;
|
128
|
+
}
|
129
|
+
});
|
130
|
+
} else {
|
131
|
+
// No matches at all, so clear all variables
|
132
|
+
Object.keys(variables).forEach((variableName) => {
|
133
|
+
delete variables[variableName];
|
134
|
+
changed = true;
|
135
|
+
});
|
136
|
+
}
|
137
|
+
|
138
|
+
return changed;
|
139
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { UIRenderProps } from '@pdfme/common';
|
2
|
+
import { MultiVariableTextSchema } from './types';
|
3
|
+
import {
|
4
|
+
uiRender as parentUiRender,
|
5
|
+
buildStyledTextContainer,
|
6
|
+
makeElementPlainTextContentEditable
|
7
|
+
} from '../text/uiRender';
|
8
|
+
import { isEditable } from '../utils';
|
9
|
+
import { substituteVariables } from './helper';
|
10
|
+
|
11
|
+
export const uiRender = async (arg: UIRenderProps<MultiVariableTextSchema>) => {
|
12
|
+
const { value, schema, rootElement, mode, onChange, ...rest } = arg;
|
13
|
+
|
14
|
+
let text = schema.text;
|
15
|
+
let numVariables = schema.variables.length;
|
16
|
+
|
17
|
+
if (mode === 'form' && numVariables > 0) {
|
18
|
+
await formUiRender(arg);
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
await parentUiRender({
|
23
|
+
value: isEditable(mode, schema) ? text : substituteVariables(text, value),
|
24
|
+
schema,
|
25
|
+
mode: mode == 'form' ? 'viewer' : mode, // if no variables for form it's just a viewer
|
26
|
+
rootElement,
|
27
|
+
onChange: (arg: { key: string; value: any; } | { key: string; value: any; }[]) => {
|
28
|
+
if (!Array.isArray(arg)) {
|
29
|
+
onChange && onChange({key: 'text', value: arg.value});
|
30
|
+
} else {
|
31
|
+
throw new Error('onChange is not an array, the parent text plugin has changed...');
|
32
|
+
}
|
33
|
+
},
|
34
|
+
...rest,
|
35
|
+
});
|
36
|
+
|
37
|
+
const textBlock = rootElement.querySelector('#text-' + schema.id) as HTMLDivElement;
|
38
|
+
if (!textBlock) {
|
39
|
+
throw new Error('Text block not found. Ensure the text block has an id of "text-" + schema.id');
|
40
|
+
}
|
41
|
+
|
42
|
+
if (mode === 'designer') {
|
43
|
+
textBlock.addEventListener('keyup', (event: KeyboardEvent) => {
|
44
|
+
text = textBlock.textContent || '';
|
45
|
+
if (keyPressShouldBeChecked(event)) {
|
46
|
+
const newNumVariables = countUniqueVariableNames(text);
|
47
|
+
if (numVariables !== newNumVariables) {
|
48
|
+
// If variables were modified during this keypress, we trigger a change
|
49
|
+
if (onChange) {
|
50
|
+
onChange({key: 'text', value: text});
|
51
|
+
}
|
52
|
+
numVariables = newNumVariables;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
});
|
56
|
+
}
|
57
|
+
};
|
58
|
+
|
59
|
+
const formUiRender = async (arg: UIRenderProps<MultiVariableTextSchema>) => {
|
60
|
+
const {
|
61
|
+
value,
|
62
|
+
schema,
|
63
|
+
rootElement,
|
64
|
+
onChange,
|
65
|
+
stopEditing,
|
66
|
+
theme,
|
67
|
+
} = arg;
|
68
|
+
const rawText = schema.text;
|
69
|
+
|
70
|
+
if (rootElement.parentElement) {
|
71
|
+
// remove the outline for the whole schema, we'll apply outlines on each individual variable field instead
|
72
|
+
rootElement.parentElement.style.outline = '';
|
73
|
+
}
|
74
|
+
|
75
|
+
const variables: Record<string, string> = JSON.parse(value) || {}
|
76
|
+
const variableIndices = getVariableIndices(rawText);
|
77
|
+
const substitutedText = substituteVariables(rawText, variables);
|
78
|
+
|
79
|
+
const textBlock = await buildStyledTextContainer(arg, substitutedText);
|
80
|
+
|
81
|
+
// Construct content-editable spans for each variable within the string
|
82
|
+
let inVarString = false;
|
83
|
+
|
84
|
+
for (let i = 0; i < rawText.length; i++) {
|
85
|
+
if (variableIndices[i]) {
|
86
|
+
inVarString = true;
|
87
|
+
let span = document.createElement('span');
|
88
|
+
span.style.outline = `${theme.colorPrimary} dashed 1px`;
|
89
|
+
makeElementPlainTextContentEditable(span)
|
90
|
+
span.textContent = variables[variableIndices[i]];
|
91
|
+
span.addEventListener('blur', (e: Event) => {
|
92
|
+
const newValue = (e.target as HTMLSpanElement).innerText;
|
93
|
+
if (newValue !== variables[variableIndices[i]]) {
|
94
|
+
variables[variableIndices[i]] = newValue;
|
95
|
+
onChange && onChange({ key: 'content', value: JSON.stringify(variables) });
|
96
|
+
stopEditing && stopEditing();
|
97
|
+
}
|
98
|
+
});
|
99
|
+
textBlock.appendChild(span);
|
100
|
+
} else if (inVarString) {
|
101
|
+
if (rawText[i] === '}') {
|
102
|
+
inVarString = false;
|
103
|
+
}
|
104
|
+
} else {
|
105
|
+
let span = document.createElement('span');
|
106
|
+
span.style.letterSpacing = rawText.length === i + 1 ? '0' : 'inherit';
|
107
|
+
span.textContent = rawText[i];
|
108
|
+
textBlock.appendChild(span);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
const getVariableIndices = (content: string) => {
|
114
|
+
const regex = /\{([^}]+)}/g;
|
115
|
+
const indices = [];
|
116
|
+
let match;
|
117
|
+
|
118
|
+
while ((match = regex.exec(content)) !== null) {
|
119
|
+
indices[match.index] = match[1];
|
120
|
+
}
|
121
|
+
|
122
|
+
return indices;
|
123
|
+
};
|
124
|
+
|
125
|
+
const countUniqueVariableNames = (content: string) => {
|
126
|
+
const regex = /\{([^}]+)}/g;
|
127
|
+
const uniqueMatchesSet = new Set();
|
128
|
+
let match;
|
129
|
+
|
130
|
+
while ((match = regex.exec(content)) !== null) {
|
131
|
+
uniqueMatchesSet.add(match[1]);
|
132
|
+
}
|
133
|
+
|
134
|
+
return uniqueMatchesSet.size;
|
135
|
+
};
|
136
|
+
|
137
|
+
/**
|
138
|
+
* An optimisation to try to minimise jank while typing.
|
139
|
+
* Only check whether variables were modified based on certain key presses.
|
140
|
+
* Regex would otherwise be performed on every key press (which isn't terrible, but this code helps).
|
141
|
+
*/
|
142
|
+
const keyPressShouldBeChecked = (event: KeyboardEvent) => {
|
143
|
+
if (event.key == "ArrowUp" || event.key == "ArrowDown" || event.key == "ArrowLeft" || event.key == "ArrowRight") {
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
|
147
|
+
const selection = window.getSelection();
|
148
|
+
const contenteditable = event.target as HTMLDivElement;
|
149
|
+
|
150
|
+
const isCursorAtEnd = selection?.focusOffset === contenteditable?.textContent?.length;
|
151
|
+
if (isCursorAtEnd) {
|
152
|
+
return event.key === '}' || event.key === 'Backspace' || event.key === 'Delete';
|
153
|
+
}
|
154
|
+
|
155
|
+
const isCursorAtStart = selection?.anchorOffset === 0;
|
156
|
+
if (isCursorAtStart) {
|
157
|
+
return event.key === '{' || event.key === 'Backspace' || event.key === 'Delete';
|
158
|
+
}
|
159
|
+
|
160
|
+
return true;
|
161
|
+
}
|
@@ -270,7 +270,7 @@ function parseInput(schema: TableSchema, body: string[][]): TableInput {
|
|
270
270
|
|
271
271
|
export function createSingleTable(body: string[][], args: CreateTableArgs) {
|
272
272
|
const { options, _cache, basePdf } = args;
|
273
|
-
if (!isBlankPdf(basePdf)) throw new Error('[@pdfme/schema/table]
|
273
|
+
if (!isBlankPdf(basePdf)) throw new Error('[@pdfme/schema/table] Custom PDF is not supported');
|
274
274
|
|
275
275
|
const input = parseInput(args.schema as TableSchema, body);
|
276
276
|
|
@@ -286,7 +286,7 @@ export function createSingleTable(body: string[][], args: CreateTableArgs) {
|
|
286
286
|
export async function createMultiTables(body: string[][], args: CreateTableArgs): Promise<Table[]> {
|
287
287
|
const { basePdf, schema } = args;
|
288
288
|
|
289
|
-
if (!isBlankPdf(basePdf)) throw new Error('[@pdfme/schema/table]
|
289
|
+
if (!isBlankPdf(basePdf)) throw new Error('[@pdfme/schema/table] Custom PDF is not supported');
|
290
290
|
const pageHeight = basePdf.height;
|
291
291
|
const paddingBottom = basePdf.padding[2];
|
292
292
|
const paddingTop = basePdf.padding[0];
|
package/src/text/propPanel.ts
CHANGED
@@ -83,7 +83,7 @@ export const propPanel: PropPanel<TextSchema> = {
|
|
83
83
|
type: 'number',
|
84
84
|
widget: 'inputNumber',
|
85
85
|
props: { step: 0.1, min: 0 },
|
86
|
-
span:
|
86
|
+
span: 7,
|
87
87
|
},
|
88
88
|
useDynamicFontSize: { type: 'boolean', widget: 'UseDynamicFontSize', bind: false, span: 16 },
|
89
89
|
dynamicFontSize: {
|