Fix comrpessing bug on null buffers, make pinned floaters resizable and optimize a few things here and there

This commit is contained in:
Clément Pons 2025-10-06 17:42:16 +02:00
parent b19d2d1b41
commit 26aa0847d9
14 changed files with 133 additions and 91 deletions

View File

@ -2,7 +2,9 @@ import { Database } from "bun:sqlite";
import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite"; import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite";
import * as schema from '../db/schema'; import * as schema from '../db/schema';
let instance: BunSQLiteDatabase<typeof schema>; let instance: BunSQLiteDatabase<typeof schema> & {
$client: Database;
};
export default function useDatabase() export default function useDatabase()
{ {
if(!instance) if(!instance)
@ -13,6 +15,7 @@ export default function useDatabase()
instance.run("PRAGMA journal_mode = WAL;"); instance.run("PRAGMA journal_mode = WAL;");
instance.run("PRAGMA foreign_keys = true;"); instance.run("PRAGMA foreign_keys = true;");
instance.run("PRAGMA optimize=0x10002;");
} }
return instance; return instance;

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -35,7 +35,7 @@ export default defineEventHandler(async (e) => {
return data.content; return data.content;
} }
return; return null;
} }
catch(_e) catch(_e)
{ {

View File

@ -16,5 +16,10 @@ export default defineEventHandler(async (event) => {
//@ts-expect-error //@ts-expect-error
_end.call(event.node.res, await Bun.zstdCompress(buffer), ...args); _end.call(event.node.res, await Bun.zstdCompress(buffer), ...args);
} }
else
{
//@ts-expect-error
_end.call(event.node.res, body, ...args);
}
}; };
}); });

View File

@ -105,8 +105,8 @@ function reshapeContent(content: string, type: FileType): string | null
return content; return content;
case "canvas": case "canvas":
const data = JSON.parse(content) as CanvasContent; const data = JSON.parse(content) as CanvasContent;
data.edges?.forEach(e => { console.log(e.color); e.color = typeof e.color === 'string' ? getColor(e.color) : undefined; console.log(e.color); }); data.edges?.forEach(e => e.color = typeof e.color === 'string' ? getColor(e.color) : undefined);
data.nodes?.forEach(e => { console.log(e.color); e.color = typeof e.color === 'string' ? getColor(e.color) : undefined; console.log(e.color); }); data.nodes?.forEach(e => e.color = typeof e.color === 'string' ? getColor(e.color) : undefined);
return JSON.stringify(data); return JSON.stringify(data);
default: default:
case 'folder': case 'folder':

View File

