# bindDebugSchema

Describe values, controls, groups, and actions once, then bind them to Tweakpane.



<DemoPreview name="bindDebugSchema" />

## Usage [#usage]

```ts
import { bindDebugSchema, bool, property, slider } from '@joycostudio/xyz/debug'
import { Pane } from 'tweakpane'

const state = { speed: 1, enabled: true }
const pane = new Pane()
const controls = bindDebugSchema(pane, {
  schema: () => ({
    speed: slider(property(state, 'speed'), { min: 0, max: 3 }),
    enabled: bool(property(state, 'enabled')),
  }),
})

// Controls write to state. Refresh after changing state from code.
state.speed = 2
controls.refresh()
controls.values() // { speed: 2, enabled: true }

// When the owner is removed:
controls.dispose()
pane.dispose()
```

## Binding [#binding]

### bindDebugSchema [#binddebugschema]

```ts
bindDebugSchema(host: FolderApi, definition: DebugDefinition, options?: BindDebugOptions): BoundDebug
```

Creates controls in an existing Tweakpane pane or folder. Use this directly when you own the pane's lifecycle. [DebugManager.bind()](./debug-manager) calls it internally when mounting a registered definition, so you do not need to call both.

| Argument                 | Description                                                                                                                                               |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `host`                   | The pane or folder that receives the controls. The caller owns and disposes this host.                                                                    |
| `definition`             | An object containing the schema factory and optional behavior described below.                                                                            |
| `definition.schema`      | Required factory returning a record of control fields.                                                                                                    |
| `definition.copy?`       | Adds a copy-values button when `true`. Off when omitted.                                                                                                  |
| `definition.dispose?`    | Cleanup function, disposable object, or array of either, run when the binding is disposed.                                                                |
| `definition.extend?`     | Receives the Tweakpane folder and `{ fields }` (see `fields()` below) after the schema is bound, to add custom controls; may return cleanup.              |
| `definition.sources?`    | Source descriptors with `label: string` and `value: () => object`, collected in `controls.sources`. The built-in copy button reads schema values instead. |
| `options.sectionName?`   | Wraps copied values under this name.                                                                                                                      |
| `options.registerFrame?` | Receives a refresh callback to register with your frame loop and returns its teardown. Required for automatic updates of controls with `live: true`.      |
| `options.copyText?`      | Receives serialized values and returns `void` or a promise. Defaults to `navigator.clipboard.writeText`.                                                  |
| `options.onError?`       | Receives `(error, context)` when a control, action, or copy operation fails. Defaults to logging to the console.                                          |

Returns a `BoundDebug` controller:

* `apply()`: Writes the controls' current values to their sources, then refreshes.
* `refresh()`: Pulls current source values into the controls, including nested folders.
* `values()`: Reads the current nested value object, excluding buttons.
* `fields()`: Lists every bound schema field, nested folders' fields included, in schema order. Each entry has `path` (schema keys from this definition's root, never labels), `field` (the bound `DebugField`), and `blade` (the `BindingApi`, `FolderApi`, or `ButtonApi` created for it). The list is fixed at bind time and excludes the copy button and anything added by `extend`.
* `sources`: Collects declared `DebugSource` entries from this definition and its children.
* `dispose()`: Removes created controls, nested folders, frame callbacks, and extension teardowns, and runs the definition's cleanup. The host remains owned by the caller.

## References [#references]

### ref [#ref]

```ts
ref<T>(read: () => T, write: (value: T) => void): ValueRef<T>
```

Adapts getter and setter functions into a mutable reference.

| Argument | Description                                                      |
| -------- | ---------------------------------------------------------------- |
| `read`   | Returns the current source value whenever the reference is read. |
| `write`  | Updates the source whenever the reference is assigned a value.   |

```ts
const state = { speed: 1 }
const speed = ref(
  () => state.speed,
  (value) => {
    state.speed = value
  }
)
```

### property [#property]

```ts
property<TObject, TKey extends keyof TObject>(
  object: TObject,
  key: TKey,
  changed?: () => void
): ValueRef<TObject[TKey]>
```

Creates a typed reference to one object property.

| Argument   | Description                                                                                                                      |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `object`   | The object to read from and write to.                                                                                            |
| `key`      | A key of that object. The reference keeps the property's value type.                                                             |
| `changed?` | A callback with no arguments, called after the reference writes to the property. Direct changes to the object do not trigger it. |

