@pdfme/ui 0.0.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.
Files changed (97) hide show
  1. package/README.md +9 -0
  2. package/__mocks__/assetsTransformer.js +7 -0
  3. package/__mocks__/form-render.js +7 -0
  4. package/__mocks__/lucide-react.js +19 -0
  5. package/dist/index.es.js +159393 -0
  6. package/dist/index.umd.js +1041 -0
  7. package/dist/types/__tests__/assets/helper.d.ts +3 -0
  8. package/dist/types/__tests__/components/Designer.test.d.ts +1 -0
  9. package/dist/types/__tests__/components/PluginIcon.test.d.ts +1 -0
  10. package/dist/types/__tests__/components/Preview.test.d.ts +1 -0
  11. package/dist/types/__tests__/helper.test.d.ts +1 -0
  12. package/dist/types/src/Designer.d.ts +21 -0
  13. package/dist/types/src/Form.d.ts +24 -0
  14. package/dist/types/src/Viewer.d.ts +15 -0
  15. package/dist/types/src/class.d.ts +89 -0
  16. package/dist/types/src/components/AppContextProvider.d.ts +11 -0
  17. package/dist/types/src/components/CtlBar.d.ts +14 -0
  18. package/dist/types/src/components/Designer/Canvas/Guides.d.ts +9 -0
  19. package/dist/types/src/components/Designer/Canvas/Mask.d.ts +4 -0
  20. package/dist/types/src/components/Designer/Canvas/Moveable.d.ts +37 -0
  21. package/dist/types/src/components/Designer/Canvas/Padding.d.ts +6 -0
  22. package/dist/types/src/components/Designer/Canvas/Selecto.d.ts +10 -0
  23. package/dist/types/src/components/Designer/Canvas/index.d.ts +22 -0
  24. package/dist/types/src/components/Designer/LeftSidebar.d.ts +8 -0
  25. package/dist/types/src/components/Designer/PluginIcon.d.ts +10 -0
  26. package/dist/types/src/components/Designer/RightSidebar/DetailView/AlignWidget.d.ts +4 -0
  27. package/dist/types/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.d.ts +4 -0
  28. package/dist/types/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.d.ts +7 -0
  29. package/dist/types/src/components/Designer/RightSidebar/DetailView/index.d.ts +8 -0
  30. package/dist/types/src/components/Designer/RightSidebar/ListView/Item.d.ts +45 -0
  31. package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.d.ts +4 -0
  32. package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.d.ts +14 -0
  33. package/dist/types/src/components/Designer/RightSidebar/ListView/index.d.ts +4 -0
  34. package/dist/types/src/components/Designer/RightSidebar/index.d.ts +4 -0
  35. package/dist/types/src/components/Designer/RightSidebar/layout.d.ts +15 -0
  36. package/dist/types/src/components/Designer/index.d.ts +11 -0
  37. package/dist/types/src/components/ErrorScreen.d.ts +7 -0
  38. package/dist/types/src/components/Paper.d.ts +20 -0
  39. package/dist/types/src/components/Preview.d.ts +15 -0
  40. package/dist/types/src/components/Renderer.d.ts +13 -0
  41. package/dist/types/src/components/Root.d.ts +9 -0
  42. package/dist/types/src/components/Spinner.d.ts +3 -0
  43. package/dist/types/src/components/StaticSchema.d.ts +10 -0
  44. package/dist/types/src/components/UnitPager.d.ts +10 -0
  45. package/dist/types/src/constants.d.ts +11 -0
  46. package/dist/types/src/contexts.d.ts +10 -0
  47. package/dist/types/src/helper.d.ts +73 -0
  48. package/dist/types/src/hooks.d.ts +46 -0
  49. package/dist/types/src/i18n.d.ts +3 -0
  50. package/dist/types/src/index.d.ts +4 -0
  51. package/dist/types/src/theme.d.ts +2 -0
  52. package/dist/types/src/types.d.ts +19 -0
  53. package/eslint.config.mjs +41 -0
  54. package/package.json +127 -0
  55. package/src/Designer.tsx +107 -0
  56. package/src/Form.tsx +102 -0
  57. package/src/Viewer.tsx +59 -0
  58. package/src/class.ts +188 -0
  59. package/src/components/AppContextProvider.tsx +78 -0
  60. package/src/components/CtlBar.tsx +183 -0
  61. package/src/components/Designer/Canvas/Guides.tsx +49 -0
  62. package/src/components/Designer/Canvas/Mask.tsx +20 -0
  63. package/src/components/Designer/Canvas/Moveable.tsx +91 -0
  64. package/src/components/Designer/Canvas/Padding.tsx +56 -0
  65. package/src/components/Designer/Canvas/Selecto.tsx +45 -0
  66. package/src/components/Designer/Canvas/index.tsx +536 -0
  67. package/src/components/Designer/LeftSidebar.tsx +120 -0
  68. package/src/components/Designer/PluginIcon.tsx +87 -0
  69. package/src/components/Designer/RightSidebar/DetailView/AlignWidget.tsx +229 -0
  70. package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +78 -0
  71. package/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.tsx +28 -0
  72. package/src/components/Designer/RightSidebar/DetailView/index.tsx +469 -0
  73. package/src/components/Designer/RightSidebar/ListView/Item.tsx +158 -0
  74. package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +204 -0
  75. package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +88 -0
  76. package/src/components/Designer/RightSidebar/ListView/index.tsx +116 -0
  77. package/src/components/Designer/RightSidebar/index.tsx +72 -0
  78. package/src/components/Designer/RightSidebar/layout.tsx +75 -0
  79. package/src/components/Designer/index.tsx +389 -0
  80. package/src/components/ErrorScreen.tsx +33 -0
  81. package/src/components/Paper.tsx +117 -0
  82. package/src/components/Preview.tsx +220 -0
  83. package/src/components/Renderer.tsx +165 -0
  84. package/src/components/Root.tsx +38 -0
  85. package/src/components/Spinner.tsx +45 -0
  86. package/src/components/StaticSchema.tsx +50 -0
  87. package/src/components/UnitPager.tsx +119 -0
  88. package/src/constants.ts +21 -0
  89. package/src/contexts.ts +14 -0
  90. package/src/helper.ts +534 -0
  91. package/src/hooks.ts +308 -0
  92. package/src/i18n.ts +903 -0
  93. package/src/index.ts +5 -0
  94. package/src/theme.ts +20 -0
  95. package/src/types.ts +20 -0
  96. package/tsconfig.json +48 -0
  97. package/vite.config.mts +22 -0