@ -2,7 +2,7 @@ export function hasPermissions(userPermissions: string[], neededPermissions: str
{ {
for(let i = 0; i < neededPermissions.length; i++) for(let i = 0; i < neededPermissions.length; i++)
{ {
const list = neededPermissions[i].split(' '); const list = neededPermissions[i]!.split(' ');
if(list.every(e => userPermissions.includes(e))) if(list.every(e => userPermissions.includes(e)))
{ {

View File

@ -173,7 +173,6 @@ export class Node extends EventTarget
{ border: `border-light-40 dark:border-dark-40`, bg: `bg-light-40 dark:bg-dark-40` } { border: `border-light-40 dark:border-dark-40`, bg: `bg-light-40 dark:bg-dark-40` }
} }
} }
export class NodeEditable extends Node export class NodeEditable extends Node
{ {
edges: Set<EdgeEditable> = new Set(); edges: Set<EdgeEditable> = new Set();
@ -440,6 +439,7 @@ export class Canvas
mount() mount()
{ {
const dragMove = (e: MouseEvent) => { const dragMove = (e: MouseEvent) => {
e.preventDefault();
this.dragMove(e); this.dragMove(e);
}; };
const dragEnd = (e: MouseEvent) => { const dragEnd = (e: MouseEvent) => {
@ -515,6 +515,7 @@ export class Canvas
this.container.removeEventListener('touchmove', touchmove); this.container.removeEventListener('touchmove', touchmove);
}; };
const touchmove = (e: TouchEvent) => { const touchmove = (e: TouchEvent) => {
e.preventDefault();
const pos = center(e.touches); const pos = center(e.touches);
this._x = this.visualX = this._x - (this.lastX - pos.x) / this._zoom; this._x = this.visualX = this._x - (this.lastX - pos.x) / this._zoom;
this._y = this.visualY = this._y - (this.lastY - pos.y) / this._zoom; this._y = this.visualY = this._y - (this.lastY - pos.y) / this._zoom;

View File

@ -502,32 +502,61 @@ export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content:
}) })
return container as HTMLDivElement & { refresh: () => void }; return container as HTMLDivElement & { refresh: () => void };
} }
export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { class?: Class, position?: Placement, pinned?: boolean, cover?: 'width' | 'height' | 'all' | 'none' }) export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { class?: Class, position?: Placement, pinned?: boolean, cover?: 'width' | 'height' | 'all' | 'none', events?: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (this: HTMLElement) => boolean, onhide?: (this: HTMLElement) => boolean }, title?: string })
{ {
let viewport = document.getElementById('mainContainer') ?? undefined; let viewport = document.getElementById('mainContainer') ?? undefined;
let diffX, diffY, targetWidth, targetHeight;
const dragstart = (e: MouseEvent) => { const dragstart = (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
window.addEventListener('mousemove', dragmove); window.addEventListener('mousemove', dragmove);
window.addEventListener('mouseup', dragend); window.addEventListener('mouseup', dragend);
const box = floating.content.getBoundingClientRect();
diffX = e.clientX - box.x;
diffY = e.clientY - box.y;
}; };
const resizestart = (e: MouseEvent) => {
e.preventDefault();
e.stopImmediatePropagation();
window.addEventListener('mousemove', resizemove);
window.addEventListener('mouseup', resizeend);
};
const dragmove = (e: MouseEvent) => { const dragmove = (e: MouseEvent) => {
const box = floating.content.getBoundingClientRect(); const box = floating.content.getBoundingClientRect();
const viewbox = viewport?.getBoundingClientRect(); const viewbox = viewport?.getBoundingClientRect() ?? { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight, left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight };
box.x = clamp(box.x + e.movementX, viewbox?.left ?? 0, (viewbox?.right ?? Infinity) - box.width); box.x = clamp(e.clientX - diffX!, viewbox?.left ?? 0, viewbox.right - box.width);
box.y = clamp(box.y + e.movementY, viewbox?.top ?? 0, (viewbox?.bottom ?? Infinity) - box.height); box.y = clamp(e.clientY - diffY!, viewbox?.top ?? 0, viewbox.bottom - box.height);
Object.assign(floating.content.style, { Object.assign(floating.content.style, {
left: `${box.x}px`, left: `${box.x}px`,
top: `${box.y}px`, top: `${box.y}px`,
}) });
}; };
const resizemove = (e: MouseEvent) => {
const box = floating.content.getBoundingClientRect();
const viewbox = viewport?.getBoundingClientRect() ?? { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight, left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight };
box.width = clamp(e.clientX - box.x, 200, Math.min(750, viewbox.right - box.x));
box.height = clamp(e.clientY - box.y, 150, Math.min(750, viewbox.bottom - box.y));
Object.assign(floating.content.style, {
width: `${box.width}px`,
height: `${box.height}px`,
});
};
const dragend = (e: MouseEvent) => { const dragend = (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
window.removeEventListener('mousemove', dragmove); window.removeEventListener('mousemove', dragmove);
window.removeEventListener('mouseup', dragend); window.removeEventListener('mouseup', dragend);
}; };
const resizeend = (e: MouseEvent) => {
e.preventDefault();
window.removeEventListener('mousemove', resizemove);
window.removeEventListener('mouseup', resizeend);
};
const floating = popper(container, { const floating = popper(container, {
arrow: true, arrow: true,
@ -535,18 +564,37 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
offset: 12, offset: 12,
cover: settings?.cover, cover: settings?.cover,
placement: settings?.position, placement: settings?.position,
class: 'bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 group-data-[pinned]:bg-light-15 dark:group-data-[pinned]:bg-dark-15 group-data-[pinned]:border-light-50 dark:group-data-[pinned]:border-dark-50 text-light-100 dark:text-dark-100 z-[45]', class: 'bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 group-data-[pinned]:bg-light-15 dark:group-data-[pinned]:bg-dark-15 group-data-[pinned]:border-light-50 dark:group-data-[pinned]:border-dark-50 text-light-100 dark:text-dark-100 z-[45] relative group-data-[pinned]:h-full',
content: () => [ settings?.pinned !== undefined ? dom('div', { class: 'hidden group-data-[pinned]:flex flex-row gap-4 justify-end border-b border-light-35 dark:border-dark-35 cursor-move', listeners: { mousedown: dragstart } }, [ tooltip(icon('radix-icons:cross-1', { width: 12, height: 12, listeners: { click: (e) => { e.preventDefault(); floating.hide(); } }, class: 'p-1 cursor-pointer' }), 'Fermer', 'right') ]) : undefined, div('min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto box-content', typeof content === 'function' ? content() : content) ], content: () => [ settings?.pinned !== undefined ? div('hidden group-data-[pinned]:flex flex-row justify-end border-b border-light-35 dark:border-dark-35', [ dom('span', { class: 'w-full cursor-move text-xs', listeners: { mousedown: dragstart }, text: settings?.title }), tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { e.stopImmediatePropagation(); floating.hide(); } } }, [icon('radix-icons:cross-1', { width: 12, height: 12, class: 'p-1' })]), 'Fermer', 'right') ]) : undefined, div('h-full group-data-[pinned]:h-[calc(100%-21px)] w-full min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] group-data-[pinned]:min-h-[initial] group-data-[pinned]:min-w-[initial] group-data-[pinned]:max-h-[initial] group-data-[pinned]:max-w-[initial] overflow-auto box-content', typeof content === 'function' ? content() : content), dom('span', { class: 'hidden group-data-[pinned]:flex absolute bottom-0 right-0 cursor-nw-resize z-50', listeners: { mousedown: resizestart } }, [ icon('ph:notches', { width: 12, height: 12 }) ]) ],
viewport viewport
}); });
if(settings?.pinned === false) if(settings?.pinned === false)
{
floating.content.addEventListener('click', () => { floating.content.addEventListener('click', () => {
if(floating.content.hasAttribute('data-pinned'))
return;
const box = floating.content.children.item(0)!.getBoundingClientRect();
Object.assign(floating.content.style, {
width: `${box.width + 21}px`,
height: `${box.height + 21}px`,
});
floating.stop(); floating.stop();
floating.content.addEventListener('mousedown', function() { floating.content.addEventListener('mousedown', function() {
if(!floating.content.hasAttribute('data-pinned'))
return;
this.parentElement?.appendChild(this); this.parentElement?.appendChild(this);
}, { passive: true, capture: true }) }, { passive: true });
}, { once: true }); });
}
else
{
floating.stop();
floating.show();
}
return container; return container;
} }

