> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joyfill.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Decorators

Decorators are tappable indicators (icon + label) attached to a **field**, **column**, **row**, or **cell**. Taps are delivered through **`onFocus`** so your app can run custom logic — navigation, uploads, etc.

## Decorator model

```swift theme={null}
import JoyfillModel

var d = Decorator()
d.action = "openHelp"     // required, unique within scope
d.icon   = "circle-info"  // optional, see Supported icons
d.label  = "Help"         // optional
d.color  = "#3B82F6"      // optional, must be #RRGGBB
```

| Property | Type      | Notes                                                    |
| -------- | --------- | -------------------------------------------------------- |
| `action` | `String`  | **Required**. Non-empty. Unique within its scope (path). |
| `icon`   | `String?` | See [Supported icons](#supported-icons).                 |
| `label`  | `String?` | Text.                                                    |
| `color`  | `String?` | 6-digit hex (`#RRGGBB`).                                 |

A decorator renders only when it has a non-empty `icon` **or** `label`. Action-only entries are stored but not displayed.

## Constructing a path

Every path starts with `pageId/fieldPositionId`. What you append after that determines what gets decorated.

> **Reserved keywords.** The path grammar uses three reserved tokens — **`schemas`**, **`rows`**, and **`columns`**. Anything else in a path slot is treated as an id (page id, field-position id, row id, column id, or schema key). Don't use these keywords as ids.

### Field decorators

Just the two ids. Applies to the field's header.

```swift theme={null}
let fieldPath = "\(pageId)/\(fpId)"
```

### Table — `/rows`, `/columns/colId`, or specific `rowId` / `rowId/colId`

A table has four decorator scopes, two **common** (defaults applied everywhere) and two **specific** (overrides for one row or cell):

| What you want                                   | Append           | Example                          |
| ----------------------------------------------- | ---------------- | -------------------------------- |
| Common decorators on **every row**              | `/rows`          | `pageId/fpId/rows`               |
| Decorators on **one specific row**              | `/rowId`         | `pageId/fpId/row_42`             |
| Common decorators on **every cell in a column** | `/columns/colId` | `pageId/fpId/columns/col_status` |
| Decorators on **one specific cell**             | `/rowId/colId`   | `pageId/fpId/row_42/col_status`  |

Specific paths inherit from the matching common path on the first write — anything you set on `/rows` shows on `row_42` until you write to `row_42` directly.

### Collection — same as table, plus `/schemas/schemaKey/…` for nested rows

A collection's **root** rows behave like a table — the four scopes above use the exact same path shapes.

Take a "People" collection where each person row holds a nested "Addresses" schema:

```
schema "people"     (root, children: [addresses])
schema "addresses"  (nested under people)

Rows:
  p_alice                                  ← row in "people"
    addresses → [ addr_home, addr_work ]   ← rows in "addresses", under p_alice
  p_bob
    addresses → [ addr_apt ]
```

**Common rows / columns** of any schema — root or nested — are schema-level defaults. Address them directly with `schemas/schemaKey/…`, no parent walk needed:

| What you want                | Path shape                             | Example                                         |
| ---------------------------- | -------------------------------------- | ----------------------------------------------- |
| Common rows in any schema    | `pageId/fpId/schemas/sk/rows`          | `pageId/fpId/schemas/addresses/rows`            |
| Common columns in any schema | `pageId/fpId/schemas/sk/columns/colId` | `pageId/fpId/schemas/addresses/columns/col_zip` |

**A specific nested row or cell** lives under a particular parent. Walk through that parent's row id, then `schemas/sk/`, then the nested row id:

| What you want        | Path shape                             | Example                                                   |
| -------------------- | -------------------------------------- | --------------------------------------------------------- |
| Specific nested row  | `…/rowId/schemas/sk/nestedRowId`       | `pageId/fpId/p_alice/schemas/addresses/addr_home`         |
| Specific nested cell | `…/rowId/schemas/sk/nestedRowId/colId` | `pageId/fpId/p_alice/schemas/addresses/addr_home/col_zip` |

If `addresses` itself had children, you'd chain another `schemas/.../rowId/…` after `addr_home` — the same pattern repeats for every level.

> **Schema keys** come from the field's `schema` map. The schema marked `root: true` holds top-level rows; its `children` array names the nested schemas reachable from a row in this schema.

## API

Four methods, all on `DocumentEditor`. Errors are reported via `onError`.

```swift theme={null}
let fieldPath = "\(pageId)/\(fieldPositionId)"

editor.getDecorators(path: fieldPath)                          // -> [Decorator]
editor.addDecorators(path: fieldPath, decorators: [d])
editor.updateDecorator(path: fieldPath, action: "openHelp", decorator: updated)
editor.removeDecorator(path: fieldPath, action: "openHelp")
```

Same shape for every path scope. A few examples:

```swift theme={null}
// Common row decorators on a table — applied to every row
editor.addDecorators(path: "\(pageId)/\(fpId)/rows", decorators: [duplicate])

// Override on a specific row
editor.addDecorators(path: "\(pageId)/\(fpId)/\(rowId)", decorators: [archive])

// Cell-specific decorator
editor.addDecorators(path: "\(pageId)/\(fpId)/\(rowId)/\(colId)", decorators: [upload])

// Nested collection row
let nestedPath = "\(pageId)/\(fpId)/\(parentRowId)/schemas/\(nestedSK)/\(nestedRowId)"
editor.addDecorators(path: nestedPath, decorators: [comment])
```

## Behavior to know

* **Copy-on-write seed.** First write to a row-self / cell scope seeds from the matching common scope, so existing common decorators stay visible on that row alongside your override. Subsequent writes diverge freely.

* **Collection license gating.** Writes against a collection field require a license that enables collection features. Without it, the call emits `decoratorError` and is rejected.

## Handling taps

Decorator taps come through **`onFocus`** with the decorator's `action` exposed on the field event's `type` / `target`. `rowIds` / `columnId` / `parentPath` on `FieldIdentifier` tell you where the user tapped.

```swift theme={null}
func onFocus(event: Joyfill.Event) {
    guard let field = event.fieldEvent else { return }

    if let action = field.type, !action.isEmpty {
        // Decorator tap
        print("Decorator:", action,
              "field:", field.fieldID,
              "rows:", field.rowIds ?? [],
              "column:", field.columnId ?? "-")
    } else {
        // Ordinary field focus
    }
}
```

See [Event handling](/ios/guides/event-handling) for the full focus/blur flow.

## Errors

All four APIs report through `onError` as `JoyfillError.decoratorError(DecoratorError)`:

* Path didn't resolve (bad ids, deleted row, malformed grammar)
* Validation (`action` empty, `color` not `#RRGGBB`)
* Duplicate `action` in batch or against an existing entry
* `removeDecorator` / `updateDecorator` with an unknown `action`
* Collection write without a valid license

Reads (`getDecorators`) on an unresolvable path also emit `onError` and return `[]`.

## Display limits

`DecoratorConfig`, passed to `DocumentEditor` at init, controls how many decorators render inline before the rest collapse into a kebab menu.

```swift theme={null}
let config = DecoratorConfig(
    visibleLimitInFields: 2,   // field + column scopes; default 2
    visibleLimitInRows: 1      // row scopes; default 1
)
let editor = DocumentEditor(document: doc, decoratorConfig: config)
```

## Supported icons

The SDK maps common names to bundled artwork or SF Symbols, including: `camera`, `import`, `paperclip`, `image`, `file`, `comment`, `comments`, `upload`, `download`, `rotate`, `cloud`, `filter`, `share`, `paper-plane`, `folder`, `folder-open`, `magnet`, `eye`, `circle-info`, `add`, `plus`, `print`, `flag`, `pencil`, `pen-to-square`. Unknown names fall back to a default symbol.
