@xignature/docx-editor 1.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.
@@ -0,0 +1,928 @@
1
+ import {
2
+ useTranslation
3
+ } from "./chunk-OG6X5JJA.mjs";
4
+
5
+ // src/react/components/dialogs/FindReplaceDialog.tsx
6
+ import { useState as useState2, useEffect, useCallback as useCallback2, useRef } from "react";
7
+
8
+ // src/react/components/dialogs/findReplaceUtils.ts
9
+ function createDefaultFindOptions() {
10
+ return {
11
+ matchCase: false,
12
+ matchWholeWord: false,
13
+ useRegex: false
14
+ };
15
+ }
16
+ function escapeRegexString(str) {
17
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+ function createSearchPattern(searchText, options) {
20
+ if (!searchText) return null;
21
+ try {
22
+ let pattern;
23
+ if (options.useRegex) {
24
+ pattern = searchText;
25
+ } else {
26
+ pattern = escapeRegexString(searchText);
27
+ }
28
+ if (options.matchWholeWord) {
29
+ pattern = `\\b${pattern}\\b`;
30
+ }
31
+ const flags = options.matchCase ? "g" : "gi";
32
+ return new RegExp(pattern, flags);
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ function findAllMatches(content, searchText, options) {
38
+ if (!content || !searchText) {
39
+ return [];
40
+ }
41
+ const matches = [];
42
+ let searchFor = searchText;
43
+ if (!options.matchCase) {
44
+ searchFor = searchText.toLowerCase();
45
+ }
46
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
+ let pattern;
48
+ if (options.matchWholeWord) {
49
+ pattern = `\\b${escapeRegex(searchFor)}\\b`;
50
+ } else {
51
+ pattern = escapeRegex(searchFor);
52
+ }
53
+ const flags = options.matchCase ? "g" : "gi";
54
+ const regex = new RegExp(pattern, flags);
55
+ let match;
56
+ while ((match = regex.exec(content)) !== null) {
57
+ matches.push({
58
+ start: match.index,
59
+ end: match.index + match[0].length
60
+ });
61
+ if (match[0].length === 0) {
62
+ regex.lastIndex++;
63
+ }
64
+ }
65
+ return matches;
66
+ }
67
+ function replaceAllInContent(content, searchText, replaceText, options) {
68
+ const pattern = createSearchPattern(searchText, options);
69
+ if (!pattern) return content;
70
+ return content.replace(pattern, replaceText);
71
+ }
72
+ function replaceFirstInContent(content, searchText, replaceText, options, startIndex = 0) {
73
+ const matches = findAllMatches(content, searchText, options);
74
+ const match = matches.find((m) => m.start >= startIndex) || matches[0];
75
+ if (!match) {
76
+ return { content, replaced: false, matchStart: -1, matchEnd: -1 };
77
+ }
78
+ const newContent = content.substring(0, match.start) + replaceText + content.substring(match.end);
79
+ return {
80
+ content: newContent,
81
+ replaced: true,
82
+ matchStart: match.start,
83
+ matchEnd: match.start + replaceText.length
84
+ };
85
+ }
86
+ function getMatchCountText(result) {
87
+ if (!result) return "";
88
+ if (result.totalCount === 0) return "No results";
89
+ if (result.totalCount === 1) return "1 match";
90
+ return `${result.currentIndex + 1} of ${result.totalCount} matches`;
91
+ }
92
+ function isEmptySearch(searchText) {
93
+ return !searchText || searchText.trim() === "";
94
+ }
95
+ function getDefaultHighlightOptions() {
96
+ return {
97
+ currentMatchColor: "#FFFF00",
98
+ otherMatchColor: "#FFFFAA"
99
+ };
100
+ }
101
+ function getRunText(run) {
102
+ if (!run || !run.content) return "";
103
+ let text = "";
104
+ for (const item of run.content) {
105
+ if (item.type === "text") {
106
+ text += item.text || "";
107
+ } else if (item.type === "tab") {
108
+ text += " ";
109
+ } else if (item.type === "break" && item.breakType === "textWrapping") {
110
+ text += "\n";
111
+ }
112
+ }
113
+ return text;
114
+ }
115
+ function getParagraphPlainText(paragraph) {
116
+ if (!paragraph || !paragraph.content) return "";
117
+ let text = "";
118
+ for (const item of paragraph.content) {
119
+ if (item.type === "run") {
120
+ text += getRunText(item);
121
+ } else if (item.type === "hyperlink") {
122
+ for (const child of item.children || []) {
123
+ if (child.type === "run") {
124
+ text += getRunText(child);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return text;
130
+ }
131
+ function findInDocument(document, searchText, options) {
132
+ if (!document || !searchText) return [];
133
+ const matches = [];
134
+ const body = document.package?.document || document.package?.document;
135
+ if (!body || !body.content) return matches;
136
+ let paragraphIndex = 0;
137
+ for (const block of body.content) {
138
+ if (block.type === "paragraph") {
139
+ const paragraphMatches = findInParagraph(block, searchText, options, paragraphIndex);
140
+ matches.push(...paragraphMatches);
141
+ paragraphIndex++;
142
+ } else if (block.type === "table") {
143
+ for (const row of block.rows || []) {
144
+ for (const cell of row.cells || []) {
145
+ for (const cellContent of cell.content || []) {
146
+ if (cellContent.type === "paragraph") {
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ return matches;
154
+ }
155
+ function findInParagraph(paragraph, searchText, options, paragraphIndex) {
156
+ const matches = [];
157
+ const paragraphText = getParagraphPlainText(paragraph);
158
+ if (!paragraphText) return matches;
159
+ const textMatches = findAllMatches(paragraphText, searchText, options);
160
+ for (const match of textMatches) {
161
+ const contentInfo = findContentAtOffset(paragraph, match.start);
162
+ matches.push({
163
+ paragraphIndex,
164
+ contentIndex: contentInfo.contentIndex,
165
+ startOffset: contentInfo.offsetInContent,
166
+ endOffset: contentInfo.offsetInContent + (match.end - match.start),
167
+ text: paragraphText.substring(match.start, match.end)
168
+ });
169
+ }
170
+ return matches;
171
+ }
172
+ function findContentAtOffset(paragraph, offset) {
173
+ if (!paragraph || !paragraph.content) {
174
+ return { contentIndex: 0, runIndex: 0, offsetInContent: offset };
175
+ }
176
+ let currentOffset = 0;
177
+ let contentIndex = 0;
178
+ for (const item of paragraph.content) {
179
+ let itemText = "";
180
+ if (item.type === "run") {
181
+ itemText = getRunText(item);
182
+ } else if (item.type === "hyperlink") {
183
+ for (const child of item.children || []) {
184
+ if (child.type === "run") {
185
+ itemText += getRunText(child);
186
+ }
187
+ }
188
+ }
189
+ const itemLength = itemText.length;
190
+ if (currentOffset + itemLength > offset) {
191
+ return {
192
+ contentIndex,
193
+ runIndex: contentIndex,
194
+ offsetInContent: offset - currentOffset
195
+ };
196
+ }
197
+ currentOffset += itemLength;
198
+ contentIndex++;
199
+ }
200
+ return {
201
+ contentIndex: Math.max(0, paragraph.content.length - 1),
202
+ runIndex: Math.max(0, paragraph.content.length - 1),
203
+ offsetInContent: 0
204
+ };
205
+ }
206
+ function scrollToMatch(containerElement, match) {
207
+ if (!containerElement || !match) return;
208
+ const paragraphElement = containerElement.querySelector(
209
+ `[data-paragraph-index="${match.paragraphIndex}"]`
210
+ );
211
+ if (paragraphElement) {
212
+ paragraphElement.scrollIntoView({ behavior: "smooth", block: "center" });
213
+ }
214
+ }
215
+
216
+ // src/react/components/dialogs/useFindReplace.ts
217
+ import { useState, useCallback } from "react";
218
+ function useFindReplace(hookOptions) {
219
+ const [state, setState] = useState({
220
+ isOpen: false,
221
+ searchText: "",
222
+ replaceText: "",
223
+ options: createDefaultFindOptions(),
224
+ matches: [],
225
+ currentIndex: 0,
226
+ replaceMode: hookOptions?.initialReplaceMode ?? false
227
+ });
228
+ const openFind = useCallback((selectedText) => {
229
+ setState((prev) => ({
230
+ ...prev,
231
+ isOpen: true,
232
+ replaceMode: false,
233
+ searchText: selectedText || prev.searchText,
234
+ matches: [],
235
+ currentIndex: 0
236
+ }));
237
+ }, []);
238
+ const openReplace = useCallback((selectedText) => {
239
+ setState((prev) => ({
240
+ ...prev,
241
+ isOpen: true,
242
+ replaceMode: true,
243
+ searchText: selectedText || prev.searchText,
244
+ matches: [],
245
+ currentIndex: 0
246
+ }));
247
+ }, []);
248
+ const close = useCallback(() => {
249
+ setState((prev) => ({
250
+ ...prev,
251
+ isOpen: false
252
+ }));
253
+ }, []);
254
+ const toggle = useCallback(() => {
255
+ setState((prev) => ({
256
+ ...prev,
257
+ isOpen: !prev.isOpen
258
+ }));
259
+ }, []);
260
+ const setSearchText = useCallback((text) => {
261
+ setState((prev) => ({
262
+ ...prev,
263
+ searchText: text
264
+ }));
265
+ }, []);
266
+ const setReplaceText = useCallback((text) => {
267
+ setState((prev) => ({
268
+ ...prev,
269
+ replaceText: text
270
+ }));
271
+ }, []);
272
+ const setOptions = useCallback((options) => {
273
+ setState((prev) => ({
274
+ ...prev,
275
+ options: { ...prev.options, ...options }
276
+ }));
277
+ }, []);
278
+ const setMatches = useCallback(
279
+ (matches, currentIndex = 0) => {
280
+ const newIndex = Math.max(0, Math.min(currentIndex, matches.length - 1));
281
+ setState((prev) => ({
282
+ ...prev,
283
+ matches,
284
+ currentIndex: matches.length > 0 ? newIndex : 0
285
+ }));
286
+ hookOptions?.onMatchesChange?.(matches);
287
+ if (matches.length > 0) {
288
+ hookOptions?.onCurrentMatchChange?.(matches[newIndex], newIndex);
289
+ } else {
290
+ hookOptions?.onCurrentMatchChange?.(null, -1);
291
+ }
292
+ },
293
+ [hookOptions]
294
+ );
295
+ const goToNextMatch = useCallback(() => {
296
+ let newIndex = 0;
297
+ setState((prev) => {
298
+ if (prev.matches.length === 0) return prev;
299
+ newIndex = (prev.currentIndex + 1) % prev.matches.length;
300
+ return { ...prev, currentIndex: newIndex };
301
+ });
302
+ return newIndex;
303
+ }, []);
304
+ const goToPreviousMatch = useCallback(() => {
305
+ let newIndex = 0;
306
+ setState((prev) => {
307
+ if (prev.matches.length === 0) return prev;
308
+ newIndex = prev.currentIndex === 0 ? prev.matches.length - 1 : prev.currentIndex - 1;
309
+ return { ...prev, currentIndex: newIndex };
310
+ });
311
+ return newIndex;
312
+ }, []);
313
+ const goToMatch = useCallback((index) => {
314
+ setState((prev) => {
315
+ if (prev.matches.length === 0 || index < 0 || index >= prev.matches.length) {
316
+ return prev;
317
+ }
318
+ return { ...prev, currentIndex: index };
319
+ });
320
+ }, []);
321
+ const getCurrentMatch = useCallback(() => {
322
+ if (state.matches.length === 0) return null;
323
+ return state.matches[state.currentIndex] || null;
324
+ }, [state.matches, state.currentIndex]);
325
+ const hasMatches = useCallback(() => state.matches.length > 0, [state.matches.length]);
326
+ return {
327
+ state,
328
+ openFind,
329
+ openReplace,
330
+ close,
331
+ toggle,
332
+ setSearchText,
333
+ setReplaceText,
334
+ setOptions,
335
+ setMatches,
336
+ goToNextMatch,
337
+ goToPreviousMatch,
338
+ goToMatch,
339
+ getCurrentMatch,
340
+ hasMatches
341
+ };
342
+ }
343
+
344
+ // src/react/components/dialogs/FindReplaceDialog.tsx
345
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
346
+ var DIALOG_OVERLAY_STYLE = {
347
+ position: "fixed",
348
+ top: 0,
349
+ left: 0,
350
+ right: 0,
351
+ bottom: 0,
352
+ backgroundColor: "transparent",
353
+ display: "flex",
354
+ alignItems: "flex-start",
355
+ justifyContent: "flex-end",
356
+ zIndex: 1e4,
357
+ pointerEvents: "none"
358
+ };
359
+ var DIALOG_CONTENT_STYLE = {
360
+ backgroundColor: "white",
361
+ borderRadius: "4px",
362
+ boxShadow: "0 4px 20px rgba(0, 0, 0, 0.2)",
363
+ minWidth: "360px",
364
+ maxWidth: "440px",
365
+ width: "100%",
366
+ margin: "60px 20px 20px 20px",
367
+ pointerEvents: "auto"
368
+ };
369
+ var DIALOG_HEADER_STYLE = {
370
+ display: "flex",
371
+ justifyContent: "space-between",
372
+ alignItems: "center",
373
+ padding: "12px 16px",
374
+ borderBottom: "1px solid var(--doc-border)",
375
+ backgroundColor: "var(--doc-bg-subtle)",
376
+ borderTopLeftRadius: "4px",
377
+ borderTopRightRadius: "4px"
378
+ };
379
+ var DIALOG_TITLE_STYLE = {
380
+ margin: 0,
381
+ fontSize: "14px",
382
+ fontWeight: 600,
383
+ color: "var(--doc-text)"
384
+ };
385
+ var CLOSE_BUTTON_STYLE = {
386
+ background: "none",
387
+ border: "none",
388
+ fontSize: "18px",
389
+ cursor: "pointer",
390
+ color: "var(--doc-text-muted)",
391
+ padding: "2px 6px",
392
+ lineHeight: 1
393
+ };
394
+ var DIALOG_BODY_STYLE = {
395
+ padding: "16px"
396
+ };
397
+ var ROW_STYLE = {
398
+ display: "flex",
399
+ alignItems: "center",
400
+ gap: "8px",
401
+ marginBottom: "12px"
402
+ };
403
+ var LABEL_STYLE = {
404
+ width: "60px",
405
+ fontSize: "13px",
406
+ color: "var(--doc-text)",
407
+ flexShrink: 0
408
+ };
409
+ var INPUT_STYLE = {
410
+ flex: 1,
411
+ padding: "8px 10px",
412
+ border: "1px solid var(--doc-border-input)",
413
+ borderRadius: "3px",
414
+ fontSize: "13px",
415
+ boxSizing: "border-box",
416
+ outline: "none"
417
+ };
418
+ var INPUT_FOCUS_STYLE = {
419
+ ...INPUT_STYLE,
420
+ borderColor: "var(--doc-link)",
421
+ boxShadow: "0 0 0 2px rgba(5, 99, 193, 0.1)"
422
+ };
423
+ var BUTTON_CONTAINER_STYLE = {
424
+ display: "flex",
425
+ flexDirection: "column",
426
+ gap: "6px",
427
+ marginLeft: "8px"
428
+ };
429
+ var BUTTON_BASE_STYLE = {
430
+ padding: "6px 12px",
431
+ borderRadius: "3px",
432
+ fontSize: "12px",
433
+ fontWeight: 500,
434
+ cursor: "pointer",
435
+ border: "1px solid var(--doc-border-input)",
436
+ backgroundColor: "var(--doc-bg-input)",
437
+ color: "var(--doc-text)",
438
+ minWidth: "80px",
439
+ textAlign: "center"
440
+ };
441
+ var BUTTON_DISABLED_STYLE = {
442
+ ...BUTTON_BASE_STYLE,
443
+ backgroundColor: "var(--doc-bg-hover)",
444
+ color: "var(--doc-text-placeholder)",
445
+ cursor: "not-allowed"
446
+ };
447
+ var NAV_BUTTON_STYLE = {
448
+ padding: "6px 10px",
449
+ borderRadius: "3px",
450
+ fontSize: "14px",
451
+ cursor: "pointer",
452
+ border: "1px solid var(--doc-border-input)",
453
+ backgroundColor: "var(--doc-bg-input)",
454
+ color: "var(--doc-text)"
455
+ };
456
+ var NAV_BUTTON_DISABLED_STYLE = {
457
+ ...NAV_BUTTON_STYLE,
458
+ color: "var(--doc-border-input)",
459
+ cursor: "not-allowed"
460
+ };
461
+ var OPTIONS_CONTAINER_STYLE = {
462
+ display: "flex",
463
+ gap: "16px",
464
+ marginTop: "4px",
465
+ marginLeft: "68px"
466
+ };
467
+ var CHECKBOX_LABEL_STYLE = {
468
+ display: "flex",
469
+ alignItems: "center",
470
+ gap: "6px",
471
+ fontSize: "12px",
472
+ color: "var(--doc-text-muted)",
473
+ cursor: "pointer"
474
+ };
475
+ var CHECKBOX_STYLE = {
476
+ width: "14px",
477
+ height: "14px",
478
+ cursor: "pointer"
479
+ };
480
+ var STATUS_STYLE = {
481
+ marginLeft: "68px",
482
+ fontSize: "12px",
483
+ color: "var(--doc-text-muted)",
484
+ marginBottom: "8px"
485
+ };
486
+ var NO_RESULTS_STYLE = {
487
+ ...STATUS_STYLE,
488
+ color: "var(--doc-error)"
489
+ };
490
+ var ChevronUpIcon = ({ style }) => /* @__PURE__ */ jsx(
491
+ "svg",
492
+ {
493
+ width: "14",
494
+ height: "14",
495
+ viewBox: "0 0 24 24",
496
+ fill: "none",
497
+ stroke: "currentColor",
498
+ strokeWidth: "2",
499
+ strokeLinecap: "round",
500
+ strokeLinejoin: "round",
501
+ style,
502
+ children: /* @__PURE__ */ jsx("polyline", { points: "18 15 12 9 6 15" })
503
+ }
504
+ );
505
+ var ChevronDownIcon = ({ style }) => /* @__PURE__ */ jsx(
506
+ "svg",
507
+ {
508
+ width: "14",
509
+ height: "14",
510
+ viewBox: "0 0 24 24",
511
+ fill: "none",
512
+ stroke: "currentColor",
513
+ strokeWidth: "2",
514
+ strokeLinecap: "round",
515
+ strokeLinejoin: "round",
516
+ style,
517
+ children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
518
+ }
519
+ );
520
+ function FindReplaceDialog({
521
+ isOpen,
522
+ onClose,
523
+ onFind,
524
+ onFindNext,
525
+ onFindPrevious,
526
+ onReplace,
527
+ onReplaceAll,
528
+ onHighlightMatches,
529
+ onClearHighlights,
530
+ initialSearchText = "",
531
+ replaceMode = false,
532
+ currentResult,
533
+ className,
534
+ style
535
+ }) {
536
+ const { t } = useTranslation();
537
+ const [searchText, setSearchText] = useState2("");
538
+ const [replaceText, setReplaceText] = useState2("");
539
+ const [showReplace, setShowReplace] = useState2(replaceMode);
540
+ const [matchCase, setMatchCase] = useState2(false);
541
+ const [matchWholeWord, setMatchWholeWord] = useState2(false);
542
+ const [result, setResult] = useState2(null);
543
+ const [searchFocused, setSearchFocused] = useState2(false);
544
+ const [replaceFocused, setReplaceFocused] = useState2(false);
545
+ const searchInputRef = useRef(null);
546
+ const replaceInputRef = useRef(null);
547
+ useEffect(() => {
548
+ if (currentResult !== void 0) {
549
+ setResult(currentResult);
550
+ }
551
+ }, [currentResult]);
552
+ useEffect(() => {
553
+ if (isOpen) {
554
+ setSearchText(initialSearchText);
555
+ setReplaceText("");
556
+ setShowReplace(replaceMode);
557
+ setResult(null);
558
+ setTimeout(() => {
559
+ searchInputRef.current?.focus();
560
+ searchInputRef.current?.select();
561
+ }, 100);
562
+ if (initialSearchText) {
563
+ const searchResult = onFind(initialSearchText, { matchCase, matchWholeWord });
564
+ setResult(searchResult);
565
+ if (searchResult?.matches && onHighlightMatches) {
566
+ onHighlightMatches(searchResult.matches);
567
+ }
568
+ }
569
+ } else {
570
+ if (onClearHighlights) {
571
+ onClearHighlights();
572
+ }
573
+ }
574
+ }, [isOpen, initialSearchText, replaceMode]);
575
+ const performSearch = useCallback2(() => {
576
+ if (!searchText.trim()) {
577
+ setResult(null);
578
+ if (onClearHighlights) {
579
+ onClearHighlights();
580
+ }
581
+ return;
582
+ }
583
+ const searchResult = onFind(searchText, { matchCase, matchWholeWord });
584
+ setResult(searchResult);
585
+ if (searchResult?.matches && onHighlightMatches) {
586
+ onHighlightMatches(searchResult.matches);
587
+ } else if (onClearHighlights) {
588
+ onClearHighlights();
589
+ }
590
+ }, [searchText, matchCase, matchWholeWord, onFind, onHighlightMatches, onClearHighlights]);
591
+ useEffect(() => {
592
+ if (isOpen && searchText.trim()) {
593
+ performSearch();
594
+ }
595
+ }, [matchCase, matchWholeWord]);
596
+ const handleSearchChange = useCallback2((e) => {
597
+ setSearchText(e.target.value);
598
+ }, []);
599
+ const handleSearchKeyDown = useCallback2(
600
+ (e) => {
601
+ if (e.key === "Enter") {
602
+ e.preventDefault();
603
+ if (e.shiftKey) {
604
+ handleFindPrevious();
605
+ } else {
606
+ if (!result) {
607
+ performSearch();
608
+ } else {
609
+ handleFindNext();
610
+ }
611
+ }
612
+ } else if (e.key === "Escape") {
613
+ onClose();
614
+ }
615
+ },
616
+ [result, performSearch, onClose]
617
+ );
618
+ const handleReplaceKeyDown = useCallback2(
619
+ (e) => {
620
+ if (e.key === "Enter") {
621
+ e.preventDefault();
622
+ handleReplace();
623
+ } else if (e.key === "Escape") {
624
+ onClose();
625
+ }
626
+ },
627
+ [onClose]
628
+ );
629
+ const handleFindNext = useCallback2(() => {
630
+ if (!searchText.trim()) {
631
+ performSearch();
632
+ return;
633
+ }
634
+ if (!result) {
635
+ performSearch();
636
+ return;
637
+ }
638
+ const match = onFindNext();
639
+ if (match && result) {
640
+ const newIndex = (result.currentIndex + 1) % result.totalCount;
641
+ setResult({
642
+ ...result,
643
+ currentIndex: newIndex
644
+ });
645
+ }
646
+ }, [searchText, result, performSearch, onFindNext]);
647
+ const handleFindPrevious = useCallback2(() => {
648
+ if (!searchText.trim()) {
649
+ performSearch();
650
+ return;
651
+ }
652
+ if (!result) {
653
+ performSearch();
654
+ return;
655
+ }
656
+ const match = onFindPrevious();
657
+ if (match && result) {
658
+ const newIndex = result.currentIndex === 0 ? result.totalCount - 1 : result.currentIndex - 1;
659
+ setResult({
660
+ ...result,
661
+ currentIndex: newIndex
662
+ });
663
+ }
664
+ }, [searchText, result, performSearch, onFindPrevious]);
665
+ const handleReplace = useCallback2(() => {
666
+ if (!result || result.totalCount === 0) return;
667
+ const success = onReplace(replaceText);
668
+ if (success) {
669
+ const newResult = onFind(searchText, { matchCase, matchWholeWord });
670
+ setResult(newResult);
671
+ if (newResult?.matches && onHighlightMatches) {
672
+ onHighlightMatches(newResult.matches);
673
+ }
674
+ }
675
+ }, [
676
+ result,
677
+ replaceText,
678
+ searchText,
679
+ matchCase,
680
+ matchWholeWord,
681
+ onReplace,
682
+ onFind,
683
+ onHighlightMatches
684
+ ]);
685
+ const handleReplaceAll = useCallback2(() => {
686
+ if (!searchText.trim()) return;
687
+ const count = onReplaceAll(searchText, replaceText, { matchCase, matchWholeWord });
688
+ if (count > 0) {
689
+ setResult({
690
+ matches: [],
691
+ totalCount: 0,
692
+ currentIndex: -1
693
+ });
694
+ if (onClearHighlights) {
695
+ onClearHighlights();
696
+ }
697
+ }
698
+ }, [searchText, replaceText, matchCase, matchWholeWord, onReplaceAll, onClearHighlights]);
699
+ const toggleReplaceMode = useCallback2(() => {
700
+ setShowReplace((prev) => {
701
+ const newValue = !prev;
702
+ if (newValue) {
703
+ setTimeout(() => replaceInputRef.current?.focus(), 100);
704
+ }
705
+ return newValue;
706
+ });
707
+ }, []);
708
+ const handleOverlayClick = useCallback2((e) => {
709
+ if (e.target === e.currentTarget) {
710
+ }
711
+ }, []);
712
+ const handleDialogKeyDown = useCallback2(
713
+ (e) => {
714
+ if (e.key === "Escape") {
715
+ onClose();
716
+ }
717
+ },
718
+ [onClose]
719
+ );
720
+ if (!isOpen) {
721
+ return null;
722
+ }
723
+ const hasMatches = result && result.totalCount > 0;
724
+ const noMatches = result && result.totalCount === 0 && searchText.trim();
725
+ return /* @__PURE__ */ jsx(
726
+ "div",
727
+ {
728
+ className: `docx-find-replace-dialog-overlay ${className || ""}`,
729
+ style: { ...DIALOG_OVERLAY_STYLE, ...style },
730
+ onClick: handleOverlayClick,
731
+ onKeyDown: handleDialogKeyDown,
732
+ children: /* @__PURE__ */ jsxs(
733
+ "div",
734
+ {
735
+ className: "docx-find-replace-dialog",
736
+ "data-testid": "find-replace-dialog",
737
+ style: DIALOG_CONTENT_STYLE,
738
+ role: "dialog",
739
+ "aria-modal": "false",
740
+ "aria-labelledby": "find-replace-dialog-title",
741
+ children: [
742
+ /* @__PURE__ */ jsxs("div", { className: "docx-find-replace-dialog-header", style: DIALOG_HEADER_STYLE, children: [
743
+ /* @__PURE__ */ jsx("h2", { id: "find-replace-dialog-title", style: DIALOG_TITLE_STYLE, children: showReplace ? t("dialogs.findReplace.titleFindReplace") : t("dialogs.findReplace.titleFind") }),
744
+ /* @__PURE__ */ jsx(
745
+ "button",
746
+ {
747
+ type: "button",
748
+ className: "docx-find-replace-dialog-close",
749
+ style: CLOSE_BUTTON_STYLE,
750
+ onClick: onClose,
751
+ "aria-label": t("common.closeDialog"),
752
+ children: "\xD7"
753
+ }
754
+ )
755
+ ] }),
756
+ /* @__PURE__ */ jsxs("div", { className: "docx-find-replace-dialog-body", style: DIALOG_BODY_STYLE, children: [
757
+ /* @__PURE__ */ jsxs("div", { className: "docx-find-replace-dialog-row", style: ROW_STYLE, children: [
758
+ /* @__PURE__ */ jsx("label", { htmlFor: "find-text", style: LABEL_STYLE, children: t("dialogs.findReplace.findLabel") }),
759
+ /* @__PURE__ */ jsx(
760
+ "input",
761
+ {
762
+ ref: searchInputRef,
763
+ id: "find-text",
764
+ type: "text",
765
+ className: "docx-find-replace-dialog-input",
766
+ style: searchFocused ? INPUT_FOCUS_STYLE : INPUT_STYLE,
767
+ value: searchText,
768
+ onChange: handleSearchChange,
769
+ onKeyDown: handleSearchKeyDown,
770
+ onFocus: () => setSearchFocused(true),
771
+ onBlur: () => {
772
+ setSearchFocused(false);
773
+ if (searchText.trim() && !result) {
774
+ performSearch();
775
+ }
776
+ },
777
+ placeholder: t("dialogs.findReplace.findPlaceholder"),
778
+ "aria-label": t("dialogs.findReplace.findAriaLabel")
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "4px" }, children: [
782
+ /* @__PURE__ */ jsx(
783
+ "button",
784
+ {
785
+ type: "button",
786
+ className: "docx-find-replace-dialog-nav",
787
+ style: hasMatches ? NAV_BUTTON_STYLE : NAV_BUTTON_DISABLED_STYLE,
788
+ onClick: handleFindPrevious,
789
+ disabled: !hasMatches,
790
+ "aria-label": t("dialogs.findReplace.findPrevious"),
791
+ title: t("dialogs.findReplace.findPreviousTitle"),
792
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
793
+ }
794
+ ),
795
+ /* @__PURE__ */ jsx(
796
+ "button",
797
+ {
798
+ type: "button",
799
+ className: "docx-find-replace-dialog-nav",
800
+ style: hasMatches ? NAV_BUTTON_STYLE : NAV_BUTTON_DISABLED_STYLE,
801
+ onClick: handleFindNext,
802
+ disabled: !hasMatches,
803
+ "aria-label": t("dialogs.findReplace.findNext"),
804
+ title: t("dialogs.findReplace.findNextTitle"),
805
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
806
+ }
807
+ )
808
+ ] })
809
+ ] }),
810
+ hasMatches && /* @__PURE__ */ jsx("div", { className: "docx-find-replace-dialog-status", style: STATUS_STYLE, children: t("dialogs.findReplace.matchCount", {
811
+ current: result.currentIndex + 1,
812
+ total: result.totalCount
813
+ }) }),
814
+ noMatches && /* @__PURE__ */ jsx("div", { className: "docx-find-replace-dialog-status", style: NO_RESULTS_STYLE, children: t("dialogs.findReplace.noResults") }),
815
+ showReplace && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "docx-find-replace-dialog-row", style: ROW_STYLE, children: [
816
+ /* @__PURE__ */ jsx("label", { htmlFor: "replace-text", style: LABEL_STYLE, children: t("dialogs.findReplace.replaceLabel") }),
817
+ /* @__PURE__ */ jsx(
818
+ "input",
819
+ {
820
+ ref: replaceInputRef,
821
+ id: "replace-text",
822
+ type: "text",
823
+ className: "docx-find-replace-dialog-input",
824
+ style: replaceFocused ? INPUT_FOCUS_STYLE : INPUT_STYLE,
825
+ value: replaceText,
826
+ onChange: (e) => setReplaceText(e.target.value),
827
+ onKeyDown: handleReplaceKeyDown,
828
+ onFocus: () => setReplaceFocused(true),
829
+ onBlur: () => setReplaceFocused(false),
830
+ placeholder: t("dialogs.findReplace.replacePlaceholder"),
831
+ "aria-label": t("dialogs.findReplace.replaceAriaLabel")
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsxs("div", { style: BUTTON_CONTAINER_STYLE, children: [
835
+ /* @__PURE__ */ jsx(
836
+ "button",
837
+ {
838
+ type: "button",
839
+ className: "docx-find-replace-dialog-button",
840
+ style: hasMatches ? BUTTON_BASE_STYLE : BUTTON_DISABLED_STYLE,
841
+ onClick: handleReplace,
842
+ disabled: !hasMatches,
843
+ title: t("dialogs.findReplace.replaceCurrentTitle"),
844
+ children: t("dialogs.findReplace.replaceButton")
845
+ }
846
+ ),
847
+ /* @__PURE__ */ jsx(
848
+ "button",
849
+ {
850
+ type: "button",
851
+ className: "docx-find-replace-dialog-button",
852
+ style: hasMatches ? BUTTON_BASE_STYLE : BUTTON_DISABLED_STYLE,
853
+ onClick: handleReplaceAll,
854
+ disabled: !hasMatches,
855
+ title: t("dialogs.findReplace.replaceAllTitle"),
856
+ children: t("dialogs.findReplace.replaceAllButton")
857
+ }
858
+ )
859
+ ] })
860
+ ] }) }),
861
+ /* @__PURE__ */ jsxs("div", { className: "docx-find-replace-dialog-options", style: OPTIONS_CONTAINER_STYLE, children: [
862
+ /* @__PURE__ */ jsxs("label", { className: "docx-find-replace-dialog-option", style: CHECKBOX_LABEL_STYLE, children: [
863
+ /* @__PURE__ */ jsx(
864
+ "input",
865
+ {
866
+ type: "checkbox",
867
+ style: CHECKBOX_STYLE,
868
+ checked: matchCase,
869
+ onChange: (e) => setMatchCase(e.target.checked)
870
+ }
871
+ ),
872
+ t("dialogs.findReplace.matchCase")
873
+ ] }),
874
+ /* @__PURE__ */ jsxs("label", { className: "docx-find-replace-dialog-option", style: CHECKBOX_LABEL_STYLE, children: [
875
+ /* @__PURE__ */ jsx(
876
+ "input",
877
+ {
878
+ type: "checkbox",
879
+ style: CHECKBOX_STYLE,
880
+ checked: matchWholeWord,
881
+ onChange: (e) => setMatchWholeWord(e.target.checked)
882
+ }
883
+ ),
884
+ t("dialogs.findReplace.wholeWords")
885
+ ] }),
886
+ !showReplace && /* @__PURE__ */ jsx(
887
+ "button",
888
+ {
889
+ type: "button",
890
+ style: {
891
+ ...CHECKBOX_LABEL_STYLE,
892
+ background: "none",
893
+ border: "none",
894
+ cursor: "pointer",
895
+ color: "var(--doc-link)",
896
+ padding: 0
897
+ },
898
+ onClick: toggleReplaceMode,
899
+ children: t("dialogs.findReplace.toggleReplace")
900
+ }
901
+ )
902
+ ] })
903
+ ] })
904
+ ]
905
+ }
906
+ )
907
+ }
908
+ );
909
+ }
910
+ var FindReplaceDialog_default = FindReplaceDialog;
911
+
912
+ export {
913
+ createDefaultFindOptions,
914
+ escapeRegexString,
915
+ createSearchPattern,
916
+ findAllMatches,
917
+ replaceAllInContent,
918
+ replaceFirstInContent,
919
+ getMatchCountText,
920
+ isEmptySearch,
921
+ getDefaultHighlightOptions,
922
+ findInDocument,
923
+ findInParagraph,
924
+ scrollToMatch,
925
+ useFindReplace,
926
+ FindReplaceDialog,
927
+ FindReplaceDialog_default
928
+ };