View File

@ -124,6 +124,9 @@ export class Content
if(Content._ready) if(Content._ready)
return Promise.resolve(true); return Promise.resolve(true);
if(Content.initPromise)
return Content.initPromise;
Content.initPromise = new Promise(async (res) => { Content.initPromise = new Promise(async (res) => {
try try
{ {

View File

@ -116,9 +116,6 @@ export interface IconProperties
rotate?: number|string; rotate?: number|string;
style?: Record<string, string | undefined> | string; style?: Record<string, string | undefined> | string;
class?: Class; class?: Class;
listeners?: {
[K in keyof HTMLElementEventMap]?: Listener<K>
};
} }
const iconCache: Map<string, HTMLElement> = new Map(); const iconCache: Map<string, HTMLElement> = new Map();
export function icon(name: string, properties?: IconProperties): HTMLElement export function icon(name: string, properties?: IconProperties): HTMLElement
@ -160,17 +157,6 @@ export function icon(name: string, properties?: IconProperties): HTMLElement
else else
for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) element.attributeStyleMap.set(k, v); for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) element.attributeStyleMap.set(k, v);
} }
if(properties?.listeners)
{
for(let [k, v] of Object.entries(properties.listeners))
{
const key = k as keyof HTMLElementEventMap, value = v as Listener<typeof key>;
if(typeof value === 'function')
element.addEventListener(key, value.bind(element));
else if(value)
element.addEventListener(key, value.listener.bind(element), value.options);
}
}
return element; return element;
} }

View File

