@stll/folio-react 0.5.0 → 0.6.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.
@@ -0,0 +1,1080 @@
1
+ import { o as useFolioUI } from "./useFindReplace-BPBpMWS9.js";
2
+ import { useEffect, useId, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/dialogs/dialogChrome.ts
5
+ const DIALOG_BACKDROP_CLASS = "fixed inset-0 z-[10000] bg-black/50";
6
+ const DIALOG_POPUP_CLASS = "bg-popover fixed start-1/2 top-1/2 z-[10001] w-full max-w-[520px] min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-xl";
7
+ const DIALOG_TITLE_CLASS = "border-b px-5 py-3 text-base font-semibold";
8
+ const DIALOG_BODY_CLASS = "flex flex-col gap-4 px-5 py-4";
9
+ const DIALOG_FOOTER_CLASS = "flex justify-end gap-2 border-t px-5 py-3";
10
+ const DIALOG_LABEL_CLASS = "text-muted-foreground text-[13px]";
11
+ const DIALOG_INPUT_CLASS = "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none";
12
+ const DIALOG_SECONDARY_BUTTON_CLASS = "border-input rounded border px-4 py-1.5 text-[13px]";
13
+ const DIALOG_PRIMARY_BUTTON_CLASS = "bg-primary text-primary-foreground rounded px-4 py-1.5 text-[13px] font-medium disabled:cursor-not-allowed disabled:opacity-50";
14
+ //#endregion
15
+ //#region src/components/dialogs/HyperlinkDialog.tsx
16
+ function HyperlinkDialog({ isOpen, onClose, onSubmit, onRemove, currentData, selectedText, bookmarks = [] }) {
17
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
18
+ const id = useId();
19
+ const [targetType, setTargetType] = useState("url");
20
+ const [url, setUrl] = useState("");
21
+ const [bookmark, setBookmark] = useState("");
22
+ const [displayText, setDisplayText] = useState("");
23
+ const [tooltip, setTooltip] = useState("");
24
+ const [touched, setTouched] = useState(false);
25
+ useEffect(() => {
26
+ if (!isOpen) {
27
+ setTouched(false);
28
+ return;
29
+ }
30
+ const href = currentData?.href ?? "";
31
+ if (href.startsWith("#")) {
32
+ setTargetType("bookmark");
33
+ setBookmark(href.slice(1));
34
+ setUrl("");
35
+ } else {
36
+ setTargetType("url");
37
+ setUrl(href);
38
+ setBookmark("");
39
+ }
40
+ setDisplayText(currentData?.displayText ?? selectedText ?? "");
41
+ setTooltip(currentData?.tooltip ?? "");
42
+ }, [
43
+ isOpen,
44
+ currentData,
45
+ selectedText
46
+ ]);
47
+ const urlError = targetType === "url" ? getUrlError(url) : "";
48
+ const canSubmit = targetType === "bookmark" ? bookmark.trim().length > 0 : url.trim().length > 0 && urlError.length === 0;
49
+ const isEditing = Boolean(currentData?.href);
50
+ const fieldIds = {
51
+ targetType: `${id}-hyperlink-target-type`,
52
+ url: `${id}-hyperlink-url`,
53
+ bookmark: `${id}-hyperlink-bookmark`,
54
+ displayText: `${id}-hyperlink-display-text`,
55
+ tooltip: `${id}-hyperlink-tooltip`
56
+ };
57
+ const handleSubmit = () => {
58
+ setTouched(true);
59
+ if (!canSubmit) return;
60
+ const href = targetType === "bookmark" ? `#${bookmark.trim()}` : normalizeUrl(url);
61
+ onSubmit({
62
+ href,
63
+ displayText: displayText.trim() || href,
64
+ ...tooltip.trim() ? { tooltip: tooltip.trim() } : {}
65
+ });
66
+ onClose();
67
+ };
68
+ const handleRemove = () => {
69
+ onRemove?.();
70
+ onClose();
71
+ };
72
+ return /* @__PURE__ */ jsx(Dialog, {
73
+ open: isOpen,
74
+ onOpenChange: (open) => {
75
+ if (!open) onClose();
76
+ },
77
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
78
+ className: DIALOG_POPUP_CLASS,
79
+ children: [
80
+ /* @__PURE__ */ jsx(DialogTitle, {
81
+ className: DIALOG_TITLE_CLASS,
82
+ children: isEditing ? "Edit Hyperlink" : "Insert Hyperlink"
83
+ }),
84
+ /* @__PURE__ */ jsxs("div", {
85
+ className: DIALOG_BODY_CLASS,
86
+ children: [
87
+ (bookmarks.length > 0 || targetType === "bookmark") && /* @__PURE__ */ jsxs("label", {
88
+ className: "flex flex-col gap-1",
89
+ htmlFor: fieldIds.targetType,
90
+ children: [/* @__PURE__ */ jsx("span", {
91
+ className: "text-muted-foreground text-[13px]",
92
+ children: "Link to"
93
+ }), /* @__PURE__ */ jsxs("select", {
94
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
95
+ id: fieldIds.targetType,
96
+ onChange: (e) => setTargetType(toTargetType(e.target.value)),
97
+ value: targetType,
98
+ children: [/* @__PURE__ */ jsx("option", {
99
+ value: "url",
100
+ children: "Web address"
101
+ }), /* @__PURE__ */ jsx("option", {
102
+ value: "bookmark",
103
+ children: "Bookmark"
104
+ })]
105
+ })]
106
+ }),
107
+ targetType === "url" && /* @__PURE__ */ jsxs("label", {
108
+ className: "flex flex-col gap-1",
109
+ htmlFor: fieldIds.url,
110
+ children: [
111
+ /* @__PURE__ */ jsx("span", {
112
+ className: "text-muted-foreground text-[13px]",
113
+ children: "Address"
114
+ }),
115
+ /* @__PURE__ */ jsx("input", {
116
+ "aria-invalid": touched && urlError.length > 0,
117
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
118
+ id: fieldIds.url,
119
+ onBlur: () => setTouched(true),
120
+ onChange: (e) => setUrl(e.target.value),
121
+ placeholder: "https://example.com",
122
+ value: url
123
+ }),
124
+ touched && urlError.length > 0 && /* @__PURE__ */ jsx("span", {
125
+ className: "text-destructive text-xs",
126
+ children: urlError
127
+ })
128
+ ]
129
+ }),
130
+ targetType === "bookmark" && /* @__PURE__ */ jsxs("label", {
131
+ className: "flex flex-col gap-1",
132
+ htmlFor: fieldIds.bookmark,
133
+ children: [/* @__PURE__ */ jsx("span", {
134
+ className: "text-muted-foreground text-[13px]",
135
+ children: "Bookmark"
136
+ }), /* @__PURE__ */ jsxs("select", {
137
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
138
+ id: fieldIds.bookmark,
139
+ onChange: (e) => setBookmark(e.target.value),
140
+ value: bookmark,
141
+ children: [/* @__PURE__ */ jsx("option", {
142
+ value: "",
143
+ children: "Select a bookmark"
144
+ }), bookmarks.map((option) => /* @__PURE__ */ jsx("option", {
145
+ value: option.name,
146
+ children: option.label ?? option.name
147
+ }, option.name))]
148
+ })]
149
+ }),
150
+ /* @__PURE__ */ jsxs("label", {
151
+ className: "flex flex-col gap-1",
152
+ htmlFor: fieldIds.displayText,
153
+ children: [/* @__PURE__ */ jsx("span", {
154
+ className: DIALOG_LABEL_CLASS,
155
+ children: "Text to display"
156
+ }), /* @__PURE__ */ jsx("input", {
157
+ className: DIALOG_INPUT_CLASS,
158
+ id: fieldIds.displayText,
159
+ onChange: (e) => setDisplayText(e.target.value),
160
+ value: displayText
161
+ })]
162
+ }),
163
+ /* @__PURE__ */ jsxs("label", {
164
+ className: "flex flex-col gap-1",
165
+ htmlFor: fieldIds.tooltip,
166
+ children: [/* @__PURE__ */ jsx("span", {
167
+ className: DIALOG_LABEL_CLASS,
168
+ children: "Screen tip"
169
+ }), /* @__PURE__ */ jsx("input", {
170
+ className: DIALOG_INPUT_CLASS,
171
+ id: fieldIds.tooltip,
172
+ onChange: (e) => setTooltip(e.target.value),
173
+ value: tooltip
174
+ })]
175
+ })
176
+ ]
177
+ }),
178
+ /* @__PURE__ */ jsxs("div", {
179
+ className: DIALOG_FOOTER_CLASS,
180
+ children: [
181
+ isEditing && onRemove && /* @__PURE__ */ jsx("button", {
182
+ className: "text-destructive me-auto rounded px-4 py-1.5 text-[13px]",
183
+ onClick: handleRemove,
184
+ type: "button",
185
+ children: "Remove"
186
+ }),
187
+ /* @__PURE__ */ jsx(DialogClose, {
188
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
189
+ children: "Cancel"
190
+ }),
191
+ /* @__PURE__ */ jsx("button", {
192
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
193
+ disabled: !canSubmit,
194
+ onClick: handleSubmit,
195
+ type: "button",
196
+ children: isEditing ? "Update" : "Insert"
197
+ })
198
+ ]
199
+ })
200
+ ]
201
+ })] })
202
+ });
203
+ }
204
+ function toTargetType(value) {
205
+ return value === "bookmark" ? "bookmark" : "url";
206
+ }
207
+ function getUrlError(value) {
208
+ const trimmed = value.trim();
209
+ if (!trimmed) return "Enter an address.";
210
+ if (/^(mailto:|tel:)/iu.test(trimmed)) return trimmed.replace(/^(mailto:|tel:)/iu, "").length > 0 ? "" : "Enter an address.";
211
+ if (/^ftp:\/\//iu.test(trimmed)) return trimmed.length > 6 ? "" : "Enter an address.";
212
+ try {
213
+ const parsed = new URL(/^https?:\/\//iu.test(trimmed) ? trimmed : `https://${trimmed}`);
214
+ return parsed.protocol === "http:" || parsed.protocol === "https:" ? "" : "Use a web address.";
215
+ } catch {
216
+ return "Enter a valid address.";
217
+ }
218
+ }
219
+ function normalizeUrl(value) {
220
+ const trimmed = value.trim();
221
+ if (/^(https?:\/\/|mailto:|tel:|ftp:\/\/)/iu.test(trimmed)) return trimmed;
222
+ return `https://${trimmed}`;
223
+ }
224
+ //#endregion
225
+ //#region src/components/dialogs/InsertImageDialog.tsx
226
+ function InsertImageDialog({ isOpen, onClose, onInsert, accept = "image/png,image/jpeg,image/gif,image/webp" }) {
227
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
228
+ const id = useId();
229
+ const [file, setFile] = useState(null);
230
+ const [alt, setAlt] = useState("");
231
+ const [width, setWidth] = useState("");
232
+ const [height, setHeight] = useState("");
233
+ useEffect(() => {
234
+ if (!isOpen) return;
235
+ setFile(null);
236
+ setAlt("");
237
+ setWidth("");
238
+ setHeight("");
239
+ }, [isOpen]);
240
+ const fieldIds = {
241
+ file: `${id}-insert-image-file`,
242
+ alt: `${id}-insert-image-alt`,
243
+ width: `${id}-insert-image-width`,
244
+ height: `${id}-insert-image-height`
245
+ };
246
+ const handleInsert = () => {
247
+ if (!file) return;
248
+ const parsedWidth = toPositiveNumber(width);
249
+ const parsedHeight = toPositiveNumber(height);
250
+ onInsert({
251
+ file,
252
+ ...alt.trim() ? { alt: alt.trim() } : {},
253
+ ...parsedWidth ? { width: parsedWidth } : {},
254
+ ...parsedHeight ? { height: parsedHeight } : {}
255
+ });
256
+ onClose();
257
+ };
258
+ return /* @__PURE__ */ jsx(Dialog, {
259
+ open: isOpen,
260
+ onOpenChange: (open) => {
261
+ if (!open) onClose();
262
+ },
263
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
264
+ className: DIALOG_POPUP_CLASS,
265
+ children: [
266
+ /* @__PURE__ */ jsx(DialogTitle, {
267
+ className: DIALOG_TITLE_CLASS,
268
+ children: "Insert Image"
269
+ }),
270
+ /* @__PURE__ */ jsxs("div", {
271
+ className: DIALOG_BODY_CLASS,
272
+ children: [
273
+ /* @__PURE__ */ jsxs("label", {
274
+ className: "flex flex-col gap-1",
275
+ htmlFor: fieldIds.file,
276
+ children: [/* @__PURE__ */ jsx("span", {
277
+ className: DIALOG_LABEL_CLASS,
278
+ children: "Image file"
279
+ }), /* @__PURE__ */ jsx("input", {
280
+ accept,
281
+ className: DIALOG_INPUT_CLASS,
282
+ id: fieldIds.file,
283
+ onChange: (e) => setFile(e.target.files?.[0] ?? null),
284
+ type: "file"
285
+ })]
286
+ }),
287
+ /* @__PURE__ */ jsxs("label", {
288
+ className: "flex flex-col gap-1",
289
+ htmlFor: fieldIds.alt,
290
+ children: [/* @__PURE__ */ jsx("span", {
291
+ className: DIALOG_LABEL_CLASS,
292
+ children: "Alt text"
293
+ }), /* @__PURE__ */ jsx("input", {
294
+ className: DIALOG_INPUT_CLASS,
295
+ id: fieldIds.alt,
296
+ onChange: (e) => setAlt(e.target.value),
297
+ placeholder: file?.name ?? "",
298
+ value: alt
299
+ })]
300
+ }),
301
+ /* @__PURE__ */ jsxs("div", {
302
+ className: "grid grid-cols-2 gap-3",
303
+ children: [/* @__PURE__ */ jsxs("label", {
304
+ className: "flex flex-col gap-1",
305
+ htmlFor: fieldIds.width,
306
+ children: [/* @__PURE__ */ jsx("span", {
307
+ className: DIALOG_LABEL_CLASS,
308
+ children: "Width"
309
+ }), /* @__PURE__ */ jsx("input", {
310
+ className: DIALOG_INPUT_CLASS,
311
+ id: fieldIds.width,
312
+ min: 1,
313
+ onChange: (e) => setWidth(e.target.value),
314
+ placeholder: "Auto",
315
+ type: "number",
316
+ value: width
317
+ })]
318
+ }), /* @__PURE__ */ jsxs("label", {
319
+ className: "flex flex-col gap-1",
320
+ htmlFor: fieldIds.height,
321
+ children: [/* @__PURE__ */ jsx("span", {
322
+ className: DIALOG_LABEL_CLASS,
323
+ children: "Height"
324
+ }), /* @__PURE__ */ jsx("input", {
325
+ className: DIALOG_INPUT_CLASS,
326
+ id: fieldIds.height,
327
+ min: 1,
328
+ onChange: (e) => setHeight(e.target.value),
329
+ placeholder: "Auto",
330
+ type: "number",
331
+ value: height
332
+ })]
333
+ })]
334
+ })
335
+ ]
336
+ }),
337
+ /* @__PURE__ */ jsxs("div", {
338
+ className: DIALOG_FOOTER_CLASS,
339
+ children: [/* @__PURE__ */ jsx(DialogClose, {
340
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
341
+ children: "Cancel"
342
+ }), /* @__PURE__ */ jsx("button", {
343
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
344
+ disabled: !file,
345
+ onClick: handleInsert,
346
+ type: "button",
347
+ children: "Insert"
348
+ })]
349
+ })
350
+ ]
351
+ })] })
352
+ });
353
+ }
354
+ function toPositiveNumber(value) {
355
+ const parsed = Number(value);
356
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
357
+ }
358
+ //#endregion
359
+ //#region src/components/dialogs/InsertTableDialog.tsx
360
+ const GRID_ROWS = 8;
361
+ const GRID_COLUMNS = 10;
362
+ const MIN_ROWS = 1;
363
+ const MAX_ROWS = 100;
364
+ const MIN_COLUMNS = 1;
365
+ const MAX_COLUMNS = 20;
366
+ const DEFAULT_STYLE_OPTIONS = [
367
+ {
368
+ id: "TableGrid",
369
+ name: "Table Grid"
370
+ },
371
+ {
372
+ id: "TableNormal",
373
+ name: "Normal Table"
374
+ },
375
+ {
376
+ id: "TableGridLight",
377
+ name: "Grid Table Light"
378
+ }
379
+ ];
380
+ function InsertTableDialog({ isOpen, onClose, onInsert, defaultRows = 3, defaultColumns = 3, styleOptions = DEFAULT_STYLE_OPTIONS }) {
381
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
382
+ const id = useId();
383
+ const [rows, setRows] = useState(defaultRows);
384
+ const [columns, setColumns] = useState(defaultColumns);
385
+ const [hoverRows, setHoverRows] = useState(0);
386
+ const [hoverColumns, setHoverColumns] = useState(0);
387
+ const [autofit, setAutofit] = useState(false);
388
+ const [styleId, setStyleId] = useState("");
389
+ useEffect(() => {
390
+ if (!isOpen) return;
391
+ setRows(defaultRows);
392
+ setColumns(defaultColumns);
393
+ setHoverRows(0);
394
+ setHoverColumns(0);
395
+ setAutofit(false);
396
+ setStyleId("");
397
+ }, [
398
+ isOpen,
399
+ defaultRows,
400
+ defaultColumns
401
+ ]);
402
+ const normalizedRows = clampInteger$1(rows, MIN_ROWS, MAX_ROWS);
403
+ const normalizedColumns = clampInteger$1(columns, MIN_COLUMNS, MAX_COLUMNS);
404
+ const canInsert = Number.isFinite(rows) && Number.isFinite(columns);
405
+ const fieldIds = {
406
+ rows: `${id}-insert-table-rows`,
407
+ columns: `${id}-insert-table-columns`,
408
+ autofit: `${id}-insert-table-autofit`,
409
+ style: `${id}-insert-table-style`
410
+ };
411
+ const handleInsert = () => {
412
+ if (!canInsert) return;
413
+ onInsert({
414
+ rows: normalizedRows,
415
+ columns: normalizedColumns,
416
+ autofit,
417
+ ...styleId ? { styleId } : {}
418
+ });
419
+ onClose();
420
+ };
421
+ return /* @__PURE__ */ jsx(Dialog, {
422
+ open: isOpen,
423
+ onOpenChange: (open) => {
424
+ if (!open) onClose();
425
+ },
426
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
427
+ className: DIALOG_POPUP_CLASS,
428
+ children: [
429
+ /* @__PURE__ */ jsx(DialogTitle, {
430
+ className: DIALOG_TITLE_CLASS,
431
+ children: "Insert Table"
432
+ }),
433
+ /* @__PURE__ */ jsxs("div", {
434
+ className: DIALOG_BODY_CLASS,
435
+ children: [
436
+ /* @__PURE__ */ jsx("div", {
437
+ className: "grid w-max grid-cols-10 gap-1",
438
+ onMouseLeave: () => {
439
+ setHoverRows(0);
440
+ setHoverColumns(0);
441
+ },
442
+ children: Array.from({ length: GRID_ROWS }).map((_, rowIndex) => Array.from({ length: GRID_COLUMNS }).map((__, columnIndex) => {
443
+ const row = rowIndex + 1;
444
+ const column = columnIndex + 1;
445
+ const active = row <= hoverRows && column <= hoverColumns;
446
+ return /* @__PURE__ */ jsx("button", {
447
+ "aria-label": `${row} by ${column} table`,
448
+ className: `h-5 w-5 rounded-sm border ${active ? "border-primary bg-primary/20" : "border-input bg-background"}`,
449
+ onClick: () => {
450
+ setRows(row);
451
+ setColumns(column);
452
+ },
453
+ onMouseEnter: () => {
454
+ setHoverRows(row);
455
+ setHoverColumns(column);
456
+ },
457
+ type: "button"
458
+ }, `${row}-${column}`);
459
+ }))
460
+ }),
461
+ /* @__PURE__ */ jsx("div", {
462
+ className: "text-muted-foreground text-xs",
463
+ children: hoverRows > 0 ? `${hoverColumns} columns x ${hoverRows} rows` : `${normalizedColumns} columns x ${normalizedRows} rows`
464
+ }),
465
+ /* @__PURE__ */ jsxs("div", {
466
+ className: "grid grid-cols-2 gap-3",
467
+ children: [/* @__PURE__ */ jsxs("label", {
468
+ className: "flex flex-col gap-1",
469
+ htmlFor: fieldIds.rows,
470
+ children: [/* @__PURE__ */ jsx("span", {
471
+ className: DIALOG_LABEL_CLASS,
472
+ children: "Rows"
473
+ }), /* @__PURE__ */ jsx("input", {
474
+ className: DIALOG_INPUT_CLASS,
475
+ id: fieldIds.rows,
476
+ max: MAX_ROWS,
477
+ min: MIN_ROWS,
478
+ onChange: (e) => setRows(Number(e.target.value) || MIN_ROWS),
479
+ type: "number",
480
+ value: rows
481
+ })]
482
+ }), /* @__PURE__ */ jsxs("label", {
483
+ className: "flex flex-col gap-1",
484
+ htmlFor: fieldIds.columns,
485
+ children: [/* @__PURE__ */ jsx("span", {
486
+ className: DIALOG_LABEL_CLASS,
487
+ children: "Columns"
488
+ }), /* @__PURE__ */ jsx("input", {
489
+ className: DIALOG_INPUT_CLASS,
490
+ id: fieldIds.columns,
491
+ max: MAX_COLUMNS,
492
+ min: MIN_COLUMNS,
493
+ onChange: (e) => setColumns(Number(e.target.value) || MIN_COLUMNS),
494
+ type: "number",
495
+ value: columns
496
+ })]
497
+ })]
498
+ }),
499
+ /* @__PURE__ */ jsxs("label", {
500
+ className: "flex items-center gap-2",
501
+ htmlFor: fieldIds.autofit,
502
+ children: [/* @__PURE__ */ jsx("input", {
503
+ checked: autofit,
504
+ id: fieldIds.autofit,
505
+ onChange: (e) => setAutofit(e.target.checked),
506
+ type: "checkbox"
507
+ }), /* @__PURE__ */ jsx("span", {
508
+ className: DIALOG_LABEL_CLASS,
509
+ children: "Autofit to contents"
510
+ })]
511
+ }),
512
+ styleOptions.length > 0 && /* @__PURE__ */ jsxs("label", {
513
+ className: "flex flex-col gap-1",
514
+ htmlFor: fieldIds.style,
515
+ children: [/* @__PURE__ */ jsx("span", {
516
+ className: "text-muted-foreground text-[13px]",
517
+ children: "Style"
518
+ }), /* @__PURE__ */ jsxs("select", {
519
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
520
+ id: fieldIds.style,
521
+ onChange: (e) => setStyleId(e.target.value),
522
+ value: styleId,
523
+ children: [/* @__PURE__ */ jsx("option", {
524
+ value: "",
525
+ children: "Plain table"
526
+ }), styleOptions.map((style) => /* @__PURE__ */ jsx("option", {
527
+ value: style.id,
528
+ children: style.name
529
+ }, style.id))]
530
+ })]
531
+ })
532
+ ]
533
+ }),
534
+ /* @__PURE__ */ jsxs("div", {
535
+ className: DIALOG_FOOTER_CLASS,
536
+ children: [/* @__PURE__ */ jsx(DialogClose, {
537
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
538
+ children: "Cancel"
539
+ }), /* @__PURE__ */ jsx("button", {
540
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
541
+ disabled: !canInsert,
542
+ onClick: handleInsert,
543
+ type: "button",
544
+ children: "Insert"
545
+ })]
546
+ })
547
+ ]
548
+ })] })
549
+ });
550
+ }
551
+ function clampInteger$1(value, min, max) {
552
+ return Math.min(max, Math.max(min, Math.trunc(value)));
553
+ }
554
+ //#endregion
555
+ //#region src/components/dialogs/PasteSpecialDialog.tsx
556
+ const PASTE_MODES = [
557
+ {
558
+ value: "keepFormatting",
559
+ label: "Keep source formatting",
560
+ description: "Preserve styles, links, and inline formatting from the clipboard."
561
+ },
562
+ {
563
+ value: "mergeFormatting",
564
+ label: "Merge formatting",
565
+ description: "Keep useful inline formatting while matching the destination paragraph."
566
+ },
567
+ {
568
+ value: "plainText",
569
+ label: "Unformatted text",
570
+ description: "Paste text only and use the destination formatting."
571
+ }
572
+ ];
573
+ function PasteSpecialDialog({ isOpen, onClose, onPaste, defaultMode = "plainText" }) {
574
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
575
+ const id = useId();
576
+ const [mode, setMode] = useState(defaultMode);
577
+ useEffect(() => {
578
+ if (isOpen) setMode(defaultMode);
579
+ }, [isOpen, defaultMode]);
580
+ const handlePaste = () => {
581
+ onPaste(mode);
582
+ onClose();
583
+ };
584
+ return /* @__PURE__ */ jsx(Dialog, {
585
+ open: isOpen,
586
+ onOpenChange: (open) => {
587
+ if (!open) onClose();
588
+ },
589
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
590
+ className: DIALOG_POPUP_CLASS,
591
+ children: [
592
+ /* @__PURE__ */ jsx(DialogTitle, {
593
+ className: DIALOG_TITLE_CLASS,
594
+ children: "Paste Special"
595
+ }),
596
+ /* @__PURE__ */ jsx("div", {
597
+ className: DIALOG_BODY_CLASS,
598
+ children: /* @__PURE__ */ jsxs("fieldset", {
599
+ className: "flex flex-col gap-3",
600
+ children: [/* @__PURE__ */ jsx("legend", {
601
+ className: DIALOG_LABEL_CLASS,
602
+ children: "Paste mode"
603
+ }), PASTE_MODES.map((option) => {
604
+ const optionId = `${id}-paste-special-${option.value}`;
605
+ return /* @__PURE__ */ jsxs("label", {
606
+ className: "flex items-start gap-3",
607
+ htmlFor: optionId,
608
+ children: [/* @__PURE__ */ jsx("input", {
609
+ checked: mode === option.value,
610
+ id: optionId,
611
+ onChange: () => setMode(option.value),
612
+ type: "radio"
613
+ }), /* @__PURE__ */ jsxs("span", {
614
+ className: "flex flex-col gap-1",
615
+ children: [/* @__PURE__ */ jsx("span", {
616
+ className: "text-foreground text-[13px] font-medium",
617
+ children: option.label
618
+ }), /* @__PURE__ */ jsx("span", {
619
+ className: "text-muted-foreground text-xs",
620
+ children: option.description
621
+ })]
622
+ })]
623
+ }, option.value);
624
+ })]
625
+ })
626
+ }),
627
+ /* @__PURE__ */ jsxs("div", {
628
+ className: DIALOG_FOOTER_CLASS,
629
+ children: [/* @__PURE__ */ jsx(DialogClose, {
630
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
631
+ children: "Cancel"
632
+ }), /* @__PURE__ */ jsx("button", {
633
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
634
+ onClick: handlePaste,
635
+ type: "button",
636
+ children: "Paste"
637
+ })]
638
+ })
639
+ ]
640
+ })] })
641
+ });
642
+ }
643
+ //#endregion
644
+ //#region src/components/dialogs/SplitCellDialog.tsx
645
+ const MIN_PARTS = 1;
646
+ const MAX_PARTS = 63;
647
+ function SplitCellDialog({ isOpen, onClose, onSplit, defaultRows = 1, defaultColumns = 2 }) {
648
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
649
+ const id = useId();
650
+ const [rows, setRows] = useState(defaultRows);
651
+ const [columns, setColumns] = useState(defaultColumns);
652
+ const [mergeBeforeSplit, setMergeBeforeSplit] = useState(false);
653
+ useEffect(() => {
654
+ if (!isOpen) return;
655
+ setRows(defaultRows);
656
+ setColumns(defaultColumns);
657
+ setMergeBeforeSplit(false);
658
+ }, [
659
+ isOpen,
660
+ defaultRows,
661
+ defaultColumns
662
+ ]);
663
+ const normalizedRows = clampInteger(rows);
664
+ const normalizedColumns = clampInteger(columns);
665
+ const canSplit = normalizedRows > 1 || normalizedColumns > 1;
666
+ const fieldIds = {
667
+ rows: `${id}-split-cell-rows`,
668
+ columns: `${id}-split-cell-columns`,
669
+ mergeBeforeSplit: `${id}-split-cell-merge-before-split`
670
+ };
671
+ const handleSplit = () => {
672
+ if (!canSplit) return;
673
+ onSplit({
674
+ rows: normalizedRows,
675
+ columns: normalizedColumns,
676
+ mergeBeforeSplit
677
+ });
678
+ onClose();
679
+ };
680
+ return /* @__PURE__ */ jsx(Dialog, {
681
+ open: isOpen,
682
+ onOpenChange: (open) => {
683
+ if (!open) onClose();
684
+ },
685
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
686
+ className: DIALOG_POPUP_CLASS,
687
+ children: [
688
+ /* @__PURE__ */ jsx(DialogTitle, {
689
+ className: DIALOG_TITLE_CLASS,
690
+ children: "Split Cell"
691
+ }),
692
+ /* @__PURE__ */ jsxs("div", {
693
+ className: DIALOG_BODY_CLASS,
694
+ children: [/* @__PURE__ */ jsxs("div", {
695
+ className: "grid grid-cols-2 gap-3",
696
+ children: [/* @__PURE__ */ jsxs("label", {
697
+ className: "flex flex-col gap-1",
698
+ htmlFor: fieldIds.columns,
699
+ children: [/* @__PURE__ */ jsx("span", {
700
+ className: DIALOG_LABEL_CLASS,
701
+ children: "Columns"
702
+ }), /* @__PURE__ */ jsx("input", {
703
+ className: DIALOG_INPUT_CLASS,
704
+ id: fieldIds.columns,
705
+ max: MAX_PARTS,
706
+ min: MIN_PARTS,
707
+ onChange: (e) => setColumns(Number(e.target.value) || MIN_PARTS),
708
+ type: "number",
709
+ value: columns
710
+ })]
711
+ }), /* @__PURE__ */ jsxs("label", {
712
+ className: "flex flex-col gap-1",
713
+ htmlFor: fieldIds.rows,
714
+ children: [/* @__PURE__ */ jsx("span", {
715
+ className: DIALOG_LABEL_CLASS,
716
+ children: "Rows"
717
+ }), /* @__PURE__ */ jsx("input", {
718
+ className: DIALOG_INPUT_CLASS,
719
+ id: fieldIds.rows,
720
+ max: MAX_PARTS,
721
+ min: MIN_PARTS,
722
+ onChange: (e) => setRows(Number(e.target.value) || MIN_PARTS),
723
+ type: "number",
724
+ value: rows
725
+ })]
726
+ })]
727
+ }), /* @__PURE__ */ jsxs("label", {
728
+ className: "flex items-center gap-2",
729
+ htmlFor: fieldIds.mergeBeforeSplit,
730
+ children: [/* @__PURE__ */ jsx("input", {
731
+ checked: mergeBeforeSplit,
732
+ id: fieldIds.mergeBeforeSplit,
733
+ onChange: (e) => setMergeBeforeSplit(e.target.checked),
734
+ type: "checkbox"
735
+ }), /* @__PURE__ */ jsx("span", {
736
+ className: DIALOG_LABEL_CLASS,
737
+ children: "Merge selected cells before splitting"
738
+ })]
739
+ })]
740
+ }),
741
+ /* @__PURE__ */ jsxs("div", {
742
+ className: DIALOG_FOOTER_CLASS,
743
+ children: [/* @__PURE__ */ jsx(DialogClose, {
744
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
745
+ children: "Cancel"
746
+ }), /* @__PURE__ */ jsx("button", {
747
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
748
+ disabled: !canSplit,
749
+ onClick: handleSplit,
750
+ type: "button",
751
+ children: "Split"
752
+ })]
753
+ })
754
+ ]
755
+ })] })
756
+ });
757
+ }
758
+ function clampInteger(value) {
759
+ return Math.min(MAX_PARTS, Math.max(MIN_PARTS, Math.trunc(value)));
760
+ }
761
+ //#endregion
762
+ //#region src/components/dialogs/WatermarkDialog.tsx
763
+ const DEFAULT_TEXT_COLOR = "#C0C0C0";
764
+ function WatermarkDialog({ isOpen, onClose, onApply, currentWatermark }) {
765
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
766
+ const id = useId();
767
+ const [mode, setMode] = useState("text");
768
+ const [text, setText] = useState("CONFIDENTIAL");
769
+ const [font, setFont] = useState("Calibri");
770
+ const [color, setColor] = useState(DEFAULT_TEXT_COLOR);
771
+ const [diagonal, setDiagonal] = useState(true);
772
+ const [opacityPercent, setOpacityPercent] = useState(50);
773
+ const [imageRId, setImageRId] = useState("");
774
+ const [imageTarget, setImageTarget] = useState("");
775
+ const [imageTargetExternal, setImageTargetExternal] = useState(false);
776
+ const [scalePercent, setScalePercent] = useState(100);
777
+ const [washout, setWashout] = useState(true);
778
+ useEffect(() => {
779
+ if (!isOpen) return;
780
+ if (!currentWatermark) {
781
+ setMode("text");
782
+ setText("CONFIDENTIAL");
783
+ setFont("Calibri");
784
+ setColor(DEFAULT_TEXT_COLOR);
785
+ setDiagonal(true);
786
+ setOpacityPercent(50);
787
+ setImageRId("");
788
+ setImageTarget("");
789
+ setImageTargetExternal(false);
790
+ setScalePercent(100);
791
+ setWashout(true);
792
+ return;
793
+ }
794
+ setMode(currentWatermark.kind);
795
+ if (currentWatermark.kind === "text") {
796
+ setText(currentWatermark.text);
797
+ setFont(currentWatermark.font ?? "Calibri");
798
+ setColor(toColorInputValue(currentWatermark.color));
799
+ setDiagonal(currentWatermark.diagonal ?? true);
800
+ setOpacityPercent(Math.round((currentWatermark.opacity ?? .5) * 100));
801
+ return;
802
+ }
803
+ setImageRId(currentWatermark.imageRId);
804
+ setImageTarget(currentWatermark.imageTarget ?? "");
805
+ setImageTargetExternal(currentWatermark.imageTargetExternal ?? false);
806
+ setScalePercent(Math.round((currentWatermark.scale ?? 1) * 100));
807
+ setWashout(currentWatermark.washout ?? true);
808
+ }, [isOpen, currentWatermark]);
809
+ const fieldIds = {
810
+ mode: `${id}-watermark-mode`,
811
+ text: `${id}-watermark-text`,
812
+ font: `${id}-watermark-font`,
813
+ color: `${id}-watermark-color`,
814
+ diagonal: `${id}-watermark-diagonal`,
815
+ opacity: `${id}-watermark-opacity`,
816
+ imageRId: `${id}-watermark-image-rid`,
817
+ imageTarget: `${id}-watermark-image-target`,
818
+ imageTargetExternal: `${id}-watermark-image-external`,
819
+ scale: `${id}-watermark-scale`,
820
+ washout: `${id}-watermark-washout`
821
+ };
822
+ const canApply = mode === "none" || mode === "text" && text.trim().length > 0 || mode === "picture" && imageRId.trim().length > 0;
823
+ const handleApply = () => {
824
+ if (!canApply) return;
825
+ if (mode === "none") {
826
+ onApply(void 0);
827
+ onClose();
828
+ return;
829
+ }
830
+ if (mode === "text") {
831
+ onApply({
832
+ kind: "text",
833
+ text: text.trim(),
834
+ ...font.trim() ? { font: font.trim() } : {},
835
+ color: stripHash(color),
836
+ diagonal,
837
+ opacity: clampPercent(opacityPercent) / 100
838
+ });
839
+ onClose();
840
+ return;
841
+ }
842
+ onApply({
843
+ kind: "picture",
844
+ imageRId: imageRId.trim(),
845
+ ...imageTarget.trim() ? { imageTarget: imageTarget.trim() } : {},
846
+ ...imageTarget.trim() ? { imageTargetExternal } : {},
847
+ scale: clampScalePercent(scalePercent) / 100,
848
+ washout
849
+ });
850
+ onClose();
851
+ };
852
+ return /* @__PURE__ */ jsx(Dialog, {
853
+ open: isOpen,
854
+ onOpenChange: (open) => {
855
+ if (!open) onClose();
856
+ },
857
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: DIALOG_BACKDROP_CLASS }), /* @__PURE__ */ jsxs(DialogPopup, {
858
+ className: DIALOG_POPUP_CLASS,
859
+ children: [
860
+ /* @__PURE__ */ jsx(DialogTitle, {
861
+ className: DIALOG_TITLE_CLASS,
862
+ children: "Watermark"
863
+ }),
864
+ /* @__PURE__ */ jsxs("div", {
865
+ className: DIALOG_BODY_CLASS,
866
+ children: [
867
+ /* @__PURE__ */ jsxs("label", {
868
+ className: "flex flex-col gap-1",
869
+ htmlFor: fieldIds.mode,
870
+ children: [/* @__PURE__ */ jsx("span", {
871
+ className: DIALOG_LABEL_CLASS,
872
+ children: "Type"
873
+ }), /* @__PURE__ */ jsxs("select", {
874
+ className: DIALOG_INPUT_CLASS,
875
+ id: fieldIds.mode,
876
+ onChange: (e) => setMode(toWatermarkMode(e.target.value)),
877
+ value: mode,
878
+ children: [
879
+ /* @__PURE__ */ jsx("option", {
880
+ value: "text",
881
+ children: "Text watermark"
882
+ }),
883
+ /* @__PURE__ */ jsx("option", {
884
+ value: "picture",
885
+ children: "Picture watermark"
886
+ }),
887
+ /* @__PURE__ */ jsx("option", {
888
+ value: "none",
889
+ children: "No watermark"
890
+ })
891
+ ]
892
+ })]
893
+ }),
894
+ mode === "text" && /* @__PURE__ */ jsxs("div", {
895
+ className: "grid grid-cols-2 gap-3",
896
+ children: [
897
+ /* @__PURE__ */ jsxs("label", {
898
+ className: "col-span-2 flex flex-col gap-1",
899
+ htmlFor: fieldIds.text,
900
+ children: [/* @__PURE__ */ jsx("span", {
901
+ className: "text-muted-foreground text-[13px]",
902
+ children: "Text"
903
+ }), /* @__PURE__ */ jsx("input", {
904
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
905
+ id: fieldIds.text,
906
+ onChange: (e) => setText(e.target.value),
907
+ value: text
908
+ })]
909
+ }),
910
+ /* @__PURE__ */ jsxs("label", {
911
+ className: "flex flex-col gap-1",
912
+ htmlFor: fieldIds.font,
913
+ children: [/* @__PURE__ */ jsx("span", {
914
+ className: "text-muted-foreground text-[13px]",
915
+ children: "Font"
916
+ }), /* @__PURE__ */ jsx("input", {
917
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
918
+ id: fieldIds.font,
919
+ onChange: (e) => setFont(e.target.value),
920
+ value: font
921
+ })]
922
+ }),
923
+ /* @__PURE__ */ jsxs("label", {
924
+ className: "flex flex-col gap-1",
925
+ htmlFor: fieldIds.color,
926
+ children: [/* @__PURE__ */ jsx("span", {
927
+ className: "text-muted-foreground text-[13px]",
928
+ children: "Color"
929
+ }), /* @__PURE__ */ jsx("input", {
930
+ className: `border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none h-[34px]`,
931
+ id: fieldIds.color,
932
+ onChange: (e) => setColor(e.target.value),
933
+ type: "color",
934
+ value: color
935
+ })]
936
+ }),
937
+ /* @__PURE__ */ jsxs("label", {
938
+ className: "flex flex-col gap-1",
939
+ htmlFor: fieldIds.opacity,
940
+ children: [/* @__PURE__ */ jsx("span", {
941
+ className: "text-muted-foreground text-[13px]",
942
+ children: "Opacity"
943
+ }), /* @__PURE__ */ jsx("input", {
944
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
945
+ id: fieldIds.opacity,
946
+ max: 100,
947
+ min: 0,
948
+ onChange: (e) => setOpacityPercent(Number(e.target.value) || 0),
949
+ type: "number",
950
+ value: opacityPercent
951
+ })]
952
+ }),
953
+ /* @__PURE__ */ jsxs("label", {
954
+ className: "flex items-center gap-2 pt-6",
955
+ htmlFor: fieldIds.diagonal,
956
+ children: [/* @__PURE__ */ jsx("input", {
957
+ checked: diagonal,
958
+ id: fieldIds.diagonal,
959
+ onChange: (e) => setDiagonal(e.target.checked),
960
+ type: "checkbox"
961
+ }), /* @__PURE__ */ jsx("span", {
962
+ className: "text-muted-foreground text-[13px]",
963
+ children: "Diagonal"
964
+ })]
965
+ })
966
+ ]
967
+ }),
968
+ mode === "picture" && /* @__PURE__ */ jsxs("div", {
969
+ className: "grid grid-cols-2 gap-3",
970
+ children: [
971
+ /* @__PURE__ */ jsxs("label", {
972
+ className: "col-span-2 flex flex-col gap-1",
973
+ htmlFor: fieldIds.imageRId,
974
+ children: [/* @__PURE__ */ jsx("span", {
975
+ className: "text-muted-foreground text-[13px]",
976
+ children: "Header image relationship id"
977
+ }), /* @__PURE__ */ jsx("input", {
978
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
979
+ id: fieldIds.imageRId,
980
+ onChange: (e) => setImageRId(e.target.value),
981
+ placeholder: "rId12",
982
+ value: imageRId
983
+ })]
984
+ }),
985
+ /* @__PURE__ */ jsxs("label", {
986
+ className: "col-span-2 flex flex-col gap-1",
987
+ htmlFor: fieldIds.imageTarget,
988
+ children: [/* @__PURE__ */ jsx("span", {
989
+ className: "text-muted-foreground text-[13px]",
990
+ children: "Image target"
991
+ }), /* @__PURE__ */ jsx("input", {
992
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
993
+ id: fieldIds.imageTarget,
994
+ onChange: (e) => setImageTarget(e.target.value),
995
+ placeholder: "word/media/image1.png",
996
+ value: imageTarget
997
+ })]
998
+ }),
999
+ /* @__PURE__ */ jsxs("label", {
1000
+ className: "flex flex-col gap-1",
1001
+ htmlFor: fieldIds.scale,
1002
+ children: [/* @__PURE__ */ jsx("span", {
1003
+ className: "text-muted-foreground text-[13px]",
1004
+ children: "Scale"
1005
+ }), /* @__PURE__ */ jsx("input", {
1006
+ className: "border-input bg-background text-foreground rounded border px-2 py-1.5 text-[13px] outline-none",
1007
+ id: fieldIds.scale,
1008
+ max: 300,
1009
+ min: 1,
1010
+ onChange: (e) => setScalePercent(Number(e.target.value) || 1),
1011
+ type: "number",
1012
+ value: scalePercent
1013
+ })]
1014
+ }),
1015
+ /* @__PURE__ */ jsxs("label", {
1016
+ className: "flex items-center gap-2 pt-6",
1017
+ htmlFor: fieldIds.washout,
1018
+ children: [/* @__PURE__ */ jsx("input", {
1019
+ checked: washout,
1020
+ id: fieldIds.washout,
1021
+ onChange: (e) => setWashout(e.target.checked),
1022
+ type: "checkbox"
1023
+ }), /* @__PURE__ */ jsx("span", {
1024
+ className: "text-muted-foreground text-[13px]",
1025
+ children: "Washout"
1026
+ })]
1027
+ }),
1028
+ /* @__PURE__ */ jsxs("label", {
1029
+ className: "col-span-2 flex items-center gap-2",
1030
+ htmlFor: fieldIds.imageTargetExternal,
1031
+ children: [/* @__PURE__ */ jsx("input", {
1032
+ checked: imageTargetExternal,
1033
+ id: fieldIds.imageTargetExternal,
1034
+ onChange: (e) => setImageTargetExternal(e.target.checked),
1035
+ type: "checkbox"
1036
+ }), /* @__PURE__ */ jsx("span", {
1037
+ className: "text-muted-foreground text-[13px]",
1038
+ children: "Target is an external URL"
1039
+ })]
1040
+ })
1041
+ ]
1042
+ })
1043
+ ]
1044
+ }),
1045
+ /* @__PURE__ */ jsxs("div", {
1046
+ className: DIALOG_FOOTER_CLASS,
1047
+ children: [/* @__PURE__ */ jsx(DialogClose, {
1048
+ className: DIALOG_SECONDARY_BUTTON_CLASS,
1049
+ children: "Cancel"
1050
+ }), /* @__PURE__ */ jsx("button", {
1051
+ className: DIALOG_PRIMARY_BUTTON_CLASS,
1052
+ disabled: !canApply,
1053
+ onClick: handleApply,
1054
+ type: "button",
1055
+ children: "Apply"
1056
+ })]
1057
+ })
1058
+ ]
1059
+ })] })
1060
+ });
1061
+ }
1062
+ function toWatermarkMode(value) {
1063
+ if (value === "none" || value === "picture" || value === "text") return value;
1064
+ return "text";
1065
+ }
1066
+ function clampPercent(value) {
1067
+ return Math.min(100, Math.max(0, value));
1068
+ }
1069
+ function clampScalePercent(value) {
1070
+ return Math.min(300, Math.max(1, value));
1071
+ }
1072
+ function stripHash(value) {
1073
+ return value.startsWith("#") ? value.slice(1) : value;
1074
+ }
1075
+ function toColorInputValue(value) {
1076
+ if (!value || value === "auto") return DEFAULT_TEXT_COLOR;
1077
+ return value.startsWith("#") ? value : `#${value}`;
1078
+ }
1079
+ //#endregion
1080
+ export { InsertImageDialog as a, InsertTableDialog as i, SplitCellDialog as n, HyperlinkDialog as o, PasteSpecialDialog as r, WatermarkDialog as t };