feat(*): one task editor, vim navigation and undo

This commit is contained in:
hh committed 2026-08-15 03:13:50 +02:00
1 parent 0873cbb366
commit 83ca32d98e
37 files changed
+4043 -505

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
<script lang="ts">
import { Portal } from "bits-ui";
import type { Snippet } from "svelte";
import { onScreen, place } from "../lib/place";
import { cn } from "../lib/utils";
import { useView } from "./context";
interface Props {
/** CSS selector for whatever this should sit under. */
anchor: string;
/** Off when the panel's contents put the caret somewhere themselves. */
autofocus?: boolean;
children: Snippet;
class?: string;
onclose: () => void;
onkeydown?: (event: KeyboardEvent) => void;
}
let {
anchor,
autofocus = true,
children,
class: className,
onclose,
onkeydown,
}: Props = $props();
const view = useView();
let panel = $state<HTMLElement | null>(null);
let at = $state({ top: 0, left: 0 });
let placed = $state(false);
/**
* Placed by hand rather than by a floating layer: these panels are summoned
* by a key press, and a floating layer wants a trigger element to hang off.
*/
function measure(): void {
const node = panel;
if (!node) return;
const boxes = [...document.querySelectorAll(anchor)].map((element) =>
element.getBoundingClientRect(),
);
at = place(onScreen(boxes), node.getBoundingClientRect(), {
width: window.innerWidth,
height: window.innerHeight,
});
placed = true;
}
function mount(node: HTMLElement) {
panel = node;
measure();
if (autofocus) {
(node.querySelector<HTMLElement>("input") ?? node).focus({
preventScroll: true,
});
}
const outside = (event: PointerEvent): void => {
if (!node.contains(event.target as Node)) onclose();
};
const reflow = (): void => measure();
document.addEventListener("pointerdown", outside, true);
window.addEventListener("resize", reflow);
return {
destroy() {
document.removeEventListener("pointerdown", outside, true);
window.removeEventListener("resize", reflow);
panel = null;
},
};
}
function keys(event: KeyboardEvent): void {
event.stopPropagation();
if (event.key === "Escape" && !event.defaultPrevented) {
event.preventDefault();
onclose();
return;
}
onkeydown?.(event);
}
/** Re-measures once the body has grown or shrunk under the cursor. */
export function reposition(): void {
measure();
}
</script>
<Portal to={view.portal()}>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class={cn(
"fixed z-50 overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-[0_16px_40px_-12px_rgb(0_0_0/45%)] outline-none",
className,
)}
onkeydown={keys}
role="dialog"
style="top: {at.top}px; left: {at.left}px; opacity: {placed ? 1 : 0}"
tabindex="-1"
use:mount
>
{@render children()}
</div>
</Portal>