@ -21,9 +21,12 @@ export interface PopperProperties extends FloatingProperties
{ {
content?: NodeChildren | (() => NodeChildren); content?: NodeChildren | (() => NodeChildren);
delay?: number; delay?: number;
events?: {
onShow?: () => boolean | void; show: Array<keyof HTMLElementEventMap>;
onHide?: () => boolean | void; hide: Array<keyof HTMLElementEventMap>;
onshow?: () => boolean;
onhide?: () => boolean;
};
} }
export interface ModalProperties export interface ModalProperties
@ -36,13 +39,13 @@ export interface ModalProperties
let teleport: HTMLDivElement; let teleport: HTMLDivElement;
export function init() export function init()
{ {
teleport = dom('div', { attributes: { id: 'popper-container' }, class: 'absolute top-0 left-0' }); teleport = dom('div', { attributes: { id: 'popper-container' }, class: 'absolute top-0 left-0 z-40' });
document.body.appendChild(teleport); document.body.appendChild(teleport);
} }
export function popper(container: HTMLElement, properties?: PopperProperties) export function popper(container: HTMLElement, properties?: PopperProperties)
{ {
let shown = false, manualStop = false, timeout: Timer; let state: 'shown' | 'showing' | 'hidden' | 'hiding' | 'pinned' = 'hidden', manualStop = false, timeout: Timer;
const arrow = svg('svg', { class: ' group-data-[pinned]:hidden absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]); const arrow = svg('svg', { class: ' group-data-[pinned]:hidden absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]);
const content = dom('div', { class: properties?.class, style: properties?.style }); const content = dom('div', { class: properties?.class, style: properties?.style });
const floater = dom('div', { class: 'fixed hidden group', attributes: { 'data-state': 'closed' } }, [ content, properties?.arrow ? arrow : undefined ]); const floater = dom('div', { class: 'fixed hidden group', attributes: { 'data-state': 'closed' } }, [ content, properties?.arrow ? arrow : undefined ]);
@ -110,7 +113,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
let _stop: () => void | undefined, empty = true; let _stop: () => void | undefined, empty = true;
function show() function show()
{ {
if(shown || !properties?.onShow || properties?.onShow() !== false) if(state !== 'shown' && state !== 'showing' && state !== 'pinned' && (!properties?.events?.onshow || properties?.events?.onshow() !== false))
{ {
if(typeof properties?.content === 'function') if(typeof properties?.content === 'function')
properties.content = properties.content(); properties.content = properties.content();
@ -122,9 +125,10 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
} }
clearTimeout(timeout); clearTimeout(timeout);
state = 'showing';
timeout = setTimeout(() => { timeout = setTimeout(() => {
if(!shown) if(state !== 'shown')
{ {
teleport!.appendChild(floater); teleport!.appendChild(floater);
@ -132,6 +136,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
floater.classList.toggle('hidden', false); floater.classList.toggle('hidden', false);
update(); update();
_stop && _stop();
_stop = FloatingUI.autoUpdate(container, floater, update, { _stop = FloatingUI.autoUpdate(container, floater, update, {
animationFrame: true, animationFrame: true,
layoutShift: false, layoutShift: false,
@ -139,35 +144,39 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
ancestorScroll: false, ancestorScroll: false,
ancestorResize: false, ancestorResize: false,
}); });
console.log("Starting", floater);
} }
shown = true; state = 'shown';
}, properties?.delay ?? 0); }, properties?.delay ?? 0);
} }
} }
function hide() function hide()
{ {
if(!manualStop && (!properties?.onHide || properties?.onHide() !== false)) if(state !== 'hiding' && state !== 'pinned' && (!properties?.events?.onhide || properties?.events?.onhide() !== false))
{ {
clearTimeout(timeout); clearTimeout(timeout);
state = 'hiding';
timeout = setTimeout(() => { timeout = setTimeout(() => {
floater.remove(); floater.remove();
_stop && _stop(); _stop && _stop();
console.log('Stoping', floater);
floater.setAttribute('data-state', 'closed'); floater.setAttribute('data-state', 'closed');
floater.classList.toggle('hidden', true); floater.classList.toggle('hidden', true);
shown = false; state = 'hidden';
}, shown ? properties?.delay ?? 0 : 0); }, properties?.delay ?? 0);
} }
} }
function start() function start()
{ {
manualStop = false; state = 'hidden';
floater.toggleAttribute('data-pinned', false); floater.toggleAttribute('data-pinned', false);
update(); update();
_stop && _stop();
_stop = FloatingUI.autoUpdate(container, floater, update, { _stop = FloatingUI.autoUpdate(container, floater, update, {
animationFrame: true, animationFrame: true,
layoutShift: false, layoutShift: false,
@ -175,33 +184,28 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
ancestorScroll: false, ancestorScroll: false,
ancestorResize: false, ancestorResize: false,
}); });
console.log('Starting', floater);
} }
function stop() function stop()
{ {
manualStop = true; state = 'pinned';
floater.toggleAttribute('data-pinned', true); floater.toggleAttribute('data-pinned', true);
_stop && _stop(); _stop && _stop();
console.log('Stoping', floater);
clearTimeout(timeout); clearTimeout(timeout);
} }
function link(element: HTMLElement) { function link(element: HTMLElement) {
Object.entries({ (properties?.events?.show ?? ['mouseenter', 'mousemove', 'focus']).forEach((e: keyof HTMLElementEventMap) => element.addEventListener(e, show));
'mouseenter': show, (properties?.events?.hide ?? ['mouseleave', 'blur']).forEach((e: keyof HTMLElementEventMap) => element.addEventListener(e, hide));
'mousemove': show,
'mouseleave': hide,
'focus': show,
'blur': hide,
} as Record<keyof HTMLElementEventMap, () => void>).forEach(([event, listener]) => {
element.addEventListener(event, listener);
});
} }
link(container); link(container);
link(floater); link(floater);
return { container, content: floater, stop, start, show: () => { return { container, content: floater, stop, start, show: () => {
if(!shown) if(state !== 'shown')
{ {
teleport!.appendChild(floater); teleport!.appendChild(floater);
@ -210,10 +214,11 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
update(); update();
} }
shown = true; state = 'shown';
}, hide: () => { }, hide: () => {
floater.remove(); floater.remove();
_stop && _stop(); _stop && _stop();
console.log('Stoping', floater);
floater.setAttribute('data-state', 'closed'); floater.setAttribute('data-state', 'closed');
floater.classList.toggle('hidden', true); floater.classList.toggle('hidden', true);
@ -221,7 +226,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
manualStop = false; manualStop = false;
floater.toggleAttribute('data-pinned', false); floater.toggleAttribute('data-pinned', false);
shown = false; state = 'hidden';
} }; } };
} }
export function followermenu(target: FloatingUI.ReferenceElement, content: NodeChildren, properties?: FollowerProperties) export function followermenu(target: FloatingUI.ReferenceElement, content: NodeChildren, properties?: FollowerProperties)

