leepi 0.0.0 → 0.0.3

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.
Files changed (86) hide show
  1. package/dist/core/active-marks.d.ts +15 -0
  2. package/dist/core/active-marks.js +57 -0
  3. package/dist/core/commands.d.ts +39 -0
  4. package/dist/core/commands.js +415 -0
  5. package/dist/core/editor.d.ts +25 -0
  6. package/dist/core/editor.js +102 -0
  7. package/dist/core/field-notifier.d.ts +21 -0
  8. package/dist/core/field-notifier.js +56 -0
  9. package/dist/core/highlight-style.js +60 -0
  10. package/dist/core/highlight.d.ts +8 -0
  11. package/dist/core/highlight.js +34 -0
  12. package/dist/core/plugins/blockquote.d.ts +11 -0
  13. package/dist/core/plugins/blockquote.js +78 -0
  14. package/dist/core/plugins/bracket.d.ts +6 -0
  15. package/dist/core/plugins/bracket.js +38 -0
  16. package/dist/core/plugins/code-block.d.ts +27 -0
  17. package/dist/core/plugins/code-block.js +207 -0
  18. package/dist/core/plugins/heading.d.ts +13 -0
  19. package/dist/core/plugins/heading.js +111 -0
  20. package/dist/core/plugins/inline.d.ts +14 -0
  21. package/dist/core/plugins/inline.js +103 -0
  22. package/dist/core/plugins/link.d.ts +25 -0
  23. package/dist/core/plugins/link.js +104 -0
  24. package/dist/core/plugins/list.d.ts +14 -0
  25. package/dist/core/plugins/list.js +91 -0
  26. package/dist/core/plugins/table.d.ts +12 -0
  27. package/dist/core/plugins/table.js +161 -0
  28. package/dist/core/plugins.d.ts +9 -0
  29. package/dist/core/plugins.js +9 -0
  30. package/dist/core/popover.d.ts +9 -0
  31. package/dist/core/popover.js +16 -0
  32. package/dist/core/registry.d.ts +10 -0
  33. package/dist/core/registry.js +8 -0
  34. package/dist/core/types.d.ts +25 -0
  35. package/dist/core/types.js +0 -0
  36. package/dist/core/utils.d.ts +13 -0
  37. package/dist/core/utils.js +32 -0
  38. package/dist/leepi.css +461 -0
  39. package/dist/react/code-block-popover.d.ts +76 -0
  40. package/dist/react/code-block-popover.js +223 -0
  41. package/dist/react/context.d.ts +42 -0
  42. package/dist/react/context.js +88 -0
  43. package/dist/react/editor.d.ts +30 -0
  44. package/dist/react/editor.js +60 -0
  45. package/dist/react/floating-toolbar.d.ts +30 -0
  46. package/dist/react/floating-toolbar.js +87 -0
  47. package/dist/react/link-popover.d.ts +70 -0
  48. package/dist/react/link-popover.js +222 -0
  49. package/dist/react/preview.d.ts +13 -0
  50. package/dist/react/preview.js +56 -0
  51. package/dist/react/toolbar.d.ts +51 -0
  52. package/dist/react/toolbar.js +161 -0
  53. package/package.json +90 -1
  54. package/src/core/active-marks.ts +89 -0
  55. package/src/core/commands.ts +461 -0
  56. package/src/core/editor.ts +139 -0
  57. package/src/core/field-notifier.ts +71 -0
  58. package/src/core/highlight-style.ts +66 -0
  59. package/src/core/highlight.ts +50 -0
  60. package/src/core/plugins/blockquote.ts +108 -0
  61. package/src/core/plugins/bracket.ts +34 -0
  62. package/src/core/plugins/code-block.ts +195 -0
  63. package/src/core/plugins/heading.ts +95 -0
  64. package/src/core/plugins/index.ts +16 -0
  65. package/src/core/plugins/inline.ts +62 -0
  66. package/src/core/plugins/link.ts +124 -0
  67. package/src/core/plugins/list.ts +68 -0
  68. package/src/core/plugins/table.ts +217 -0
  69. package/src/core/popover.ts +17 -0
  70. package/src/core/registry.ts +18 -0
  71. package/src/core/types.ts +25 -0
  72. package/src/core/utils.ts +38 -0
  73. package/src/react/code-block-popover.tsx +387 -0
  74. package/src/react/context.tsx +153 -0
  75. package/src/react/editor.tsx +106 -0
  76. package/src/react/floating-toolbar.tsx +161 -0
  77. package/src/react/link-popover.tsx +354 -0
  78. package/src/react/preview.tsx +80 -0
  79. package/src/react/toolbar.tsx +294 -0
  80. package/src/styles/floating-toolbar.css +52 -0
  81. package/src/styles/leepi.css +2 -0
  82. package/src/styles/popover.css +93 -0
  83. package/src/styles/preview.css +191 -0
  84. package/src/styles/theme.css +99 -0
  85. package/src/styles/tokens.css +63 -0
  86. package/src/styles/toolbar.css +55 -0
@@ -0,0 +1,223 @@
1
+ import { applyCodeBlock } from "../core/commands.js";
2
+ import { closePopover, popoverField } from "../core/popover.js";
3
+ import { useEditorContext, useStateField, useVirtualAnchor } from "./context.js";
4
+ import { createContext, useCallback, useContext, useId, useMemo, useState } from "react";
5
+ import { Popover } from "@base-ui/react/popover";
6
+ import { Form } from "@base-ui/react/form";
7
+ import { Field } from "@base-ui/react/field";
8
+ import { useRender } from "@base-ui/react/use-render";
9
+ import { mergeProps } from "@base-ui/react/merge-props";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/react/code-block-popover.tsx
12
+ const CodeBlockPopoverCtx = createContext(null);
13
+ function useCodeBlockPopoverCtx() {
14
+ const ctx = useContext(CodeBlockPopoverCtx);
15
+ if (!ctx) throw new Error("leepi: CodeBlockPopover sub-components must be used inside CodeBlockPopover.Root");
16
+ return ctx;
17
+ }
18
+ function LanguageSelect({ autoFocus, render, label, children, ...restProps }) {
19
+ const { lang, setLang } = useCodeBlockPopoverCtx();
20
+ const id = useId();
21
+ const element = useRender({
22
+ render,
23
+ defaultTagName: "select",
24
+ props: mergeProps({
25
+ id,
26
+ value: lang,
27
+ onChange: (e) => setLang(e.target.value),
28
+ autoFocus,
29
+ className: "lp-popover-select"
30
+ }, restProps)
31
+ });
32
+ const defaultSelect = !render ? /* @__PURE__ */ jsx("select", {
33
+ id,
34
+ value: lang,
35
+ onChange: (e) => setLang(e.target.value),
36
+ autoFocus,
37
+ className: "lp-popover-select",
38
+ ...restProps,
39
+ children
40
+ }) : null;
41
+ return /* @__PURE__ */ jsxs(Field.Root, {
42
+ className: "lp-popover-field",
43
+ children: [/* @__PURE__ */ jsx(Field.Label, {
44
+ htmlFor: id,
45
+ className: "lp-popover-label",
46
+ children: label
47
+ }), render ? element : defaultSelect]
48
+ });
49
+ }
50
+ function LanguageInput({ autoFocus, placeholder, render, children, ...restProps }) {
51
+ const { lang, setLang } = useCodeBlockPopoverCtx();
52
+ const id = useId();
53
+ const element = useRender({
54
+ render,
55
+ defaultTagName: "input",
56
+ props: mergeProps({
57
+ id,
58
+ type: "text",
59
+ value: lang,
60
+ onChange: (e) => setLang(e.target.value),
61
+ autoFocus,
62
+ placeholder,
63
+ className: "lp-popover-input"
64
+ }, restProps)
65
+ });
66
+ return /* @__PURE__ */ jsxs(Field.Root, {
67
+ className: "lp-popover-field",
68
+ children: [/* @__PURE__ */ jsx(Field.Label, {
69
+ htmlFor: id,
70
+ className: "lp-popover-label",
71
+ children
72
+ }), element]
73
+ });
74
+ }
75
+ function FilenameInput({ placeholder, render, children, ...restProps }) {
76
+ const { filename, setFilename } = useCodeBlockPopoverCtx();
77
+ const id = useId();
78
+ const element = useRender({
79
+ render,
80
+ defaultTagName: "input",
81
+ props: mergeProps({
82
+ id,
83
+ type: "text",
84
+ value: filename,
85
+ onChange: (e) => setFilename(e.target.value),
86
+ placeholder,
87
+ className: "lp-popover-input"
88
+ }, restProps)
89
+ });
90
+ return /* @__PURE__ */ jsxs(Field.Root, {
91
+ className: "lp-popover-field",
92
+ children: [/* @__PURE__ */ jsx(Field.Label, {
93
+ htmlFor: id,
94
+ className: "lp-popover-label",
95
+ children
96
+ }), element]
97
+ });
98
+ }
99
+ function Actions({ cancelLabel, submitLabel }) {
100
+ const { onClose } = useCodeBlockPopoverCtx();
101
+ return /* @__PURE__ */ jsxs("div", {
102
+ className: "lp-popover-actions",
103
+ children: [/* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsxs("div", {
104
+ className: "lp-popover-actions-end",
105
+ children: [/* @__PURE__ */ jsx("button", {
106
+ type: "button",
107
+ onClick: onClose,
108
+ className: "lp-popover-btn lp-popover-btn--secondary",
109
+ children: cancelLabel
110
+ }), /* @__PURE__ */ jsx("button", {
111
+ type: "submit",
112
+ className: "lp-popover-btn lp-popover-btn--primary",
113
+ children: submitLabel
114
+ })]
115
+ })]
116
+ });
117
+ }
118
+ function CodeBlockPopoverContent({ initialLang, initialFilename, onSubmit, onClose, children }) {
119
+ const [lang, setLang] = useState(initialLang || "");
120
+ const [filename, setFilename] = useState(initialFilename || "");
121
+ const handleSubmit = useCallback((e) => {
122
+ e.preventDefault();
123
+ onSubmit(lang, filename);
124
+ }, [
125
+ lang,
126
+ filename,
127
+ onSubmit
128
+ ]);
129
+ return /* @__PURE__ */ jsx(CodeBlockPopoverCtx, {
130
+ value: useMemo(() => ({
131
+ lang,
132
+ setLang,
133
+ filename,
134
+ setFilename,
135
+ onClose
136
+ }), [
137
+ lang,
138
+ filename,
139
+ onClose
140
+ ]),
141
+ children: /* @__PURE__ */ jsx(Form, {
142
+ onSubmit: handleSubmit,
143
+ className: "lp-popover-form",
144
+ children
145
+ })
146
+ });
147
+ }
148
+ function CodeBlockPopoverRoot({ x, y, onClose, ...contentProps }) {
149
+ const virtualAnchor = useMemo(() => ({ getBoundingClientRect: () => ({
150
+ x,
151
+ y,
152
+ width: 0,
153
+ height: 0,
154
+ top: y,
155
+ left: x,
156
+ right: x,
157
+ bottom: y,
158
+ toJSON() {
159
+ return this;
160
+ }
161
+ }) }), [x, y]);
162
+ return /* @__PURE__ */ jsx(Popover.Root, {
163
+ open: true,
164
+ onOpenChange: onClose,
165
+ children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
166
+ anchor: virtualAnchor,
167
+ positionMethod: "fixed",
168
+ side: "bottom",
169
+ align: "start",
170
+ sideOffset: 8,
171
+ className: "lp-popover-positioner",
172
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
173
+ className: "lp-popover",
174
+ children: /* @__PURE__ */ jsx(CodeBlockPopoverContent, {
175
+ onClose,
176
+ ...contentProps
177
+ })
178
+ })
179
+ }) })
180
+ });
181
+ }
182
+ function CodeBlockPopoverConnected({ children }) {
183
+ const { view } = useEditorContext();
184
+ const popoverReq = useStateField(popoverField, null);
185
+ const codeBlockReq = popoverReq?.type === "codeblock" ? popoverReq : null;
186
+ const handleSubmit = useCallback((lang, filename) => {
187
+ if (!view || !codeBlockReq) return;
188
+ applyCodeBlock(view, codeBlockReq.data, lang, filename);
189
+ view.dispatch({ effects: closePopover.of() });
190
+ }, [view, codeBlockReq]);
191
+ const handleClose = useCallback(() => {
192
+ if (!view) return;
193
+ view.dispatch({ effects: closePopover.of() });
194
+ view.focus();
195
+ }, [view]);
196
+ const virtualAnchor = useVirtualAnchor(codeBlockReq ? codeBlockReq.data.isNew ? codeBlockReq.data.insertPos ?? 0 : codeBlockReq.data.fenceFrom : null);
197
+ return /* @__PURE__ */ jsx(Popover.Root, {
198
+ open: !!codeBlockReq,
199
+ onOpenChange: (open) => {
200
+ if (!open) handleClose();
201
+ },
202
+ children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
203
+ anchor: virtualAnchor,
204
+ positionMethod: "fixed",
205
+ side: "bottom",
206
+ align: "start",
207
+ sideOffset: 8,
208
+ className: "lp-popover-positioner",
209
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
210
+ className: "lp-popover",
211
+ children: codeBlockReq && /* @__PURE__ */ jsx(CodeBlockPopoverContent, {
212
+ initialLang: codeBlockReq.data.lang,
213
+ initialFilename: codeBlockReq.data.filename,
214
+ onSubmit: handleSubmit,
215
+ onClose: handleClose,
216
+ children
217
+ }, `${codeBlockReq.data.fenceFrom}-${codeBlockReq.data.isNew}`)
218
+ })
219
+ }) })
220
+ });
221
+ }
222
+ //#endregion
223
+ export { Actions, CodeBlockPopoverConnected, CodeBlockPopoverContent as Content, FilenameInput, LanguageInput, LanguageSelect, CodeBlockPopoverRoot as Root };
@@ -0,0 +1,42 @@
1
+ import { ActiveMarks } from "../core/active-marks.js";
2
+ import { ShortcutEntry } from "../core/types.js";
3
+ import { EditorView } from "@codemirror/view";
4
+ import { StateField } from "@codemirror/state";
5
+ import { JSX, ReactNode } from "react";
6
+
7
+ //#region src/react/context.d.ts
8
+ interface EditorContextValue {
9
+ view: EditorView | null;
10
+ setView: (view: EditorView | null) => void;
11
+ }
12
+ declare function useEditorContext(): EditorContextValue;
13
+ interface EditorProviderProps {
14
+ children: ReactNode;
15
+ }
16
+ declare function EditorProvider({
17
+ children
18
+ }: EditorProviderProps): JSX.Element;
19
+ /**
20
+ * Returns the currently active formatting marks at the cursor position.
21
+ * Updates reactively when the cursor moves or the document changes.
22
+ */
23
+ declare function useActiveMarks(): ActiveMarks;
24
+ /**
25
+ * Subscribe to a CM6 StateField and re-render when its value changes.
26
+ */
27
+ declare function useStateField<T>(field: StateField<T>, defaultValue: T): T;
28
+ /**
29
+ * Returns a virtual anchor element that dynamically resolves its position
30
+ * from a CodeMirror document position. Useful for anchoring popovers to
31
+ * editor content that may scroll after the popover opens.
32
+ */
33
+ interface VirtualAnchor {
34
+ getBoundingClientRect: () => DOMRect;
35
+ }
36
+ declare function useVirtualAnchor(pos: number | null): VirtualAnchor | undefined;
37
+ /**
38
+ * Returns all shortcuts registered by plugins via the shortcutRegistry Facet.
39
+ */
40
+ declare function useShortcuts(): ShortcutEntry[];
41
+ //#endregion
42
+ export { EditorContextValue, EditorProvider, EditorProviderProps, VirtualAnchor, useActiveMarks, useEditorContext, useShortcuts, useStateField, useVirtualAnchor };
@@ -0,0 +1,88 @@
1
+ import { shortcutRegistry } from "../core/registry.js";
2
+ import { emptyMarks, getMarksSnapshot, subscribeToMarks } from "../core/active-marks.js";
3
+ import { getFieldSnapshot, subscribeToField } from "../core/field-notifier.js";
4
+ import { createContext, useCallback, useContext, useMemo, useState, useSyncExternalStore } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/react/context.tsx
7
+ const EditorContext = createContext(null);
8
+ function useEditorContext() {
9
+ const ctx = useContext(EditorContext);
10
+ if (!ctx) throw new Error("leepi: <Toolbar />, <FloatingToolbar /> must be rendered inside <EditorProvider>");
11
+ return ctx;
12
+ }
13
+ function EditorProvider({ children }) {
14
+ const [view, setView] = useState(null);
15
+ const editorCtx = useMemo(() => ({
16
+ view,
17
+ setView
18
+ }), [view]);
19
+ return /* @__PURE__ */ jsx(EditorContext.Provider, {
20
+ value: editorCtx,
21
+ children
22
+ });
23
+ }
24
+ /**
25
+ * Returns the currently active formatting marks at the cursor position.
26
+ * Updates reactively when the cursor moves or the document changes.
27
+ */
28
+ function useActiveMarks() {
29
+ const { view } = useEditorContext();
30
+ return useSyncExternalStore(useCallback((onStoreChange) => {
31
+ if (!view) return () => {};
32
+ return subscribeToMarks(view, onStoreChange);
33
+ }, [view]), useCallback(() => {
34
+ if (!view) return emptyMarks;
35
+ return getMarksSnapshot(view);
36
+ }, [view]));
37
+ }
38
+ /**
39
+ * Subscribe to a CM6 StateField and re-render when its value changes.
40
+ */
41
+ function useStateField(field, defaultValue) {
42
+ const { view } = useEditorContext();
43
+ return useSyncExternalStore(useCallback((onStoreChange) => {
44
+ if (!view) return () => {};
45
+ return subscribeToField(view, field, onStoreChange);
46
+ }, [view, field]), useCallback(() => {
47
+ if (!view) return defaultValue;
48
+ return getFieldSnapshot(view, field) ?? defaultValue;
49
+ }, [
50
+ view,
51
+ field,
52
+ defaultValue
53
+ ]));
54
+ }
55
+ function useVirtualAnchor(pos) {
56
+ const { view } = useEditorContext();
57
+ return useMemo(() => {
58
+ if (pos == null || !view) return void 0;
59
+ return { getBoundingClientRect: () => {
60
+ const coords = view.coordsAtPos(pos);
61
+ const x = coords?.left ?? 0;
62
+ const y = coords?.bottom ?? 0;
63
+ return {
64
+ x,
65
+ y,
66
+ width: 0,
67
+ height: 0,
68
+ top: y,
69
+ left: x,
70
+ right: x,
71
+ bottom: y,
72
+ toJSON() {
73
+ return this;
74
+ }
75
+ };
76
+ } };
77
+ }, [pos, view]);
78
+ }
79
+ /**
80
+ * Returns all shortcuts registered by plugins via the shortcutRegistry Facet.
81
+ */
82
+ function useShortcuts() {
83
+ const { view } = useEditorContext();
84
+ if (!view) return [];
85
+ return view.state.facet(shortcutRegistry);
86
+ }
87
+ //#endregion
88
+ export { EditorProvider, useActiveMarks, useEditorContext, useShortcuts, useStateField, useVirtualAnchor };
@@ -0,0 +1,30 @@
1
+ import { Extension } from "@codemirror/state";
2
+ import { ComponentProps, JSX, ReactNode } from "react";
3
+
4
+ //#region src/react/editor.d.ts
5
+ type EditorProps = {
6
+ onChange?: (value: string) => void;
7
+ placeholder?: string; /** Additional CM6 extensions / plugins */
8
+ plugins?: Extension[];
9
+ children?: ReactNode;
10
+ } & Pick<ComponentProps<"textarea">, "className" | "style" | "readOnly" | "autoFocus"> & ({
11
+ value: string;
12
+ defaultValue?: never;
13
+ } | {
14
+ value?: never;
15
+ defaultValue: string;
16
+ });
17
+ declare function EditorRoot({
18
+ value,
19
+ defaultValue,
20
+ onChange,
21
+ placeholder,
22
+ plugins,
23
+ readOnly,
24
+ autoFocus,
25
+ children,
26
+ className,
27
+ style
28
+ }: EditorProps): JSX.Element;
29
+ //#endregion
30
+ export { EditorRoot as Editor, EditorProps };
@@ -0,0 +1,60 @@
1
+ import { createEditorState, editableCompartment, loadLanguageSupport } from "../core/editor.js";
2
+ import { useEditorContext } from "./context.js";
3
+ import { EditorView } from "@codemirror/view";
4
+ import { useEffect, useRef } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/react/editor.tsx
7
+ function EditorRoot({ value, defaultValue, onChange, placeholder = "", plugins, readOnly = false, autoFocus = false, children, className, style }) {
8
+ const isControlled = value !== void 0;
9
+ const containerRef = useRef(null);
10
+ const viewRef = useRef(null);
11
+ const onChangeRef = useRef(onChange);
12
+ onChangeRef.current = onChange;
13
+ const { setView } = useEditorContext();
14
+ useEffect(() => {
15
+ if (!containerRef.current) return;
16
+ containerRef.current.style.display = "contents";
17
+ const view = new EditorView({
18
+ state: createEditorState({
19
+ doc: value ?? defaultValue ?? "",
20
+ onUpdate: (doc) => onChangeRef.current?.(doc),
21
+ plugins,
22
+ placeholder,
23
+ readOnly
24
+ }),
25
+ parent: containerRef.current
26
+ });
27
+ viewRef.current = view;
28
+ setView(view);
29
+ if (autoFocus) view.focus();
30
+ loadLanguageSupport(view);
31
+ return () => {
32
+ view.destroy();
33
+ viewRef.current = null;
34
+ setView(null);
35
+ };
36
+ }, [setView]);
37
+ useEffect(() => {
38
+ if (!isControlled) return;
39
+ const view = viewRef.current;
40
+ if (!view) return;
41
+ const currentDoc = view.state.doc.toString();
42
+ if (currentDoc !== value) view.dispatch({ changes: {
43
+ from: 0,
44
+ to: currentDoc.length,
45
+ insert: value ?? ""
46
+ } });
47
+ }, [isControlled, value]);
48
+ useEffect(() => {
49
+ const view = viewRef.current;
50
+ if (!view) return;
51
+ view.dispatch({ effects: editableCompartment.reconfigure(EditorView.editable.of(!readOnly)) });
52
+ }, [readOnly]);
53
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
54
+ ref: containerRef,
55
+ className,
56
+ style
57
+ }), children] });
58
+ }
59
+ //#endregion
60
+ export { EditorRoot as Editor };
@@ -0,0 +1,30 @@
1
+ import { ComponentProps, JSX, ReactNode } from "react";
2
+ import { Toolbar } from "@base-ui/react/toolbar";
3
+
4
+ //#region src/react/floating-toolbar.d.ts
5
+ type FloatingToolbarButtonProps = ComponentProps<typeof Toolbar.Button>;
6
+ declare function FloatingToolbarButton({
7
+ className,
8
+ ...props
9
+ }: FloatingToolbarButtonProps): JSX.Element;
10
+ type FloatingToolbarSeparatorProps = ComponentProps<typeof Toolbar.Separator>;
11
+ declare function FloatingToolbarSeparator({
12
+ className,
13
+ ...props
14
+ }: FloatingToolbarSeparatorProps): JSX.Element;
15
+ type FloatingToolbarGroupProps = ComponentProps<typeof Toolbar.Group>;
16
+ declare function FloatingToolbarGroup({
17
+ className,
18
+ ...props
19
+ }: FloatingToolbarGroupProps): JSX.Element;
20
+ type FloatingToolbarProps = {
21
+ className?: string;
22
+ children?: ReactNode;
23
+ } & Omit<ComponentProps<typeof Toolbar.Root>, "children">;
24
+ declare function FloatingToolbarRoot({
25
+ className,
26
+ children,
27
+ style: propStyle
28
+ }: FloatingToolbarProps): JSX.Element | null;
29
+ //#endregion
30
+ export { FloatingToolbarButton as Button, FloatingToolbarProps, FloatingToolbarGroup as Group, FloatingToolbarRoot as Root, FloatingToolbarSeparator as Separator };
@@ -0,0 +1,87 @@
1
+ import { isInsideCodeBlock } from "../core/utils.js";
2
+ import { popoverField } from "../core/popover.js";
3
+ import { useEditorContext, useStateField } from "./context.js";
4
+ import { useEffect, useMemo, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import { Toolbar } from "@base-ui/react/toolbar";
7
+ //#region src/react/floating-toolbar.tsx
8
+ function useFloatingPosition() {
9
+ const { view } = useEditorContext();
10
+ const popoverOpen = useStateField(popoverField, null) != null;
11
+ const [position, setPosition] = useState(null);
12
+ useEffect(() => {
13
+ if (!view) return;
14
+ const updatePosition = () => {
15
+ const { state } = view;
16
+ const { from, to } = state.selection.main;
17
+ if (from === to || isInsideCodeBlock(view)) {
18
+ setPosition(null);
19
+ return;
20
+ }
21
+ if (state.doc.lineAt(from).number !== state.doc.lineAt(to).number) {
22
+ setPosition(null);
23
+ return;
24
+ }
25
+ const fromCoords = view.coordsAtPos(from);
26
+ const toCoords = view.coordsAtPos(to);
27
+ if (!fromCoords || !toCoords) {
28
+ setPosition(null);
29
+ return;
30
+ }
31
+ const left = (fromCoords.left + toCoords.left) / 2;
32
+ setPosition({
33
+ top: Math.min(fromCoords.top, toCoords.top) - 8,
34
+ left
35
+ });
36
+ };
37
+ const handler = () => requestAnimationFrame(updatePosition);
38
+ view.contentDOM.addEventListener("mouseup", handler);
39
+ view.contentDOM.addEventListener("keyup", handler);
40
+ if (!popoverOpen) updatePosition();
41
+ return () => {
42
+ view.contentDOM.removeEventListener("mouseup", handler);
43
+ view.contentDOM.removeEventListener("keyup", handler);
44
+ };
45
+ }, [view, popoverOpen]);
46
+ if (popoverOpen) return null;
47
+ return position;
48
+ }
49
+ function FloatingToolbarButton({ className, ...props }) {
50
+ return /* @__PURE__ */ jsx(Toolbar.Button, {
51
+ className: `lp-floating-toolbar-btn ${className || ""}`,
52
+ ...props
53
+ });
54
+ }
55
+ function FloatingToolbarSeparator({ className, ...props }) {
56
+ return /* @__PURE__ */ jsx(Toolbar.Separator, {
57
+ className: `lp-floating-toolbar-separator ${className || ""}`,
58
+ ...props
59
+ });
60
+ }
61
+ function FloatingToolbarGroup({ className, ...props }) {
62
+ return /* @__PURE__ */ jsx(Toolbar.Group, {
63
+ className: `lp-floating-toolbar-group ${className || ""}`,
64
+ ...props
65
+ });
66
+ }
67
+ function FloatingToolbarRoot({ className, children, style: propStyle }) {
68
+ const position = useFloatingPosition();
69
+ const style = useMemo(() => {
70
+ if (!position) return propStyle;
71
+ return {
72
+ ...propStyle,
73
+ position: "fixed",
74
+ top: position.top,
75
+ left: position.left,
76
+ transform: "translate(-50%, -100%)"
77
+ };
78
+ }, [position, propStyle]);
79
+ if (!position) return null;
80
+ return /* @__PURE__ */ jsx(Toolbar.Root, {
81
+ className: `lp-floating-toolbar ${className || ""}`,
82
+ style,
83
+ children
84
+ });
85
+ }
86
+ //#endregion
87
+ export { FloatingToolbarButton as Button, FloatingToolbarGroup as Group, FloatingToolbarRoot as Root, FloatingToolbarSeparator as Separator };
@@ -0,0 +1,70 @@
1
+ import { JSX, ReactNode } from "react";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+
4
+ //#region src/react/link-popover.d.ts
5
+ interface LabelInputProps extends useRender.ComponentProps<"input"> {
6
+ autoFocus?: boolean;
7
+ placeholder?: string;
8
+ children?: ReactNode;
9
+ }
10
+ declare function LabelInput({
11
+ autoFocus,
12
+ placeholder,
13
+ render,
14
+ children,
15
+ ...restProps
16
+ }: LabelInputProps): JSX.Element;
17
+ interface UrlInputProps extends useRender.ComponentProps<"input"> {
18
+ autoFocus?: boolean;
19
+ placeholder?: string;
20
+ children?: ReactNode;
21
+ }
22
+ declare function UrlInput({
23
+ autoFocus,
24
+ placeholder,
25
+ render,
26
+ children,
27
+ ...restProps
28
+ }: UrlInputProps): JSX.Element;
29
+ declare function Actions({
30
+ cancelLabel,
31
+ submitLabel,
32
+ removeLabel
33
+ }: {
34
+ cancelLabel?: ReactNode;
35
+ submitLabel?: ReactNode;
36
+ removeLabel?: ReactNode;
37
+ }): JSX.Element;
38
+ interface LinkPopoverContentProps {
39
+ initialLabel?: string;
40
+ initialUrl?: string;
41
+ onSubmit: (label: string, url: string) => void;
42
+ onRemove: () => void;
43
+ onClose: () => void;
44
+ children: ReactNode;
45
+ }
46
+ declare function LinkPopoverContent({
47
+ initialLabel,
48
+ initialUrl,
49
+ onSubmit,
50
+ onRemove,
51
+ onClose,
52
+ children
53
+ }: LinkPopoverContentProps): JSX.Element;
54
+ interface LinkPopoverProps extends LinkPopoverContentProps {
55
+ x: number;
56
+ y: number;
57
+ }
58
+ declare function LinkPopoverRoot({
59
+ x,
60
+ y,
61
+ onClose,
62
+ ...contentProps
63
+ }: LinkPopoverProps): JSX.Element;
64
+ declare function LinkPopoverConnected({
65
+ children
66
+ }: {
67
+ children: ReactNode;
68
+ }): JSX.Element;
69
+ //#endregion
70
+ export { Actions, LinkPopoverContent as Content, LabelInput, LinkPopoverConnected, LinkPopoverContentProps, LinkPopoverProps, LinkPopoverRoot as Root, UrlInput };