@inshapardaz/likhari-react 0.1.25-dev.224 → 0.1.25-dev.228
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/dist/EditorRoot.d.ts +6 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +6 -2
- package/dist/autocomplete/AutocompletePlugin.d.ts +6 -1
- package/dist/autocomplete/AutocompletePlugin.d.ts.map +1 -1
- package/dist/autocomplete/AutocompletePlugin.js +66 -23
- package/dist/autocomplete/completionStores.d.ts +25 -0
- package/dist/autocomplete/completionStores.d.ts.map +1 -0
- package/dist/autocomplete/completionStores.js +33 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ import { type DraftRestoreMode } from './components/DraftRestore.js';
|
|
|
7
7
|
import { type AutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
8
8
|
import type { UrduNormalizationOptions } from './normalization/urduNormalize.js';
|
|
9
9
|
import type { PunctuationOptions } from './autocorrect/punctuationRules.js';
|
|
10
|
+
import { type CompletionStore } from './autocomplete/completionStores.js';
|
|
10
11
|
import { type UserWordStore } from './spellcheck/userWords.js';
|
|
12
|
+
import type { SpellLanguage } from './spellcheck/spellDictionaries.js';
|
|
11
13
|
import { type Locale } from './i18n/index.js';
|
|
12
14
|
/** What happens when the user is about to leave with unsaved changes. */
|
|
13
15
|
export type NavigationGuardMode = 'confirm' | 'save-draft' | 'off';
|
|
@@ -21,6 +23,10 @@ export interface EditorRootProps {
|
|
|
21
23
|
autoCorrectStores?: AutoCorrectStore[];
|
|
22
24
|
/** Where words added to the spelling dictionary are saved, in priority order. Pass a stable array. */
|
|
23
25
|
dictionaryStores?: UserWordStore[];
|
|
26
|
+
/** Where autocomplete words come from, combined per language. Defaults to the dictionaries and the user's words. Pass a stable array. */
|
|
27
|
+
completionStores?: CompletionStore[];
|
|
28
|
+
/** The language autocomplete works in: 'auto' follows each block's direction (English for LTR, Urdu for RTL). */
|
|
29
|
+
autoCompleteLanguage?: SpellLanguage | 'auto';
|
|
24
30
|
/** Urdu normalisation applied as part of auto-correct. Diacritics are kept unless `removeDiacritics` is set. */
|
|
25
31
|
urduNormalization?: UrduNormalizationOptions;
|
|
26
32
|
/** Common punctuation fixes, and whether a straight " becomes ”. Both on by default. */
|
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIhF,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACtG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"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"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -33,6 +33,7 @@ import { localStorageAutoCorrectStore } from './autocorrect/autoCorrectStores.js
|
|
|
33
33
|
import { SpellcheckPanel } from './components/SpellcheckPanel.js';
|
|
34
34
|
import { SpellHighlightPlugin } from './spellcheck/SpellHighlightPlugin.js';
|
|
35
35
|
import { AutocompletePlugin } from './autocomplete/AutocompletePlugin.js';
|
|
36
|
+
import { dictionaryCompletionStore } from './autocomplete/completionStores.js';
|
|
36
37
|
import { loadUserWords, localStorageUserWordStore } from './spellcheck/userWords.js';
|
|
37
38
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
38
39
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
@@ -44,6 +45,8 @@ const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
|
|
|
44
45
|
const DEFAULT_AUTOSAVE_MAX_DRAFTS = 20;
|
|
45
46
|
/** Used when the host passes no stores: corrections are kept in this browser. */
|
|
46
47
|
const DEFAULT_AUTOCORRECT_STORES = [localStorageAutoCorrectStore()];
|
|
48
|
+
/** Used when the host passes no completion stores: the dictionary's words and the user's added words. */
|
|
49
|
+
const DEFAULT_COMPLETION_STORES = [dictionaryCompletionStore()];
|
|
47
50
|
/** Used when the host passes no dictionary stores: added words are kept in this browser. */
|
|
48
51
|
const DEFAULT_DICTIONARY_STORES = [localStorageUserWordStore()];
|
|
49
52
|
function initialEditorStateJson(initialContent) {
|
|
@@ -52,7 +55,7 @@ function initialEditorStateJson(initialContent) {
|
|
|
52
55
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
53
56
|
return JSON.stringify(state);
|
|
54
57
|
}
|
|
55
|
-
export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
|
|
58
|
+
export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, completionStores, autoCompleteLanguage = 'auto', urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
|
|
56
59
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
57
60
|
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
58
61
|
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
@@ -88,6 +91,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
88
91
|
const [autoCorrectVersion, setAutoCorrectVersion] = useState(0);
|
|
89
92
|
const stores = autoCorrectStores ?? DEFAULT_AUTOCORRECT_STORES;
|
|
90
93
|
const wordStores = dictionaryStores ?? DEFAULT_DICTIONARY_STORES;
|
|
94
|
+
const resolvedCompletionStores = completionStores ?? DEFAULT_COMPLETION_STORES;
|
|
91
95
|
useEffect(() => {
|
|
92
96
|
void loadUserWords(wordStores);
|
|
93
97
|
}, [wordStores]);
|
|
@@ -306,7 +310,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
306
310
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
307
311
|
return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onSave: handleSave, findOpen: openPanel === 'find', onToggleFind: () => togglePanel('find'), spellOpen: openPanel === 'spell', onToggleSpell: () => togglePanel('spell'), autoCorrectOpen: openPanel === 'autocorrect', onToggleAutoCorrect: () => togglePanel('autocorrect'), isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
|
|
308
312
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
309
|
-
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && openPanel === 'find' && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setOpenPanel(null) }), config.language.spellCheck && openPanel === 'spell' && (_jsx(SpellcheckPanel, { strings: strings, dir: dir, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, initialFrom: autoCorrectPrefill?.from, initialLanguage: autoCorrectPrefill?.language, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) }, autoCorrectPrefill?.id ?? 0)), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, spellCheck: false, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.language.spellCheck && _jsx(SpellHighlightPlugin, {}), config.language.autocomplete && _jsx(AutocompletePlugin, { label: strings.autoComplete.label }), (config.language.autocorrect || config.language.textCleanup) && _jsx(AutoCorrectPlugin, { stores: stores, version: autoCorrectVersion, enabled: true, typing: config.language.autocorrect, urduNormalization: urduNormalization, punctuation: punctuation }), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
|
|
313
|
+
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && openPanel === 'find' && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setOpenPanel(null) }), config.language.spellCheck && openPanel === 'spell' && (_jsx(SpellcheckPanel, { strings: strings, dir: dir, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, initialFrom: autoCorrectPrefill?.from, initialLanguage: autoCorrectPrefill?.language, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) }, autoCorrectPrefill?.id ?? 0)), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, spellCheck: false, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.language.spellCheck && _jsx(SpellHighlightPlugin, {}), config.language.autocomplete && (_jsx(AutocompletePlugin, { label: strings.autoComplete.label, stores: resolvedCompletionStores, language: autoCompleteLanguage })), (config.language.autocorrect || config.language.textCleanup) && _jsx(AutoCorrectPlugin, { stores: stores, version: autoCorrectVersion, enabled: true, typing: config.language.autocorrect, urduNormalization: urduNormalization, punctuation: punctuation }), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
|
|
310
314
|
handleSave();
|
|
311
315
|
settleLeave(true);
|
|
312
316
|
}, onSaveDraft: () => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
2
|
+
import type { CompletionStore } from './completionStores.js';
|
|
1
3
|
/**
|
|
2
4
|
* Suggests completions for the word being typed, from the dictionary's words and
|
|
3
5
|
* the user's own. Shown only while typing, at the caret. Tab or Enter accepts the
|
|
@@ -5,7 +7,10 @@
|
|
|
5
7
|
* moving the caret) dismisses it. Left-to-right text uses the English dictionary,
|
|
6
8
|
* right-to-left text the Urdu one.
|
|
7
9
|
*/
|
|
8
|
-
export declare function AutocompletePlugin({ label }: {
|
|
10
|
+
export declare function AutocompletePlugin({ label, stores, language: chosenLanguage, }: {
|
|
9
11
|
label: string;
|
|
12
|
+
stores: CompletionStore[];
|
|
13
|
+
/** The language to complete in, or 'auto' to follow the block's direction. */
|
|
14
|
+
language: SpellLanguage | 'auto';
|
|
10
15
|
}): import("react").JSX.Element | null;
|
|
11
16
|
//# sourceMappingURL=AutocompletePlugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompletePlugin.d.ts","sourceRoot":"","sources":["../../src/autocomplete/AutocompletePlugin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AutocompletePlugin.d.ts","sourceRoot":"","sources":["../../src/autocomplete/AutocompletePlugin.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAqB1D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,cAAc,GACzB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;CAClC,sCAsLA"}
|
|
@@ -3,12 +3,12 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
4
|
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, } from 'lexical';
|
|
5
5
|
import { $replaceMatch } from '../find/findReplaceActions.js';
|
|
6
|
-
import {
|
|
7
|
-
import { acceptedWords, onSpellWordsChange } from '../spellcheck/userWords.js';
|
|
6
|
+
import { onSpellWordsChange } from '../spellcheck/userWords.js';
|
|
8
7
|
import { WordIndex } from './wordIndex.js';
|
|
9
8
|
/** The letters at the end of the text before the caret. */
|
|
10
9
|
const WORD_END = /[\p{L}\p{M}]+$/u;
|
|
11
10
|
const LETTER = /[\p{L}\p{M}]/u;
|
|
11
|
+
const MOVEMENT_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown']);
|
|
12
12
|
/** Suggestions only appear once the typed part of a word is this long. */
|
|
13
13
|
const MIN_PREFIX = 2;
|
|
14
14
|
/**
|
|
@@ -18,8 +18,13 @@ const MIN_PREFIX = 2;
|
|
|
18
18
|
* moving the caret) dismisses it. Left-to-right text uses the English dictionary,
|
|
19
19
|
* right-to-left text the Urdu one.
|
|
20
20
|
*/
|
|
21
|
-
export function AutocompletePlugin({ label }) {
|
|
21
|
+
export function AutocompletePlugin({ label, stores, language: chosenLanguage, }) {
|
|
22
22
|
const [editor] = useLexicalComposerContext();
|
|
23
|
+
// Effects run once per editor, so they read the latest props through refs.
|
|
24
|
+
const storesRef = useRef(stores);
|
|
25
|
+
storesRef.current = stores;
|
|
26
|
+
const languageRef = useRef(chosenLanguage);
|
|
27
|
+
languageRef.current = chosenLanguage;
|
|
23
28
|
const [popup, setPopup] = useState(null);
|
|
24
29
|
const popupRef = useRef(null);
|
|
25
30
|
const typing = useRef(false);
|
|
@@ -32,10 +37,7 @@ export function AutocompletePlugin({ label }) {
|
|
|
32
37
|
const indexFor = (language) => {
|
|
33
38
|
let index = indexes.current.get(language);
|
|
34
39
|
if (!index) {
|
|
35
|
-
|
|
36
|
-
if (!words)
|
|
37
|
-
return null;
|
|
38
|
-
index = words.then((dictionary) => new WordIndex([...dictionary, ...acceptedWords(language)]));
|
|
40
|
+
index = Promise.all(storesRef.current.map((store) => store.load(language).catch(() => []))).then((lists) => new WordIndex(lists.flat()));
|
|
39
41
|
indexes.current.set(language, index);
|
|
40
42
|
}
|
|
41
43
|
return index;
|
|
@@ -58,38 +60,56 @@ export function AutocompletePlugin({ label }) {
|
|
|
58
60
|
const after = text.charAt(selection.anchor.offset);
|
|
59
61
|
if (!prefix || prefix.length < MIN_PREFIX || (after !== '' && LETTER.test(after)))
|
|
60
62
|
return;
|
|
61
|
-
|
|
62
|
-
target = { prefix, language: rtl ? 'ur' : 'en', dir: rtl ? 'rtl' : 'ltr' };
|
|
63
|
+
target = { prefix, rtl: node.getParentOrThrow().getDirection() === 'rtl' };
|
|
63
64
|
});
|
|
64
65
|
if (target === null)
|
|
65
66
|
return hide();
|
|
66
|
-
const { prefix,
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
const { prefix, rtl } = target;
|
|
68
|
+
const language = languageRef.current === 'auto' ? (rtl ? 'ur' : 'en') : languageRef.current;
|
|
69
|
+
void indexFor(language).then((loaded) => {
|
|
70
|
+
// Same word as the open list: keep the user's highlight rather than resetting it.
|
|
71
|
+
const open = popupRef.current;
|
|
72
|
+
if (open && open.prefix === prefix && open.dir === (rtl ? 'rtl' : 'ltr'))
|
|
73
|
+
return;
|
|
71
74
|
const items = loaded.complete(prefix);
|
|
72
75
|
if (items.length === 0)
|
|
73
76
|
return hide();
|
|
74
77
|
const rect = caretRect(editor);
|
|
75
|
-
show({ items, index: 0, x: rect.left, y: rect.bottom, dir });
|
|
78
|
+
show({ prefix, items, index: 0, selected: false, x: rect.left, y: rect.bottom, dir: rtl ? 'rtl' : 'ltr' });
|
|
76
79
|
});
|
|
77
80
|
};
|
|
78
|
-
|
|
81
|
+
// Handles an arrow key for the list, and cancels the browser's own caret movement.
|
|
82
|
+
const move = (delta, event) => {
|
|
79
83
|
const current = popupRef.current;
|
|
80
|
-
|
|
84
|
+
// Still loading the list for the word being typed: the arrow is for the list, not the caret.
|
|
85
|
+
if (!current && prefixAtCaret(editor) === null)
|
|
81
86
|
return false;
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
event?.preventDefault();
|
|
88
|
+
if (current) {
|
|
89
|
+
const count = current.items.length;
|
|
90
|
+
show({ ...current, index: (current.index + delta + count) % count, selected: true });
|
|
91
|
+
}
|
|
84
92
|
return true;
|
|
85
93
|
};
|
|
86
94
|
const onKeyDown = (event) => {
|
|
95
|
+
// Up, Down, Tab and Enter act on the list through their editor commands, which
|
|
96
|
+
// run after this listener, so the list must stay open for them.
|
|
97
|
+
const current = popupRef.current;
|
|
98
|
+
if (current && (event.key === 'Tab' || (event.key === 'Enter' && current.selected)))
|
|
99
|
+
return;
|
|
100
|
+
if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && (current || prefixAtCaret(editor) !== null))
|
|
101
|
+
return;
|
|
87
102
|
if (event.key === 'Escape' && popupRef.current) {
|
|
88
103
|
event.preventDefault();
|
|
89
104
|
show(null);
|
|
90
105
|
return;
|
|
91
106
|
}
|
|
92
|
-
//
|
|
107
|
+
// Left, Right, Home and End move the caret away from the word, so the list closes.
|
|
108
|
+
if (MOVEMENT_KEYS.has(event.key)) {
|
|
109
|
+
typing.current = false;
|
|
110
|
+
return hide();
|
|
111
|
+
}
|
|
112
|
+
// Printable keys and Backspace are typing; anything else (Enter, Tab, ...) ends the word.
|
|
93
113
|
typing.current = (event.key.length === 1 && !event.ctrlKey && !event.metaKey) || event.key === 'Backspace';
|
|
94
114
|
if (!typing.current)
|
|
95
115
|
hide();
|
|
@@ -120,10 +140,10 @@ export function AutocompletePlugin({ label }) {
|
|
|
120
140
|
setTimeout(refresh, 0);
|
|
121
141
|
});
|
|
122
142
|
const unregisterCommands = [
|
|
123
|
-
editor.registerCommand(KEY_ARROW_DOWN_COMMAND, () => move(1), COMMAND_PRIORITY_HIGH),
|
|
124
|
-
editor.registerCommand(KEY_ARROW_UP_COMMAND, () => move(-1), COMMAND_PRIORITY_HIGH),
|
|
143
|
+
editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => move(1, event), COMMAND_PRIORITY_HIGH),
|
|
144
|
+
editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => move(-1, event), COMMAND_PRIORITY_HIGH),
|
|
125
145
|
editor.registerCommand(KEY_TAB_COMMAND, (event) => acceptHighlighted(event), COMMAND_PRIORITY_HIGH),
|
|
126
|
-
editor.registerCommand(KEY_ENTER_COMMAND, (event) => acceptHighlighted(event), COMMAND_PRIORITY_HIGH),
|
|
146
|
+
editor.registerCommand(KEY_ENTER_COMMAND, (event) => (popupRef.current?.selected ? acceptHighlighted(event) : false), COMMAND_PRIORITY_HIGH),
|
|
127
147
|
];
|
|
128
148
|
function acceptHighlighted(event) {
|
|
129
149
|
const current = popupRef.current;
|
|
@@ -143,6 +163,10 @@ export function AutocompletePlugin({ label }) {
|
|
|
143
163
|
show(null);
|
|
144
164
|
};
|
|
145
165
|
}, [editor]);
|
|
166
|
+
// The index is per store set and language; a change of stores starts again.
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
indexes.current.clear();
|
|
169
|
+
}, [stores]);
|
|
146
170
|
if (!popup)
|
|
147
171
|
return null;
|
|
148
172
|
return (_jsx("div", { className: "likhari-autocomplete", role: "listbox", "aria-label": label, dir: popup.dir, style: { position: 'fixed', left: popup.x, top: popup.y + 4 }, children: popup.items.map((item, i) => (_jsx("div", { role: "option", "aria-selected": i === popup.index, className: i === popup.index ? 'likhari-autocomplete-item likhari-autocomplete-item--active' : 'likhari-autocomplete-item', onMouseDown: (event) => {
|
|
@@ -150,6 +174,25 @@ export function AutocompletePlugin({ label }) {
|
|
|
150
174
|
acceptItem(editor, item);
|
|
151
175
|
}, children: item }, item))) }));
|
|
152
176
|
}
|
|
177
|
+
/** The typed part of the word at the caret, when the caret is at its end and the word is long enough to complete. */
|
|
178
|
+
function prefixAtCaret(editor) {
|
|
179
|
+
let prefix = null;
|
|
180
|
+
editor.getEditorState().read(() => {
|
|
181
|
+
const selection = $getSelection();
|
|
182
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
183
|
+
return;
|
|
184
|
+
const node = selection.anchor.getNode();
|
|
185
|
+
if (!$isTextNode(node))
|
|
186
|
+
return;
|
|
187
|
+
const text = node.getTextContent();
|
|
188
|
+
const before = text.slice(0, selection.anchor.offset);
|
|
189
|
+
const word = WORD_END.exec(before)?.[0];
|
|
190
|
+
const after = text.charAt(selection.anchor.offset);
|
|
191
|
+
if (word && word.length >= MIN_PREFIX && !(after !== '' && LETTER.test(after)))
|
|
192
|
+
prefix = word;
|
|
193
|
+
});
|
|
194
|
+
return prefix;
|
|
195
|
+
}
|
|
153
196
|
/** Where to put the popup: under the caret, or the editor's top-left if the caret has no position yet. */
|
|
154
197
|
function caretRect(editor) {
|
|
155
198
|
const selection = window.getSelection();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where autocomplete words come from. The host can pass several; their words
|
|
4
|
+
* are combined per language. Like the other stores, a failing store is skipped.
|
|
5
|
+
*/
|
|
6
|
+
export interface CompletionStore {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
load(language: SpellLanguage): Promise<string[]>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The default: the dictionary's words plus the words the user has added. Nothing
|
|
12
|
+
* to configure; it follows whichever spelling dictionaries are registered.
|
|
13
|
+
*/
|
|
14
|
+
export declare function dictionaryCompletionStore(): CompletionStore;
|
|
15
|
+
/**
|
|
16
|
+
* Words served by an API. `load` calls `GET {url}?language=xx` and expects a JSON
|
|
17
|
+
* array of strings.
|
|
18
|
+
*/
|
|
19
|
+
export declare function apiCompletionStore(options: {
|
|
20
|
+
id?: string;
|
|
21
|
+
url: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
}): CompletionStore;
|
|
25
|
+
//# sourceMappingURL=completionStores.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completionStores.d.ts","sourceRoot":"","sources":["../../src/autocomplete/completionStores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGzF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAS3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,GAAG,eAAe,CAWlB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getDictionaryWords } from '../spellcheck/spellDictionaries.js';
|
|
2
|
+
import { acceptedWords } from '../spellcheck/userWords.js';
|
|
3
|
+
/**
|
|
4
|
+
* The default: the dictionary's words plus the words the user has added. Nothing
|
|
5
|
+
* to configure; it follows whichever spelling dictionaries are registered.
|
|
6
|
+
*/
|
|
7
|
+
export function dictionaryCompletionStore() {
|
|
8
|
+
return {
|
|
9
|
+
id: 'dictionary',
|
|
10
|
+
async load(language) {
|
|
11
|
+
const words = getDictionaryWords(language);
|
|
12
|
+
const dictionary = words ? await words : [];
|
|
13
|
+
return [...dictionary, ...acceptedWords(language)];
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Words served by an API. `load` calls `GET {url}?language=xx` and expects a JSON
|
|
19
|
+
* array of strings.
|
|
20
|
+
*/
|
|
21
|
+
export function apiCompletionStore(options) {
|
|
22
|
+
const doFetch = options.fetch ?? fetch;
|
|
23
|
+
return {
|
|
24
|
+
id: options.id ?? 'api',
|
|
25
|
+
async load(language) {
|
|
26
|
+
const response = await doFetch(`${options.url}?language=${encodeURIComponent(language)}`, { headers: options.headers });
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
throw new Error(`Completion API failed: ${response.status}`);
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
return Array.isArray(data) ? data.filter((w) => typeof w === 'string') : [];
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,4 +16,6 @@ export type { UrduNormalizationOptions } from './normalization/urduNormalize.js'
|
|
|
16
16
|
export type { PunctuationOptions, PunctuationRule } from './autocorrect/punctuationRules.js';
|
|
17
17
|
export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
|
|
18
18
|
export type { UserWordStore } from './spellcheck/userWords.js';
|
|
19
|
+
export { dictionaryCompletionStore, apiCompletionStore } from './autocomplete/completionStores.js';
|
|
20
|
+
export type { CompletionStore } from './autocomplete/completionStores.js';
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACrI,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAChI,YAAY,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClI,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACrI,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAChI,YAAY,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClI,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { registerSpellDictionary, hasSpellDictionary } from './spellcheck/spellD
|
|
|
7
7
|
export { localStorageAutoCorrectStore, apiAutoCorrectStore, fileAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
8
8
|
export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
|
|
9
9
|
export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
|
|
10
|
+
export { dictionaryCompletionStore, apiCompletionStore } from './autocomplete/completionStores.js';
|