```ts
const state = { speed: 1 }
const speed = property(state, 'speed', () => console.log(state.speed))
```

## Number and boolean controls [#number-and-boolean-controls]

Control helpers return a `DebugField` to place in a schema. Their optional `label` overrides the schema key displayed beside the control. `live` defaults to off; set it to `true` and provide `registerFrame` in the binding options to reflect source changes automatically.

### slider [#slider]

```ts
slider(store: ValueRef<number>, options: SliderOptions): DebugField
```

Creates one numeric slider.

| Argument         | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `store`          | A numeric reference, usually from `property` or `ref`. |
| `options.min`    | Required lower bound.                                  |
| `options.max`    | Required upper bound.                                  |
| `options.step?`  | Numeric increment. Omitted by default.                 |
| `options.label?` | Display label.                                         |
| `options.live?`  | Refresh from the source on registered frames.          |

```ts
const state = { speed: 1 }
slider(property(state, 'speed'), { min: 0, max: 3, step: 0.1, label: 'Speed' })
```

### sliders [#sliders]

```ts
sliders(object, specs, changed?): DebugSchema
```

Creates several sliders for numeric properties of one object. Spread the result into your schema.

| Argument   | Description                                                                                                        |
| ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `object`   | The object whose numeric properties will be edited.                                                                |
| `specs`    | A map of numeric property keys to `SliderSpec` tuples: `[min, max, step?, label?]`. Only listed keys get controls. |
| `changed?` | A callback with no arguments, called after any generated control writes to the object.                             |

```ts
const state = { speed: 1, intensity: 0.5 }
const schema = () => ({
  ...sliders(state, {
    speed: [0, 3, 0.1, 'Speed'],
    intensity: [0, 1],
  }),
})
```

### bool [#bool]

```ts
bool(store: ValueRef<boolean>, options?: { label?: string; live?: boolean }): DebugField
```

Creates one boolean toggle.

| Argument         | Description                                   |
| ---------------- | --------------------------------------------- |
| `store`          | A boolean reference.                          |
| `options.label?` | Display label.                                |
| `options.live?`  | Refresh from the source on registered frames. |

```ts
const state = { enabled: true }
bool(property(state, 'enabled'), { label: 'Enabled' })
```

### toggles [#toggles]

```ts
toggles(object, specs, changed?): DebugSchema
```

Creates several toggles for boolean properties of one object. Spread the result into your schema.

