@inshapardaz/likhari-react 0.1.18-dev.165 → 0.1.18-dev.167
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.
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Strings } from '../i18n/strings.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* A compact find widget, right-aligned above the canvas (VS Code style). The
|
|
4
|
+
* first row is search; a chevron reveals the second row for replace. Typing
|
|
5
|
+
* only counts matches — the editor's selection moves to a match only when the
|
|
6
|
+
* user asks (next, previous, replace), so typing keeps going into the bar.
|
|
7
|
+
*/
|
|
4
8
|
export declare function FindReplaceBar({ strings, onClose }: {
|
|
5
9
|
strings: Strings;
|
|
6
10
|
onClose: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FindReplaceBar.d.ts","sourceRoot":"","sources":["../../src/components/FindReplaceBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C;
|
|
1
|
+
{"version":3,"file":"FindReplaceBar.d.ts","sourceRoot":"","sources":["../../src/components/FindReplaceBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BAyH7F"}
|
|
@@ -1,57 +1,71 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
-
import { IconX } from '@tabler/icons-react';
|
|
4
|
+
import { IconChevronDown, IconChevronRight, IconX } from '@tabler/icons-react';
|
|
5
5
|
import { $findMatches, $replaceAll, $replaceMatch, $selectMatch } from '../find/findReplaceActions.js';
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
6
|
+
/**
|
|
7
|
+
* A compact find widget, right-aligned above the canvas (VS Code style). The
|
|
8
|
+
* first row is search; a chevron reveals the second row for replace. Typing
|
|
9
|
+
* only counts matches — the editor's selection moves to a match only when the
|
|
10
|
+
* user asks (next, previous, replace), so typing keeps going into the bar.
|
|
11
|
+
*/
|
|
8
12
|
export function FindReplaceBar({ strings, onClose }) {
|
|
9
13
|
const [editor] = useLexicalComposerContext();
|
|
10
14
|
const t = strings.findReplace;
|
|
11
15
|
const [query, setQuery] = useState('');
|
|
12
16
|
const [replacement, setReplacement] = useState('');
|
|
17
|
+
const [showReplace, setShowReplace] = useState(false);
|
|
13
18
|
const [index, setIndex] = useState(0);
|
|
14
19
|
const [count, setCount] = useState(0);
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
// the
|
|
20
|
+
const findInput = useRef(null);
|
|
21
|
+
// Runs `action` against the current matches inside a committed update, then
|
|
22
|
+
// syncs the count and index. Selecting a match is up to the action, so
|
|
23
|
+
// typing never moves the editor's selection.
|
|
18
24
|
const run = (action) => {
|
|
19
|
-
let result = { nextIndex: 0, matches: [] };
|
|
25
|
+
let result = { nextIndex: 0, matches: [], select: false };
|
|
20
26
|
editor.update(() => {
|
|
21
27
|
result = action($findMatches(query));
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
const match = result.matches[result.nextIndex];
|
|
29
|
+
if (result.select && match)
|
|
30
|
+
$selectMatch(match);
|
|
24
31
|
}, { discrete: true });
|
|
25
32
|
setCount(result.matches.length);
|
|
26
33
|
setIndex(result.nextIndex);
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
editor.getElementByKey(
|
|
34
|
+
const match = result.matches[result.nextIndex];
|
|
35
|
+
if (result.select && match) {
|
|
36
|
+
editor.getElementByKey(match.anchorKey)?.scrollIntoView({ block: 'center' });
|
|
37
|
+
findInput.current?.focus();
|
|
38
|
+
}
|
|
30
39
|
};
|
|
31
40
|
useEffect(() => {
|
|
32
|
-
run((matches) => ({ nextIndex: 0, matches }));
|
|
33
|
-
// Re-
|
|
41
|
+
run((matches) => ({ nextIndex: 0, matches, select: false }));
|
|
42
|
+
// Re-count when the query changes; `run` closes over it.
|
|
34
43
|
}, [query]);
|
|
35
44
|
const step = (delta) => run((matches) => ({
|
|
36
45
|
matches,
|
|
37
46
|
nextIndex: matches.length === 0 ? 0 : (index + delta + matches.length) % matches.length,
|
|
47
|
+
select: true,
|
|
38
48
|
}));
|
|
39
49
|
const replaceCurrent = () => run((matches) => {
|
|
40
50
|
const match = matches[index];
|
|
41
51
|
if (match)
|
|
42
52
|
$replaceMatch(match, replacement);
|
|
43
53
|
const after = $findMatches(query);
|
|
44
|
-
return { matches: after, nextIndex: Math.min(index, Math.max(0, after.length - 1)) };
|
|
54
|
+
return { matches: after, nextIndex: Math.min(index, Math.max(0, after.length - 1)), select: true };
|
|
45
55
|
});
|
|
46
56
|
const replaceEverything = () => run(() => {
|
|
47
57
|
$replaceAll(query, replacement);
|
|
48
|
-
return { matches: $findMatches(query), nextIndex: 0 };
|
|
58
|
+
return { matches: $findMatches(query), nextIndex: 0, select: false };
|
|
49
59
|
});
|
|
50
60
|
const status = query === '' ? '' : count === 0 ? t.noMatches : `${index + 1} / ${count}`;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const Chevron = showReplace ? IconChevronDown : IconChevronRight;
|
|
62
|
+
return (_jsxs("div", { className: "likhari-find-widget", role: "search", children: [_jsxs("div", { className: "likhari-find-row", children: [_jsx("button", { type: "button", className: "likhari-find-icon", onClick: () => setShowReplace((v) => !v), "aria-label": t.replace, "aria-expanded": showReplace, title: t.replace, children: _jsx(Chevron, { size: 14, stroke: 1.75 }) }), _jsx("input", { ref: findInput, "aria-label": t.find, placeholder: t.findPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => {
|
|
63
|
+
if (e.key === 'Enter')
|
|
64
|
+
step(e.shiftKey ? -1 : 1);
|
|
65
|
+
if (e.key === 'Escape')
|
|
66
|
+
onClose();
|
|
67
|
+
}, autoFocus: true }), _jsx("span", { className: "likhari-find-status", "aria-live": "polite", children: status }), _jsx("button", { type: "button", className: "likhari-find-icon", onClick: () => step(-1), disabled: count === 0, "aria-label": t.previous, title: t.previous, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75, style: { transform: 'rotate(180deg)' } }) }), _jsx("button", { type: "button", className: "likhari-find-icon", onClick: () => step(1), disabled: count === 0, "aria-label": t.next, title: t.next, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75 }) }), _jsx("button", { type: "button", className: "likhari-find-icon", onClick: onClose, "aria-label": t.close, title: t.close, children: _jsx(IconX, { size: 14, stroke: 1.75 }) })] }), showReplace && (_jsxs("div", { className: "likhari-find-row", children: [_jsx("span", { className: "likhari-find-icon-spacer", "aria-hidden": "true" }), _jsx("input", { "aria-label": t.replace, placeholder: t.replacePlaceholder, value: replacement, onChange: (e) => setReplacement(e.target.value), onKeyDown: (e) => {
|
|
68
|
+
if (e.key === 'Enter' && count > 0)
|
|
69
|
+
replaceCurrent();
|
|
70
|
+
} }), _jsx("button", { type: "button", onClick: replaceCurrent, disabled: count === 0, children: t.replaceOne }), _jsx("button", { type: "button", onClick: replaceEverything, disabled: count === 0, children: t.replaceAll })] }))] }));
|
|
57
71
|
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -756,19 +756,32 @@
|
|
|
756
756
|
outline-offset: 1px;
|
|
757
757
|
}
|
|
758
758
|
|
|
759
|
-
|
|
759
|
+
/* A compact widget, sized to its controls and pushed to the inline end — not a
|
|
760
|
+
full-width bar. margin-inline-start: auto works in block and flex parents. */
|
|
761
|
+
.likhari-find-widget {
|
|
760
762
|
display: flex;
|
|
761
|
-
flex-
|
|
762
|
-
align-items: center;
|
|
763
|
+
flex-direction: column;
|
|
763
764
|
gap: 6px;
|
|
764
|
-
|
|
765
|
-
|
|
765
|
+
width: fit-content;
|
|
766
|
+
margin-inline: auto 10px;
|
|
767
|
+
margin-block: 6px;
|
|
768
|
+
padding: 6px;
|
|
769
|
+
border: 1px solid var(--editor-line);
|
|
770
|
+
border-radius: 6px;
|
|
766
771
|
background: var(--editor-paper-dim);
|
|
772
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
767
773
|
font-size: 13px;
|
|
768
774
|
}
|
|
769
775
|
|
|
770
|
-
.likhari-find-
|
|
771
|
-
|
|
776
|
+
.likhari-find-row {
|
|
777
|
+
display: flex;
|
|
778
|
+
align-items: center;
|
|
779
|
+
justify-content: flex-end;
|
|
780
|
+
gap: 4px;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.likhari-find-widget input {
|
|
784
|
+
width: 180px;
|
|
772
785
|
padding: 3px 6px;
|
|
773
786
|
border: 1px solid var(--editor-line);
|
|
774
787
|
border-radius: 4px;
|
|
@@ -776,27 +789,39 @@
|
|
|
776
789
|
color: inherit;
|
|
777
790
|
}
|
|
778
791
|
|
|
779
|
-
.likhari-find-
|
|
780
|
-
padding:
|
|
792
|
+
.likhari-find-widget button {
|
|
793
|
+
padding: 2px 8px;
|
|
781
794
|
border: 1px solid var(--editor-line);
|
|
782
795
|
border-radius: 4px;
|
|
783
796
|
background: transparent;
|
|
784
797
|
color: inherit;
|
|
785
798
|
cursor: pointer;
|
|
799
|
+
white-space: nowrap;
|
|
786
800
|
}
|
|
787
801
|
|
|
788
|
-
.likhari-find-
|
|
802
|
+
.likhari-find-widget button:disabled {
|
|
789
803
|
opacity: 0.5;
|
|
790
804
|
cursor: default;
|
|
791
805
|
}
|
|
792
806
|
|
|
793
|
-
.likhari-find-
|
|
794
|
-
min-width: 64px;
|
|
795
|
-
color: var(--editor-ink-muted, inherit);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
.likhari-find-close {
|
|
807
|
+
.likhari-find-widget .likhari-find-icon {
|
|
799
808
|
display: inline-flex;
|
|
800
809
|
align-items: center;
|
|
801
|
-
|
|
810
|
+
justify-content: center;
|
|
811
|
+
width: 24px;
|
|
812
|
+
height: 24px;
|
|
813
|
+
padding: 0;
|
|
814
|
+
border: none;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.likhari-find-icon-spacer {
|
|
818
|
+
display: inline-block;
|
|
819
|
+
width: 24px;
|
|
820
|
+
flex: 0 0 24px;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.likhari-find-status {
|
|
824
|
+
min-width: 56px;
|
|
825
|
+
text-align: end;
|
|
826
|
+
opacity: 0.8;
|
|
802
827
|
}
|