@adecore/editor-react 0.23.0 → 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/CallHierarchyPanel.js +6 -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/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.js +6 -2
- 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-model.js +3 -9
- package/dist/call-hierarchy.js +17 -17
- 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 +11 -1
- 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 +5 -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 +5 -1
- package/dist/locales/nl.json +5 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +2 -2
- package/dist/navigation.js +3 -2
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- package/dist/peek.d.ts +3 -1
- package/dist/peek.js +8 -3
- 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.d.ts +4 -0
- package/dist/project-language.js +23 -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.d.ts +2 -0
- package/dist/rendered-docs.js +15 -8
- 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/dist/timers.d.ts +1 -0
- package/dist/timers.js +4 -0
- package/dist/use-panel-body.d.ts +6 -0
- package/dist/use-panel-body.js +13 -0
- package/package.json +6 -6
- package/src/AnchoredPopup.tsx +4 -2
- package/src/CallHierarchyPanel.tsx +9 -3
- 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/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +9 -3
- package/src/PickPopup.tsx +9 -1
- package/src/RenameCard.tsx +15 -9
- package/src/RenderedDocs.tsx +30 -8
- package/src/call-hierarchy-model.ts +15 -22
- package/src/call-hierarchy.ts +17 -17
- 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 +13 -2
- 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 +9 -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 +5 -1
- package/src/locales/nl.json +5 -1
- package/src/models.ts +1 -1
- package/src/navigation.ts +3 -2
- package/src/panel-size.ts +28 -0
- package/src/peek.ts +8 -3
- package/src/pick.ts +2 -1
- package/src/popup-focus.ts +17 -0
- package/src/popups.ts +1 -2
- package/src/project-language.ts +25 -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 +17 -8
- package/src/signature.ts +4 -3
- package/src/snippet-session.ts +9 -3
- package/src/symbol-picker.ts +3 -2
- package/src/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- 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
|
}
|
|
@@ -7,6 +7,9 @@ import { fileUriToPath } from '@adecore/lsp';
|
|
|
7
7
|
import { Icon, IconButton, Segmented, Spinner, Tooltip } from '@adecore/ui';
|
|
8
8
|
import { formatNumber } from '@adecore/ui/format';
|
|
9
9
|
import { CALL_HIERARCHY_HEIGHT } from './call-hierarchy.js';
|
|
10
|
+
import { PanelResizeHandle } from './PanelResizeHandle.js';
|
|
11
|
+
import { usePanelBody } from './use-panel-body.js';
|
|
12
|
+
import { PANEL_BODY_HEIGHT } from './panel-size.js';
|
|
10
13
|
import { CALL_LEVEL_LIMIT } from './call-hierarchy-model.js';
|
|
11
14
|
import { basenameOf, relativeTo } from './paths.js';
|
|
12
15
|
import { PathText } from './PathText.js';
|
|
@@ -75,14 +78,15 @@ function StatusText({ row, direction }) {
|
|
|
75
78
|
*/
|
|
76
79
|
export function CallHierarchyPanel({ language, view }) {
|
|
77
80
|
const { t } = useTranslation('editor');
|
|
81
|
+
const body = usePanelBody(language);
|
|
78
82
|
if (view.container === null) {
|
|
79
83
|
return null;
|
|
80
84
|
}
|
|
81
85
|
const folder = language.project.folder;
|
|
82
86
|
const path = view.preview === null ? null : fileUriToPath(view.preview.uri);
|
|
83
87
|
const shown = path === null ? '' : folderOf(relativeTo(path, folder));
|
|
84
|
-
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: CALL_HIERARCHY_HEIGHT }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[33px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx(Segmented, { value: view.direction, onValueChange: (direction) => language.callHierarchy.setDirection(direction), label: t('language.callHierarchy.direction'), options: [
|
|
88
|
+
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: CALL_HIERARCHY_HEIGHT - PANEL_BODY_HEIGHT + body.height }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[33px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx(Segmented, { value: view.direction, onValueChange: (direction) => language.callHierarchy.setDirection(direction), label: t('language.callHierarchy.direction'), options: [
|
|
85
89
|
{ id: 'incoming', label: t('language.callHierarchy.incoming') },
|
|
86
90
|
{ id: 'outgoing', label: t('language.callHierarchy.outgoing') }
|
|
87
|
-
] }), _jsx("span", { className: "shrink-0 font-medium", children: view.name }), _jsx("span", { className: "shrink-0 font-medium text-text-muted", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [view.sites > 1 && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: ChevronLeft, size: "xs", label: t('language.callHierarchy.previousSite'), onClick: () => language.callHierarchy.stepSite(-1) }), _jsx("span", { className: "text-text-muted", children: t('language.callHierarchy.site', { current: formatNumber(view.site + 1), total: formatNumber(view.sites) }) }), _jsx(IconButton, { icon: ChevronRight, size: "xs", label: t('language.callHierarchy.nextSite'), onClick: () => language.callHierarchy.stepSite(1) })] })), _jsx(IconButton, { icon: X, size: "xs", label: t('language.callHierarchy.close'), onClick: () => language.callHierarchy.close() })] })] }), _jsxs("div", { className: "flex
|
|
91
|
+
] }), _jsx("span", { className: "shrink-0 font-medium", children: view.name }), _jsx("span", { className: "shrink-0 font-medium text-text-muted", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [view.sites > 1 && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: ChevronLeft, size: "xs", label: t('language.callHierarchy.previousSite'), onClick: () => language.callHierarchy.stepSite(-1) }), _jsx("span", { className: "text-text-muted", children: t('language.callHierarchy.site', { current: formatNumber(view.site + 1), total: formatNumber(view.sites) }) }), _jsx(IconButton, { icon: ChevronRight, size: "xs", label: t('language.callHierarchy.nextSite'), onClick: () => language.callHierarchy.stepSite(1) })] })), _jsx(IconButton, { icon: X, size: "xs", label: t('language.callHierarchy.close'), onClick: () => language.callHierarchy.close() })] })] }), _jsxs("div", { className: "flex shrink-0", style: { height: body.height }, children: [_jsx(Tree, { language: language, view: view }), _jsx(Preview, { preview: view.preview })] }), _jsx("div", { className: "peek-hatch relative h-[14px]", children: _jsx(PanelResizeHandle, { container: view.container, height: body.height, onHeight: body.set, onCommit: body.commit }) })] }), view.container);
|
|
88
92
|
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
container: HTMLElement;
|
|
3
|
+
height: number;
|
|
4
|
+
onHeight(height: number): void;
|
|
5
|
+
onCommit(height: number): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function PanelResizeHandle({ container, height, onHeight, onCommit }: Props): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { PANEL_BODY_MIN, PANEL_KEY_STEP, availableHeightOf, clampPanelBody, panelBodyMax } from './panel-size.js';
|
|
5
|
+
import { realTimers, revealRow } from './timers.js';
|
|
6
|
+
/* The bottom edge of a panel: a drag, or the arrow keys, change the height of its body. */
|
|
7
|
+
export function PanelResizeHandle({ container, height, onHeight, onCommit }) {
|
|
8
|
+
const { t } = useTranslation('editor');
|
|
9
|
+
const drag = useRef(null);
|
|
10
|
+
const [dragging, setDragging] = useState(false);
|
|
11
|
+
const [max, setMax] = useState(() => panelBodyMax(availableHeightOf(container)));
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const viewport = container.closest('.se-viewport');
|
|
14
|
+
const Observer = viewport?.ownerDocument.defaultView?.ResizeObserver;
|
|
15
|
+
if (viewport === null || viewport === undefined || Observer === undefined) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const observer = new Observer(() => setMax(panelBodyMax(availableHeightOf(container))));
|
|
19
|
+
observer.observe(viewport);
|
|
20
|
+
return () => observer.disconnect();
|
|
21
|
+
}, [container]);
|
|
22
|
+
function settle(value, from) {
|
|
23
|
+
const next = clampPanelBody(value, availableHeightOf(container));
|
|
24
|
+
onHeight(next);
|
|
25
|
+
if (next !== from) {
|
|
26
|
+
onCommit(next);
|
|
27
|
+
revealRow(realTimers, container);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function heightAt(clientY) {
|
|
31
|
+
return clampPanelBody(drag.current.startHeight + clientY - drag.current.startY, availableHeightOf(container));
|
|
32
|
+
}
|
|
33
|
+
function down(event) {
|
|
34
|
+
if (event.button !== 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
38
|
+
drag.current = { startY: event.clientY, startHeight: height, lastY: event.clientY };
|
|
39
|
+
setDragging(true);
|
|
40
|
+
}
|
|
41
|
+
function move(event) {
|
|
42
|
+
if (drag.current !== null) {
|
|
43
|
+
drag.current.lastY = event.clientY;
|
|
44
|
+
onHeight(heightAt(event.clientY));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Also the end of a drag whose capture was lost, such as a container that was replaced under the pointer.
|
|
48
|
+
function end(event) {
|
|
49
|
+
if (drag.current === null) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const { startHeight } = drag.current;
|
|
53
|
+
const next = heightAt(drag.current.lastY);
|
|
54
|
+
drag.current = null;
|
|
55
|
+
setDragging(false);
|
|
56
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
57
|
+
settle(next, startHeight);
|
|
58
|
+
}
|
|
59
|
+
function key(event) {
|
|
60
|
+
const step = event.shiftKey ? PANEL_KEY_STEP * 4 : PANEL_KEY_STEP;
|
|
61
|
+
const end = panelBodyMax(availableHeightOf(container));
|
|
62
|
+
const target = { ArrowUp: height - step, ArrowDown: height + step, Home: PANEL_BODY_MIN, End: end }[event.key];
|
|
63
|
+
if (target === undefined) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
settle(target, height);
|
|
69
|
+
}
|
|
70
|
+
return (_jsx("div", { role: "separator", "aria-orientation": "horizontal", "aria-label": t('language.panel.resize'), "aria-valuemin": PANEL_BODY_MIN, "aria-valuemax": max, "aria-valuenow": height, tabIndex: 0, "data-dragging": dragging, className: "absolute inset-x-0 bottom-0 z-10 h-1 cursor-row-resize touch-none outline-none hover:bg-accent-soft focus-visible:bg-accent-soft data-[dragging=true]:bg-accent-soft", onPointerDown: down, onPointerMove: move, onPointerUp: end, onPointerCancel: end, onLostPointerCapture: end, onKeyDown: key }));
|
|
71
|
+
}
|
package/dist/PeekPanel.js
CHANGED
|
@@ -9,6 +9,9 @@ import { formatNumber } from '@adecore/ui/format';
|
|
|
9
9
|
import { basenameOf, relativeTo } from './paths.js';
|
|
10
10
|
import { shikiLanguageOfPath } from './language-ids.js';
|
|
11
11
|
import { PathText } from './PathText.js';
|
|
12
|
+
import { PanelResizeHandle } from './PanelResizeHandle.js';
|
|
13
|
+
import { usePanelBody } from './use-panel-body.js';
|
|
14
|
+
import { PANEL_BODY_HEIGHT } from './panel-size.js';
|
|
12
15
|
import { PEEK_HEIGHT } from './peek.js';
|
|
13
16
|
import { distinguishingFolders, visualColumnOf } from './peek-model.js';
|
|
14
17
|
import { useEditorRendering } from './rendering-context.js';
|
|
@@ -93,11 +96,12 @@ export function PeekPanel({ language, view }) {
|
|
|
93
96
|
const path = active === undefined ? null : fileUriToPath(active.location.uri);
|
|
94
97
|
const folder = language.project.folder;
|
|
95
98
|
const shown = path === null ? '' : folderOf(relativeTo(path, folder));
|
|
99
|
+
const body = usePanelBody(language);
|
|
96
100
|
if (view.container === null) {
|
|
97
101
|
return null;
|
|
98
102
|
}
|
|
99
|
-
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: PEEK_HEIGHT }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[31px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx("span", { className: "shrink-0 font-medium", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [_jsx("span", { className: "text-text-muted", children: t(view.kind === 'definitions' ? 'language.peek.definitions' : 'language.peek.references', {
|
|
103
|
+
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: PEEK_HEIGHT - PANEL_BODY_HEIGHT + body.height }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[31px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx("span", { className: "shrink-0 font-medium", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [_jsx("span", { className: "text-text-muted", children: t(view.kind === 'definitions' ? 'language.peek.definitions' : 'language.peek.references', {
|
|
100
104
|
count: view.count,
|
|
101
105
|
formatted: formatNumber(view.count)
|
|
102
|
-
}) }), _jsx(IconButton, { icon: X, size: "xs", label: t('language.peek.close'), onClick: () => language.peek.close() })] })] }), _jsxs("div", { className: "flex
|
|
106
|
+
}) }), _jsx(IconButton, { icon: X, size: "xs", label: t('language.peek.close'), onClick: () => language.peek.close() })] })] }), _jsxs("div", { className: "flex shrink-0", style: { height: body.height }, children: [_jsx(Preview, { preview: view.preview }), _jsx(Places, { language: language, view: view })] }), _jsx("div", { className: "peek-hatch relative h-[14px]", children: _jsx(PanelResizeHandle, { container: view.container, height: body.height, onHeight: body.set, onCommit: body.commit }) })] }), view.container);
|
|
103
107
|
}
|
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 {};
|