View File

@ -41,7 +41,7 @@ export const a: Prose = {
{ {
const canvas = new Canvas((_content as LocalContent<'canvas'>).content); const canvas = new Canvas((_content as LocalContent<'canvas'>).content);
queueMicrotask(() => canvas.mount()); queueMicrotask(() => canvas.mount());
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]); return dom('div', { class: 'w-[600px] h-[600px] group-data-[pinned]:h-full group-data-[pinned]:w-full h-[600px] relative w-[600px] relative' }, [canvas.container]);
} }
return div(''); return div('');
})).current], { position: 'bottom-start', pinned: false }) : element; })).current], { position: 'bottom-start', pinned: false }) : element;
@ -55,23 +55,13 @@ export const preview: Prose = {
const overview = Content.getFromPath(pathname === '' && hash.length > 0 ? unifySlug(router.currentRoute.value.params.path ?? '') : pathname); const overview = Content.getFromPath(pathname === '' && hash.length > 0 ? unifySlug(router.currentRoute.value.params.path ?? '') : pathname);
const el = dom('span', { class: ['cursor-pointer text-accent-blue inline-flex items-center', properties?.class] }, [ const element = dom('span', { class: ['cursor-pointer text-accent-blue inline-flex items-center', properties?.class] }, [
...(children ?? []), ...(children ?? []),
overview && overview.type !== 'markdown' ? icon(iconByType[overview.type], { class: 'w-4 h-4 inline-block', inline: true }) : undefined overview && overview.type !== 'markdown' ? icon(iconByType[overview.type], { class: 'w-4 h-4 inline-block', inline: true }) : undefined
]); ]);
const magicKeys = useMagicKeys(); const magicKeys = useMagicKeys();
return overview ? popper(el, { return !!overview ? floater(element, () => [async('large', Content.getContent(overview.id).then((_content) => {
arrow: true,
delay: 150,
offset: 12,
cover: "height",
placement: 'bottom-start',
class: ['data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 data-[state=open]:transition-transform text-light-100 dark:text-dark-100 w-full z-[45]',
{ 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px]': !properties?.size || properties.size === 'large', 'max-w-[400px] max-h-[250px]': properties.size === 'small' }
],
content: () => {
return [async('large', Content.getContent(overview.id).then((_content) => {
if(_content?.type === 'markdown') if(_content?.type === 'markdown')
{ {
return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' }); return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' });
@ -80,16 +70,17 @@ export const preview: Prose = {
{ {
const canvas = new Canvas((_content as LocalContent<'canvas'>).content); const canvas = new Canvas((_content as LocalContent<'canvas'>).content);
queueMicrotask(() => canvas.mount()); queueMicrotask(() => canvas.mount());
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]); return dom('div', { class: 'w-[600px] h-[600px] group-data-[pinned]:h-full group-data-[pinned]:w-full h-[600px] relative w-[600px] relative' }, [canvas.container]);
} }
return div(''); return div('');
})).current]; })).current], { position: 'bottom-start', pinned: false,
}, events: {
onShow() { show: ['mouseenter', 'mousemove'],
if(!magicKeys.current.has('control') || magicKeys.current.has('meta')) hide: ['mouseleave'],
return false; onshow() {
}, return !magicKeys.current.has('control') || !magicKeys.current.has('meta');
}).container : el; }
}, }) : element;
} }
} }
export const callout: Prose = { export const callout: Prose = {