# Reform v1 Documentation

## Page template

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>TODO</title>
    <meta name="description" content="TODO" />
    <meta name="theme-color" content="#4fa1a7" />

    <link rel="stylesheet" type="text/css" href="/sheep3.css" />
    <link rel="stylesheet" type="text/css" href="/reform/v1/index.css" />
    <script src="/sheep3.js" charset="utf-8"></script>
  </head>
  <body>
    <form class="main" role="main" action="javascript:">
      <h1>TODO</h1>
      <p>TODO</p>
    </form>

    <script type="module">
      import { on, Out } from '/reform/v1/index.js'
    </script>
  </body>
</html>
```

## I/O components

- Add `reform:paste-target` to the file input class to allow it to receive files from paste.
- Set `data-default` on the file input to the URL of a file to load by default.
- For single-column outputs, I think output-controls should go before output-content.
- Add `no-output-controls` to `reform:io` when there are no output controls.

### Image input

```html
<label class="input-controls file">
  <input
    type="file"
    name="TODO"
    accept="image/*"
    class="hidden-accessible reform:image-input"
  />
  <span class="icon icon-upload"></span>
  <span class="file-label">Choose or drop TODO</span>
  <span class="file-name">No file selected</span>
</label>
<div class="image-content input-content"><canvas></canvas></div>
```

### Text input

```html
<label class="input-controls file">
  <input type="file" name="TODO" class="hidden-accessible reform:text-input" />
  <span class="icon icon-upload"></span>
  <span class="file-label">Choose or drop TODO or type below</span>
  <span class="file-name">No file selected</span>
</label>
<textarea name="TODO" aria-label="TODO" class="input-content">TODO</textarea>
```

### Image output

```html
<div class="image-content output-content">
  <canvas id="TODO" data-deps="TODO"></canvas>
</div>
<div class="output-controls">
  <a class="file download">
    <span class="icon icon-download"></span>
    <span class="file-label">Download</span>
    <span class="file-name">No file available</span>
  </a>
  <button type="button" class="icon icon-copy" aria-label="Copy"></button>
  <button type="button" class="icon icon-share" aria-label="Share"></button>
</div>
```

### Text output

```html
<textarea
  class="output-content"
  id="TODO"
  data-deps="TODO"
  aria-label="TODO"
  readonly
></textarea>
<div class="output-controls">
  <a class="file download">
    <span class="icon icon-download"></span>
    <span class="file-label">Download</span>
    <span class="file-name">No file available</span>
  </a>
  <button type="button" class="icon icon-copy" aria-label="Copy"></button>
  <button type="button" class="icon icon-share" aria-label="Share"></button>
</div>
```

### Two-column layout

```html
<div class="two-col-io reform:io">TODO</div>
```

### One-column layout

```html
<div class="col-io reform:io">TODO</div>
```

### File I/O, no preview

- Note the use of `no-contents`.
- As used in [Byte sorter](https://sheeptester.github.io/javascripts/byte-sorter.html) and [schedule maker](https://sheeptester.github.io/words-go-here/misc/schedulemaker.html).
- The output ID goes in the wrapper.

```html
<div class="two-col-io reform:io no-contents" id="TODO" data-deps="TODO">
  <label class="input-controls file">
    <input
      type="file"
      name="TODO"
      class="hidden-accessible reform:file-input reform:paste-target"
    />
    <span class="icon icon-upload"></span>
    <span class="file-label">Choose, drop, or paste TODO</span>
    <span class="file-name">No file selected</span>
  </label>
  <div class="output-controls">
    <a class="file download">
      <span class="icon icon-download"></span>
      <span class="file-label">Download</span>
      <span class="file-name">No file available</span>
    </a>
    <button type="button" class="icon icon-copy" aria-label="Copy"></button>
    <button type="button" class="icon icon-share" aria-label="Share"></button>
  </div>
</div>
```

```html
<div class="col-io reform:io no-contents">
  <label class="input-controls file">
    <input
      type="file"
      name="TODO"
      accept="TODO"
      class="hidden-accessible reform:file-input"
    />
    <span class="icon icon-upload"></span>
    <span class="file-label">Choose or drop TODO</span>
    <span class="file-name">No file selected</span>
  </label>