| Argument   | Description                                                                                                                                  |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`   | The object whose boolean properties will be edited.                                                                                          |
| `specs`    | A map of boolean property keys to a label string or `true`. Use `true` to keep the property key as the label. Only listed keys get controls. |
| `changed?` | A callback with no arguments, called after any generated control writes to the object.                                                       |

```ts
const state = { enabled: true, wireframe: false }
const schema = () => ({
  ...toggles(state, { enabled: true, wireframe: 'Show wireframe' }),
})
```

## Choices and colors [#choices-and-colors]

### select [#select]

```ts
select<T extends string | number>(
  store: ValueRef<T>,
  options: Record<string, T>,
  config?: { label?: string; live?: boolean }
): DebugField
```

Creates a dropdown for string or number values.

| Argument        | Description                                          |
| --------------- | ---------------------------------------------------- |
| `store`         | A reference to the selected value.                   |
| `options`       | A map from displayed option labels to stored values. |
| `config.label?` | Display label for the control.                       |
| `config.live?`  | Refresh from the source on registered frames.        |

```ts
const state = { quality: 'high' }
select(property(state, 'quality'), { Low: 'low', High: 'high' }, { label: 'Quality' })
```

### enumField [#enumfield]

```ts
enumField(store, options, config?): DebugField
```

Alias of [select](#select), with the same arguments and behavior. Pass the selected-value reference, the label-to-value map, and optional `label` / `live` configuration.

```ts
const state = { quality: 'high' }
enumField(property(state, 'quality'), { Low: 'low', High: 'high' })
```

### color [#color]

```ts
color<T extends RgbColor>(store: ValueRef<T>, options?: { label?: string; live?: boolean }): DebugField
```

Creates an RGB color control configured for floating-point channels. Edits call the existing object's `setRGB` method, preserving its identity.

| Argument         | Description                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------ |
| `store`          | A reference to an object with `r`, `g`, and `b` numeric channels and a `setRGB(r, g, b)` method. |
| `options.label?` | Display label.                                                                                   |
| `options.live?`  | Refresh from the source on registered frames.                                                    |

```ts
const state = {
  tint: {
    r: 1,
    g: 0.5,
    b: 0,
    setRGB(r: number, g: number, b: number) {
      Object.assign(this, { r, g, b })
    },
  },
}
color(property(state, 'tint'), { label: 'Tint' })
```

### colorString [#colorstring]

```ts
colorString(store: ValueRef<string>, options?: { label?: string; live?: boolean }): DebugField
```

Creates a color picker backed by a CSS color string.

| Argument         | Description                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------- |
| `store`          | A string reference containing a color value supported by Tweakpane, such as `'#ff8800'`. |
| `options.label?` | Display label.                                                                           |
| `options.live?`  | Refresh from the source on registered frames.                                            |

```ts
const state = { tint: '#ff8800' }
colorString(property(state, 'tint'), { label: 'Tint' })
```

### hexColor [#hexcolor]

```ts
hexColor(store: ValueRef<string>, options?: { label?: string; live?: boolean }): DebugField
```

Alias of [colorString](#colorstring), with the same string reference and optional `label` / `live` arguments. It does not add hex-only validation.

```ts
const state = { tint: '#ff8800' }
hexColor(property(state, 'tint'))
```

## Structure and actions [#structure-and-actions]

### folder [#folder]

```ts
folder(definition: DebugDefinition & { title?: string; expanded?: boolean }): DebugField
```

Nests a debug definition inside the current schema. Also accepts an existing `DebugFolder`, such as a single definition returned by an object's `debug()` method or passed to `DebugManager.bind()`.

| Argument               | Description                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------- |
| `definition`           | The nested schema factory and optional copy, extension, source, and cleanup behavior. |
| `definition.title?`    | Folder title. Defaults to the schema key.                                             |
| `definition.expanded?` | Initial expanded state. When omitted, uses Tweakpane's default.                       |

```ts
const state = { speed: 1 }
const schema = () => ({
  motion: folder({
    title: 'Motion',
    expanded: true,
    schema: () => ({ speed: slider(property(state, 'speed'), { min: 0, max: 3 }) }),
  }),
})
```

### section [#section]

```ts
section(schema: () => DebugSchema, options?: SectionOptions): DebugField
```

Creates a nested folder that is collapsed and includes a copy-values button by default.

| Argument            | Description                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `schema`            | Factory returning the nested fields.                                                                                |
| `options.label?`    | Folder title. Defaults to the schema key.                                                                           |
| `options.expanded?` | Initial expanded state. Defaults to `false`.                                                                        |
| `options.copy?`     | Show the copy-values button. Defaults to `true`.                                                                    |
| `options.dispose?`  | Teardown to run when the section is disposed.                                                                       |
| `options.extend?`   | Receives the section's Tweakpane folder and `{ fields }` with paths relative to the section; may return a teardown. |
| `options.sources?`  | Source descriptors included in the bound controller's `sources` array.                                              |

```ts
const state = { speed: 1 }
const schema = () => ({
  motion: section(() => ({ speed: slider(property(state, 'speed'), { min: 0, max: 3 }) }), { label: 'Motion' }),
})
```

### button [#button]

```ts
button(title: string, run: () => void | Promise<void>, feedback?: string): DebugField
```

Creates an action button. After the action completes successfully, the bound controls refresh from their sources.

| Argument    | Description                                                        |
| ----------- | ------------------------------------------------------------------ |
| `title`     | Button text.                                                       |
| `run`       | Callback invoked on click. May be synchronous or return a promise. |
| `feedback?` | Temporary button text shown for 1.2 seconds after success.         |

```ts
const state = { speed: 1 }
button(
  'Reset speed',
  () => {
    state.speed = 1
  },
  'Reset ✓'
)
```

### schemaValues [#schemavalues]

```ts
schemaValues(schema: DebugSchema): Record<string, unknown>
```

Reads current values without creating any Tweakpane controls.

| Argument | Description                                                                                                           |
| -------- | --------------------------------------------------------------------------------------------------------------------- |
| `schema` | The field record to read. Nested folder factories are evaluated recursively, and buttons are omitted from the result. |

```ts
const state = { speed: 1 }
schemaValues({ speed: slider(property(state, 'speed'), { min: 0, max: 3 }) })
// { speed: 1 }
```
