@stll/folio-react 0.4.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.
- package/README.md +1 -1
- package/dist/{CommentsSidebar-BsYOInaX.js → CommentsSidebar-_utmEkA9.js} +1 -1
- package/dist/{FindReplaceDialog-EFjmqEbg.js → FindReplaceDialog-DLdBekSR.js} +4 -3
- package/dist/{FootnotePropertiesDialog-VfF7SQo2.js → FootnotePropertiesDialog-CUjq9c6H.js} +4 -2
- package/dist/{ImagePositionDialog-FGLCOf0Q.js → ImagePositionDialog-BMuO9rLT.js} +4 -2
- package/dist/{ImagePropertiesDialog-Baokui4M.js → ImagePropertiesDialog-C8iEgdmN.js} +4 -2
- package/dist/{PageSetupDialog-Df2HjHiu.js → PageSetupDialog-BKsSMCGb.js} +4 -2
- package/dist/{TablePropertiesDialog-ZmzjSqME.js → TablePropertiesDialog-Dw8gvL78.js} +4 -2
- package/dist/compat/eigenpal.d.ts +114 -0
- package/dist/compat/eigenpal.js +51 -0
- package/dist/dialogs-CzvXYJyG.js +1080 -0
- package/dist/dialogs-j7qyw17x.d.ts +318 -0
- package/dist/dialogs.d.ts +2 -0
- package/dist/dialogs.js +8 -0
- package/dist/index.d.ts +15 -983
- package/dist/index.js +22 -12647
- package/dist/messages.js +1 -26
- package/dist/renderAsync-B7UJsZye.d.ts +915 -0
- package/dist/renderAsync-CwHTsxBN.js +12731 -0
- package/dist/rolldown-runtime-BBjsoOtd.js +27 -0
- package/dist/standalone.css +1 -1
- package/dist/{folio-ui-CA59webC.js → useFindReplace-BPBpMWS9.js} +354 -2
- package/package.json +11 -3
- package/dist/contained-handler-4uiqUVRn.js +0 -45
- package/dist/useFindReplace-MZ8wi31A.js +0 -354
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createContext, useContext } from "react";
|
|
1
|
+
import { createContext, useCallback, useContext, useState } from "react";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { prefersReducedMotionBehavior } from "@stll/folio-core/paged-layout/scrollNavigation";
|
|
3
4
|
import { Checkbox } from "@base-ui/react/checkbox";
|
|
4
5
|
import { Popover } from "@base-ui/react/popover";
|
|
5
6
|
import { Dialog } from "@base-ui/react/dialog";
|
|
@@ -487,4 +488,355 @@ function useFolioUI() {
|
|
|
487
488
|
return useContext(FolioUIContext);
|
|
488
489
|
}
|
|
489
490
|
//#endregion
|
|
490
|
-
|
|
491
|
+
//#region src/components/dialogs/findReplaceUtils.ts
|
|
492
|
+
/**
|
|
493
|
+
* Find & Replace Utility Functions
|
|
494
|
+
*
|
|
495
|
+
* Pure utility functions for text search, pattern matching, and document search.
|
|
496
|
+
* Extracted from FindReplaceDialog.tsx.
|
|
497
|
+
*/
|
|
498
|
+
/**
|
|
499
|
+
* Create default find options
|
|
500
|
+
*/
|
|
501
|
+
function createDefaultFindOptions() {
|
|
502
|
+
return {
|
|
503
|
+
matchCase: false,
|
|
504
|
+
matchWholeWord: false,
|
|
505
|
+
useRegex: false
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Find all matches of search text in content
|
|
510
|
+
*/
|
|
511
|
+
function findAllMatches(content, searchText, options) {
|
|
512
|
+
if (!content || !searchText) return [];
|
|
513
|
+
const matches = [];
|
|
514
|
+
let searchFor = searchText;
|
|
515
|
+
if (!options.matchCase) searchFor = searchText.toLowerCase();
|
|
516
|
+
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
517
|
+
let pattern;
|
|
518
|
+
if (options.matchWholeWord) pattern = `\\b${escapeRegex(searchFor)}\\b`;
|
|
519
|
+
else pattern = escapeRegex(searchFor);
|
|
520
|
+
const flags = options.matchCase ? "g" : "gi";
|
|
521
|
+
const regex = new RegExp(pattern, flags);
|
|
522
|
+
let match;
|
|
523
|
+
while ((match = regex.exec(content)) !== null) {
|
|
524
|
+
matches.push({
|
|
525
|
+
start: match.index,
|
|
526
|
+
end: match.index + match[0].length
|
|
527
|
+
});
|
|
528
|
+
if (match[0].length === 0) regex.lastIndex++;
|
|
529
|
+
}
|
|
530
|
+
return matches;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get plain text from a run
|
|
534
|
+
*/
|
|
535
|
+
function getRunText(run) {
|
|
536
|
+
let text = "";
|
|
537
|
+
for (const item of run.content) if (item.type === "text") text += item.text;
|
|
538
|
+
else if (item.type === "tab") text += " ";
|
|
539
|
+
else if (item.type === "break" && item.breakType === "textWrapping") text += "\n";
|
|
540
|
+
return text;
|
|
541
|
+
}
|
|
542
|
+
function getHyperlinkText(hyperlink) {
|
|
543
|
+
let text = "";
|
|
544
|
+
for (const child of hyperlink.children) if (child.type === "run") text += getRunText(child);
|
|
545
|
+
return text;
|
|
546
|
+
}
|
|
547
|
+
function getParagraphContentText(content) {
|
|
548
|
+
if (content.type === "run") return getRunText(content);
|
|
549
|
+
if (content.type === "hyperlink") return getHyperlinkText(content);
|
|
550
|
+
if (content.type === "inlineSdt") {
|
|
551
|
+
let text = "";
|
|
552
|
+
for (const child of content.content) text += getParagraphContentText(child);
|
|
553
|
+
return text;
|
|
554
|
+
}
|
|
555
|
+
if (content.type === "simpleField") {
|
|
556
|
+
let text = "";
|
|
557
|
+
for (const child of content.content) {
|
|
558
|
+
if (child.type === "run") {
|
|
559
|
+
text += getRunText(child);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
text += getHyperlinkText(child);
|
|
563
|
+
}
|
|
564
|
+
return text;
|
|
565
|
+
}
|
|
566
|
+
if (content.type === "complexField") {
|
|
567
|
+
let text = "";
|
|
568
|
+
for (const run of content.fieldResult) text += getRunText(run);
|
|
569
|
+
return text;
|
|
570
|
+
}
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get plain text from a paragraph
|
|
575
|
+
*/
|
|
576
|
+
function getParagraphPlainText(paragraph) {
|
|
577
|
+
let text = "";
|
|
578
|
+
for (const item of paragraph.content) text += getParagraphContentText(item);
|
|
579
|
+
return text;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Find all matches in a document
|
|
583
|
+
*/
|
|
584
|
+
function findInDocument(document, searchText, options) {
|
|
585
|
+
if (!document || !searchText) return [];
|
|
586
|
+
const matches = [];
|
|
587
|
+
const body = document.package.document;
|
|
588
|
+
if (!isDocumentBody(body)) return matches;
|
|
589
|
+
forEachParagraph(body.content, (block, paragraphIndex) => {
|
|
590
|
+
const paragraphMatches = findInParagraph(block, searchText, options, paragraphIndex);
|
|
591
|
+
matches.push(...paragraphMatches);
|
|
592
|
+
});
|
|
593
|
+
return matches;
|
|
594
|
+
}
|
|
595
|
+
function forEachParagraph(blocks, visit) {
|
|
596
|
+
let paragraphIndex = 0;
|
|
597
|
+
const walkBlocks = (items) => {
|
|
598
|
+
for (const block of items) {
|
|
599
|
+
if (isParagraph(block)) {
|
|
600
|
+
visit(block, paragraphIndex);
|
|
601
|
+
paragraphIndex++;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (isTable(block)) {
|
|
605
|
+
walkTable(block);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (isBlockSdt(block)) walkBlocks(block.content);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const walkTable = (table) => {
|
|
612
|
+
for (const row of table.rows) {
|
|
613
|
+
if (!isTableRow(row)) continue;
|
|
614
|
+
for (const cell of row.cells) {
|
|
615
|
+
if (!isTableCell(cell)) continue;
|
|
616
|
+
walkBlocks(cell.content);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
walkBlocks(blocks);
|
|
621
|
+
}
|
|
622
|
+
function isRecord(value) {
|
|
623
|
+
return typeof value === "object" && value !== null;
|
|
624
|
+
}
|
|
625
|
+
function isDocumentBody(value) {
|
|
626
|
+
return isRecord(value) && Array.isArray(value["content"]);
|
|
627
|
+
}
|
|
628
|
+
function isParagraph(value) {
|
|
629
|
+
return isRecord(value) && value["type"] === "paragraph" && Array.isArray(value["content"]);
|
|
630
|
+
}
|
|
631
|
+
function isTable(value) {
|
|
632
|
+
return isRecord(value) && value["type"] === "table" && Array.isArray(value["rows"]);
|
|
633
|
+
}
|
|
634
|
+
function isTableRow(value) {
|
|
635
|
+
return isRecord(value) && Array.isArray(value["cells"]);
|
|
636
|
+
}
|
|
637
|
+
function isTableCell(value) {
|
|
638
|
+
return isRecord(value) && Array.isArray(value["content"]);
|
|
639
|
+
}
|
|
640
|
+
function isBlockSdt(value) {
|
|
641
|
+
return isRecord(value) && value["type"] === "blockSdt" && Array.isArray(value["content"]);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Find matches in a single paragraph
|
|
645
|
+
*/
|
|
646
|
+
function findInParagraph(paragraph, searchText, options, paragraphIndex) {
|
|
647
|
+
const matches = [];
|
|
648
|
+
const paragraphText = getParagraphPlainText(paragraph);
|
|
649
|
+
if (!paragraphText) return matches;
|
|
650
|
+
const textMatches = findAllMatches(paragraphText, searchText, options);
|
|
651
|
+
for (const match of textMatches) {
|
|
652
|
+
const contentInfo = findContentAtOffset(paragraph, match.start);
|
|
653
|
+
matches.push({
|
|
654
|
+
paragraphIndex,
|
|
655
|
+
contentIndex: contentInfo.contentIndex,
|
|
656
|
+
startOffset: match.start,
|
|
657
|
+
endOffset: match.end,
|
|
658
|
+
text: paragraphText.slice(match.start, match.end)
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return matches;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Find the content (run) at a specific character offset in a paragraph
|
|
665
|
+
*/
|
|
666
|
+
function findContentAtOffset(paragraph, offset) {
|
|
667
|
+
let currentOffset = 0;
|
|
668
|
+
let contentIndex = 0;
|
|
669
|
+
for (const item of paragraph.content) {
|
|
670
|
+
const itemLength = getParagraphContentText(item).length;
|
|
671
|
+
if (currentOffset + itemLength > offset) return {
|
|
672
|
+
contentIndex,
|
|
673
|
+
runIndex: contentIndex,
|
|
674
|
+
offsetInContent: offset - currentOffset
|
|
675
|
+
};
|
|
676
|
+
currentOffset += itemLength;
|
|
677
|
+
contentIndex++;
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
contentIndex: Math.max(0, paragraph.content.length - 1),
|
|
681
|
+
runIndex: Math.max(0, paragraph.content.length - 1),
|
|
682
|
+
offsetInContent: 0
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Scroll to a match in the document
|
|
687
|
+
*/
|
|
688
|
+
function scrollToMatch(containerElement, match) {
|
|
689
|
+
if (!containerElement) return;
|
|
690
|
+
(containerElement.querySelector(`[data-paragraph-index="${match.paragraphIndex}"]`) ?? containerElement.querySelector(`.layout-paragraph[data-block-id="block-${match.paragraphIndex + 1}"]`) ?? containerElement.querySelectorAll(".layout-paragraph").item(match.paragraphIndex)).scrollIntoView({
|
|
691
|
+
behavior: prefersReducedMotionBehavior(),
|
|
692
|
+
block: "center"
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/components/dialogs/useFindReplace.ts
|
|
697
|
+
/**
|
|
698
|
+
* useFindReplace Hook
|
|
699
|
+
*
|
|
700
|
+
* React hook for managing find/replace dialog state.
|
|
701
|
+
* Extracted from FindReplaceDialog.tsx.
|
|
702
|
+
*/
|
|
703
|
+
/**
|
|
704
|
+
* Hook for managing find/replace dialog state
|
|
705
|
+
*/
|
|
706
|
+
function useFindReplace(hookOptions) {
|
|
707
|
+
const [state, setState] = useState({
|
|
708
|
+
...closedDialogState(hookOptions?.initialReplaceMode ? "replace" : "find"),
|
|
709
|
+
searchText: "",
|
|
710
|
+
replaceText: "",
|
|
711
|
+
options: createDefaultFindOptions(),
|
|
712
|
+
matches: [],
|
|
713
|
+
currentIndex: 0
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
state,
|
|
717
|
+
openFind: useCallback((selectedText) => {
|
|
718
|
+
setState((prev) => ({
|
|
719
|
+
...prev,
|
|
720
|
+
...openDialogState("find"),
|
|
721
|
+
searchText: selectedText || prev.searchText,
|
|
722
|
+
matches: [],
|
|
723
|
+
currentIndex: 0
|
|
724
|
+
}));
|
|
725
|
+
}, []),
|
|
726
|
+
openReplace: useCallback((selectedText) => {
|
|
727
|
+
setState((prev) => ({
|
|
728
|
+
...prev,
|
|
729
|
+
...openDialogState("replace"),
|
|
730
|
+
searchText: selectedText || prev.searchText,
|
|
731
|
+
matches: [],
|
|
732
|
+
currentIndex: 0
|
|
733
|
+
}));
|
|
734
|
+
}, []),
|
|
735
|
+
close: useCallback(() => {
|
|
736
|
+
setState((prev) => ({
|
|
737
|
+
...prev,
|
|
738
|
+
...closedDialogState(prev.lastMode)
|
|
739
|
+
}));
|
|
740
|
+
}, []),
|
|
741
|
+
toggle: useCallback(() => {
|
|
742
|
+
setState((prev) => ({
|
|
743
|
+
...prev,
|
|
744
|
+
...prev.dialog.status === "closed" ? openDialogState(prev.lastMode) : closedDialogState(prev.lastMode)
|
|
745
|
+
}));
|
|
746
|
+
}, []),
|
|
747
|
+
setSearchText: useCallback((text) => {
|
|
748
|
+
setState((prev) => ({
|
|
749
|
+
...prev,
|
|
750
|
+
searchText: text
|
|
751
|
+
}));
|
|
752
|
+
}, []),
|
|
753
|
+
setReplaceText: useCallback((text) => {
|
|
754
|
+
setState((prev) => ({
|
|
755
|
+
...prev,
|
|
756
|
+
replaceText: text
|
|
757
|
+
}));
|
|
758
|
+
}, []),
|
|
759
|
+
setOptions: useCallback((options) => {
|
|
760
|
+
setState((prev) => ({
|
|
761
|
+
...prev,
|
|
762
|
+
options: {
|
|
763
|
+
...prev.options,
|
|
764
|
+
...options
|
|
765
|
+
}
|
|
766
|
+
}));
|
|
767
|
+
}, []),
|
|
768
|
+
setMatches: useCallback((matches, currentIndex = 0) => {
|
|
769
|
+
const newIndex = Math.max(0, Math.min(currentIndex, matches.length - 1));
|
|
770
|
+
setState((prev) => ({
|
|
771
|
+
...prev,
|
|
772
|
+
matches,
|
|
773
|
+
currentIndex: matches.length > 0 ? newIndex : 0
|
|
774
|
+
}));
|
|
775
|
+
hookOptions?.onMatchesChange?.(matches);
|
|
776
|
+
if (matches.length > 0) hookOptions?.onCurrentMatchChange?.(matches[newIndex] ?? null, newIndex);
|
|
777
|
+
else hookOptions?.onCurrentMatchChange?.(null, -1);
|
|
778
|
+
}, [hookOptions]),
|
|
779
|
+
goToNextMatch: useCallback(() => {
|
|
780
|
+
let newIndex = 0;
|
|
781
|
+
setState((prev) => {
|
|
782
|
+
if (prev.matches.length === 0) return prev;
|
|
783
|
+
newIndex = (prev.currentIndex + 1) % prev.matches.length;
|
|
784
|
+
return {
|
|
785
|
+
...prev,
|
|
786
|
+
currentIndex: newIndex
|
|
787
|
+
};
|
|
788
|
+
});
|
|
789
|
+
return newIndex;
|
|
790
|
+
}, []),
|
|
791
|
+
goToPreviousMatch: useCallback(() => {
|
|
792
|
+
let newIndex = 0;
|
|
793
|
+
setState((prev) => {
|
|
794
|
+
if (prev.matches.length === 0) return prev;
|
|
795
|
+
newIndex = prev.currentIndex === 0 ? prev.matches.length - 1 : prev.currentIndex - 1;
|
|
796
|
+
return {
|
|
797
|
+
...prev,
|
|
798
|
+
currentIndex: newIndex
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
return newIndex;
|
|
802
|
+
}, []),
|
|
803
|
+
goToMatch: useCallback((index) => {
|
|
804
|
+
setState((prev) => {
|
|
805
|
+
if (prev.matches.length === 0 || index < 0 || index >= prev.matches.length) return prev;
|
|
806
|
+
return {
|
|
807
|
+
...prev,
|
|
808
|
+
currentIndex: index
|
|
809
|
+
};
|
|
810
|
+
});
|
|
811
|
+
}, []),
|
|
812
|
+
getCurrentMatch: useCallback(() => {
|
|
813
|
+
if (state.matches.length === 0) return null;
|
|
814
|
+
return state.matches[state.currentIndex] || null;
|
|
815
|
+
}, [state.matches, state.currentIndex]),
|
|
816
|
+
hasMatches: useCallback(() => state.matches.length > 0, [state.matches.length])
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function closedDialogState(mode) {
|
|
820
|
+
return {
|
|
821
|
+
dialog: { status: "closed" },
|
|
822
|
+
lastMode: mode
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function openDialogState(mode) {
|
|
826
|
+
if (mode === "replace") return {
|
|
827
|
+
dialog: {
|
|
828
|
+
status: "open",
|
|
829
|
+
mode: "replace"
|
|
830
|
+
},
|
|
831
|
+
lastMode: "replace"
|
|
832
|
+
};
|
|
833
|
+
return {
|
|
834
|
+
dialog: {
|
|
835
|
+
status: "open",
|
|
836
|
+
mode: "find"
|
|
837
|
+
},
|
|
838
|
+
lastMode: "find"
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
//#endregion
|
|
842
|
+
export { FolioUIProvider as a, DEFAULT_COMPONENTS as i, findInDocument as n, useFolioUI as o, scrollToMatch as r, cn as s, useFindReplace as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React editor for folio: a Word-document (.docx) editor for the browser, built on @stll/folio-core and ProseMirror.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-editor",
|
|
@@ -32,6 +32,14 @@
|
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"import": "./dist/index.js"
|
|
34
34
|
},
|
|
35
|
+
"./compat/eigenpal": {
|
|
36
|
+
"types": "./dist/compat/eigenpal.d.ts",
|
|
37
|
+
"import": "./dist/compat/eigenpal.js"
|
|
38
|
+
},
|
|
39
|
+
"./dialogs": {
|
|
40
|
+
"types": "./dist/dialogs.d.ts",
|
|
41
|
+
"import": "./dist/dialogs.js"
|
|
42
|
+
},
|
|
35
43
|
"./messages": {
|
|
36
44
|
"types": "./dist/messages.d.ts",
|
|
37
45
|
"import": "./dist/messages.js"
|
|
@@ -74,8 +82,8 @@
|
|
|
74
82
|
"y-prosemirror": "^1.3.7"
|
|
75
83
|
},
|
|
76
84
|
"peerDependencies": {
|
|
77
|
-
"react": "^19.0.0",
|
|
78
|
-
"react-dom": "^19.0.0",
|
|
85
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
86
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
79
87
|
"use-intl": ">=4.0.0"
|
|
80
88
|
},
|
|
81
89
|
"main": "./dist/index.js",
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region src/utils/contained-handler.ts
|
|
2
|
-
/**
|
|
3
|
-
* Wrap a React event handler so it only fires when the event target
|
|
4
|
-
* is a DOM descendant of the given ref.
|
|
5
|
-
*
|
|
6
|
-
* React forwards synthetic events through the parent React tree even
|
|
7
|
-
* when descendants are rendered via createPortal. That makes it unsafe
|
|
8
|
-
* for a parent element to attach `onMouseDown`, `onClick`, `onFocus`,
|
|
9
|
-
* etc. that side-effect (focus steal, preventDefault, selection changes)
|
|
10
|
-
* under the assumption "the target lives inside me" — a portaled popup
|
|
11
|
-
* (Dialog, Combobox, Tooltip) silently bypasses the DOM boundary and
|
|
12
|
-
* triggers the side effect, dismissing itself.
|
|
13
|
-
*
|
|
14
|
-
* Only use for event types whose `target` is the meaningful subject of
|
|
15
|
-
* the event: pointer/mouse/touch/click and `focus`. Do **not** wrap
|
|
16
|
-
* `onBlur`: blur's `target` is the element losing focus, not the new
|
|
17
|
-
* focus destination, so the containment test cannot answer "is focus
|
|
18
|
-
* leaving for a portaled child?". For that case test `relatedTarget`
|
|
19
|
-
* directly inside the handler.
|
|
20
|
-
*
|
|
21
|
-
* The `require-contained-handler` oxlint rule enforces this on any JSX
|
|
22
|
-
* element carrying both `ref={…}` and one of the watched handler props.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* const barRef = useRef<HTMLDivElement>(null);
|
|
26
|
-
* return (
|
|
27
|
-
* <div
|
|
28
|
-
* ref={barRef}
|
|
29
|
-
* onMouseDown={containedHandler(barRef, (e) => {
|
|
30
|
-
* e.preventDefault();
|
|
31
|
-
* focusEditor();
|
|
32
|
-
* })}
|
|
33
|
-
* >
|
|
34
|
-
* ...
|
|
35
|
-
* </div>
|
|
36
|
-
* );
|
|
37
|
-
*/
|
|
38
|
-
const containedHandler = (ref, handler) => (event) => {
|
|
39
|
-
if (handler === void 0) return;
|
|
40
|
-
const container = ref?.current ?? null;
|
|
41
|
-
if (container !== null && event.target instanceof Node && !container.contains(event.target)) return;
|
|
42
|
-
handler(event);
|
|
43
|
-
};
|
|
44
|
-
//#endregion
|
|
45
|
-
export { containedHandler as t };
|