</div>
```

### Output controls, primary copy

This is used by [font-colour-remover](https://sheeptester.github.io/javascripts/font-colour-remover.html) because it's intended to clean the clipboard.

```html
<div class="output-controls">
  <button type="button" class="reform:copy">
    <span class="icon icon-copy"></span>
    <span>TODO</span>
  </button>
  <a class="download icon-btn icon icon-download" aria-label="Download"></a>
  <button type="button" class="icon icon-share" aria-label="Share"></button>
</div>
```

## Form components

### General inputs

- Should work with any text field or select box by default.
- `label-secondary` is optional.

```html
<label class="field-label">
  <span class="label-primary">TODO</span>
  <span class="label-secondary"> TODO </span>
  TODO
</label>
```

### Range

```html
<div class="field-label range-wrapper">
  <label class="range-label">
    <span class="label-primary">TODO</span>
    <input type="range" name="TODO" min="TODO" max="TODO" value="TODO" />
  </label>
  <input
    type="text"
    inputmode="numeric"
    pattern="[0-9]*"
    name="TODO"
    min="TODO"
    max="TODO"
    value="TODO"
    aria-label="TODO value"
  />
</div>
```

### Radio group

- If all options don't need both a `label-primary` and `label-secondary`, then prefer using `label-secondary` for all options.

```html
<fieldset class="radio-set">
  <legend class="label-primary">TODO</legend>
  <p class="label-secondary">TODO</p>
  <label class="radio-label">
    <input type="radio" name="TODO" value="TODO" class="hidden-accessible" />
    <span class="radio-button"></span>
    <span class="label-primary">TODO</span>
    <span class="label-secondary">TODO</span>
  </label>
  <label class="radio-label">
    <input
      type="radio"
      name="TODO"
      value="no-desc"
      class="hidden-accessible"
      checked
    />
    <span class="radio-button"></span>
    <span class="label-primary">TODO</span>
    <span class="label-secondary">TODO</span>
  </label>
  <label class="radio-label">
    <input type="radio" name="TODO" value="TODO" class="hidden-accessible" />
    <span class="radio-button"></span>
    <span class="label-primary">TODO</span>
    <span class="label-secondary">TODO</span>
  </label>
</fieldset>
```

### Checkbox

```html
<label class="radio-label">
  <input type="checkbox" name="TODO" class="hidden-accessible" checked />
  <span class="radio-button"></span>
  <span class="label-primary">TODO</span>
  <span class="label-secondary"> TODO </span>
</label>
```

### Button row

```html
<div class="button-row">
  <button type="submit" class="button primary-btn">TODO</button>
  <button type="button" class="button outline-btn">TODO</button>
  <button type="button" class="button text-btn">TODO</button>
</div>
```

### Horizontal layout

```html
<div class="cols">
  <label class="field-label">TODO</label>
  <label class="field-label">TODO</label>
</div>
```

## CSS utility class reference

- `.link`: For text links.
- `.hidden-accessible`: Hides an element but make it tab-accessible.
- `.code`: Monospace font.

## JavaScript API

Reform is built on the concept of sources. All form elements are automatically sources, and the `on` function can be used to register custom sources. Even outputs are sources, and they may produce values for output controls to consume.

```ts
declare module '/reform/v1/index.js' {
  export type SourceSpec =
    | {
        name: string
        deps?: string[]
      }
    | string

  export function on<T> (
    spec: SourceSpec,
    callback: (
      object: HTMLElement | CanvasRenderingContext2D | null,
      args: Record<string, unknown>
    ) => Promise<T>
  ): void

  export abstract class Out {
    fileName?: string
    /**
     * Canvas contexts are turned into PNG images.
     */
    value?: Blob | CanvasRenderingContext2D

    /**
     * MIME type of download. Ignored if `value` is set. Currently only used as
     * fallback when `clipboardTypeHint` is omitted, but it is recommended to set
     * this in case I add blob type feature detection in the future.
     */
    downloadTypeHint?: string
    /** Ignored if `value` is set. */
    provideDownload? (): PromiseLike<Blob> | Blob

    /**
     * MIME type of clipboard blob. Preferred over `downloadTypeHint`. Must be
     * specified if `provideClipboard` is also specified.
     */
    clipboardTypeHint?: string
    /**
     * If the blob type is not supported by the Clipboard API, then specify this
     * method for the copy button, which when set will be preferred over `value`
     * or `provideDownload`.
     *
     * Guaranteed to be called at most once.
     */
    provideClipboard? (): PromiseLike<Blob> | Blob

    /**
     * @param type Defaults to `text/plain`
     */
    static from (
      fileName: string,
      value: CanvasRenderingContext2D | BlobPart,
      type?: string
    ): Out
  }
}
```

### Form element sources

Reform automatically subscribes to all form elements in the page, unless `[data-ignore]` is set (used by the special input sources below).

- Number, range, and `[inputmode=numeric]` inputs produce a number.
  - If there's an ancestor `.range-wrapper`, then it'll find the corresponding input to update its value.
- Checkboxes produce a boolean.
- File inputs produce a non-empty `Array` of `File`s.
- Text areas, select boxes, radios, and all other input types produce a string.

Generally, classes prefixed with `reform:` indicate special JavaScript behavior.
Here are the special input sources:

- `input.reform:image-input`: Enables image input handling.
  - It will create a canvas if none already exists in the `.reform:io` wrapper. `willReadFrequently` is set to `data-will-read-frequently` (used by [Intense contrast](https://sheeptester.github.io/javascripts/intense-contrast.html))
  - Selected images are drawn onto the canvas. This is also used for previewing.
  - Produces `CanvasRenderingContext2D`s instead of image files.
  - The canvas element's `data-name` attribute is set to the file name without the extension.
- `input.reform:video-input`: Enables video input handling.
  - It will create a `<video>` if none already exists in the `.reform:io` wrapper.
  - Selected videos are loaded in the video element. This is also used for previewing.
  - Produces `HTMLVideoElement` once the video loads (fully?).
  - The video element's `data-name` attribute is set to the file name without the extension.
- `input.reform:text-input`: Enables text input handling.
  - Produces a string.
- `input.reform:file-input`: Enables single file input handling.
  - Produces a `File`.
- `canvas.reform:canvas-element`: Forces the source object to be a canvas element (like other HTML elements) rather than creating a 2D context.
- `.reform:io-ignore`: Prevents the element's output from being attached to its ancestor output controls (e.g. if the real output is handled by a hidden sibling element).

Some more info and options:

- `.reform:io` indicates the ancestor element surrounding the input element and its preview. It also becomes the target for dropping files.
  - `.file-name` is populated with the selected file name and size.
- `input.reform:paste-target` makes the source handle files pasted into the web page.
- `input[data-default]` contains the URL of a file to load and use as the default file.

### Custom sources

`on` is used to register a custom source.

```ts
export type SourceSpec =
  | {
      name: string
      deps?: string[]
    }
  | string
declare export function on<T> (
  spec: SourceSpec,
  callback: (
    object: HTMLElement | CanvasRenderingContext2D | null,
    args: Record<string, unknown>
  ) => Promise<T>
): void
```

The source name is the identifier of the source. It may refer to an element (`object`) in the DOM by its ID (priority) or `name`. If the element is a canvas, then the object is instead the canvas's `CanvasRenderingContext2D`; you can opt out of this with the `.reform:canvas-element` class.

#### Dependencies

If `deps` isn't specified in `spec`, then it'll pull the dependencies from space-separated `[data-deps]` if it exists. The dependency list is a list of source identifiers.

If a dependency is prefixed with an ampersand `&`, then the dependency becomes a reference to the object (i.e. HTML element or canvas context) instead of the source's value. For example, `[data-deps="a &b"]` may mean dependency `a` will be the value of an input of name `a`, and dependency `b` is the DOM element of ID or name `b`.

The `callback` will not run until all dependencies are ready, i.e. all their values are not `undefined`. For example, a file input may not have a file selected yet, so it is not considered ready.

#### Callback

`callback` is called with the source's referenced object (i.e. HTML element or canvas context) and `args`, which is an object mapping from each dependency's name to its value.

`args` also contains a function `args.callback` which can be used to make the produce multiple values even after its dependencies have not changed. Since `on` already gets an element by ID for you, it's sometimes used just to attach event listeners to the element and produce values from there.

The callback can return a promise, which is awaited. There is some protection against race conditions: the custom provider's output will not be used if the inputs have changed while a callback hasn't yet resolved. However, if you do any other side effects like drawing to a canvas during the callback, you should add your own race condition checks.

#### Output controls

Custom sources do not need to produce values, but producing an `Out` instance is recommended to populate output controls.
If the referenced element has an ancestor `.reform:io` (or itself has the class `.reform:io`), it will try finding a `.output-controls` to add as a dependency to the custom source.
If the custom source produces an unsupported type, it is safely ignored by output controls.

The share button is automatically hidden in browsers that don't support the Web Share API.
