@adecore/editor-react 0.23.1 → 0.24.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/dist/AnchoredPopup.d.ts +2 -1
- package/dist/AnchoredPopup.js +2 -2
- package/dist/CodeAuthorsCard.d.ts +4 -0
- package/dist/CodeAuthorsCard.js +8 -4
- package/dist/CompletionPopup.js +7 -3
- package/dist/EditorView.d.ts +8 -3
- package/dist/EditorView.js +79 -9
- package/dist/HoverCard.d.ts +3 -1
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.d.ts +8 -4
- package/dist/LanguagePopups.js +4 -4
- package/dist/PickPopup.js +2 -1
- package/dist/RenameCard.js +10 -8
- package/dist/RenderedDocs.d.ts +10 -4
- package/dist/RenderedDocs.js +10 -6
- package/dist/call-hierarchy.js +15 -14
- package/dist/code-actions.js +5 -2
- package/dist/completion-model.js +3 -1
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +49 -27
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +1 -1
- package/dist/diagnostics.d.ts +1 -0
- package/dist/diagnostics.js +28 -10
- package/dist/diff-overlays.js +2 -2
- package/dist/editor-language.d.ts +3 -1
- package/dist/editor-language.js +10 -0
- package/dist/format-on-type.js +2 -3
- package/dist/highlights.js +3 -2
- package/dist/history.js +3 -2
- package/dist/holder.d.ts +3 -3
- package/dist/holder.js +2 -1
- package/dist/host-types.d.ts +3 -0
- package/dist/hover.d.ts +5 -0
- package/dist/hover.js +13 -5
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/key-priority.d.ts +23 -0
- package/dist/key-priority.js +39 -0
- package/dist/locales/en.json +2 -1
- package/dist/locales/nl.json +2 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +2 -2
- package/dist/navigation.js +3 -2
- package/dist/peek.js +3 -2
- package/dist/pick.js +2 -1
- package/dist/popup-focus.d.ts +5 -0
- package/dist/popup-focus.js +12 -0
- package/dist/popups.d.ts +1 -1
- package/dist/project-language.js +5 -4
- package/dist/project-problems.d.ts +10 -1
- package/dist/project-problems.js +34 -10
- package/dist/proposal-model.d.ts +0 -1
- package/dist/proposal-model.js +0 -8
- package/dist/rename-model.d.ts +2 -1
- package/dist/rename-model.js +57 -0
- package/dist/rename.d.ts +1 -0
- package/dist/rename.js +21 -5
- package/dist/rendered-docs.js +3 -2
- package/dist/signature.js +4 -3
- package/dist/snippet-session.d.ts +1 -1
- package/dist/snippet-session.js +8 -2
- package/dist/symbol-picker.js +3 -2
- package/package.json +6 -6
- package/src/AnchoredPopup.tsx +4 -2
- package/src/CodeAuthorsCard.tsx +21 -12
- package/src/CompletionPopup.tsx +11 -1
- package/src/EditorView.tsx +122 -11
- package/src/HoverCard.tsx +6 -1
- package/src/LanguagePopups.tsx +23 -4
- package/src/PickPopup.tsx +9 -1
- package/src/RenameCard.tsx +15 -9
- package/src/RenderedDocs.tsx +30 -8
- package/src/call-hierarchy.ts +15 -14
- package/src/code-actions.ts +5 -2
- package/src/completion-model.ts +3 -1
- package/src/completion.ts +48 -28
- package/src/diagnostics-model.ts +1 -1
- package/src/diagnostics.ts +29 -13
- package/src/diff-overlays.tsx +12 -8
- package/src/editor-language.ts +12 -1
- package/src/format-on-type.ts +2 -3
- package/src/highlights.ts +3 -2
- package/src/history.ts +3 -2
- package/src/holder.ts +5 -4
- package/src/host-types.ts +5 -0
- package/src/hover.ts +19 -5
- package/src/index.ts +6 -2
- package/src/key-priority.ts +49 -0
- package/src/locales/en.json +2 -1
- package/src/locales/nl.json +2 -1
- package/src/models.ts +1 -1
- package/src/navigation.ts +3 -2
- package/src/peek.ts +3 -2
- package/src/pick.ts +2 -1
- package/src/popup-focus.ts +17 -0
- package/src/popups.ts +1 -2
- package/src/project-language.ts +5 -4
- package/src/project-problems.ts +46 -11
- package/src/proposal-model.ts +0 -9
- package/src/rename-model.ts +60 -1
- package/src/rename.ts +21 -5
- package/src/rendered-docs.ts +3 -2
- package/src/signature.ts +4 -3
- package/src/snippet-session.ts +9 -3
- package/src/symbol-picker.ts +3 -2
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.js +0 -9
- package/src/relative-time.ts +0 -10
package/dist/AnchoredPopup.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { type PlaceOptions } from './popup-placement.ts';
|
|
|
4
4
|
export interface AnchoredPopupProps {
|
|
5
5
|
rect: EditorRect;
|
|
6
6
|
className?: string;
|
|
7
|
+
overflow?: 'scroll' | 'clip';
|
|
7
8
|
children: ReactNode;
|
|
8
9
|
placement?: PlaceOptions;
|
|
9
10
|
onPointerEnter?(): void;
|
|
10
11
|
onPointerLeave?(): void;
|
|
11
12
|
}
|
|
12
|
-
export declare function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps): import("react").ReactPortal;
|
|
13
|
+
export declare function AnchoredPopup({ rect, className, overflow, children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps): import("react").ReactPortal;
|
package/dist/AnchoredPopup.js
CHANGED
|
@@ -6,7 +6,7 @@ import { placePopup } from './popup-placement.js';
|
|
|
6
6
|
* A card next to a character of the editor, portaled to the page: inside a scaled canvas node it would be
|
|
7
7
|
* drawn at that scale and clipped by the node. Placed from the character's screen rectangle, so it holds at any zoom.
|
|
8
8
|
*/
|
|
9
|
-
export function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }) {
|
|
9
|
+
export function AnchoredPopup({ rect, className, overflow = 'scroll', children, placement, onPointerEnter, onPointerLeave }) {
|
|
10
10
|
const element = useRef(null);
|
|
11
11
|
// Every render, since the character moves when the editor scrolls and the card's own size follows its content.
|
|
12
12
|
useLayoutEffect(() => {
|
|
@@ -22,5 +22,5 @@ export function AnchoredPopup({ rect, className, children, placement, onPointerE
|
|
|
22
22
|
popup.style.maxHeight = `${Math.floor(placed.maxHeight)}px`;
|
|
23
23
|
popup.style.visibility = 'visible';
|
|
24
24
|
});
|
|
25
|
-
return createPortal(_jsx("div", { ref: element, className: `fixed top-0 left-0 z-(--z-popup) overflow-y-auto rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow) ${className ?? ''}`, style: { visibility: 'hidden' }, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, children: children }), document.body);
|
|
25
|
+
return createPortal(_jsx("div", { ref: element, className: `fixed top-0 left-0 z-(--z-popup) ${overflow === 'clip' ? 'overflow-hidden' : 'overflow-y-auto'} rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow) ${className ?? ''}`, style: { visibility: 'hidden' }, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, children: children }), document.body);
|
|
26
26
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
import type { GitBlameCommit } from './host-types.ts';
|
|
2
3
|
import type { AuthorsView } from './popups.ts';
|
|
4
|
+
export declare function LatestCommit({ commit }: {
|
|
5
|
+
commit: GitBlameCommit;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
3
7
|
export declare function CodeAuthorsCard({ language, view }: {
|
|
4
8
|
language: EditorLanguage;
|
|
5
9
|
view: AuthorsView;
|
package/dist/CodeAuthorsCard.js
CHANGED
|
@@ -3,20 +3,24 @@ import { useMemo } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { GitCommitHorizontal, User, Users } from 'lucide-react';
|
|
5
5
|
import { Button, Icon, Popover, Separator, Tooltip, useNow } from '@adecore/ui';
|
|
6
|
-
import { formatNumber } from '@adecore/ui/format';
|
|
7
|
-
import { relativeTime } from './relative-time.js';
|
|
6
|
+
import { formatAge, formatNumber } from '@adecore/ui/format';
|
|
8
7
|
const MINUTE_MS = 60_000;
|
|
8
|
+
/* The newest commit that touched the declaration. Its clock ticks here, so a minute redraws this block and not the card. */
|
|
9
|
+
export function LatestCommit({ commit }) {
|
|
10
|
+
const { t } = useTranslation('editor');
|
|
11
|
+
const now = useNow(MINUTE_MS);
|
|
12
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-text-muted", children: t('language.codeVision.latest') }), _jsx("span", { className: "text-text select-text", children: commit.summary }), _jsxs("span", { className: "flex items-center gap-1.5 text-text-muted", children: [_jsx("span", { className: "font-mono select-text", children: commit.shortHash }), _jsx("span", { className: "min-w-0 truncate", children: commit.author }), _jsx("span", { className: "shrink-0", children: formatAge(commit.at * 1000, now) })] })] }));
|
|
13
|
+
}
|
|
9
14
|
/*
|
|
10
15
|
* Who wrote a declaration, by lines, and the newest commit that touched it. It opens beside the entry that
|
|
11
16
|
* was pressed and closes on Escape or a press anywhere else; Escape hands the keyboard back to the editor.
|
|
12
17
|
*/
|
|
13
18
|
export function CodeAuthorsCard({ language, view }) {
|
|
14
19
|
const { t } = useTranslation('editor');
|
|
15
|
-
const now = Math.floor(useNow(MINUTE_MS) / 1000);
|
|
16
20
|
const { authors, uncommittedLines, latest } = view.authorship;
|
|
17
21
|
const anchor = useMemo(() => {
|
|
18
22
|
const { left, top, right, bottom } = view.anchor;
|
|
19
23
|
return { getBoundingClientRect: () => new DOMRect(left, top, right - left, bottom - top) };
|
|
20
24
|
}, [view.anchor]);
|
|
21
|
-
return (_jsx(Popover.Root, { open: true, onOpenChange: (open, details) => !open && language.codeVision.closeAuthors(details.reason === 'escape-key'), children: _jsxs(Popover.Popup, { anchor: anchor, side: "bottom", align: "start", sideOffset: 4, className: "flex w-72 flex-col gap-2 p-3 text-xs", children: [_jsxs(Popover.Title, { className: "flex items-center gap-1.5 font-medium text-text", children: [_jsx(Icon, { icon: authors.length > 1 ? Users : User, size: 14, className: "shrink-0 text-text-muted" }), _jsx("span", { className: "min-w-0 truncate", children: view.name })] }), _jsxs("ul", { className: "flex flex-col gap-1", children: [authors.map((author) => (_jsxs("li", { className: "flex items-baseline gap-2", children: [_jsx(Tooltip, { label: author.email, children: _jsx("span", { className: "min-w-0 grow truncate text-text", children: author.name }) }), _jsx("span", { className: "shrink-0 text-text-muted", children: t('language.codeVision.lines', { count: author.lines, formatted: formatNumber(author.lines) }) })] }, author.name))), uncommittedLines > 0 && (_jsxs("li", { className: "flex items-baseline gap-2 text-text-muted", children: [_jsx("span", { className: "min-w-0 grow truncate", children: t('language.codeVision.uncommitted') }), _jsx("span", { className: "shrink-0", children: t('language.codeVision.lines', { count: uncommittedLines, formatted: formatNumber(uncommittedLines) }) })] }))] }), latest !== null && (_jsxs(_Fragment, { children: [_jsx(Separator, {}),
|
|
25
|
+
return (_jsx(Popover.Root, { open: true, onOpenChange: (open, details) => !open && language.codeVision.closeAuthors(details.reason === 'escape-key'), children: _jsxs(Popover.Popup, { anchor: anchor, side: "bottom", align: "start", sideOffset: 4, className: "flex w-72 flex-col gap-2 p-3 text-xs", children: [_jsxs(Popover.Title, { className: "flex items-center gap-1.5 font-medium text-text", children: [_jsx(Icon, { icon: authors.length > 1 ? Users : User, size: 14, className: "shrink-0 text-text-muted" }), _jsx("span", { className: "min-w-0 truncate", children: view.name })] }), _jsxs("ul", { className: "flex flex-col gap-1", children: [authors.map((author) => (_jsxs("li", { className: "flex items-baseline gap-2", children: [_jsx(Tooltip, { label: author.email, children: _jsx("span", { className: "min-w-0 grow truncate text-text", children: author.name }) }), _jsx("span", { className: "shrink-0 text-text-muted", children: t('language.codeVision.lines', { count: author.lines, formatted: formatNumber(author.lines) }) })] }, author.name))), uncommittedLines > 0 && (_jsxs("li", { className: "flex items-baseline gap-2 text-text-muted", children: [_jsx("span", { className: "min-w-0 grow truncate", children: t('language.codeVision.uncommitted') }), _jsx("span", { className: "shrink-0", children: t('language.codeVision.lines', { count: uncommittedLines, formatted: formatNumber(uncommittedLines) }) })] }))] }), latest !== null && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(LatestCommit, { commit: latest }), view.openCommit !== null && (_jsx("div", { className: "flex justify-end", children: _jsx(Popover.Close, { render: _jsxs(Button, { size: "xs", variant: "secondary", onClick: view.openCommit, children: [_jsx(Icon, { icon: GitCommitHorizontal, size: 14 }), t('language.codeVision.showCommit')] }) }) }))] }))] }) }));
|
|
22
26
|
}
|
package/dist/CompletionPopup.js
CHANGED
|
@@ -2,9 +2,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useId, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { formatShortcut } from '@adecore/ui';
|
|
5
6
|
import { kindLetterOf, kindToneOf, rowWindow } from './completion-model.js';
|
|
6
7
|
import { SourceLine, SymbolSections } from './HoverSections.js';
|
|
7
8
|
import { PathText } from './PathText.js';
|
|
9
|
+
import { keepFocusOutOfList } from './popup-focus.js';
|
|
8
10
|
import { placeBeside, placePopup } from './popup-placement.js';
|
|
9
11
|
const TONES = {
|
|
10
12
|
callable: 'bg-accent-soft text-accent',
|
|
@@ -47,6 +49,8 @@ const ROW_CHROME = 72;
|
|
|
47
49
|
*/
|
|
48
50
|
export function CompletionPopup({ language, view, rect }) {
|
|
49
51
|
const { t } = useTranslation('editor');
|
|
52
|
+
const toggle = language.shortcuts.triggerCompletion;
|
|
53
|
+
const detailsKey = toggle === null ? null : formatShortcut(toggle, toggle.apple);
|
|
50
54
|
const listId = useId();
|
|
51
55
|
const list = useRef(null);
|
|
52
56
|
const docs = useRef(null);
|
|
@@ -102,10 +106,10 @@ export function CompletionPopup({ language, view, rect }) {
|
|
|
102
106
|
language.editor.setInputPopup({ controls: listId, activeDescendant: activeId });
|
|
103
107
|
return () => language.editor.setInputPopup(null);
|
|
104
108
|
}, [language, listId, activeId]);
|
|
105
|
-
return createPortal(_jsxs(_Fragment, { children: [_jsxs("div", { ref: list, className: `${CARD} flex w-max max-w-[min(560px,calc(100vw-16px))] min-w-[340px] flex-col`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { "aria-hidden": true, className: "h-0 overflow-hidden font-mono text-code", style: {
|
|
109
|
+
return createPortal(_jsxs(_Fragment, { children: [_jsxs("div", { ref: list, className: `${CARD} flex w-max max-w-[min(560px,calc(100vw-16px))] min-w-[340px] flex-col`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), ...keepFocusOutOfList(() => language.editor.focus()), children: [_jsx("div", { "aria-hidden": true, className: "h-0 overflow-hidden font-mono text-code", style: {
|
|
106
110
|
width: `calc(${Math.max(widest, need)}ch + ${ROW_CHROME}px)`
|
|
107
111
|
} }), _jsxs("div", { id: listId, role: "listbox", className: "w-0 min-w-full overflow-y-auto p-1", style: { maxHeight: ROW_HEIGHT * VISIBLE_ROWS + 8 }, onScroll: (event) => setListTop(event.currentTarget.scrollTop), children: [_jsx("div", { "aria-hidden": true, style: { height: first * ROW_HEIGHT } }), view.rows.slice(first, last).map((row, offset) => {
|
|
108
112
|
const index = first + offset;
|
|
109
|
-
return (_jsxs("button", { id: optionId(index), ref: index === view.active ? activeRow : undefined, type: "button", role: "option", "aria-selected": index === view.active, "aria-setsize": total, "aria-posinset": index + 1, "data-active": index === view.active, className: "flex h-6 w-full items-center gap-2 overflow-hidden rounded-md px-1.5 text-left text-xs cursor-row", onClick: () => void language.completion.accept(false, index), children: [_jsx("span", { className: `grid size-4 shrink-0 place-items-center rounded-sm font-mono text-xs leading-none font-semibold ${TONES[kindToneOf(row.kind)]}`, children: kindLetterOf(row.kind) }), _jsx("span", { className: `max-w-full shrink-0 truncate font-mono text-code ${row.deprecated ? 'line-through' : ''}`, children: _jsx(MarkedLabel, { label: row.label, matches: row.matches }) }), _jsx("span", { className: "min-w-0 grow truncate font-mono text-text-faint", children: row.detail }), _jsx(PathText, { path: row.description, className: "max-w-[60%] font-mono text-text-faint" })] }, `${index}:${row.label}`));
|
|
110
|
-
}), _jsx("div", { "aria-hidden": true, style: { height: (total - last) * ROW_HEIGHT } })] }), _jsxs("div", { className: "flex items-center gap-3 border-t border-border px-2 py-1 text-xs whitespace-nowrap text-text-faint", children: [_jsxs("span", { children: ["\u21B5 ", t('language.completion.insert')] }), _jsxs("span", { children: ["\u21E5 ", t('language.completion.replace')] }), _jsxs("span", { children: ["
|
|
113
|
+
return (_jsxs("button", { id: optionId(index), ref: index === view.active ? activeRow : undefined, type: "button", role: "option", tabIndex: -1, "aria-selected": index === view.active, "aria-setsize": total, "aria-posinset": index + 1, "data-active": index === view.active, className: "flex h-6 w-full items-center gap-2 overflow-hidden rounded-md px-1.5 text-left text-xs cursor-row", onClick: () => void language.completion.accept(false, index), children: [_jsx("span", { className: `grid size-4 shrink-0 place-items-center rounded-sm font-mono text-xs leading-none font-semibold ${TONES[kindToneOf(row.kind)]}`, children: kindLetterOf(row.kind) }), _jsx("span", { className: `max-w-full shrink-0 truncate font-mono text-code ${row.deprecated ? 'line-through' : ''}`, children: _jsx(MarkedLabel, { label: row.label, matches: row.matches }) }), _jsx("span", { className: "min-w-0 grow truncate font-mono text-text-faint", children: row.detail }), _jsx(PathText, { path: row.description, className: "max-w-[60%] font-mono text-text-faint" })] }, `${index}:${row.label}`));
|
|
114
|
+
}), _jsx("div", { "aria-hidden": true, style: { height: (total - last) * ROW_HEIGHT } })] }), _jsxs("div", { className: "flex items-center gap-3 border-t border-border px-2 py-1 text-xs whitespace-nowrap text-text-faint", children: [_jsxs("span", { children: ["\u21B5 ", t('language.completion.insert')] }), _jsxs("span", { children: ["\u21E5 ", t('language.completion.replace')] }), detailsKey !== null && (_jsxs("span", { children: [detailsKey, " ", t('language.completion.details')] })), _jsx("span", { className: "ml-auto", children: view.server })] })] }), view.detailsOpen && view.docs !== null && (_jsxs("div", { ref: docs, className: `${CARD} flex w-[360px] flex-col divide-y divide-border overflow-y-auto select-text`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [view.docs.source !== '' && (_jsx("div", { className: "px-3 py-2", children: _jsx(SourceLine, { text: view.docs.source, language: view.highlightLanguage }) })), _jsx(SymbolSections, { text: view.docs.text })] }))] }), document.body);
|
|
111
115
|
}
|
package/dist/EditorView.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type ReactNode, type Ref } from 'react';
|
|
2
|
-
import type
|
|
3
|
-
import { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
import { type Editor, type EditorEngine, type EditorOptions } from '@adecore/editor';
|
|
3
|
+
import { EditorLanguage, type EditorLanguageOptions } from './editor-language.ts';
|
|
4
4
|
import type { ProjectLanguage } from './project-language.ts';
|
|
5
|
+
import type { HoverView } from './popups.ts';
|
|
6
|
+
import type { Timers } from './timers.ts';
|
|
5
7
|
export interface EditorViewProps {
|
|
6
8
|
engine: EditorEngine;
|
|
7
9
|
options: EditorOptions;
|
|
8
10
|
project?: ProjectLanguage;
|
|
9
11
|
uri?: string;
|
|
10
12
|
languageId?: string;
|
|
13
|
+
timers?: Timers;
|
|
14
|
+
languageOptions?: EditorLanguageOptions;
|
|
11
15
|
className?: string;
|
|
12
16
|
ref?: Ref<HTMLDivElement>;
|
|
13
17
|
onMount?(editor: Editor, language: EditorLanguage | null): void | (() => void);
|
|
14
18
|
contextMenuItems?(language: EditorLanguage): ReactNode;
|
|
19
|
+
hoverFooter?(view: HoverView, language: EditorLanguage): ReactNode;
|
|
15
20
|
}
|
|
16
|
-
export declare function EditorView({ engine, options, project, uri, languageId, className, ref, onMount, contextMenuItems }: EditorViewProps): import("react").JSX.Element;
|
|
21
|
+
export declare function EditorView({ engine, options, project, uri, languageId, timers, languageOptions, className, ref, onMount, contextMenuItems, hoverFooter }: EditorViewProps): import("react").JSX.Element;
|
package/dist/EditorView.js
CHANGED
|
@@ -2,30 +2,88 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { createHolder } from './holder.js';
|
|
3
3
|
import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { DEFAULT_SMART_KEYS } from '@adecore/editor';
|
|
5
6
|
import { EditorLanguage } from './editor-language.js';
|
|
6
7
|
import { LanguagePopups } from './LanguagePopups.js';
|
|
8
|
+
const DEFAULT_INDENTATION = { tabSize: 4, insertSpaces: true };
|
|
7
9
|
/* The names of the fold controls in the editor's `{start}` and `{end}` placeholders, which the editor fills in per control. */
|
|
8
10
|
function foldMessages(t) {
|
|
9
11
|
const lines = { start: '{start}', end: '{end}' };
|
|
10
12
|
return { fold: t('language.fold.fold', lines), unfold: t('language.fold.unfold', lines) };
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
function sameFields(left, right) {
|
|
15
|
+
if (left === right) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (left === undefined || right === undefined) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
22
|
+
return [...keys].every((key) => left[key] === right[key]);
|
|
23
|
+
}
|
|
24
|
+
/* Hands the editor the settings that changed by value, so a new options object with the same values costs nothing. */
|
|
25
|
+
function applyChangedOptions(editor, before, after) {
|
|
26
|
+
if (before.theme !== after.theme) {
|
|
27
|
+
editor.setTheme(after.theme);
|
|
28
|
+
}
|
|
29
|
+
if (before.readOnly !== after.readOnly || before.readOnlyReason !== after.readOnlyReason) {
|
|
30
|
+
editor.setReadOnly(after.readOnly ?? false, after.readOnlyReason);
|
|
31
|
+
}
|
|
32
|
+
if (before.wrap !== after.wrap) {
|
|
33
|
+
editor.setWrap(after.wrap ?? false);
|
|
34
|
+
}
|
|
35
|
+
if (!sameFields(before.indentation, after.indentation)) {
|
|
36
|
+
editor.setIndentation(after.indentation ?? DEFAULT_INDENTATION);
|
|
37
|
+
}
|
|
38
|
+
if (!sameFields(before.smartKeys, after.smartKeys)) {
|
|
39
|
+
// The setter keeps the keys left out, where the options mean the default.
|
|
40
|
+
editor.setSmartKeys({ ...DEFAULT_SMART_KEYS, ...after.smartKeys });
|
|
41
|
+
}
|
|
42
|
+
if (before.guides !== after.guides) {
|
|
43
|
+
editor.setGuides(after.guides ?? true);
|
|
44
|
+
}
|
|
45
|
+
if (before.stickyScroll !== after.stickyScroll) {
|
|
46
|
+
editor.setStickyScroll?.(after.stickyScroll ?? true);
|
|
47
|
+
}
|
|
48
|
+
if (before.whitespace !== after.whitespace) {
|
|
49
|
+
editor.setWhitespace(after.whitespace ?? false);
|
|
50
|
+
}
|
|
51
|
+
if (before.rightMargin !== after.rightMargin) {
|
|
52
|
+
editor.setRightMargin(after.rightMargin ?? null);
|
|
53
|
+
}
|
|
54
|
+
if (before.foldOutline !== after.foldOutline) {
|
|
55
|
+
editor.setFoldOutline(after.foldOutline ?? 'hover');
|
|
56
|
+
}
|
|
57
|
+
if (after.label !== undefined && before.label !== after.label) {
|
|
58
|
+
editor.setLabel(after.label);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function EditorView({ engine, options, project, uri, languageId, timers, languageOptions, className, ref, onMount, contextMenuItems, hoverFooter }) {
|
|
13
62
|
const { t } = useTranslation('editor');
|
|
14
63
|
const element = useRef(null);
|
|
15
64
|
const mounted = useRef(null);
|
|
16
65
|
const words = useRef(t);
|
|
66
|
+
const latest = useRef({ options, timers, languageOptions, onMount });
|
|
67
|
+
const applied = useRef(options);
|
|
17
68
|
const holder = useMemo(() => createHolder(), []);
|
|
18
69
|
const language = useSyncExternalStore(holder.subscribe, holder.get, holder.get);
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
latest.current = { options, timers, languageOptions, onMount };
|
|
72
|
+
});
|
|
19
73
|
useLayoutEffect(() => {
|
|
20
74
|
const host = element.current;
|
|
21
75
|
if (host === null) {
|
|
22
76
|
return;
|
|
23
77
|
}
|
|
24
|
-
const
|
|
78
|
+
const inputs = latest.current;
|
|
79
|
+
const editor = engine.mount(host, { ...inputs.options, messages: { ...foldMessages(words.current), ...inputs.options.messages } });
|
|
25
80
|
mounted.current = editor;
|
|
26
|
-
|
|
81
|
+
applied.current = inputs.options;
|
|
82
|
+
const attached = project === undefined || uri === undefined || languageId === undefined
|
|
83
|
+
? null
|
|
84
|
+
: new EditorLanguage(project, editor, uri, languageId, inputs.timers, inputs.languageOptions);
|
|
27
85
|
holder.set(attached);
|
|
28
|
-
const stop = onMount?.(editor, attached);
|
|
86
|
+
const stop = inputs.onMount?.(editor, attached);
|
|
29
87
|
return () => {
|
|
30
88
|
mounted.current = null;
|
|
31
89
|
holder.set(null);
|
|
@@ -33,12 +91,24 @@ export function EditorView({ engine, options, project, uri, languageId, classNam
|
|
|
33
91
|
attached?.dispose();
|
|
34
92
|
editor.dispose();
|
|
35
93
|
};
|
|
36
|
-
}, [engine,
|
|
94
|
+
}, [engine, project, uri, languageId, holder]);
|
|
95
|
+
useLayoutEffect(() => {
|
|
96
|
+
const editor = mounted.current;
|
|
97
|
+
if (editor === null || applied.current === options) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
applyChangedOptions(editor, applied.current, options);
|
|
101
|
+
applied.current = options;
|
|
102
|
+
});
|
|
103
|
+
const { fold, unfold, noMoreOccurrences } = options.messages ?? {};
|
|
37
104
|
useLayoutEffect(() => {
|
|
38
105
|
words.current = t;
|
|
39
|
-
const { fold, unfold } = options.messages ?? {};
|
|
40
106
|
const localized = foldMessages(t);
|
|
41
|
-
mounted.current?.setMessages({
|
|
42
|
-
|
|
43
|
-
|
|
107
|
+
mounted.current?.setMessages({
|
|
108
|
+
fold: fold ?? localized.fold,
|
|
109
|
+
unfold: unfold ?? localized.unfold,
|
|
110
|
+
...(noMoreOccurrences === undefined ? {} : { noMoreOccurrences })
|
|
111
|
+
});
|
|
112
|
+
}, [t, fold, unfold, noMoreOccurrences]);
|
|
113
|
+
return (_jsxs("div", { className: className, ref: ref, style: { position: 'relative', minHeight: 0 }, children: [_jsx("div", { ref: element, style: { position: 'absolute', inset: 0 } }), language !== null && _jsx(LanguagePopups, { language: language, contextMenuItems: contextMenuItems, hoverFooter: hoverFooter })] }));
|
|
44
114
|
}
|
package/dist/HoverCard.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import { type Location } from '@adecore/lsp';
|
|
2
3
|
import type { EditorPosition } from '@adecore/editor';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
@@ -8,10 +9,11 @@ export declare function DefinitionPlace({ definition, uri, folder }: {
|
|
|
8
9
|
uri: string;
|
|
9
10
|
folder: string;
|
|
10
11
|
}): import("react").JSX.Element | null;
|
|
11
|
-
export declare function HoverCard({ language, problems, info, anchor, position }: {
|
|
12
|
+
export declare function HoverCard({ language, problems, info, anchor, position, footer }: {
|
|
12
13
|
language: EditorLanguage;
|
|
13
14
|
problems: readonly Problem[];
|
|
14
15
|
info: HoverInfo | null;
|
|
15
16
|
anchor: EditorPosition;
|
|
16
17
|
position: EditorPosition;
|
|
18
|
+
footer?: ReactNode;
|
|
17
19
|
}): import("react").JSX.Element;
|
package/dist/HoverCard.js
CHANGED
|
@@ -41,7 +41,7 @@ function ProblemSection({ problem, language }) {
|
|
|
41
41
|
}
|
|
42
42
|
}, children: t('language.hover.quickFix') }) }) })] }));
|
|
43
43
|
}
|
|
44
|
-
export function HoverCard({ language, problems, info, anchor, position }) {
|
|
44
|
+
export function HoverCard({ language, problems, info, anchor, position, footer }) {
|
|
45
45
|
const { t } = useTranslation('editor');
|
|
46
46
|
return (_jsxs("div", { className: "flex w-max min-w-[280px] max-w-[min(520px,calc(100vw-16px))] flex-col divide-y divide-border", children: [info !== null && (_jsxs(_Fragment, { children: [_jsx(SymbolSections, { text: info.text, onName: (name, declared) => {
|
|
47
47
|
language.hover.hide();
|
|
@@ -49,5 +49,5 @@ export function HoverCard({ language, problems, info, anchor, position }) {
|
|
|
49
49
|
} }), info.plain !== true && (_jsxs("div", { className: "flex flex-col gap-0.5 px-3 py-1.5 text-xs", children: [_jsxs("div", { className: "flex items-center gap-3 whitespace-nowrap", children: [info.definition !== null && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => language.goTo(info.definition), children: t('language.hover.definition') })), info.references !== null && info.references > 0 && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => {
|
|
50
50
|
language.hover.hide();
|
|
51
51
|
void language.peek.open(position);
|
|
52
|
-
}, children: t('language.hover.references', { count: info.references, formatted: formatNumber(info.references) }) }))] }), info.definition !== null && _jsx(DefinitionPlace, { definition: info.definition, uri: language.uri, folder: language.project.folder })] }))] })), problems.map((problem, index) => (_jsx(ProblemSection, { problem: problem, language: language }, index)))] }));
|
|
52
|
+
}, children: t('language.hover.references', { count: info.references, formatted: formatNumber(info.references) }) }))] }), info.definition !== null && _jsx(DefinitionPlace, { definition: info.definition, uri: language.uri, folder: language.project.folder })] }))] })), problems.map((problem, index) => (_jsx(ProblemSection, { problem: problem, language: language }, index))), footer] }));
|
|
53
53
|
}
|
package/dist/LanguagePopups.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import type { HoverView } from './popups.ts';
|
|
4
|
+
export interface LanguagePopupsProps<Language extends EditorLanguage = EditorLanguage> {
|
|
5
|
+
language: Language;
|
|
6
|
+
contextMenuItems?(language: Language): ReactNode;
|
|
7
|
+
hoverFooter?(view: HoverView, language: Language): ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function LanguagePopups<Language extends EditorLanguage>({ language, contextMenuItems, hoverFooter, children }: LanguagePopupsProps<Language>): import("react").JSX.Element;
|
package/dist/LanguagePopups.js
CHANGED
|
@@ -16,9 +16,9 @@ import { SignatureCard } from './SignatureCard.js';
|
|
|
16
16
|
/*
|
|
17
17
|
* Every card a language feature opens over the editor. Each feature keeps its own state and these
|
|
18
18
|
* only draw it, placed by the screen position of the character it belongs to, which is read again
|
|
19
|
-
* whenever the editor scrolls or resizes.
|
|
19
|
+
* whenever the editor scrolls or resizes.
|
|
20
20
|
*/
|
|
21
|
-
export function LanguagePopups({ language, contextMenuItems }) {
|
|
21
|
+
export function LanguagePopups({ language, contextMenuItems, hoverFooter, children }) {
|
|
22
22
|
const [, redraw] = useReducer((count) => count + 1, 0);
|
|
23
23
|
const hover = useStore(language.popups, (state) => state.hover);
|
|
24
24
|
const completion = useStore(language.popups, (state) => state.completion);
|
|
@@ -26,7 +26,7 @@ export function LanguagePopups({ language, contextMenuItems }) {
|
|
|
26
26
|
const pick = useStore(language.popups, (state) => state.pick);
|
|
27
27
|
const rename = useStore(language.popups, (state) => state.rename);
|
|
28
28
|
const peek = useStore(language.popups, (state) => state.peek);
|
|
29
|
-
const callHierarchy = useStore(language.popups, (state) => state.callHierarchy
|
|
29
|
+
const callHierarchy = useStore(language.popups, (state) => state.callHierarchy);
|
|
30
30
|
const symbols = useStore(language.popups, (state) => state.symbols);
|
|
31
31
|
const menu = useStore(language.popups, (state) => state.menu);
|
|
32
32
|
const authors = useStore(language.popups, (state) => state.authors);
|
|
@@ -37,5 +37,5 @@ export function LanguagePopups({ language, contextMenuItems }) {
|
|
|
37
37
|
const pickRect = pick === null ? null : language.editor.rectAt(pick.anchor);
|
|
38
38
|
const renameRect = rename === null ? null : language.editor.rectAt(rename.range.start);
|
|
39
39
|
const renameEnd = rename === null ? null : language.editor.rectAt(rename.range.end);
|
|
40
|
-
return (_jsxs(_Fragment, { children: [_jsx(RenderedDocs, { language: language }), hover !== null && hoverRect !== null && (_jsx(AnchoredPopup, { rect: hoverRect, onPointerEnter: () => language.hover.holdCard(true), onPointerLeave: () => language.hover.holdCard(false), children: _jsx(HoverCard, { language: language, problems: hover.problems, info: hover.info, anchor: hover.anchor, position: hover.position }) })), signature !== null && signatureRect !== null && (_jsx(AnchoredPopup, { rect: signatureRect, placement: { prefer: 'above' }, children: _jsx(SignatureCard, { model: signature.model }) })), completion !== null && completionRect !== null && _jsx(CompletionPopup, { language: language, view: completion, rect: completionRect }), pick !== null && pickRect !== null && _jsx(PickPopup, { language: language, view: pick, rect: pickRect }), menu !== null && (_jsx(EditorContextMenu, { language: language, view: menu, children: contextMenuItems?.(language) })), symbols !== null && _jsx(SymbolPicker, { language: language, view: symbols }), peek !== null && _jsx(PeekPanel, { language: language, view: peek }), callHierarchy !== null && _jsx(CallHierarchyPanel, { language: language, view: callHierarchy }), authors !== null && _jsx(CodeAuthorsCard, { language: language, view: authors }), rename !== null && renameRect !== null && _jsx(RenameCard, { language: language, view: rename, rect: renameRect, endRect: renameEnd })] }));
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx(RenderedDocs, { language: language }), hover !== null && hoverRect !== null && (_jsx(AnchoredPopup, { rect: hoverRect, onPointerEnter: () => language.hover.holdCard(true), onPointerLeave: () => language.hover.holdCard(false), children: _jsx(HoverCard, { language: language, problems: hover.problems, info: hover.info, anchor: hover.anchor, position: hover.position, footer: hoverFooter?.(hover, language) }) })), signature !== null && signatureRect !== null && (_jsx(AnchoredPopup, { rect: signatureRect, placement: { prefer: 'above' }, children: _jsx(SignatureCard, { model: signature.model }) })), completion !== null && completionRect !== null && _jsx(CompletionPopup, { language: language, view: completion, rect: completionRect }), pick !== null && pickRect !== null && _jsx(PickPopup, { language: language, view: pick, rect: pickRect }), menu !== null && (_jsx(EditorContextMenu, { language: language, view: menu, children: contextMenuItems?.(language) })), symbols !== null && _jsx(SymbolPicker, { language: language, view: symbols }), peek !== null && _jsx(PeekPanel, { language: language, view: peek }), callHierarchy !== null && _jsx(CallHierarchyPanel, { language: language, view: callHierarchy }), authors !== null && _jsx(CodeAuthorsCard, { language: language, view: authors }), children, rename !== null && renameRect !== null && _jsx(RenameCard, { language: language, view: rename, rect: renameRect, endRect: renameEnd })] }));
|
|
41
41
|
}
|
package/dist/PickPopup.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useId, useLayoutEffect, useRef } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { PathText } from './PathText.js';
|
|
5
|
+
import { keepFocusOutOfList } from './popup-focus.js';
|
|
5
6
|
import { placePopup } from './popup-placement.js';
|
|
6
7
|
const CARD = 'fixed top-0 left-0 z-(--z-popup) flex w-[420px] flex-col overflow-hidden rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow)';
|
|
7
8
|
function PreviewLines({ preview }) {
|
|
@@ -42,5 +43,5 @@ export function PickPopup({ language, view, rect }) {
|
|
|
42
43
|
language.editor.setInputPopup({ controls: listId, activeDescendant: activeId });
|
|
43
44
|
return () => language.editor.setInputPopup(null);
|
|
44
45
|
}, [language, listId, activeId]);
|
|
45
|
-
return createPortal(_jsxs("div", { ref: card, className: CARD, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [view.title !== null && _jsx("div", { className: "border-b border-border px-3 py-1.5 text-xs text-text-muted", children: view.title }), _jsx("div", { id: listId, role: "listbox", className: "min-h-0 flex-1 overflow-y-auto p-1", children: view.groups.map((group, groupIndex) => (_jsxs("div", { role: "group", className: "flex flex-col", children: [group.title !== null && _jsx("div", { className: "px-2 pt-1.5 pb-0.5 text-xs font-medium text-text-faint", children: group.title }), group.rows.map((row) => (_jsxs("button", { id: optionIds.get(row.id), ref: row.id === view.active ? activeRow : undefined, type: "button", role: "option", "aria-selected": row.id === view.active, "data-active": row.id === view.active, className: "flex h-7 w-full items-center gap-2 rounded-md px-2 text-left text-xs cursor-row", onClick: () => language.pick.choose(row.id), children: [_jsx("span", { className: row.path ? 'shrink-0' : 'min-w-0 truncate', children: row.label }), row.path ? (_jsx(PathText, { path: row.detail, className: "ml-auto font-mono text-text-faint" })) : (_jsx("span", { className: "ml-auto shrink-0 font-mono text-text-faint", children: row.id === view.active && row.detail === '' ? '↵' : row.detail }))] }, row.id)))] }, group.title ?? groupIndex))) }), view.preview !== null && _jsx(PreviewLines, { preview: view.preview })] }), document.body);
|
|
46
|
+
return createPortal(_jsxs("div", { ref: card, className: CARD, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), ...keepFocusOutOfList(() => language.editor.focus()), children: [view.title !== null && _jsx("div", { className: "border-b border-border px-3 py-1.5 text-xs text-text-muted", children: view.title }), _jsx("div", { id: listId, role: "listbox", className: "min-h-0 flex-1 overflow-y-auto p-1", children: view.groups.map((group, groupIndex) => (_jsxs("div", { role: "group", className: "flex flex-col", children: [group.title !== null && _jsx("div", { className: "px-2 pt-1.5 pb-0.5 text-xs font-medium text-text-faint", children: group.title }), group.rows.map((row) => (_jsxs("button", { id: optionIds.get(row.id), ref: row.id === view.active ? activeRow : undefined, type: "button", role: "option", tabIndex: -1, "aria-selected": row.id === view.active, "data-active": row.id === view.active, className: "flex h-7 w-full items-center gap-2 rounded-md px-2 text-left text-xs cursor-row", onClick: () => language.pick.choose(row.id), children: [_jsx("span", { className: row.path ? 'shrink-0' : 'min-w-0 truncate', children: row.label }), row.path ? (_jsx(PathText, { path: row.detail, className: "ml-auto font-mono text-text-faint" })) : (_jsx("span", { className: "ml-auto shrink-0 font-mono text-text-faint", children: row.id === view.active && row.detail === '' ? '↵' : row.detail }))] }, row.id)))] }, group.title ?? groupIndex))) }), view.preview !== null && _jsx(PreviewLines, { preview: view.preview })] }), document.body);
|
|
46
47
|
}
|
package/dist/RenameCard.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Button, Icon } from '@adecore/ui';
|
|
|
8
8
|
import { formatNumber } from '@adecore/ui/format';
|
|
9
9
|
import { basenameOf, relativeTo } from './paths.js';
|
|
10
10
|
import { AnchoredPopup } from './AnchoredPopup.js';
|
|
11
|
+
import { keepFocusOutOfList } from './popup-focus.js';
|
|
11
12
|
const CARD_ATTRIBUTE = 'data-rename-card';
|
|
12
13
|
/* Whether the focus moved to something that belongs to the rename, which is not leaving it. */
|
|
13
14
|
function staysInside(target) {
|
|
@@ -17,14 +18,14 @@ function staysInside(target) {
|
|
|
17
18
|
* Suggested names from the host, in a list under the input; a press fills the input and the
|
|
18
19
|
* typed name stays what Enter applies. While it is up the card says only its two keys.
|
|
19
20
|
*/
|
|
20
|
-
function Suggestions({ names, active, onPick }) {
|
|
21
|
+
function Suggestions({ names, active, onPick, onFocusBack }) {
|
|
21
22
|
const { t } = useTranslation('editor');
|
|
22
|
-
return (_jsxs("div", { className: "flex w-[250px] flex-col p-1.25", children: [_jsxs("div", { className: "flex items-center gap-1.5 px-2.25 pt-1.25 pb-1 text-xs font-semibold text-text-faint", children: [_jsx(Icon, { icon: Sparkles, size: 12, className: "shrink-0" }), t('language.rename.suggestions')] }), names.map((name, index) => (_jsxs("button", { type: "button", tabIndex: -1, className: `flex h-6.5 items-center gap-2 rounded-md px-2.25 text-left font-mono text-code text-text ${index === active ? 'bg-accent/20' : 'hover:bg-surface-hover'}`, onClick: () => onPick(name), children: [_jsx(Icon, { icon: Sparkles, size: 12, className: `shrink-0 ${index === active ? 'text-accent' : 'text-text-faint'}` }), name] }, name))), _jsxs("div", { className: "mt-1 flex gap-3 border-t border-border px-2.25 pt-1.75 pb-0.75 text-xs text-text-muted", children: [_jsxs("span", { children: ["\u21B5 ", t('language.rename.rename')] }), _jsxs("span", { children: ["\u21E5 ", t('language.rename.useSuggestion')] })] })] }));
|
|
23
|
+
return (_jsxs("div", { className: "flex w-[250px] flex-col p-1.25", ...keepFocusOutOfList(onFocusBack), children: [_jsxs("div", { className: "flex items-center gap-1.5 px-2.25 pt-1.25 pb-1 text-xs font-semibold text-text-faint", children: [_jsx(Icon, { icon: Sparkles, size: 12, className: "shrink-0" }), t('language.rename.suggestions')] }), names.map((name, index) => (_jsxs("button", { type: "button", tabIndex: -1, className: `flex h-6.5 items-center gap-2 rounded-md px-2.25 text-left font-mono text-code text-text ${index === active ? 'bg-accent/20' : 'hover:bg-surface-hover'}`, onClick: () => onPick(name), children: [_jsx(Icon, { icon: Sparkles, size: 12, className: `shrink-0 ${index === active ? 'text-accent' : 'text-text-faint'}` }), name] }, name))), _jsxs("div", { className: "mt-1 flex gap-3 border-t border-border px-2.25 pt-1.75 pb-0.75 text-xs text-text-muted", children: [_jsxs("span", { children: ["\u21B5 ", t('language.rename.rename')] }), _jsxs("span", { children: ["\u21E5 ", t('language.rename.useSuggestion')] })] })] }));
|
|
23
24
|
}
|
|
24
|
-
function Hints({ view, active, onPick }) {
|
|
25
|
+
function Hints({ view, active, onPick, onFocusBack }) {
|
|
25
26
|
const { t } = useTranslation('editor');
|
|
26
27
|
const { occurrences } = view;
|
|
27
|
-
return (_jsxs("div", { className: "flex flex-col", children: [view.error !== null && (_jsx("div", { role: "alert", className: "max-w-[420px] px-2.5 py-1.5 text-xs text-status-error", children: view.error })), view.suggestions.length > 0 ? (_jsx(Suggestions, { names: view.suggestions, active: active, onPick: onPick })) : (_jsxs("div", { className: "flex items-center gap-3 px-2.5 py-1.5 text-xs whitespace-nowrap text-text-muted", children: [occurrences !== null && (_jsx("span", { children: occurrences.files > 1
|
|
28
|
+
return (_jsxs("div", { className: "flex flex-col", children: [view.error !== null && (_jsx("div", { role: "alert", className: "max-w-[420px] px-2.5 py-1.5 text-xs text-status-error", children: view.error })), view.suggestions.length > 0 ? (_jsx(Suggestions, { names: view.suggestions, active: active, onPick: onPick, onFocusBack: onFocusBack })) : (_jsxs("div", { className: "flex items-center gap-3 px-2.5 py-1.5 text-xs whitespace-nowrap text-text-muted", children: [occurrences !== null && (_jsx("span", { children: occurrences.files > 1
|
|
28
29
|
? t('language.rename.occurrencesFiles', {
|
|
29
30
|
count: occurrences.count,
|
|
30
31
|
formatted: formatNumber(occurrences.count),
|
|
@@ -97,11 +98,12 @@ export function RenameCard({ language, view, rect, endRect }) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
const symbolWidth = endRect === null ? 0 : Math.max(0, endRect.left - rect.left);
|
|
100
|
-
return createPortal(_jsxs(_Fragment, { children: [_jsx("input", { ref: input, [CARD_ATTRIBUTE]: '', value: view.phase === 'preview' ? view.name : value, readOnly: view.phase === 'preview' || view.busy, spellCheck: false, autoComplete: "off", "aria-label": view.original, className: "fixed z-(--z-popup) rounded-sm border border-accent bg-surface px-
|
|
101
|
-
|
|
101
|
+
return createPortal(_jsxs(_Fragment, { children: [_jsx("input", { ref: input, [CARD_ATTRIBUTE]: '', value: view.phase === 'preview' ? view.name : value, readOnly: view.phase === 'preview' || view.busy, spellCheck: false, autoComplete: "off", "aria-label": view.original, className: "fixed z-(--z-popup) rounded-sm border border-accent bg-surface px-0 font-mono text-(length:--code-font-size) text-text shadow-(--float-shadow) outline-none [font-kerning:none] [field-sizing:content]", style: {
|
|
102
|
+
// Only the border stands beside the name: any padding would cover the character after it, often a `(`.
|
|
103
|
+
left: Math.round(rect.left) - 1,
|
|
102
104
|
top: Math.round(rect.top) - 2,
|
|
103
105
|
height: Math.round(rect.bottom - rect.top) + 4,
|
|
104
|
-
minWidth: Math.round(symbolWidth) +
|
|
106
|
+
minWidth: Math.round(symbolWidth) + 2
|
|
105
107
|
}, onChange: (event) => {
|
|
106
108
|
setValue(event.target.value);
|
|
107
109
|
language.rename.edited();
|
|
@@ -109,5 +111,5 @@ export function RenameCard({ language, view, rect, endRect }) {
|
|
|
109
111
|
if (!staysInside(event.relatedTarget)) {
|
|
110
112
|
language.rename.cancel();
|
|
111
113
|
}
|
|
112
|
-
} }), _jsx(AnchoredPopup, { rect: { ...rect, top: rect.top - 2, bottom: rect.bottom + 2 },
|
|
114
|
+
} }), _jsx(AnchoredPopup, { rect: { ...rect, top: rect.top - 2, bottom: rect.bottom + 2 }, overflow: "clip", placement: { gap: 2 }, children: _jsx("div", { [CARD_ATTRIBUTE]: '', onPointerDown: (event) => event.preventDefault(), children: view.phase === 'preview' ? (_jsx(Preview, { language: language, view: view })) : (_jsx(Hints, { view: view, active: active, onPick: fill, onFocusBack: () => input.current?.focus() })) }) })] }), document.body);
|
|
113
115
|
}
|
package/dist/RenderedDocs.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { DocComment } from '@adecore/editor';
|
|
1
2
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
-
import type
|
|
3
|
-
|
|
3
|
+
import { type ParsedDocComment } from './doc-comment-model.ts';
|
|
4
|
+
interface DocBlockProps {
|
|
4
5
|
language: EditorLanguage;
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
comment: DocComment;
|
|
7
|
+
parsed: ParsedDocComment;
|
|
8
|
+
linkable: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function DocBlock({ language, comment, parsed, linkable }: DocBlockProps): import("react").JSX.Element;
|
|
11
|
+
export declare const RenderedDocBlock: import("react").MemoExoticComponent<typeof DocBlock>;
|
|
7
12
|
export declare function RenderedDocs({ language }: {
|
|
8
13
|
language: EditorLanguage;
|
|
9
14
|
}): import("react").JSX.Element;
|
|
15
|
+
export {};
|
package/dist/RenderedDocs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { memo, useSyncExternalStore } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { DOC_LINK_PREFIX, sectionsOf } from './doc-comment-model.js';
|
|
6
6
|
import { markdownParts } from './hover-content.js';
|
|
@@ -39,12 +39,11 @@ function TagRow({ tag, language, linkable, spaced }) {
|
|
|
39
39
|
const link = target === null ? null : /^https?:\/\//i.test(target) ? `[${target}](${target})` : `[\`${target}\`](${DOC_LINK_PREFIX}${encodeURIComponent(target)})`;
|
|
40
40
|
return (_jsx("dd", { className: `col-start-2 m-0 flex min-w-0 flex-wrap items-baseline gap-x-2 ${spaced ? 'mt-1' : ''}`, children: tag.name === 'example' ? (_jsx("div", { className: "w-full rounded-md bg-surface-sunken px-2 py-1.5", children: _jsx(Signature, { code: tag.text, language: language }) })) : (_jsxs(_Fragment, { children: [tag.subject !== null && target === null && _jsx(Code, { children: tag.subject }), tag.type !== null && _jsx(Code, { faint: true, children: tag.type }), (link !== null || tag.text !== '') && (_jsx("div", { className: "min-w-0 grow basis-48 text-text", children: _jsx(Prose, { text: [link, tag.text].filter((part) => part !== null && part !== '').join(' '), language: language, linkable: linkable, compact: true }) }))] })) }));
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
function DocBlock({ language, comment, parsed, linkable }) {
|
|
43
43
|
const { t } = useTranslation('editor');
|
|
44
44
|
const feature = language.renderedDocs;
|
|
45
|
-
const { description, tags } =
|
|
45
|
+
const { description, tags } = parsed;
|
|
46
46
|
const highlighter = shikiLanguageOf(language.languageId);
|
|
47
|
-
const linkable = feature.canFollow;
|
|
48
47
|
const sections = sectionsOf(tags);
|
|
49
48
|
const deprecated = sections.find((section) => section.name === 'deprecated');
|
|
50
49
|
const shown = sections.filter((section) => section.name !== 'deprecated');
|
|
@@ -61,7 +60,7 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
if (href.startsWith(DOC_LINK_PREFIX)) {
|
|
64
|
-
feature.follow(decodeURIComponent(href.slice(DOC_LINK_PREFIX.length)),
|
|
63
|
+
feature.follow(decodeURIComponent(href.slice(DOC_LINK_PREFIX.length)), comment);
|
|
65
64
|
}
|
|
66
65
|
else if (/^(https?:|mailto:)/i.test(href)) {
|
|
67
66
|
window.open(href, '_blank', 'noopener,noreferrer');
|
|
@@ -72,6 +71,8 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
72
71
|
...section.tags.map((tag, index) => (_jsx(TagRow, { tag: tag, language: highlighter, linkable: linkable, spaced: row > 0 && index === 0 }, `${section.name}:${index}`)))
|
|
73
72
|
]) }))] }));
|
|
74
73
|
}
|
|
74
|
+
/* Drawn again only when its comment or its links change, since every scroll of the editor draws the popups again. */
|
|
75
|
+
export const RenderedDocBlock = memo(DocBlock);
|
|
75
76
|
/*
|
|
76
77
|
* The documentation comments the editor drew as rows, filled with their formatted text. It portals into
|
|
77
78
|
* the element the editor made for each row, so the editor decides what is in view; a comment is parsed
|
|
@@ -80,5 +81,8 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
80
81
|
export function RenderedDocs({ language }) {
|
|
81
82
|
const feature = language.renderedDocs;
|
|
82
83
|
useSyncExternalStore(feature.subscribe, feature.getVersion, feature.getVersion);
|
|
83
|
-
|
|
84
|
+
const linkable = feature.canFollow;
|
|
85
|
+
return (_jsx(_Fragment, { children: feature
|
|
86
|
+
.entries()
|
|
87
|
+
.map((entry) => createPortal(_jsx(RenderedDocBlock, { language: language, comment: entry.comment, parsed: entry.parsed, linkable: linkable }), entry.container, entry.id)) }));
|
|
84
88
|
}
|