Skip to content

Declarative templating for Adobe ScriptUI/ExtendScript

fartinmartin/extendscript-ui

Repository files navigation

extendscript-ui

Declarative templating for ScriptUI/ExtendScript

example windows

Overview

Have you ever wanted to compose ScriptUI with JSX, like so:

<dialog text="Neat!" properties={{ closeButton: true }}>
  <button text="Click me!" size={[100, 200]} onClick={() => alert("Doink!")} />
</dialog>

Well, now you can! Plus, TypeScript will guide you through each prop with auto completions!

You can even create functional components:

const MyHeader = ({ text }: { text: string }) => (
  <group orientation="row" alignChildren="fill">
    <static-text text={text}></static-text>
  </group>
);

const MyUI = (
  <dialog text="Could it be?!" properties={{ resizeable: true }}>
    <MyHeader text="Neat!" />
  </dialog>
);

And what would JSX be without "hooks":

import { onWindow, uniqueId, type ScriptUIElements } from "extendscript-ui";

const MyButton = ({ text, onClick }: ScriptUIElements["button"]) => {
  const name = uniqueId("my_button");

  onWindow((window) => {
    const el = window.findElement(name);
    el.addEventListener("mouseover", () => (el.text = "Hello mouse!"));
    el.addEventListener("mouseout", () => (el.text = text));
  });

  return <button text={text} onClick={onClick} properties={{ name }}></button>;
};

That's not enough, you say? What if you could render SVGs? (You can!)

const MySVG = ({ fill, stroke }: { fill: string, stroke: string }) => (
  <svg>
    <circle cx="75" cy="75" r="70" fill={fill} stroke={stroke} stroke-width="10" />
  </svg>
));

Try it

Tip

Want to skip all this setup? Jump right into an /examples project!

Prerequisites

You'll need TypeScript and a bundler for your code. Here are some ExtendScript starters with TypeScript support to check out:

Installation

npm i extendscript-ui

Configuration

Update your tsconfig.json:

{
  "compilerOptions": {
    // ...your other config options

    // tell TypeScript how to find extendscript-ui's jsx.d.ts declarations:
    "typeRoots": ["./node_modules/extendscript-ui/dist"],
    "types": ["types/jsx.d.ts"],

    // tell TypeScript how to transform your JSX code and the name of the jsxFactory fn to use when doing so:
    "jsx": "react",
    "jsxFactory": "jsx" // this is the fn that extendscript-ui exports!
  }
  // ...any other options
}

Usage

Be sure to use .tsx files for JSX syntax highlighting. Import jsx to satisfy TypeScript and for code completion:

// index.tsx
import { jsx } from "extendscript-ui";

export const ExampleUI = (
  <dialog text="Neat!" properties={{ closeButton: true }}>
    <button text="Click me!" onClick={() => alert("Doink!")} />
  </dialog>
);

Then, use createWindow to render your template. This will create a Window, wire up your event callbacks, and return the Window.

import { createWindow } from "extendscript-ui";

const window = createWindow(ExampleUI); // ExampleUI from previous code block
window.show();

To define behavior that requires the elements to be rendered (e.g. element.addEventListener) use the onWindow "hook". The callback function passed to onWindow will run after the Window object has been created and receives a reference to it.

onWindow((window) => {
  window.addEventListener("mouseover", () => alert("Hello!"));
});

Tip

ScriptUI has a helpful findElement method to use inside this hook! extendscript-ui also exports a uniqueId helper to ensure element properties.name values are valid.

import { onWindow, uniqueId } from "extendscript-ui";

const MyText = ({ text, properties }) => {
  const name = properties?.name ?? uniqueId("my_text");

  onWindow((window) => {
    const el = window.findElement(name);
    el.addEventListener("mouseover", () => (el.text = "Hello mouse!"));
    el.addEventListener("mouseout", () => (el.text = text));
  });

  return <static-text text={text} properties={{ name }}></static-text>;
};

How?

extendscript-ui uses a custom jsxFactory to transform JSX into a ScriptUI Resource Specifications-compliant string. When you pass your UI to createWindow it creates a new Window object using your compiled specification string. Then, it attaches any event handlers (e.g. onClick, etc.) to their respective UI elements and runs all of the side effect functions you passed to the onWindow "hook".

TODO

  • Set element's size based on <svg width="100" height="100">?
  • Move properties into InstanceProps props?
  • Default text nodes to <static-text/>? e.g <button>hello!</button> === <button text="hello!"/>
  • Figure out TreeView | ListBox | DropDownList rendering
  • Remove type attribute from native types since it's defined by tag?
  • ProgressBar helpers?
  • Turn these TODOs into issues...?

About

Declarative templating for Adobe ScriptUI/ExtendScript

Topics

Resources

Stars

Watchers

Forks