Macotron

macotron.panel

Open a floating window that shows your HTML or a web page, and pass messages between the page and your plugin.

// Ask for a name, then greet it.
const html = `
  <h2>Your name</h2>
  <input id="input" placeholder="Ada">
  <button class="primary" onclick="send()">Greet</button>
  <script>
    function send() {
      webkit.messageHandlers.macotron.postMessage({ name: input.value });
    }
  </script>`;

const id = macotron.panel.open({ title: "Greet", width: 320, height: 160, html, closeOnBlur: true });
macotron.panel.onMessage(id, ({ name }) => {
  macotron.notify.toast("Hello", name);
  macotron.panel.close(id);
});

Opening and closing

open(opts: PanelOptions): string

Opens a panel and returns its id. The panel floats above other windows, shows on every Space, and opens centered with keyboard focus. Throws a TypeError when opts is missing or url is not an http or https URL.

OptionTypeWhat it does
titlestringTitle bar text. Default "Macotron".
width, heightnumberContent size in points. Default 420 x 520.
htmlstringBody markup. Macotron wraps it in a page with the system font, padding, form styles and light and dark colors. See The host page.
rawHtmlstringA complete HTML document, used as is with no host styles. It takes priority over html.
urlstringAn http or https page to load instead of html or rawHtml. It loads first-party, so its cookies and localStorage persist between opens, unlike an iframe.
glassboolean | stringtrue or "regular" for Liquid Glass, "clear" for the clearer Liquid Glass, "translucent" for a HUD blur. The host page background becomes transparent so the material shows through.
framelessbooleanNo title bar, with rounded corners. Escape closes it.
fullscreenbooleanCovers the whole screen under the pointer, with no title bar. Escape closes it.
escapeClosesbooleanDefault true. Pass false so Escape does not close a frameless or fullscreen panel, and close it from your own button instead.
closeOnBlurbooleanCloses the panel when it loses keyboard focus, for example when the user clicks another window.
idstringUse this id instead of a random one. An open panel with the same id closes first, so calling open again replaces it.
qrstringText to encode as a QR code. Macotron appends the code as an <img> to the end of html or rawHtml.

In every panel, Command-Plus and Command-Minus zoom the page and Command-0 resets the zoom.

close(id: string): void

Closes the panel. Does nothing for an unknown id.

focus(id: string): boolean

Brings an open panel to the front and gives it keyboard focus without reloading the page. Returns false when no panel has that id. Use it to show a panel again instead of opening a second one.

Every panel closes when plugins reload.

Messages

postMessage(id: string, data: any): void

Sends data to the page as JSON. Messages sent before the page finishes loading wait and arrive in order, so you can call this right after open. Send any value JSON can hold: an object, an array, a string, a number, a boolean or null. Other values, such as a function, arrive as null.

onMessage(id: string, callback: (data: any) => void): void

Runs callback for each message the page sends. You can add more than one handler. Handlers are dropped when the panel closes, so add them again after each open.

If you pass only a callback, onMessage(callback), it receives messages from every panel the plugin opens and stays until plugins reload.

In the page

The page talks to the plugin with three functions that every panel provides:

// Page side: render whatever the plugin sends.
window.__macotronReceive = (d) => {
  if (d && d.html) document.getElementById("list").innerHTML = d.html;
};

The host page

With html, the body is a flex column with 16px padding and a 12px gap, in the system font at 13px. Inputs, text areas, selects and buttons are styled to match macOS in light and dark mode. When the panel opens, or gets focus again, the first element with autofocus, then #input, then the first text area or input, gets the focus.

ClassWhat it does
button.primaryA button in the accent color.
button.secondaryA quieter button.
button.blockA full-width, left-aligned button, for lists of choices.
.toolbarA row of inputs, selects and buttons at one height. Inputs stretch to fill.
.grow, .scrollFill the leftover height, and scroll the overflow.
.muted, .ok, .badSecondary, green and red text.
.monoThe monospaced system font. pre and code use it too.
input.inlineAn input at its natural width instead of full width.

The page also defines CSS variables for the system colors: --macotron-accent, --macotron-accent-text, --macotron-label, --macotron-secondary-label, --macotron-fill, --macotron-control, --macotron-control-text, --macotron-control-border, --macotron-field, --macotron-field-text, --macotron-selected, --macotron-selected-text and --macotron-link.

Events

panel:closed

Fires when a panel goes away for any reason: the close button, Escape, closeOnBlur, close(), or open with the same id. The payload is { id }.

macotron.on("panel:closed", ({ id }) => {
  if (id === "chat") localStorage.removeItem("chat.draft");
});

See also