package/src/helper.ts ADDED
@@ -0,0 +1,534 @@
1
+ import hotkeysJs from 'hotkeys-js';
2
+ import { useContext } from 'react';
3
+ import {
4
+ cloneDeep,
5
+ ZOOM,
6
+ getB64BasePdf,
7
+ b64toUint8Array,
8
+ Template,
9
+ BasePdf,
10
+ SchemaForUI,
11
+ Size,
12
+ isBlankPdf,
13
+ PluginRegistry,
14
+ } from '@pdfme/common';
15
+ import { pdf2size } from '@pdfme/converter';
16
+ import { DEFAULT_MAX_ZOOM, RULER_HEIGHT } from './constants.js';
17
+ import { OptionsContext } from './contexts.js';
18
+
19
+ // Define a type for the hotkeys function with additional properties
20
+ type HotkeysFunction = {
21
+ (keys: string, callback: (e: KeyboardEvent, handler: { shortcut: string }) => void): unknown;
22
+ shift: boolean;
23
+ unbind: (keys: string) => void;
24
+ };
25
+
26
+ // Create a simple mock for hotkeys to avoid TypeScript errors
27
+ const hotkeys = function (
28
+ keys: string,
29
+ callback: (e: KeyboardEvent, handler: { shortcut: string }) => void,
30
+ ) {
31
+ return hotkeysJs(keys, callback);
32
+ } as HotkeysFunction;
33
+
34
+ // Add properties to the hotkeys function
35
+ hotkeys.shift = false;
36
+ hotkeys.unbind = function (keys: string) {
37
+ // Do nothing if hotkeysJs doesn't have unbind
38
+ const hotkeysFn = hotkeysJs as unknown as { unbind?: (keys: string) => void };
39
+ if (typeof hotkeysFn.unbind === 'function') {
40
+ hotkeysFn.unbind(keys);
41
+ }
42
+ };
43
+
44
+ export const uuid = () =>
45
+ 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
46
+ const r = (Math.random() * 16) | 0;
47
+ const v = c == 'x' ? r : (r & 0x3) | 0x8;
48
+ return v.toString(16);
49
+ });
50
+
51
+ const set = <T extends object>(obj: T, path: string | string[], value: unknown) => {
52
+ path = Array.isArray(path) ? path : path.replace(/\[/g, '.').replace(/\]/g, '').split('.');
53
+ let src: Record<string, unknown> = obj as Record<string, unknown>;
54
+ path.forEach((key, index, array) => {
55
+ if (index == path.length - 1) {
56
+ src[key] = value;
57
+ } else {
58
+ if (!Object.prototype.hasOwnProperty.call(src, key)) {
59
+ const next = array[index + 1];
60
+ src[key] = String(Number(next)) === next ? [] : {};
61
+ }
62
+ src = src[key] as Record<string, unknown>;
63
+ }
64
+ });
65
+ };
66
+
67
+ export const debounce = <T extends (...args: unknown[]) => unknown>(cb: T, wait = 20) => {
68
+ let h: null | ReturnType<typeof setTimeout> = null;
69
+ const callable = (...args: Parameters<T>) => {
70
+ if (h) clearTimeout(h);
71
+ h = setTimeout(() => cb(...args), wait);
72
+ };
73
+ return callable as T;
74
+ };
75
+
76
+ const shift = (number: number, precision: number, reverseShift: boolean) => {
77
+ if (reverseShift) {
78
+ precision = -precision;
79
+ }
80
+ const numArray = `${number}`.split('e');
81
+
82
+ return Number(`${numArray[0]}e${numArray[1] ? Number(numArray[1]) + precision : precision}`);
83
+ };
84
+
85
+ export const round = (number: number, precision: number) => {
86
+ return shift(Math.round(shift(number, precision, false)), precision, true);
87
+ };
88
+
89
+ export const flatten = <T>(arr: T[][]): T[] => ([] as T[]).concat(...arr);
90
+
91
+ const up = 'up';
92
+ const shiftUp = 'shift+up';
93
+ const down = 'down';
94
+ const shiftDown = 'shift+down';
95
+ const left = 'left';
96
+ const shiftLeft = 'shift+left';
97
+ const right = 'right';
98
+ const shiftRight = 'shift+right';
99
+
100
+ const rmWin = 'backspace';
101
+ const rmMac = 'delete';
102
+ const esc = 'esc';
103
+ const copyWin = 'ctrl+c';
104
+ const copyMac = 'command+c';
105
+ const pasteWin = 'ctrl+v';
106
+ const pasteMac = 'command+v';
107
+ const redoWin = 'ctrl+y';
108
+ const redoMac = 'shift+command+z';
109
+ const undoWin = 'ctrl+z';
110
+ const undoMac = 'command+z';
111
+ const saveWin = 'ctrl+s';
112
+ const saveMac = 'command+s';
113
+ const selectAllWin = 'ctrl+a';
114
+ const selectAllMac = 'command+a';
115
+
116
+ const keys = [
117
+ up,
118
+ shiftUp,
119
+ down,
120
+ shiftDown,
121
+ left,
122
+ shiftLeft,
123
+ right,
124
+ shiftRight,
125
+ rmMac,
126
+ rmWin,
127
+ esc,
128
+ copyWin,
129
+ copyMac,
130
+ pasteWin,
131
+ pasteMac,
132
+ redoWin,
133
+ redoMac,
134
+ undoWin,
135
+ undoMac,
136
+ saveWin,
137
+ saveMac,
138
+ selectAllWin,
139
+ selectAllMac,
140
+ ];
141
+
142
+ export const initShortCuts = (arg: {
143
+ move: (command: 'up' | 'down' | 'left' | 'right', isShift: boolean) => void;
144
+ remove: () => void;
145
+ esc: () => void;
146
+ copy: () => void;
147
+ paste: () => void;
148
+ redo: () => void;
149
+ undo: () => void;
150
+ save: () => void;
151
+ selectAll: () => void;
152
+ }) => {
153
+ hotkeys(keys.join(), (e: KeyboardEvent, handler: { shortcut: string }) => {
154
+ switch (handler.shortcut) {
155
+ case up:
156
+ case shiftUp:
157
+ e.preventDefault();
158
+ arg.move('up', hotkeys.shift);
159
+ break;
160
+ case down:
161
+ case shiftDown:
162
+ e.preventDefault();
163
+ arg.move('down', hotkeys.shift);
164
+ break;
165
+ case left:
166
+ case shiftLeft:
167
+ e.preventDefault();
168
+ arg.move('left', hotkeys.shift);
169
+ break;
170
+ case right:
171
+ case shiftRight:
172
+ e.preventDefault();
173
+ arg.move('right', hotkeys.shift);
174
+ break;
175
+ case rmWin:
176
+ case rmMac:
177
+ arg.remove();
178
+ break;
179
+ case esc:
180
+ arg.esc();
181
+ break;
182
+ case copyWin:
183
+ case copyMac:
184
+ arg.copy();
185
+ break;
186
+ case pasteWin:
187
+ case pasteMac:
188
+ arg.paste();
189
+ break;
190
+ case redoWin:
191
+ case redoMac:
192
+ arg.redo();
193
+ break;
194
+ case undoWin:
195
+ case undoMac:
196
+ arg.undo();
197
+ break;
198
+ case saveWin:
199
+ case saveMac:
200
+ e.preventDefault();
201
+ arg.save();
202
+ break;
203
+ case selectAllWin:
204
+ case selectAllMac:
205
+ e.preventDefault();
206
+ arg.selectAll();
207
+ break;
208
+ default:
209
+ break;
210
+ }
211
+ });
212
+ };
213
+
214
+ export const destroyShortCuts = () => {
215
+ hotkeys.unbind(keys.join());
216
+ };
217
+
218
+ /**
219
+ * Guess the MIME type by checking the first few bytes of the ArrayBuffer.
220
+ * Currently checks for PNG, JPEG, and GIF signatures.
221
+ */
222
+ function detectMimeType(arrayBuffer: ArrayBuffer): string {
223
+ const dataView = new DataView(arrayBuffer);
224
+
225
+ // Check for PNG signature: 0x89 0x50 0x4E 0x47
226
+ if (
227
+ dataView.getUint8(0) === 0x89 &&
228
+ dataView.getUint8(1) === 0x50 &&
229
+ dataView.getUint8(2) === 0x4e &&
230
+ dataView.getUint8(3) === 0x47
231
+ ) {
232
+ return 'image/png';
233
+ }
234
+
235
+ // Check for JPEG signature: 0xFF 0xD8 0xFF
236
+ if (
237
+ dataView.getUint8(0) === 0xff &&
238
+ dataView.getUint8(1) === 0xd8 &&
239
+ dataView.getUint8(2) === 0xff
240
+ ) {
241
+ return 'image/jpeg';
242
+ }
243
+
244
+ return ''; // Unknown type
245
+ }
246
+
247
+ export const arrayBufferToBase64 = (arrayBuffer: ArrayBuffer): string => {
248
+ // Detect the MIME type
249
+ const mimeType = detectMimeType(arrayBuffer);
250
+
251
+ // Convert ArrayBuffer to raw Base64
252
+ const bytes = new Uint8Array(arrayBuffer);
253
+ let binary = '';
254
+ for (let i = 0; i < bytes.length; i++) {
255
+ binary += String.fromCharCode(bytes[i]);
256
+ }
257
+ const base64String = btoa(binary);
258
+
259
+ // Optionally prepend a data: URL if a known MIME type is found;
260
+ // otherwise just return the raw Base64.
261
+ if (mimeType) {
262
+ return `data:${mimeType};base64,${base64String}`;
263
+ } else {
264
+ // or you can default to `application/octet-stream` if unknown
265
+ return `data:application/octet-stream;base64,${base64String}`;
266
+ }
267
+ };
268
+
269
+ const convertSchemasForUI = (template: Template): SchemaForUI[][] => {
270
+ template.schemas.forEach((page) => {
271
+ page.forEach((schema) => {
272
+ (schema as SchemaForUI).id = uuid();
273
+ (schema as SchemaForUI).content = schema.content || '';
274
+ });
275
+ });
276
+
277
+ return template.schemas as SchemaForUI[][];
278
+ };
279
+
280
+ export const template2SchemasList = async (_template: Template) => {
281
+ const template = cloneDeep(_template);
282
+ const { basePdf, schemas } = template;
283
+ const schemasForUI = convertSchemasForUI(template);
284
+
285
+ let pageSizes: Size[] = [];
286
+ if (isBlankPdf(basePdf)) {
287
+ pageSizes = schemas.map(() => ({
288
+ width: basePdf.width,
289
+ height: basePdf.height,
290
+ }));
291
+ } else {
292
+ const b64BasePdf = await getB64BasePdf(basePdf);
293
+ // pdf2size accepts both ArrayBuffer and Uint8Array
294
+ const pdfArrayBuffer = b64toUint8Array(b64BasePdf);
295
+
296
+ pageSizes = await pdf2size(pdfArrayBuffer);
297
+ }
298
+
299
+ const ssl = schemasForUI.length;
300
+ const psl = pageSizes.length;
301
+
302
+ return (
303
+ ssl < psl
304
+ ? schemasForUI.concat(new Array(psl - ssl).fill(cloneDeep([])))
305
+ : schemasForUI.slice(0, pageSizes.length)
306
+ ).map((schema, i) => {
307
+ Object.values(schema).forEach((value) => {
308
+ const { width, height } = pageSizes[i];
309
+ const xEdge = value.position.x + value.width;
310
+ const yEdge = value.position.y + value.height;
311
+ if (xEdge > width) {
312
+ value.position.x = Math.max(0, width - value.width);
313
+ }
314
+ if (yEdge > height) {
315
+ value.position.y = Math.max(0, height - value.height);
316
+ }
317
+ });
318
+
319
+ return schema;
320
+ });
321
+ };
322
+
323
+ export const schemasList2template = (schemasList: SchemaForUI[][], basePdf: BasePdf): Template => ({
324
+ schemas: cloneDeep(schemasList).map((page) =>
325
+ page.map((schema) => {
326
+ // @ts-expect-error Property 'id' is used only in UI
327
+ delete schema.id;
328
+ return schema;
329
+ }),
330
+ ),
331
+ basePdf,
332
+ });
333
+
334
+ export const getUniqueSchemaName = (arg: {
335
+ copiedSchemaName: string;
336
+ schema: SchemaForUI[];
337
+ stackUniqueSchemaNames: string[];
338
+ }) => {
339
+ const { copiedSchemaName, schema, stackUniqueSchemaNames } = arg;
340
+ const schemaNames = schema.map((s) => s.name).concat(stackUniqueSchemaNames);
341
+ const tmp: { [originalName: string]: number } = schemaNames.reduce(
342
+ (acc, cur) => Object.assign(acc, { originalName: cur, copiedNum: 0 }),
343
+ {},
344
+ );
345
+ const extractOriginalName = (name: string) => name.replace(/ copy$| copy [0-9]*$/, '');
346
+ schemaNames
347
+ .filter((name) => / copy$| copy [0-9]*$/.test(name))
348
+ .forEach((name) => {
349
+ const originalName = extractOriginalName(name);
350
+ const match = name.match(/[0-9]*$/);
351
+ const copiedNum = match && match[0] ? Number(match[0]) : 1;
352
+ if ((tmp[originalName] ?? 0) < copiedNum) {
353
+ tmp[originalName] = copiedNum;
354
+ }
355
+ });
356
+
357
+ const originalName = extractOriginalName(copiedSchemaName);
358
+ if (tmp[originalName]) {
359
+ const copiedNum = tmp[originalName];
360
+ const uniqueName = `${originalName} copy ${copiedNum + 1}`;
361
+ stackUniqueSchemaNames.push(uniqueName);
362
+
363
+ return uniqueName;
364
+ }
365
+ const uniqueName = `${copiedSchemaName} copy`;
366
+ stackUniqueSchemaNames.push(uniqueName);
367
+
368
+ return uniqueName;
369
+ };
370
+
371
+ export const moveCommandToChangeSchemasArg = (props: {
372
+ command: 'up' | 'down' | 'left' | 'right';
373
+ activeSchemas: SchemaForUI[];
374
+ isShift: boolean;
375
+ pageSize: Size;
376
+ }) => {
377
+ const { command, activeSchemas, isShift, pageSize } = props;
378
+ const key = command === 'up' || command === 'down' ? 'y' : 'x';
379
+ const num = isShift ? 0.1 : 1;
380
+
381
+ const getValue = (as: SchemaForUI) => {
382
+ let value = 0;
383
+ const { position } = as;
384
+ switch (command) {
385
+ case 'up':
386
+ value = round(position.y - num, 2);
387
+ break;
388
+ case 'down':
389
+ value = round(position.y + num, 2);
390
+ break;
391
+ case 'left':
392
+ value = round(position.x - num, 2);
393
+ break;
394
+ case 'right':
395
+ value = round(position.x + num, 2);
396
+ break;
397
+ default:
398
+ break;
399
+ }
400
+
401
+ return value > 0 ? value : 0;
402
+ };
403
+
404
+ return activeSchemas.map((as) => {
405
+ let value = getValue(as);
406
+ const { width, height } = as;
407
+ if (key === 'x') {
408
+ value = value > pageSize.width - width ? round(pageSize.width - width, 2) : value;
409
+ } else {
410
+ value = value > pageSize.height - height ? round(pageSize.height - height, 2) : value;
411
+ }
412
+
413
+ return { key: `position.${key}`, value, schemaId: as.id };
414
+ });
415
+ };
416
+
417
+ export const getPagesScrollTopByIndex = (pageSizes: Size[], index: number, scale: number) => {
418
+ return pageSizes
419
+ .slice(0, index)
420
+ .reduce((acc, cur) => acc + (cur.height * ZOOM + RULER_HEIGHT * scale) * scale, 0);
421
+ };
422
+
423
+ const handlePositionSizeChange = (
424
+ schema: SchemaForUI,
425
+ key: string,
426
+ value: unknown,
427
+ basePdf: BasePdf,
428
+ pageSize: Size,
429
+ ) => {
430
+ const padding = isBlankPdf(basePdf) ? basePdf.padding : [0, 0, 0, 0];
431
+ const [pt, pr, pb, pl] = padding;
432
+ const { width: pw, height: ph } = pageSize;
433
+ const calcBounds = (v: unknown, min: number, max: number) =>
434
+ Math.min(Math.max(Number(v), min), max);
435
+ if (key === 'position.x') {
436
+ schema.position.x = calcBounds(value, pl, pw - schema.width - pr);
437
+ } else if (key === 'position.y') {
438
+ schema.position.y = calcBounds(value, pt, ph - schema.height - pb);
439
+ } else if (key === 'width') {
440
+ schema.width = calcBounds(value, 0, pw - schema.position.x - pr);
441
+ } else if (key === 'height') {
442
+ schema.height = calcBounds(value, 0, ph - schema.position.y - pb);
443
+ }
444
+ };
445
+
446
+ const handleTypeChange = (
447
+ schema: SchemaForUI,
448
+ key: string,
449
+ value: unknown,
450
+ pluginsRegistry: PluginRegistry,
451
+ ) => {
452
+ if (key !== 'type') return;
453
+ const keysToKeep = ['id', 'name', 'type', 'position', 'required'];
454
+ Object.keys(schema).forEach((key) => {
455
+ if (!keysToKeep.includes(key)) {
456
+ delete schema[key as keyof typeof schema];
457
+ }
458
+ });
459
+
460
+ const plugin = pluginsRegistry.findByType(value as string);
461
+
462
+ // Apply default schema properties if available
463
+ if (plugin?.propPanel.defaultSchema) {
464
+ const defaultSchema = plugin.propPanel.defaultSchema;
465
+ const schemaRecord = schema as Record<string, unknown>;
466
+
467
+ // Use a type-safe approach to copy properties
468
+ for (const key of Object.keys(defaultSchema)) {
469
+ // Only add properties that don't already exist in the schema
470
+ if (!Object.prototype.hasOwnProperty.call(schema, key)) {
471
+ const propertyValue = defaultSchema[key];
472
+ if (propertyValue !== undefined) {
473
+ schemaRecord[key] = propertyValue;
474
+ }
475
+ }
476
+ }
477
+ }
478
+ if (schema.readOnly) {
479
+ schema.required = false;
480
+ }
481
+ };
482
+
483
+ export const changeSchemas = (args: {
484
+ objs: { key: string; value: unknown; schemaId: string }[];
485
+ schemas: SchemaForUI[];
486
+ basePdf: BasePdf;
487
+ pluginsRegistry: PluginRegistry;
488
+ pageSize: { width: number; height: number };
489
+ commitSchemas: (newSchemas: SchemaForUI[]) => void;
490
+ }) => {
491
+ const { objs, schemas, basePdf, pluginsRegistry, pageSize, commitSchemas } = args;
492
+ const newSchemas = objs.reduce((acc, { key, value, schemaId }) => {
493
+ const tgt = acc.find((s) => s.id === schemaId);
494
+ if (!tgt) return acc;
495
+ // Assign to reference
496
+ set(tgt, key, value);
497
+
498
+ if (key === 'type') {
499
+ handleTypeChange(tgt, key, value, pluginsRegistry);
500
+ } else if (['position.x', 'position.y', 'width', 'height'].includes(key)) {
501
+ handlePositionSizeChange(tgt, key, value, basePdf, pageSize);
502
+ }
503
+
504
+ return acc;
505
+ }, cloneDeep(schemas));
506
+ commitSchemas(newSchemas);
507
+ };
508
+
509
+ export const useMaxZoom = () => {
510
+ const options = useContext(OptionsContext);
511
+
512
+ return options.maxZoom ? options.maxZoom / 100 : DEFAULT_MAX_ZOOM;
513
+ };
514
+
515
+ export const setFontNameRecursively = (
516
+ obj: Record<string, unknown>,
517
+ fontName: string,
518
+ seen = new WeakSet(),
519
+ ): void => {
520
+ if (!obj || typeof obj !== 'object' || seen.has(obj)) return;
521
+ seen.add(obj);
522
+
523
+ for (const key in obj) {
524
+ if (
525
+ key === 'fontName' &&
526
+ Object.prototype.hasOwnProperty.call(obj, key) &&
527
+ obj[key] === undefined
528
+ ) {
529
+ obj[key] = fontName;
530
+ } else if (typeof obj[key] === 'object' && obj[key] !== null) {
531
+ setFontNameRecursively(obj[key] as Record<string, unknown>, fontName, seen);
532
+ }
533
+ }
534
+ };