Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Repository instructions

## Local web preview

- Do not validate this app by opening `index.html` with a `file://` URL. ES modules and the SwiftWasm bundle require an HTTP server, and a file URL will leave the editor stuck loading.
- After every implementation change, build the real browser artifacts and start a preview server for the user to inspect. Do not substitute a mock or stub for the SwiftWasm module.
- Use the installed Swift 6.2.3 toolchain that matches the repository's WebAssembly SDK:

```sh
TOOLCHAINS=org.swift.623202512101a swift package \
--swift-sdk 6.2-SNAPSHOT-2026-01-12-a-wasm32-unknown-wasip1 \
js -c release
npm ci
npm run preview
```

- Keep the preview server running after verification so the user can open it. Report the complete HTTP URL, including the `/DisplayListExplorer/` base path.
- If the default preview port is occupied, reuse the existing server when it serves this worktree; otherwise choose another port and report it explicitly.
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ <h2 id="source-title">DisplayList Description</h2>
</div>
</article>

<div
id="pane-splitter"
class="pane-splitter"
role="separator"
aria-label="Resize source and output panes"
aria-orientation="vertical"
aria-valuemin="20"
aria-valuemax="80"
aria-valuenow="50"
title="Drag to resize panes; double-click to reset"
tabindex="0"
></div>

<article class="pane detail-pane">
<div class="detail-toolbar">
<div class="tabs" role="tablist" aria-label="DisplayList details">
Expand Down
122 changes: 122 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,126 @@ globalThis.displayListEditor = {
},
};

const explorer = document.querySelector(".explorer");
const paneSplitter = document.getElementById("pane-splitter");
const verticalLayout = window.matchMedia("(max-width: 980px)");
const defaultPaneRatio = 0.5;
const minimumPaneRatio = 0.2;
const minimumPaneSize = 240;
let paneRatio = defaultPaneRatio;
let isResizingPanes = false;

function isVerticalLayout() {
return verticalLayout.matches;
}

function paneRatioBounds() {
const vertical = isVerticalLayout();
const explorerSize = vertical ? explorer.clientHeight : explorer.clientWidth;
const splitterSize = vertical ? paneSplitter.offsetHeight : paneSplitter.offsetWidth;
const availableSize = Math.max(1, explorerSize - splitterSize);
const lowerBound = Math.max(
minimumPaneRatio,
Math.min(0.5, minimumPaneSize / availableSize),
);
return { lower: lowerBound, upper: 1 - lowerBound };
}

function setPaneRatio(nextRatio) {
const { lower, upper } = paneRatioBounds();
paneRatio = Math.min(upper, Math.max(lower, nextRatio));
explorer.style.setProperty("--source-pane-share", `${paneRatio}fr`);
explorer.style.setProperty("--detail-pane-share", `${1 - paneRatio}fr`);

const percentage = Math.round(paneRatio * 100);
paneSplitter.setAttribute("aria-valuemin", String(Math.round(lower * 100)));
paneSplitter.setAttribute("aria-valuemax", String(Math.round(upper * 100)));
paneSplitter.setAttribute("aria-valuenow", String(percentage));
paneSplitter.setAttribute("aria-valuetext", `Source pane ${percentage}%`);
}

function setPaneRatioFromPointer(event) {
const vertical = isVerticalLayout();
const explorerRect = explorer.getBoundingClientRect();
const splitterSize = vertical ? paneSplitter.offsetHeight : paneSplitter.offsetWidth;
const availableSize = Math.max(
1,
(vertical ? explorerRect.height : explorerRect.width) - splitterSize,
);
const pointerPosition = vertical
? event.clientY - explorerRect.top
: event.clientX - explorerRect.left;
setPaneRatio((pointerPosition - splitterSize / 2) / availableSize);
}

paneSplitter.addEventListener("pointerdown", (event) => {
if (event.button !== 0) {
return;
}

isResizingPanes = true;
paneSplitter.setPointerCapture(event.pointerId);
paneSplitter.classList.add("is-dragging");
document.body.classList.add("is-resizing-panes");
setPaneRatioFromPointer(event);
event.preventDefault();
});

paneSplitter.addEventListener("pointermove", (event) => {
if (isResizingPanes && paneSplitter.hasPointerCapture(event.pointerId)) {
setPaneRatioFromPointer(event);
}
});

function finishPaneResize(event) {
if (!isResizingPanes) {
return;
}

isResizingPanes = false;
if (paneSplitter.hasPointerCapture(event.pointerId)) {
paneSplitter.releasePointerCapture(event.pointerId);
}
paneSplitter.classList.remove("is-dragging");
document.body.classList.remove("is-resizing-panes");
}

paneSplitter.addEventListener("pointerup", finishPaneResize);
paneSplitter.addEventListener("pointercancel", finishPaneResize);
paneSplitter.addEventListener("lostpointercapture", finishPaneResize);
paneSplitter.addEventListener("dblclick", () => setPaneRatio(defaultPaneRatio));

paneSplitter.addEventListener("keydown", (event) => {
const vertical = isVerticalLayout();
const decreaseKey = vertical ? "ArrowUp" : "ArrowLeft";
const increaseKey = vertical ? "ArrowDown" : "ArrowRight";
const step = event.shiftKey ? 0.1 : 0.02;

if (event.key === decreaseKey) {
setPaneRatio(paneRatio - step);
} else if (event.key === increaseKey) {
setPaneRatio(paneRatio + step);
} else if (event.key === "Home") {
setPaneRatio(paneRatioBounds().lower);
} else if (event.key === "End") {
setPaneRatio(paneRatioBounds().upper);
} else {
return;
}

event.preventDefault();
});

function updateSplitterOrientation() {
paneSplitter.setAttribute(
"aria-orientation",
isVerticalLayout() ? "horizontal" : "vertical",
);
setPaneRatio(paneRatio);
}

verticalLayout.addEventListener("change", updateSplitterOrientation);
window.addEventListener("resize", () => setPaneRatio(paneRatio));
updateSplitterOrientation();

await init();
75 changes: 69 additions & 6 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,16 @@ h1 {
}

.explorer {
--source-pane-share: 1fr;
--detail-pane-share: 1fr;

height: max(640px, calc(100svh - 224px));
max-height: 920px;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-columns:
minmax(0, var(--source-pane-share))
9px
minmax(0, var(--detail-pane-share));
border: 1px solid var(--line-strong);
border-radius: 14px;
overflow: hidden;
Expand All @@ -231,7 +237,47 @@ h1 {
}

.source-pane {
border-right: 1px solid var(--line-strong);
border-right: 0;
}

.pane-splitter {
min-width: 0;
position: relative;
z-index: 3;
border: 0;
padding: 0;
cursor: col-resize;
touch-action: none;
background: #f4f6f8;
}

.pane-splitter::before {
width: 3px;
height: 34px;
position: absolute;
top: 50%;
left: 50%;
border-radius: 999px;
background: var(--line-strong);
content: "";
transform: translate(-50%, -50%);
transition: background 120ms ease, box-shadow 120ms ease;
}

.pane-splitter:hover::before,
.pane-splitter:focus-visible::before,
.pane-splitter.is-dragging::before {
background: var(--blue);
box-shadow: 0 0 0 3px rgb(36 107 253 / 14%);
}

.pane-splitter:focus-visible {
outline-offset: -2px;
}

body.is-resizing-panes {
cursor: col-resize;
user-select: none;
}

.pane-header,
Expand Down Expand Up @@ -728,19 +774,36 @@ footer {
}

.explorer {
height: auto;
height: max(1180px, calc(100svh - 120px));
max-height: none;
grid-template-columns: 1fr;
grid-template-rows:
minmax(0, var(--source-pane-share))
9px
minmax(0, var(--detail-pane-share));
}

.source-pane {
height: 560px;
height: auto;
border-right: 0;
border-bottom: 1px solid var(--line-strong);
border-bottom: 0;
}

.detail-pane {
min-height: 620px;
min-height: 0;
}

.pane-splitter {
cursor: row-resize;
}

.pane-splitter::before {
width: 34px;
height: 3px;
}

body.is-resizing-panes {
cursor: row-resize;
}
}

Expand Down
Loading