@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.
Files changed (72) hide show
  1. package/dist/cjs/src/constants.js +1 -1
  2. package/dist/cjs/src/constants.js.map +1 -1
  3. package/dist/cjs/src/graphics/image.js +3 -1
  4. package/dist/cjs/src/graphics/image.js.map +1 -1
  5. package/dist/cjs/src/index.js +11 -9
  6. package/dist/cjs/src/index.js.map +1 -1
  7. package/dist/cjs/src/multiVariableText/helper.js +19 -0
  8. package/dist/cjs/src/multiVariableText/helper.js.map +1 -0
  9. package/dist/cjs/src/multiVariableText/index.js +8 -0
  10. package/dist/cjs/src/multiVariableText/index.js.map +1 -0
  11. package/dist/cjs/src/multiVariableText/pdfRender.js +16 -0
  12. package/dist/cjs/src/multiVariableText/pdfRender.js.map +1 -0
  13. package/dist/cjs/src/multiVariableText/propPanel.js +128 -0
  14. package/dist/cjs/src/multiVariableText/propPanel.js.map +1 -0
  15. package/dist/cjs/src/multiVariableText/types.js +3 -0
  16. package/dist/cjs/src/multiVariableText/types.js.map +1 -0
  17. package/dist/cjs/src/multiVariableText/uiRender.js +133 -0
  18. package/dist/cjs/src/multiVariableText/uiRender.js.map +1 -0
  19. package/dist/cjs/src/tables/tableHelper.js +2 -2
  20. package/dist/cjs/src/tables/tableHelper.js.map +1 -1
  21. package/dist/cjs/src/text/extraFormatter.js +1 -1
  22. package/dist/cjs/src/text/propPanel.js +1 -1
  23. package/dist/cjs/src/text/uiRender.js +122 -103
  24. package/dist/cjs/src/text/uiRender.js.map +1 -1
  25. package/dist/esm/src/constants.js +1 -1
  26. package/dist/esm/src/constants.js.map +1 -1
  27. package/dist/esm/src/graphics/image.js +3 -1
  28. package/dist/esm/src/graphics/image.js.map +1 -1
  29. package/dist/esm/src/index.js +2 -1
  30. package/dist/esm/src/index.js.map +1 -1
  31. package/dist/esm/src/multiVariableText/helper.js +15 -0
  32. package/dist/esm/src/multiVariableText/helper.js.map +1 -0
  33. package/dist/esm/src/multiVariableText/index.js +6 -0
  34. package/dist/esm/src/multiVariableText/index.js.map +1 -0
  35. package/dist/esm/src/multiVariableText/pdfRender.js +12 -0
  36. package/dist/esm/src/multiVariableText/pdfRender.js.map +1 -0
  37. package/dist/esm/src/multiVariableText/propPanel.js +125 -0
  38. package/dist/esm/src/multiVariableText/propPanel.js.map +1 -0
  39. package/dist/esm/src/multiVariableText/types.js +2 -0
  40. package/dist/esm/src/multiVariableText/types.js.map +1 -0
  41. package/dist/esm/src/multiVariableText/uiRender.js +129 -0
  42. package/dist/esm/src/multiVariableText/uiRender.js.map +1 -0
  43. package/dist/esm/src/tables/tableHelper.js +2 -2
  44. package/dist/esm/src/tables/tableHelper.js.map +1 -1
  45. package/dist/esm/src/text/extraFormatter.js +1 -1
  46. package/dist/esm/src/text/propPanel.js +1 -1
  47. package/dist/esm/src/text/uiRender.js +118 -101
  48. package/dist/esm/src/text/uiRender.js.map +1 -1
  49. package/dist/types/src/constants.d.ts +1 -1
  50. package/dist/types/src/index.d.ts +2 -1
  51. package/dist/types/src/multiVariableText/helper.d.ts +1 -0
  52. package/dist/types/src/multiVariableText/index.d.ts +4 -0
  53. package/dist/types/src/multiVariableText/pdfRender.d.ts +3 -0
  54. package/dist/types/src/multiVariableText/propPanel.d.ts +3 -0
  55. package/dist/types/src/multiVariableText/types.d.ts +5 -0
  56. package/dist/types/src/multiVariableText/uiRender.d.ts +3 -0
  57. package/dist/types/src/shapes/rectAndEllipse.d.ts +2 -0
  58. package/dist/types/src/text/uiRender.d.ts +6 -0
  59. package/package.json +1 -1
  60. package/src/constants.ts +1 -1
  61. package/src/graphics/image.ts +2 -1
  62. package/src/index.ts +2 -0
  63. package/src/multiVariableText/helper.ts +18 -0
  64. package/src/multiVariableText/index.ts +8 -0
  65. package/src/multiVariableText/pdfRender.ts +16 -0
  66. package/src/multiVariableText/propPanel.ts +139 -0
  67. package/src/multiVariableText/types.ts +6 -0
  68. package/src/multiVariableText/uiRender.ts +161 -0
  69. package/src/tables/tableHelper.ts +2 -2
  70. package/src/text/extraFormatter.ts +1 -1
  71. package/src/text/propPanel.ts +1 -1
  72. 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,6 @@
1
+ import type { TextSchema } from '../text/types';
2
+
3
+ export interface MultiVariableTextSchema extends TextSchema {
4
+ text: string;
5
+ variables: string[];
6
+ }
@@ -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] Blank PDF is not supported');
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] Blank PDF is not supported');
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];
@@ -72,6 +72,6 @@ export function getExtraFormatterSchema(
72
72
  title: i18n('schemas.text.format'),
73
73
  widget: 'ButtonGroup',
74
74
  buttons,
75
- span: 16,
75
+ span: 17,
76
76
  };
77
77
  }
@@ -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: 8,
86
+ span: 7,
87
87
  },
88
88
  useDynamicFontSize: { type: 'boolean', widget: 'UseDynamicFontSize', bind: false, span: 16 },
89
89
  dynamicFontSize: {