mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
Merge remote-tracking branch 'origin/main' into release/v0.9.0
This commit is contained in:
commit
7b00e1de87
3 changed files with 79 additions and 2 deletions
|
|
@ -259,6 +259,36 @@ const pageHtml = renderToStaticMarkup(
|
|||
);
|
||||
}
|
||||
|
||||
// Hamburger menu toggle. Active only at narrow viewports (CSS
|
||||
// hides the toggle button at ≥1080px). Click toggles `.is-open`
|
||||
// on the header; outside-click, Escape, and clicking any link
|
||||
// inside the menu close it again. Keeps `aria-expanded` in sync.
|
||||
// This mirrors the handler in `header-enhancer.astro` — the
|
||||
// homepage runs its own inline enhancer instead of importing
|
||||
// that component, so the toggle has to be wired up here too.
|
||||
const navToggle = document.querySelector('[data-nav-toggle]');
|
||||
const primaryNav = document.querySelector('[data-nav-primary]');
|
||||
const navEl = navToggle ? navToggle.closest('header.nav') : null;
|
||||
if (navToggle && primaryNav && navEl) {
|
||||
const setNavOpen = (open) => {
|
||||
navEl.classList.toggle('is-open', open);
|
||||
navToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
};
|
||||
navToggle.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
setNavOpen(!navEl.classList.contains('is-open'));
|
||||
});
|
||||
primaryNav.querySelectorAll('a').forEach((link) => {
|
||||
link.addEventListener('click', () => setNavOpen(false));
|
||||
});
|
||||
document.addEventListener('click', (ev) => {
|
||||
if (!navEl.contains(ev.target)) setNavOpen(false);
|
||||
});
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (ev.key === 'Escape') setNavOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
const stars = document.querySelector('[data-github-stars]');
|
||||
if (stars) {
|
||||
fetch('https://api.github.com/repos/nexu-io/open-design', {
|
||||
|
|
|
|||
|
|
@ -172,6 +172,18 @@ export function PreviewDrawOverlay({
|
|||
) ?? null;
|
||||
}
|
||||
|
||||
// The snapshot bridge only lives in the srcDoc transport iframe. For URL-load
|
||||
// previews (e.g. decks) that iframe is mounted but hidden (data-od-active is on
|
||||
// the bridgeless URL iframe), so snapshotting the *active* frame times out and
|
||||
// capture fails. Prefer the srcDoc-render-mode frame; capture mode keeps it on
|
||||
// full content, so it carries the bridge.
|
||||
function snapshotHostIframe(): HTMLIFrameElement | null {
|
||||
return (
|
||||
wrapRef.current?.querySelector<HTMLIFrameElement>('iframe[data-od-render-mode="srcdoc"]') ??
|
||||
activePreviewIframe()
|
||||
);
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
if (!active || sending) return;
|
||||
(e.target as Element).setPointerCapture?.(e.pointerId);
|
||||
|
|
@ -335,9 +347,16 @@ export function PreviewDrawOverlay({
|
|||
}
|
||||
|
||||
async function requestSnapshot(): Promise<{ dataUrl: string; w: number; h: number } | null> {
|
||||
const iframe = activePreviewIframe();
|
||||
const iframe = snapshotHostIframe();
|
||||
if (!iframe) return null;
|
||||
return requestPreviewSnapshot(iframe);
|
||||
// Capture mode may still be swapping the srcDoc frame to full content when
|
||||
// the user submits, so retry with growing timeouts before giving up.
|
||||
const timeouts = [1500, 3000, 6000];
|
||||
for (const timeout of timeouts) {
|
||||
const snapshot = await requestPreviewSnapshot(iframe, timeout);
|
||||
if (snapshot) return snapshot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function drawCaptureTarget(
|
||||
|
|
|
|||
|
|
@ -4,9 +4,19 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PreviewDrawOverlay } from '../../src/components/PreviewDrawOverlay';
|
||||
import { requestPreviewSnapshot } from '../../src/runtime/exports';
|
||||
|
||||
vi.mock('../../src/runtime/exports', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../../src/runtime/exports')>();
|
||||
return {
|
||||
...actual,
|
||||
requestPreviewSnapshot: vi.fn(async () => ({ dataUrl: 'data:image/png;base64,AAAA', w: 10, h: 10 })),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.mocked(requestPreviewSnapshot).mockClear();
|
||||
});
|
||||
|
||||
describe('PreviewDrawOverlay', () => {
|
||||
|
|
@ -160,4 +170,22 @@ describe('PreviewDrawOverlay', () => {
|
|||
|
||||
expect(onActiveChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('snapshots the srcDoc bridge iframe, not the visible URL-load frame', async () => {
|
||||
const snapshot = vi.mocked(requestPreviewSnapshot);
|
||||
const { getByRole } = render(
|
||||
<PreviewDrawOverlay active captureViewport>
|
||||
{/* URL-load frame is the visible/active one (e.g. a deck) but has no bridge */}
|
||||
<iframe title="url" data-od-active="true" />
|
||||
{/* srcDoc frame is mounted but hidden; it hosts the snapshot bridge */}
|
||||
<iframe title="srcdoc" data-od-render-mode="srcdoc" data-od-active="false" />
|
||||
</PreviewDrawOverlay>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Send' }));
|
||||
|
||||
await waitFor(() => expect(snapshot).toHaveBeenCalled());
|
||||
const usedIframe = snapshot.mock.calls[0]?.[0] as HTMLIFrameElement;
|
||||
expect(usedIframe.getAttribute('data-od-render-mode')).toBe('srcdoc');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue