@campxdev/pdfme 1.2.2 → 1.3.0
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/chunks/{index-C8qZMUOU.js → fontSizePxWidget-Drk8HKGH.js} +138 -9
- package/dist/cjs/chunks/fontSizeTransform-CQQ_O42f.js +37 -0
- package/dist/cjs/chunks/{helper-BfoMn47R.js → helper-DGH62Z2s.js} +4 -0
- package/dist/cjs/chunks/{index-COKtXyPp.js → index-CoNR0xQU.js} +6 -2
- package/dist/cjs/chunks/{index-CVqJfcgy.js → index-CsEKt088.js} +697 -547
- package/dist/cjs/chunks/{pluginRegistry-C8bMreez.js → pluginRegistry-D2vr9MUy.js} +1 -1
- package/dist/cjs/common.js +7 -3
- package/dist/cjs/converter.js +1 -1
- package/dist/cjs/generator.js +3 -3
- package/dist/cjs/index.js +23 -16
- package/dist/cjs/print-designer-editor.js +3320 -3296
- package/dist/cjs/schemas.js +500 -38
- package/dist/cjs/ui.js +2007 -1868
- package/dist/esm/chunks/{index-C4F7EwBG.js → fontSizePxWidget-CbzQrSSM.js} +130 -5
- package/dist/esm/chunks/fontSizeTransform-CkTVJdRF.js +34 -0
- package/dist/esm/chunks/{helper-D5PPN6Bv.js → helper-DSxGxZ0j.js} +4 -1
- package/dist/esm/chunks/{index-CDhErAtE.js → index-DJkUkUo9.js} +4 -3
- package/dist/esm/chunks/{index-C7jr4GIK.js → index-D_-j4c4P.js} +691 -544
- package/dist/esm/chunks/{pluginRegistry-B-XSNgmK.js → pluginRegistry-Bgrz5qWG.js} +1 -1
- package/dist/esm/common.js +4 -3
- package/dist/esm/converter.js +1 -1
- package/dist/esm/generator.js +3 -3
- package/dist/esm/index.js +7 -6
- package/dist/esm/print-designer-editor.js +3307 -3286
- package/dist/esm/schemas.js +472 -13
- package/dist/esm/ui.js +2007 -1868
- package/dist/types/_vendors/common/fontSizeTransform.d.ts +5 -0
- package/dist/types/_vendors/common/helper.d.ts +1 -0
- package/dist/types/_vendors/common/index.d.ts +3 -2
- package/dist/types/_vendors/print-designer-editor/index.d.ts +2 -1
- package/dist/types/_vendors/print-designer-editor/types.d.ts +1 -1
- package/dist/types/_vendors/print-designer-editor/useDesigner.d.ts +1 -1
- package/dist/types/_vendors/schemas/index.d.ts +8 -2
- package/dist/types/_vendors/schemas/richText/helper.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/index.d.ts +4 -0
- package/dist/types/_vendors/schemas/richText/pdfRender.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/propPanel.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/types.d.ts +7 -0
- package/dist/types/_vendors/schemas/richText/uiRender.d.ts +3 -0
- package/dist/types/_vendors/schemas/singleVariableText/index.d.ts +4 -0
- package/dist/types/_vendors/schemas/singleVariableText/propPanel.d.ts +3 -0
- package/dist/types/_vendors/schemas/singleVariableText/types.d.ts +4 -0
- package/dist/types/_vendors/schemas/text/fontSizePxWidget.d.ts +9 -0
- package/dist/types/_vendors/ui/components/CtlBar.d.ts +1 -1
- package/dist/types/_vendors/ui/components/Paper.d.ts +1 -0
- package/package.json +1 -1
package/dist/esm/schemas.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { b as builtInPlugins, g as getDynamicHeightsForTable, s as
|
|
3
|
-
import { c as validateBarcodeInput,
|
|
4
|
-
export { d as dynamicBarcode, a as dynamicQrCode, b as dynamicTable, e as ellipse, i as image, l as line, s as signature, v as variableBarcodes } from './chunks/
|
|
5
|
-
import {
|
|
6
|
-
import { E as px2mm, Z as ZOOM, r as getFallbackFontName, D as DEFAULT_FONT_NAME } from './chunks/helper-
|
|
1
|
+
import { p as pdfRender$3, l as propPanel$2, u as uiRender$3, i as isEditable, m as getFontKitFont, n as buildStyledTextContainer, o as makeElementPlainTextContentEditable, c as createSvgStr, e as convertForPdfLayoutProps, d as addAlphaToHex, k as createErrorElm, D as DEFAULT_OPACITY, H as HEX_COLOR_PATTERN, q as getBodyWithRange, v as createSingleTable, w as getBody, j as getDefaultCellStyles, x as getColumnStylesPropPanelSchema, y as getCellPropPanelSchema, z as DEFAULT_FONT_COLOR, A as DEFAULT_CHARACTER_SPACING, B as DEFAULT_FONT_SIZE, C as DEFAULT_ALIGNMENT, E as getExtraFormatterSchema, F as Formatter, t as textSchema, G as DEFAULT_LINE_HEIGHT, V as VERTICAL_ALIGN_MIDDLE, I as mapVerticalAlignToFlex } from './chunks/index-D_-j4c4P.js';
|
|
2
|
+
export { b as builtInPlugins, g as getDynamicHeightsForTable, s as richText, a as singleVariableText } from './chunks/index-D_-j4c4P.js';
|
|
3
|
+
import { c as validateVariables, g as substituteVariables, h as createInsertVariableWidget, j as validateBarcodeInput, k as createBarCode, D as DEFAULT_BARCODE_COLOR, m as DEFAULT_BARCODE_BG_COLOR, n as DEFAULT_BARCODE_INCLUDETEXT, B as BARCODE_TYPES, r as rectangle, o as cellSchema } from './chunks/fontSizePxWidget-CbzQrSSM.js';
|
|
4
|
+
export { d as dynamicBarcode, a as dynamicQrCode, b as dynamicTable, e as ellipse, f as fontSizePxWidget, i as image, l as line, s as signature, v as variableBarcodes } from './chunks/fontSizePxWidget-CbzQrSSM.js';
|
|
5
|
+
import { Type, Route, QrCode, Barcode, Table, CalendarClock, Calendar, Clock, ChevronDown, CircleDot, Circle, SquareCheck, Square } from 'lucide';
|
|
6
|
+
import { q as getDefaultFont, E as px2mm, Z as ZOOM, r as getFallbackFontName, D as DEFAULT_FONT_NAME } from './chunks/helper-DSxGxZ0j.js';
|
|
7
7
|
import AirDatepicker from 'air-datepicker';
|
|
8
8
|
import localeAr from 'air-datepicker/locale/ar';
|
|
9
9
|
import localeBg from 'air-datepicker/locale/bg';
|
|
@@ -52,11 +52,470 @@ import 'uuid';
|
|
|
52
52
|
import 'bwip-js';
|
|
53
53
|
import 'zod';
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const pdfRender$2 = async (arg) => {
|
|
56
|
+
const { value, schema, pageContext, ...rest } = arg;
|
|
57
|
+
// Static mode: no template text → render value directly as plain text
|
|
58
|
+
if (!schema.text) {
|
|
59
|
+
await pdfRender$3({ value, schema, ...rest });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// readOnly: value is already resolved by generate.ts via replacePlaceholders
|
|
63
|
+
if (schema.readOnly) {
|
|
64
|
+
await pdfRender$3({ value, schema, ...rest });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Dynamic mode (form): substitute variables in template
|
|
68
|
+
if (!validateVariables(value, schema)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const renderArgs = {
|
|
72
|
+
value: substituteVariables(schema.text, value || '{}', pageContext),
|
|
73
|
+
schema,
|
|
74
|
+
...rest,
|
|
75
|
+
};
|
|
76
|
+
await pdfRender$3(renderArgs);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const insertVariableWidget = createInsertVariableWidget('text');
|
|
80
|
+
const mapDynamicVariables = (props) => {
|
|
81
|
+
const { rootElement, changeSchemas, activeSchema } = props;
|
|
82
|
+
const mvtSchema = activeSchema;
|
|
83
|
+
const text = mvtSchema.text ?? '';
|
|
84
|
+
if (!text) {
|
|
85
|
+
rootElement.style.display = 'none';
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const variables = JSON.parse(mvtSchema.content || '{}');
|
|
89
|
+
const variablesChanged = updateVariablesFromText(text, variables);
|
|
90
|
+
const varNames = Object.keys(variables);
|
|
91
|
+
if (variablesChanged) {
|
|
92
|
+
changeSchemas([
|
|
93
|
+
{ key: 'content', value: JSON.stringify(variables), schemaId: activeSchema.id },
|
|
94
|
+
{ key: 'variables', value: varNames, schemaId: activeSchema.id },
|
|
95
|
+
{ key: 'readOnly', value: varNames.length === 0, schemaId: activeSchema.id },
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
// No UI needed — sample data is auto-generated from variable paths
|
|
99
|
+
rootElement.style.display = 'none';
|
|
100
|
+
};
|
|
101
|
+
const propPanel$1 = {
|
|
102
|
+
schema: (propPanelProps) => {
|
|
103
|
+
if (typeof propPanel$2.schema !== 'function') {
|
|
104
|
+
throw new Error('Oops, is text schema no longer a function?');
|
|
105
|
+
}
|
|
106
|
+
// Safely call schema function with proper type handling
|
|
107
|
+
const parentSchema = typeof propPanel$2.schema === 'function' ? propPanel$2.schema(propPanelProps) : {};
|
|
108
|
+
return {
|
|
109
|
+
insertVariablePicker: {
|
|
110
|
+
type: 'void',
|
|
111
|
+
widget: 'insertVariableWidget',
|
|
112
|
+
bind: false,
|
|
113
|
+
span: 24,
|
|
114
|
+
},
|
|
115
|
+
'----': { type: 'void', widget: 'Divider' },
|
|
116
|
+
...parentSchema,
|
|
117
|
+
dynamicVariables: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
widget: 'mapDynamicVariables',
|
|
120
|
+
bind: false,
|
|
121
|
+
span: 0,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
widgets: { ...(propPanel$2.widgets || {}), mapDynamicVariables, insertVariableWidget },
|
|
126
|
+
defaultSchema: {
|
|
127
|
+
...propPanel$2.defaultSchema,
|
|
128
|
+
readOnly: false,
|
|
129
|
+
type: 'text',
|
|
130
|
+
text: 'Type Something...',
|
|
131
|
+
width: 50,
|
|
132
|
+
height: 15,
|
|
133
|
+
content: '{}',
|
|
134
|
+
variables: [],
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
/** Known JS globals/keywords that should NOT be treated as user-defined variables */
|
|
138
|
+
const RESERVED_NAMES$1 = new Set([
|
|
139
|
+
'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'in',
|
|
140
|
+
'void', 'delete', 'new', 'this', 'NaN', 'Infinity',
|
|
141
|
+
'Math', 'String', 'Number', 'Boolean', 'Array', 'Object', 'Date', 'JSON',
|
|
142
|
+
'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent',
|
|
143
|
+
'encodeURI', 'encodeURIComponent', 'date', 'dateTime',
|
|
144
|
+
'currentPage', 'totalPages',
|
|
145
|
+
]);
|
|
146
|
+
/**
|
|
147
|
+
* Extract full dot-notation paths from an expression string.
|
|
148
|
+
* E.g. "student.marks.sem1 > 80" → ["student.marks.sem1"]
|
|
149
|
+
* Handles method calls: "student.name.toUpperCase()" → ["student.name"]
|
|
150
|
+
* Skips string literals and reserved names.
|
|
151
|
+
*/
|
|
152
|
+
const extractDotPaths = (expr) => {
|
|
153
|
+
// Replace string literals with spaces (preserving positions for nextChar lookup)
|
|
154
|
+
const cleaned = expr.replace(/'[^']*'|"[^"]*"|`[^`]*`/g, (m) => ' '.repeat(m.length));
|
|
155
|
+
const pathRegex = /[a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/g;
|
|
156
|
+
const paths = new Set();
|
|
157
|
+
let m;
|
|
158
|
+
while ((m = pathRegex.exec(cleaned)) !== null) {
|
|
159
|
+
let path = m[0];
|
|
160
|
+
// If followed by '(', the last segment is a method call — trim it
|
|
161
|
+
const nextChar = cleaned[m.index + path.length];
|
|
162
|
+
if (nextChar === '(') {
|
|
163
|
+
const lastDot = path.lastIndexOf('.');
|
|
164
|
+
if (lastDot !== -1) {
|
|
165
|
+
path = path.substring(0, lastDot);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Standalone function call like parseInt(...) — skip
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const root = path.split('.')[0];
|
|
173
|
+
if (!RESERVED_NAMES$1.has(root))
|
|
174
|
+
paths.add(path);
|
|
175
|
+
}
|
|
176
|
+
return Array.from(paths);
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Build a nested default object from dot-paths.
|
|
180
|
+
* E.g. ["student.name", "student.marks.sem1"] →
|
|
181
|
+
* { name: "NAME", marks: { sem1: "SEM1" } }
|
|
182
|
+
* Merges into an existing object, only adding missing leaves.
|
|
183
|
+
* Returns true if anything was added.
|
|
184
|
+
*/
|
|
185
|
+
const buildNestedDefault = (obj, paths) => {
|
|
186
|
+
let added = false;
|
|
187
|
+
for (const path of paths) {
|
|
188
|
+
const parts = path.split('.');
|
|
189
|
+
if (parts.length <= 1)
|
|
190
|
+
continue; // no nested parts
|
|
191
|
+
let current = obj;
|
|
192
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
193
|
+
if (!(parts[i] in current) || typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
194
|
+
current[parts[i]] = {};
|
|
195
|
+
added = true;
|
|
196
|
+
}
|
|
197
|
+
current = current[parts[i]];
|
|
198
|
+
}
|
|
199
|
+
const leaf = parts[parts.length - 1];
|
|
200
|
+
if (!(leaf in current)) {
|
|
201
|
+
current[leaf] = path.replace(/\./g, '_').toUpperCase();
|
|
202
|
+
added = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return added;
|
|
206
|
+
};
|
|
207
|
+
const updateVariablesFromText = (text, variables) => {
|
|
208
|
+
// Find all {...} blocks and extract dot-notation paths from each
|
|
209
|
+
const blockRegex = /\{([^{}]+)\}/g;
|
|
210
|
+
const allPaths = new Set();
|
|
211
|
+
let blockMatch;
|
|
212
|
+
while ((blockMatch = blockRegex.exec(text)) !== null) {
|
|
213
|
+
for (const path of extractDotPaths(blockMatch[1])) {
|
|
214
|
+
allPaths.add(path);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Group paths by root identifier
|
|
218
|
+
const rootToPaths = new Map();
|
|
219
|
+
for (const path of allPaths) {
|
|
220
|
+
const root = path.split('.')[0];
|
|
221
|
+
if (!rootToPaths.has(root))
|
|
222
|
+
rootToPaths.set(root, []);
|
|
223
|
+
rootToPaths.get(root).push(path);
|
|
224
|
+
}
|
|
225
|
+
const allRoots = new Set(rootToPaths.keys());
|
|
226
|
+
let changed = false;
|
|
227
|
+
for (const [root, paths] of rootToPaths) {
|
|
228
|
+
const hasNested = paths.some((p) => p.includes('.'));
|
|
229
|
+
if (hasNested) {
|
|
230
|
+
// Parse existing value or start fresh
|
|
231
|
+
let obj = {};
|
|
232
|
+
if (root in variables) {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(variables[root]);
|
|
235
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
236
|
+
obj = parsed;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
/* not JSON, will rebuild */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const added = buildNestedDefault(obj, paths);
|
|
244
|
+
if (!(root in variables) || added) {
|
|
245
|
+
variables[root] = JSON.stringify(obj);
|
|
246
|
+
changed = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
if (!(root in variables)) {
|
|
251
|
+
variables[root] = root.toUpperCase();
|
|
252
|
+
changed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Remove variables whose root is no longer referenced
|
|
257
|
+
for (const varName of Object.keys(variables)) {
|
|
258
|
+
if (!allRoots.has(varName)) {
|
|
259
|
+
delete variables[varName];
|
|
260
|
+
changed = true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return changed;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const uiRender$2 = async (arg) => {
|
|
267
|
+
const { value, schema, rootElement, mode, onChange, pageContext, ...rest } = arg;
|
|
268
|
+
// Static mode: no template text → delegate to plain text behavior
|
|
269
|
+
if (!schema.text) {
|
|
270
|
+
await uiRender$3(arg);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Dynamic mode: template with optional variables
|
|
274
|
+
let text = schema.text;
|
|
275
|
+
let numVariables = (schema.variables || []).length;
|
|
276
|
+
if (mode === 'form' && numVariables > 0) {
|
|
277
|
+
await formUiRender(arg);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
await uiRender$3({
|
|
281
|
+
value: isEditable(mode, schema) ? text : substituteVariables(text, value, pageContext),
|
|
282
|
+
schema,
|
|
283
|
+
mode: mode === 'form' ? 'viewer' : mode, // if no variables for form it's just a viewer
|
|
284
|
+
rootElement,
|
|
285
|
+
onChange: (arg) => {
|
|
286
|
+
if (!Array.isArray(arg)) {
|
|
287
|
+
if (onChange) {
|
|
288
|
+
onChange({ key: 'text', value: arg.value });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
throw new Error('onChange is not an array, the parent text plugin has changed...');
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
...rest,
|
|
296
|
+
});
|
|
297
|
+
const textBlock = rootElement.querySelector('#text-' + String(schema.id));
|
|
298
|
+
if (!textBlock) {
|
|
299
|
+
throw new Error('Text block not found. Ensure the text block has an id of "text-" + schema.id');
|
|
300
|
+
}
|
|
301
|
+
if (mode === 'designer') {
|
|
302
|
+
textBlock.addEventListener('keyup', (event) => {
|
|
303
|
+
text = textBlock.textContent || '';
|
|
304
|
+
if (keyPressShouldBeChecked(event)) {
|
|
305
|
+
const newNumVariables = countUniqueVariableNames(text);
|
|
306
|
+
if (numVariables !== newNumVariables) {
|
|
307
|
+
if (onChange) {
|
|
308
|
+
onChange({ key: 'text', value: text });
|
|
309
|
+
}
|
|
310
|
+
numVariables = newNumVariables;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
/** Re-evaluate all expression spans inside a container using current variable values */
|
|
317
|
+
const refreshExpressionSpans = (container, variables, pageCtx) => {
|
|
318
|
+
container.querySelectorAll('[data-expr]').forEach((span) => {
|
|
319
|
+
const expr = span.getAttribute('data-expr');
|
|
320
|
+
span.textContent = substituteVariables(`{${expr}}`, variables, pageCtx);
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
const formUiRender = async (arg) => {
|
|
324
|
+
const { value, schema, rootElement, onChange, stopEditing, theme, _cache, options, pageContext } = arg;
|
|
325
|
+
const rawText = schema.text;
|
|
326
|
+
if (rootElement.parentElement) {
|
|
327
|
+
// remove the outline for the whole schema, we'll apply outlines on each individual variable field instead
|
|
328
|
+
rootElement.parentElement.style.outline = '';
|
|
329
|
+
}
|
|
330
|
+
const variables = value
|
|
331
|
+
? JSON.parse(value) || {}
|
|
332
|
+
: {};
|
|
333
|
+
const substitutedText = substituteVariables(rawText, variables, pageContext);
|
|
334
|
+
const font = options?.font || getDefaultFont();
|
|
335
|
+
const fontKitFont = await getFontKitFont(schema.fontName, font, _cache);
|
|
336
|
+
const textBlock = buildStyledTextContainer(arg, fontKitFont, substitutedText);
|
|
337
|
+
// Tokenize rawText into static text, simple-identifier variables (editable), and
|
|
338
|
+
// complex expressions (evaluated, static). This supports both {name} and {price * 1.1}.
|
|
339
|
+
const tokens = tokenizeTemplate(rawText, variables, pageContext);
|
|
340
|
+
// Find variables that have inline editable spans (standalone {var} tokens)
|
|
341
|
+
const inlineVarNames = new Set(tokens.filter((t) => t.type === 'simpleVar')
|
|
342
|
+
.map(t => t.name));
|
|
343
|
+
// Variables that only appear inside expressions — need separate input fields
|
|
344
|
+
const expressionOnlyVars = (schema.variables || []).filter(v => !inlineVarNames.has(v));
|
|
345
|
+
if (expressionOnlyVars.length > 0) {
|
|
346
|
+
const varSection = document.createElement('div');
|
|
347
|
+
Object.assign(varSection.style, {
|
|
348
|
+
width: '100%',
|
|
349
|
+
padding: '2px 4px',
|
|
350
|
+
boxSizing: 'border-box',
|
|
351
|
+
display: 'flex',
|
|
352
|
+
flexWrap: 'wrap',
|
|
353
|
+
gap: '4px',
|
|
354
|
+
marginBottom: '2px',
|
|
355
|
+
});
|
|
356
|
+
for (const varName of expressionOnlyVars) {
|
|
357
|
+
const row = document.createElement('div');
|
|
358
|
+
Object.assign(row.style, { display: 'flex', alignItems: 'center', gap: '2px' });
|
|
359
|
+
const label = document.createElement('label');
|
|
360
|
+
label.textContent = varName + ':';
|
|
361
|
+
Object.assign(label.style, {
|
|
362
|
+
fontSize: '9px', whiteSpace: 'nowrap', color: theme.colorText,
|
|
363
|
+
});
|
|
364
|
+
const input = document.createElement('input');
|
|
365
|
+
input.type = 'text';
|
|
366
|
+
input.value = variables[varName] ?? '';
|
|
367
|
+
Object.assign(input.style, {
|
|
368
|
+
width: '60px',
|
|
369
|
+
fontSize: '9px',
|
|
370
|
+
border: `1px dashed ${theme.colorPrimary}`,
|
|
371
|
+
borderRadius: '2px',
|
|
372
|
+
padding: '1px 3px',
|
|
373
|
+
outline: 'none',
|
|
374
|
+
});
|
|
375
|
+
input.addEventListener('input', (e) => {
|
|
376
|
+
variables[varName] = e.target.value;
|
|
377
|
+
if (onChange)
|
|
378
|
+
onChange({ key: 'content', value: JSON.stringify(variables) });
|
|
379
|
+
refreshExpressionSpans(textBlock, variables, pageContext);
|
|
380
|
+
});
|
|
381
|
+
row.appendChild(label);
|
|
382
|
+
row.appendChild(input);
|
|
383
|
+
varSection.appendChild(row);
|
|
384
|
+
}
|
|
385
|
+
rootElement.insertBefore(varSection, textBlock);
|
|
386
|
+
}
|
|
387
|
+
for (const token of tokens) {
|
|
388
|
+
if (token.type === 'text') {
|
|
389
|
+
const span = document.createElement('span');
|
|
390
|
+
span.style.letterSpacing = 'inherit';
|
|
391
|
+
span.textContent = token.text;
|
|
392
|
+
textBlock.appendChild(span);
|
|
393
|
+
}
|
|
394
|
+
else if (token.type === 'simpleVar') {
|
|
395
|
+
const varName = token.name;
|
|
396
|
+
const span = document.createElement('span');
|
|
397
|
+
span.style.outline = `${theme.colorPrimary} dashed 1px`;
|
|
398
|
+
makeElementPlainTextContentEditable(span);
|
|
399
|
+
span.textContent = variables[varName] ?? '';
|
|
400
|
+
span.addEventListener('blur', (e) => {
|
|
401
|
+
const newValue = e.target.textContent || '';
|
|
402
|
+
if (newValue !== variables[varName]) {
|
|
403
|
+
variables[varName] = newValue;
|
|
404
|
+
if (onChange)
|
|
405
|
+
onChange({ key: 'content', value: JSON.stringify(variables) });
|
|
406
|
+
if (stopEditing)
|
|
407
|
+
stopEditing();
|
|
408
|
+
refreshExpressionSpans(textBlock, variables, pageContext);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
textBlock.appendChild(span);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// Complex expression — show evaluated result as static span with data-expr for re-evaluation
|
|
415
|
+
const span = document.createElement('span');
|
|
416
|
+
span.style.letterSpacing = 'inherit';
|
|
417
|
+
span.textContent = token.evaluated;
|
|
418
|
+
span.setAttribute('data-expr', token.raw);
|
|
419
|
+
textBlock.appendChild(span);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
// Simple identifier regex — matches {name}, {orderId}, etc. but NOT {price * 1.1}
|
|
424
|
+
const SIMPLE_VAR_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
425
|
+
/** Tokenize a template string into static text, simple-identifier vars, and complex expressions. */
|
|
426
|
+
const tokenizeTemplate = (rawText, variables, pageCtx) => {
|
|
427
|
+
const tokens = [];
|
|
428
|
+
let i = 0;
|
|
429
|
+
while (i < rawText.length) {
|
|
430
|
+
if (rawText[i] !== '{') {
|
|
431
|
+
const start = i;
|
|
432
|
+
while (i < rawText.length && rawText[i] !== '{')
|
|
433
|
+
i++;
|
|
434
|
+
tokens.push({ type: 'text', text: rawText.slice(start, i) });
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// Find matching closing brace (handle nested braces)
|
|
438
|
+
let depth = 1;
|
|
439
|
+
let j = i + 1;
|
|
440
|
+
while (j < rawText.length && depth > 0) {
|
|
441
|
+
if (rawText[j] === '{')
|
|
442
|
+
depth++;
|
|
443
|
+
else if (rawText[j] === '}')
|
|
444
|
+
depth--;
|
|
445
|
+
j++;
|
|
446
|
+
}
|
|
447
|
+
const inner = rawText.slice(i + 1, j - 1);
|
|
448
|
+
if (SIMPLE_VAR_RE.test(inner)) {
|
|
449
|
+
tokens.push({ type: 'simpleVar', name: inner });
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Evaluate the expression using substituteVariables on just this segment
|
|
453
|
+
const evaluated = substituteVariables(`{${inner}}`, variables, pageCtx);
|
|
454
|
+
tokens.push({ type: 'expression', evaluated, raw: inner });
|
|
455
|
+
}
|
|
456
|
+
i = j;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return tokens;
|
|
460
|
+
};
|
|
461
|
+
/** Reserved names — JS globals and expression builtins (must match propPanel's set) */
|
|
462
|
+
const RESERVED_NAMES = new Set([
|
|
463
|
+
'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'in',
|
|
464
|
+
'void', 'delete', 'new', 'this', 'NaN', 'Infinity',
|
|
465
|
+
'Math', 'String', 'Number', 'Boolean', 'Array', 'Object', 'Date', 'JSON',
|
|
466
|
+
'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent',
|
|
467
|
+
'encodeURI', 'encodeURIComponent', 'date', 'dateTime',
|
|
468
|
+
'currentPage', 'totalPages',
|
|
469
|
+
]);
|
|
470
|
+
/** Count unique user-defined variable identifiers in all {...} blocks */
|
|
471
|
+
const countUniqueVariableNames = (content) => {
|
|
472
|
+
const blockRegex = /\{([^{}]+)\}/g;
|
|
473
|
+
const allIds = new Set();
|
|
474
|
+
let blockMatch;
|
|
475
|
+
while ((blockMatch = blockRegex.exec(content)) !== null) {
|
|
476
|
+
const cleaned = blockMatch[1].replace(/'[^']*'|"[^"]*"|`[^`]*`/g, '');
|
|
477
|
+
const tokenRegex = /\.?[a-zA-Z_$][a-zA-Z0-9_$]*/g;
|
|
478
|
+
let m;
|
|
479
|
+
while ((m = tokenRegex.exec(cleaned)) !== null) {
|
|
480
|
+
const token = m[0];
|
|
481
|
+
if (!token.startsWith('.') && !RESERVED_NAMES.has(token)) {
|
|
482
|
+
allIds.add(token);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return allIds.size;
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* An optimisation to try to minimise jank while typing.
|
|
490
|
+
* Only check whether variables were modified based on certain key presses.
|
|
491
|
+
* Regex would otherwise be performed on every key press (which isn't terrible, but this code helps).
|
|
492
|
+
*/
|
|
493
|
+
const keyPressShouldBeChecked = (event) => {
|
|
494
|
+
if (event.key === 'ArrowUp' ||
|
|
495
|
+
event.key === 'ArrowDown' ||
|
|
496
|
+
event.key === 'ArrowLeft' ||
|
|
497
|
+
event.key === 'ArrowRight') {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
const selection = window.getSelection();
|
|
501
|
+
const contenteditable = event.target;
|
|
502
|
+
const isCursorAtEnd = selection?.focusOffset === contenteditable?.textContent?.length;
|
|
503
|
+
if (isCursorAtEnd) {
|
|
504
|
+
return event.key === '}' || event.key === 'Backspace' || event.key === 'Delete';
|
|
505
|
+
}
|
|
506
|
+
const isCursorAtStart = selection?.anchorOffset === 0;
|
|
507
|
+
if (isCursorAtStart) {
|
|
508
|
+
return event.key === '{' || event.key === 'Backspace' || event.key === 'Delete';
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const schema$3 = {
|
|
56
514
|
pdf: pdfRender$2,
|
|
57
515
|
ui: uiRender$2,
|
|
58
516
|
propPanel: propPanel$1,
|
|
59
|
-
icon: createSvgStr(
|
|
517
|
+
icon: createSvgStr(Type),
|
|
518
|
+
uninterruptedEditMode: true,
|
|
60
519
|
};
|
|
61
520
|
|
|
62
521
|
const isValidSVG = (svgString) => {
|
|
@@ -199,7 +658,7 @@ const barcodeDefaults = [
|
|
|
199
658
|
defaultSchema: {
|
|
200
659
|
name: '',
|
|
201
660
|
type: 'qrcode',
|
|
202
|
-
content: '
|
|
661
|
+
content: '',
|
|
203
662
|
position,
|
|
204
663
|
...defaultColors,
|
|
205
664
|
width: 30,
|
|
@@ -1600,13 +2059,13 @@ const schema$2 = {
|
|
|
1600
2059
|
pdf: textSchema.pdf,
|
|
1601
2060
|
propPanel: {
|
|
1602
2061
|
...textSchema.propPanel,
|
|
1603
|
-
widgets: { ...propPanel$
|
|
2062
|
+
widgets: { ...propPanel$2.widgets, addOptions },
|
|
1604
2063
|
schema: (propPanelProps) => {
|
|
1605
|
-
if (typeof propPanel$
|
|
2064
|
+
if (typeof propPanel$2.schema !== 'function') {
|
|
1606
2065
|
throw Error('Oops, is text schema no longer a function?');
|
|
1607
2066
|
}
|
|
1608
2067
|
// Safely call the parent schema function with proper type checking
|
|
1609
|
-
const parentSchema = propPanel$
|
|
2068
|
+
const parentSchema = propPanel$2.schema(propPanelProps);
|
|
1610
2069
|
// Create a type-safe return object
|
|
1611
2070
|
return {
|
|
1612
2071
|
...parentSchema,
|
|
@@ -1764,4 +2223,4 @@ const schema = {
|
|
|
1764
2223
|
icon: getCheckedIcon(),
|
|
1765
2224
|
};
|
|
1766
2225
|
|
|
1767
|
-
export { barcodes, schema as checkbox, date, dateTime, schema$1 as radioGroup, rectangle, schema$2 as select, svgSchema as svg, tableSchema as table, textSchema as text, time };
|
|
2226
|
+
export { barcodes, schema as checkbox, date, dateTime, schema$3 as multiVariableText, schema$1 as radioGroup, rectangle, schema$2 as select, svgSchema as svg, tableSchema as table, textSchema as text, time };
|