@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
- /** Find-and-replace controls shown above the canvas. Matches are recomputed
3
- * from the document on every action, so edits made in between are respected. */
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;gFACgF;AAChF,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BA2F7F"}
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
- /** Find-and-replace controls shown above the canvas. Matches are recomputed
7
- * from the document on every action, so edits made in between are respected. */
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
- // Runs `action` against the current matches and returns the new count, then
16
- // syncs the index and selection. `discrete` makes the update commit before
17
- // the next read, so the count reflects this action's changes.
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
- if (result.matches[result.nextIndex])
23
- $selectMatch(result.matches[result.nextIndex]);
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 anchor = result.matches[result.nextIndex];
28
- if (anchor)
29
- editor.getElementByKey(anchor.anchorKey)?.scrollIntoView({ block: 'center' });
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-run when the query changes; `run` closes over it.
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
- return (_jsxs("div", { className: "likhari-find-bar", role: "search", children: [_jsx("input", { "aria-label": t.find, placeholder: t.findPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => {
52
- if (e.key === 'Enter')
53
- step(e.shiftKey ? -1 : 1);
54
- if (e.key === 'Escape')
55
- onClose();
56
- } }), _jsx("span", { className: "likhari-find-status", "aria-live": "polite", children: status }), _jsx("button", { type: "button", onClick: () => step(-1), disabled: count === 0, children: t.previous }), _jsx("button", { type: "button", onClick: () => step(1), disabled: count === 0, children: t.next }), _jsx("input", { "aria-label": t.replace, placeholder: t.replacePlaceholder, value: replacement, onChange: (e) => setReplacement(e.target.value) }), _jsx("button", { type: "button", onClick: replaceCurrent, disabled: count === 0, children: t.replaceOne }), _jsx("button", { type: "button", onClick: replaceEverything, disabled: count === 0, children: t.replaceAll }), _jsx("button", { type: "button", className: "likhari-find-close", onClick: onClose, "aria-label": t.close, title: t.close, children: _jsx(IconX, { size: 16, stroke: 1.75 }) })] }));
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
  }
@@ -756,19 +756,32 @@
756
756
  outline-offset: 1px;
757
757
  }
758
758
 
759
- .likhari-find-bar {
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-wrap: wrap;
762
- align-items: center;
763
+ flex-direction: column;
763
764
  gap: 6px;
764
- padding: 6px 10px;
765
- border-bottom: 1px solid var(--editor-line);
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-bar input {
771
- min-width: 140px;
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-bar button {
780
- padding: 3px 8px;
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-bar button:disabled {
802
+ .likhari-find-widget button:disabled {
789
803
  opacity: 0.5;
790
804
  cursor: default;
791
805
  }
792
806
 
793
- .likhari-find-status {
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
- margin-inline-start: auto;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.18-dev.165",
3
+ "version": "0.1.18-dev.167",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {