5 files changed,
+471,
-90
1@@ -55,6 +55,8 @@ function addEventListener(element, eventName, handler) {
2
3 let KeyboardEvent = {};
4
5+let MouseEvent = {};
6+
7 let InputElement = {};
8
9 export {
10@@ -63,6 +65,7 @@ export {
11 Ref,
12 addEventListener,
13 KeyboardEvent,
14+ MouseEvent,
15 InputElement,
16 }
17 /* No side effect */
+243,
-66
1@@ -1,8 +1,11 @@
2 // Generated by ReScript, PLEASE EDIT WITH CARE
3
4+import * as Log from "../utils/Log.res.mjs";
5 import * as Editor from "./Editor.res.mjs";
6 import * as NanoID from "../ffi/NanoID.res.mjs";
7 import * as Preact from "../ffi/Preact.res.mjs";
8+import * as PreactDOM from "../ffi/PreactDOM.res.mjs";
9+import * as Hooks from "preact/hooks";
10 import * as LucidePreact from "lucide-preact";
11 import * as Signals from "@preact/signals";
12 import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
13@@ -11,6 +14,8 @@ import * as Menus from "@tiptap/react/menus";
14
15 let show = Signals.signal(false);
16
17+let focusedIndex = Signals.signal(0);
18+
19 let state = {
20 timeout: null,
21 editor: null
22@@ -36,7 +41,11 @@ function stopClose() {
23
24 function setEditor(e) {
25 state.editor = e;
26- return e.isFocused;
27+ if (e.isFocused) {
28+ return true;
29+ } else {
30+ return show.value;
31+ }
32 }
33
34 function $$do(callback) {
35@@ -96,87 +105,253 @@ function selectFn(sig) {
36 };
37 }
38
39+function selectWaypoint(docID) {
40+ let sig = Editor.onSearchSelectCache.get(docID);
41+ if (sig === undefined) {
42+ return;
43+ }
44+ let sig$1 = Primitive_option.valFromOption(sig);
45+ sig$1.value = selectFn(sig$1);
46+}
47+
48 function waypointClickHandler(docID) {
49- return _evt => {
50- let sig = Editor.onSearchSelectCache.get(docID);
51- if (sig === undefined) {
52- return;
53- }
54- let sig$1 = Primitive_option.valFromOption(sig);
55- sig$1.value = selectFn(sig$1);
56- };
57+ return _evt => selectWaypoint(docID);
58 }
59
60 function FloatingMenu(props) {
61- let btn = "bg-[var(--color-paper)] py-2 md:py-1 w-screen md:w-full text-xl md:text-sm hover:cursor-pointer hover:bg-[var(--color-blue-100)] flex flex-row items-center";
62+ let doc = props.doc;
63+ let baseBtn = "py-3 md:py-1 w-full text-lg md:text-sm hover:cursor-pointer flex flex-row items-center";
64+ let highlighted = " bg-[var(--color-blue-100)]";
65+ let containerRef = Hooks.useRef(null);
66+ let menuRef = Hooks.useRef(null);
67+ let item0Id = "floating-menu-waypoint-" + doc;
68+ let item1Id = "floating-menu-image-" + doc;
69+ let item2Id = "floating-menu-video-" + doc;
70+ let getItemId = index => {
71+ switch (index) {
72+ case 1 :
73+ return item1Id;
74+ case 2 :
75+ return item2Id;
76+ default:
77+ return item0Id;
78+ }
79+ };
80+ Signals.effect(() => {
81+ if (!show.value) {
82+ return;
83+ }
84+ let el = menuRef.current;
85+ if (!(el == null)) {
86+ el.focus();
87+ return;
88+ }
89+ });
90+ Signals.effect(() => {
91+ if (!show.value) {
92+ focusedIndex.value = 0;
93+ return;
94+ }
95+ });
96+ Hooks.useEffect(() => {
97+ let handleKeyDown = evt => {
98+ Log.info(`key: ` + evt.key, undefined);
99+ if (evt.metaKey && evt.shiftKey && evt.key === "P") {
100+ evt.preventDefault();
101+ Log.info("got menu open shortcut", undefined);
102+ if (!show.value) {
103+ show.value = true;
104+ return;
105+ } else {
106+ return;
107+ }
108+ }
109+ };
110+ let documentElement = document;
111+ let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown);
112+ return () => cleanup();
113+ }, []);
114+ let isOpen = show.value;
115+ Hooks.useEffect(() => {
116+ if (!isOpen) {
117+ return;
118+ }
119+ let handleKeyDown = evt => {
120+ let key = evt.key;
121+ let shiftKey = evt.shiftKey;
122+ let current = focusedIndex.value;
123+ let exit = 0;
124+ switch (key) {
125+ case "ArrowUp" :
126+ evt.preventDefault();
127+ let prev = current > 0 ? current - 1 | 0 : 2;
128+ focusedIndex.value = prev;
129+ break;
130+ case "Enter" :
131+ if (current === 0) {
132+ selectWaypoint(doc);
133+ }
134+ break;
135+ case "Escape" :
136+ evt.preventDefault();
137+ show.value = false;
138+ let e = state.editor;
139+ if (!(e == null)) {
140+ e.commands.focus();
141+ }
142+ break;
143+ case "ArrowDown" :
144+ case "Tab" :
145+ exit = 1;
146+ break;
147+ }
148+ if (exit === 1 && !(key === "Tab" && shiftKey)) {
149+ evt.preventDefault();
150+ let next = current < 2 ? current + 1 | 0 : 0;
151+ focusedIndex.value = next;
152+ }
153+ if (!(key === "Tab" && shiftKey)) {
154+ return;
155+ }
156+ evt.preventDefault();
157+ let prev$1 = current > 0 ? current - 1 | 0 : 2;
158+ focusedIndex.value = prev$1;
159+ };
160+ let documentElement = document;
161+ let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown);
162+ return () => cleanup();
163+ }, [isOpen]);
164+ Hooks.useEffect(() => {
165+ if (!isOpen) {
166+ return;
167+ }
168+ let handleMouseDown = evt => {
169+ let target = evt.target;
170+ let container = containerRef.current;
171+ if (container == null) {
172+ return;
173+ }
174+ let isInside = container.contains(target);
175+ if (isInside) {
176+ return;
177+ }
178+ show.value = false;
179+ let e = state.editor;
180+ if (!(e == null)) {
181+ e.commands.focus();
182+ return;
183+ }
184+ };
185+ let documentElement = document;
186+ let cleanup = PreactDOM.addEventListener(documentElement, "mousedown", handleMouseDown);
187+ return () => cleanup();
188+ }, [isOpen]);
189+ let index = focusedIndex.value;
190 return JsxRuntime.jsx(Menus.FloatingMenu, {
191 options: {
192 placement: "left-start"
193 },
194 shouldShow: props => setEditor(props.editor),
195- children: JsxRuntime.jsx("div", {
196- children: show.value ? JsxRuntime.jsxs("div", {
197- children: [
198- JsxRuntime.jsxs("button", {
199- children: [
200- JsxRuntime.jsx(LucidePreact.LocateFixed, {
201- className: "p-[2px]"
202- }),
203- JsxRuntime.jsx("span", {
204- children: "Add waypoint",
205- className: "ml-2"
206- })
207- ],
208- className: btn,
209- onClick: waypointClickHandler(props.doc)
210- }),
211- JsxRuntime.jsxs("button", {
212- children: [
213- JsxRuntime.jsx(LucidePreact.Image, {
214- className: "p-[2px]"
215- }),
216- JsxRuntime.jsx("span", {
217- children: "Upload image",
218- className: "ml-2"
219- }),
220- JsxRuntime.jsx("span", {
221- children: " (soon!)",
222- className: "italic ml-2 text-xs"
223- })
224- ],
225- className: btn,
226- disabled: true
227- }),
228- JsxRuntime.jsxs("button", {
229- children: [
230- JsxRuntime.jsx(LucidePreact.SquarePlay, {
231- className: "p-[2px]"
232- }),
233- JsxRuntime.jsx("span", {
234- children: "Link YT Video",
235- className: "ml-2"
236- }),
237- JsxRuntime.jsx("span", {
238- children: " (soon!)",
239- className: "italic ml-2 text-xs"
240- })
241- ],
242- className: btn,
243- disabled: true
244- })
245- ],
246- className: "w-screen h-screen md:w-fit md:h-fit fixed md:relative top-0 left-0 pt-2 px-2 md:px-0 md:pt-0 flex flex-col md:rounded-sm bg-[var(--color-paper)]",
247- onMouseEnter: _evt => stopClose(),
248- onMouseLeave: _evt => closeMenu()
249- }) : JsxRuntime.jsx("button", {
250+ children: JsxRuntime.jsxs("div", {
251+ children: [
252+ JsxRuntime.jsx("button", {
253 children: JsxRuntime.jsx(LucidePreact.GripVertical, {
254 className: "w-5"
255 }),
256+ "aria-expanded": show.value,
257+ "aria-haspopup": "menu",
258 className: "hover:cursor-pointer hover:bg-[var(--color-bg-2)] rounded-sm text-[var(--color-blue)] my-auto",
259+ type: "button",
260 onClick: _evt => {
261- show.value = true;
262+ show.value = !show.value;
263 }
264 }),
265+ show.value ? JsxRuntime.jsxs("div", {
266+ children: [
267+ JsxRuntime.jsxs("button", {
268+ children: [
269+ JsxRuntime.jsx(LucidePreact.LocateFixed, {
270+ className: "p-[2px]"
271+ }),
272+ JsxRuntime.jsx("span", {
273+ children: "Add waypoint",
274+ className: "ml-2"
275+ })
276+ ],
277+ className: baseBtn + (
278+ index === 0 ? highlighted : " bg-[var(--color-paper)]"
279+ ),
280+ id: item0Id,
281+ role: "menuitem",
282+ tabIndex: -1,
283+ onClick: _evt => selectWaypoint(doc),
284+ onMouseEnter: _evt => {
285+ focusedIndex.value = 0;
286+ }
287+ }),
288+ JsxRuntime.jsxs("button", {
289+ children: [
290+ JsxRuntime.jsx(LucidePreact.Image, {
291+ className: "p-[2px]"
292+ }),
293+ JsxRuntime.jsx("span", {
294+ children: "Upload image",
295+ className: "ml-2"
296+ }),
297+ JsxRuntime.jsx("span", {
298+ children: " (soon!)",
299+ className: "italic ml-2 text-xs"
300+ })
301+ ],
302+ "aria-disabled": true,
303+ className: baseBtn + (
304+ index === 1 ? highlighted : " bg-[var(--color-paper)]"
305+ ),
306+ id: item1Id,
307+ role: "menuitem",
308+ tabIndex: -1,
309+ disabled: true,
310+ onMouseEnter: _evt => {
311+ focusedIndex.value = 1;
312+ }
313+ }),
314+ JsxRuntime.jsxs("button", {
315+ children: [
316+ JsxRuntime.jsx(LucidePreact.SquarePlay, {
317+ className: "p-[2px]"
318+ }),
319+ JsxRuntime.jsx("span", {
320+ children: "Link YT Video",
321+ className: "ml-2"
322+ }),
323+ JsxRuntime.jsx("span", {
324+ children: " (soon!)",
325+ className: "italic ml-2 text-xs"
326+ })
327+ ],
328+ "aria-disabled": true,
329+ className: baseBtn + (
330+ index === 2 ? highlighted : " bg-[var(--color-paper)]"
331+ ),
332+ id: item2Id,
333+ role: "menuitem",
334+ tabIndex: -1,
335+ disabled: true,
336+ onMouseEnter: _evt => {
337+ focusedIndex.value = 2;
338+ }
339+ })
340+ ],
341+ ref: Primitive_option.some(menuRef),
342+ "aria-activedescendant": getItemId(index),
343+ className: "flex flex-col active:outline-0 focus:outline-0",
344+ role: "menu",
345+ tabIndex: 0,
346+ onMouseEnter: _evt => stopClose(),
347+ onMouseLeave: _evt => closeMenu()
348+ }) : null
349+ ],
350+ ref: Primitive_option.some(containerRef),
351 className: Preact.cls("fixed left-0 box-border flex flex-row items-center", {
352 "shadow-xl/20 bg-[var(--color-paper)] ring-2 p-[2px] ring-[var(--color-blue)] rounded-sm ml-2": show.value
353 })
354@@ -188,6 +363,7 @@ let make = FloatingMenu;
355
356 export {
357 show,
358+ focusedIndex,
359 state,
360 closeMenu,
361 stopClose,
362@@ -196,6 +372,7 @@ export {
363 content,
364 html,
365 selectFn,
366+ selectWaypoint,
367 waypointClickHandler,
368 make,
369 }
+11,
-0
1@@ -112,6 +112,17 @@ module KeyboardEvent = {
2 @send external stopPropagation: t => unit = "stopPropagation"
3 }
4
5+// Native DOM MouseEvent bindings for document-level handlers
6+module MouseEvent = {
7+ type t
8+
9+ @get external target: t => Dom.element = "target"
10+ @get external clientX: t => int = "clientX"
11+ @get external clientY: t => int = "clientY"
12+ @send external preventDefault: t => unit = "preventDefault"
13+ @send external stopPropagation: t => unit = "stopPropagation"
14+}
15+
16 // HTMLInputElement bindings for form inputs
17 module InputElement = {
18 // Type for HTMLInputElement, extends Dom.element
+213,
-24
1@@ -1,14 +1,15 @@
2 open Preact
3+
4 let show = Signal.make(false)
5+let focusedIndex = Signal.make(0)
6
7 type t = {
8 mutable timeout: nullable<Window.timeoutId>,
9 mutable editor: nullable<Editor.t>,
10 }
11
12-// menu close handling - must be outside the box more than 500ms then auto close
13-// if overshoot a little and go back into the box, it stays open
14 let state = {timeout: Nullable.null, editor: Nullable.null}
15+
16 let closeMenu = () => {
17 switch state.timeout->Nullable.toOption {
18 | Some(id) => Window.clearTimeout(id)
19@@ -16,18 +17,19 @@ let closeMenu = () => {
20 }
21 state.timeout = Nullable.make(Window.setTimeout(() => show->Signal.set(false), 500))
22 }
23+
24 let stopClose = () => {
25 switch state.timeout->Nullable.toOption {
26 | Some(id) => Window.clearTimeout(id)
27 | None => ()
28 }
29 }
30-// store a mutable reference to the current editor every time the menu opens
31+
32 let setEditor = (e: Editor.t) => {
33 state.editor = Nullable.make(e)
34- // returns a bool indicating whether it should open, when focused
35- e->Tiptap.isFocused
36+ e->Tiptap.isFocused || show->Signal.get
37 }
38+
39 let do = (callback: Editor.t => unit) => {
40 switch state.editor->Nullable.toOption {
41 | Some(e) => callback(e)
42@@ -35,6 +37,7 @@ let do = (callback: Editor.t => unit) => {
43 }
44 show->Signal.set(false)
45 }
46+
47 let content = (c: {..}) => {
48 switch state.editor->Nullable.toOption {
49 | Some(e) => e->Tiptap.Commands.insertContent(c->Obj.magic)
50@@ -42,6 +45,7 @@ let content = (c: {..}) => {
51 }
52 show->Signal.set(false)
53 }
54+
55 let html = (h: string) => {
56 switch state.editor->Nullable.toOption {
57 | Some(e) => e->Tiptap.Commands.insertContent(h->Obj.magic)
58@@ -68,7 +72,6 @@ let selectFn = sig =>
59 })
60
61 let content2 = Obj.magic({"type": "text", "content": " "})
62- // Insert at the range from the event
63 switch state.editor->Nullable.toOption {
64 | Some(editor) => {
65 editor->Tiptap.Commands.insertContent(content1)
66@@ -80,21 +83,175 @@ let selectFn = sig =>
67 sig->Preact.Signal.set(None)
68 }
69
70+let selectWaypoint = (docID: string) => {
71+ switch Editor.onSearchSelectCache->Map.get(docID) {
72+ | Some(sig) => sig->Preact.Signal.set(Some(selectFn(sig)))
73+ | None => ()
74+ }
75+}
76+
77 let waypointClickHandler = (docID: string) =>
78 _evt => {
79- switch Editor.onSearchSelectCache->Map.get(docID) {
80- | Some(sig) => sig->Preact.Signal.set(Some(selectFn(sig)))
81- | None => ()
82- }
83+ selectWaypoint(docID)
84 }
85
86 @jsx.component
87 let make = (~doc: string) => {
88- let btn = "bg-[var(--color-paper)] py-2 md:py-1 w-screen md:w-full text-xl md:text-sm hover:cursor-pointer hover:bg-[var(--color-blue-100)] flex flex-row items-center"
89+ let baseBtn = "py-3 md:py-1 w-full text-lg md:text-sm hover:cursor-pointer flex flex-row items-center"
90+ let highlighted = " bg-[var(--color-blue-100)]"
91+ let containerRef = Preact.useRef(Nullable.null)
92+ let menuRef = Preact.useRef(Nullable.null)
93+
94+ let item0Id = "floating-menu-waypoint-" ++ doc
95+ let item1Id = "floating-menu-image-" ++ doc
96+ let item2Id = "floating-menu-video-" ++ doc
97+
98+ let getItemId = (index: int) => {
99+ switch index {
100+ | 0 => item0Id
101+ | 1 => item1Id
102+ | 2 => item2Id
103+ | _ => item0Id
104+ }
105+ }
106+
107+ // Focus the menu container when it opens
108+ Preact.Signal.effect(() => {
109+ if show->Signal.get {
110+ switch menuRef.current->Nullable.toOption {
111+ | Some(el) => el->PreactDOM.focus()
112+ | None => ()
113+ }
114+ }
115+ })
116+
117+ // Reset focusedIndex when menu closes
118+ Preact.Signal.effect(() => {
119+ if !(show->Signal.get) {
120+ focusedIndex->Signal.set(0)
121+ }
122+ })
123+
124+ // Global keyboard shortcut: Cmd+Shift+P opens menu
125+ Preact.useEffect(() => {
126+ let handleKeyDown = (evt: PreactDOM.KeyboardEvent.t) => {
127+ if (
128+ evt->PreactDOM.KeyboardEvent.metaKey &&
129+ evt->PreactDOM.KeyboardEvent.shiftKey &&
130+ evt->PreactDOM.KeyboardEvent.key == "P"
131+ ) {
132+ evt->PreactDOM.KeyboardEvent.preventDefault
133+ if !(show->Signal.get) {
134+ show->Signal.set(true)
135+ }
136+ }
137+ }
138+ let documentElement = Obj.magic(WebAPI.Global.document)
139+ let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown)
140+ Some(() => cleanup())
141+ }, [])
142+
143+ let isOpen = show->Signal.get
144+
145+ // Keyboard navigation when menu is open
146+ Preact.useEffect1(() => {
147+ if isOpen {
148+ let handleKeyDown = (evt: PreactDOM.KeyboardEvent.t) => {
149+ let key = evt->PreactDOM.KeyboardEvent.key
150+ let shiftKey = evt->PreactDOM.KeyboardEvent.shiftKey
151+ let current = focusedIndex->Signal.get
152+
153+ switch key {
154+ | "Escape" => {
155+ evt->PreactDOM.KeyboardEvent.preventDefault
156+ show->Signal.set(false)
157+ switch state.editor->Nullable.toOption {
158+ | Some(e) => e->Tiptap.Commands.focus
159+ | None => ()
160+ }
161+ }
162+ | "ArrowDown" | "Tab" =>
163+ if key == "Tab" && shiftKey {
164+ () // Shift+Tab handled below
165+ } else {
166+ evt->PreactDOM.KeyboardEvent.preventDefault
167+ let next = if current < 2 {
168+ current + 1
169+ } else {
170+ 0
171+ }
172+ focusedIndex->Signal.set(next)
173+ }
174+ | "ArrowUp" => {
175+ evt->PreactDOM.KeyboardEvent.preventDefault
176+ let prev = if current > 0 {
177+ current - 1
178+ } else {
179+ 2
180+ }
181+ focusedIndex->Signal.set(prev)
182+ }
183+ | "Enter" =>
184+ if current == 0 {
185+ selectWaypoint(doc)
186+ }
187+ // Enter on disabled items (1, 2) is a no-op
188+ | _ => ()
189+ }
190+
191+ if key == "Tab" && shiftKey {
192+ evt->PreactDOM.KeyboardEvent.preventDefault
193+ let prev = if current > 0 {
194+ current - 1
195+ } else {
196+ 2
197+ }
198+ focusedIndex->Signal.set(prev)
199+ }
200+ }
201+
202+ let documentElement = Obj.magic(WebAPI.Global.document)
203+ let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown)
204+ Some(() => cleanup())
205+ } else {
206+ None
207+ }
208+ }, [isOpen])
209+
210+ // Outside click closes menu
211+ Preact.useEffect1(() => {
212+ if isOpen {
213+ let handleMouseDown = (evt: PreactDOM.MouseEvent.t) => {
214+ let target = evt->PreactDOM.MouseEvent.target
215+ switch containerRef.current->Nullable.toOption {
216+ | Some(container) => {
217+ let isInside = (container->Obj.magic)["contains"](target)
218+ if !isInside {
219+ show->Signal.set(false)
220+ switch state.editor->Nullable.toOption {
221+ | Some(e) => e->Tiptap.Commands.focus
222+ | None => ()
223+ }
224+ }
225+ }
226+ | None => ()
227+ }
228+ }
229+ let documentElement = Obj.magic(WebAPI.Global.document)
230+ let cleanup = PreactDOM.addEventListener(documentElement, "mousedown", handleMouseDown)
231+ Some(() => cleanup())
232+ } else {
233+ None
234+ }
235+ }, [isOpen])
236+
237+ let index = focusedIndex->Signal.get
238+
239 <Editor.FloatingMenu
240 shouldShow={props => setEditor(props.editor)} options={{placement: #"left-start"}}
241 >
242 <div
243+ ref={PreactDOM.Ref.domRef(containerRef)}
244 className={cls(
245 "fixed left-0 box-border flex flex-row items-center",
246 dict{
247@@ -102,35 +259,67 @@ let make = (~doc: string) => {
248 },
249 )}
250 >
251- {!(show->Signal.get)
252- ? <button
253- className="hover:cursor-pointer hover:bg-[var(--color-bg-2)] rounded-sm text-[var(--color-blue)] my-auto"
254- onClick={_evt => show->Signal.set(true)}
255- >
256- <Icons.GripVertical className="w-5" />
257- </button>
258- : <div
259- className="w-screen h-screen md:w-fit md:h-fit fixed md:relative top-0 left-0 pt-2 px-2 md:px-0 md:pt-0 flex flex-col md:rounded-sm bg-[var(--color-paper)]"
260+ <button
261+ type_="button"
262+ className="hover:cursor-pointer hover:bg-[var(--color-bg-2)] rounded-sm text-[var(--color-blue)] my-auto"
263+ onClick={_evt => show->Signal.set(!(show->Signal.get))}
264+ ariaHaspopup={#menu}
265+ ariaExpanded={show->Signal.get}
266+ >
267+ <Icons.GripVertical className="w-5" />
268+ </button>
269+
270+ {show->Signal.get
271+ ? <div
272+ ref={PreactDOM.Ref.domRef(menuRef)}
273+ className="flex flex-col active:outline-0 focus:outline-0"
274 onMouseLeave={_evt => closeMenu()}
275 onMouseEnter={_evt => stopClose()}
276+ role="menu"
277+ tabIndex={0}
278+ ariaActivedescendant={getItemId(index)}
279 >
280- <button className={btn} onClick={waypointClickHandler(doc)}>
281+ <button
282+ id={item0Id}
283+ className={baseBtn ++ (index === 0 ? highlighted : " bg-[var(--color-paper)]")}
284+ onClick={waypointClickHandler(doc)}
285+ onMouseEnter={_evt => focusedIndex->Signal.set(0)}
286+ role="menuitem"
287+ tabIndex={-1}
288+ >
289 <Icons.Waypoint className="p-[2px]" />
290 <span className="ml-2"> {"Add waypoint"->Preact.string} </span>
291 </button>
292
293- <button className={btn} disabled={true}>
294+ <button
295+ id={item1Id}
296+ className={baseBtn ++ (index === 1 ? highlighted : " bg-[var(--color-paper)]")}
297+ disabled={true}
298+ onMouseEnter={_evt => focusedIndex->Signal.set(1)}
299+ role="menuitem"
300+ ariaDisabled={true}
301+ tabIndex={-1}
302+ >
303 <Icons.Image className="p-[2px]" />
304 <span className="ml-2"> {"Upload image"->Preact.string} </span>
305 <span className="italic ml-2 text-xs"> {" (soon!)"->Preact.string} </span>
306 </button>
307
308- <button className={btn} disabled={true}>
309+ <button
310+ id={item2Id}
311+ className={baseBtn ++ (index === 2 ? highlighted : " bg-[var(--color-paper)]")}
312+ disabled={true}
313+ onMouseEnter={_evt => focusedIndex->Signal.set(2)}
314+ role="menuitem"
315+ ariaDisabled={true}
316+ tabIndex={-1}
317+ >
318 <Icons.Video className="p-[2px]" />
319 <span className="ml-2"> {"Link YT Video"->Preact.string} </span>
320 <span className="italic ml-2 text-xs"> {" (soon!)"->Preact.string} </span>
321 </button>
322- </div>}
323+ </div>
324+ : Preact.null}
325 </div>
326 </Editor.FloatingMenu>
327 }
+1,
-0
1@@ -52,6 +52,7 @@ module Portal = {
2 ~keepMounted: bool=?,
3 ~children: Jsx.element=?,
4 ~className: string=?,
5+ ~container: Preact.ref<nullable<Dom.element>>=?,
6 ) => Jsx.element = "Portal"
7 }
8