# TypeScript

Mirror exports a props interface for every part, plus options types, composables and context keys. Read this for how they are named and where to import them.

Every component module exports its types and its values from the same
entry point, `@maas/mirror/vue/<Component>`.

```ts
import { Button, type ButtonProps } from '@maas/mirror/vue/Button'
```

## Props interfaces

Every part exports an interface named after it. Multi-part components use
`<Component><Part>Props`, single-part components use `<Component>Props`.

```ts
import type { ButtonProps } from '@maas/mirror/vue/Button'
import type { FieldRootProps } from '@maas/mirror/vue/Field'
import type { RadioGroupProps } from '@maas/mirror/vue/Radio'
import type { SelectContentProps } from '@maas/mirror/vue/Select'
import type { SliderThumbProps } from '@maas/mirror/vue/Slider'
import type { TabsContentProps } from '@maas/mirror/vue/Tabs'
```

Extend one to add your own props without restating what the part already
accepts. See
[Composition](/components/composition#extend-the-exported-props).

Every interface that renders an element extends `PrimitiveProps`, which holds
`as` and `asChild`, and which is re-exported alongside `Primitive` and
`PrimitiveSlot`.

```ts
import { Primitive, type PrimitiveProps } from '@maas/mirror/vue'
```

## Options types

Components that take a bundled `options` object export its type, plus a
`Resolved` form with every key filled in from the defaults.

```ts
import type { SelectOptions, ResolvedSelectOptions } from '@maas/mirror/vue/Select'
```

`AvatarOptions`, `CheckboxOptions`, `ComboboxOptions`, `FieldOptions`,
`ProgressOptions`, `RadioOptions`, `SelectOptions`, `SliderOptions`,
`SwitchOptions` and `TabsOptions` each have one.

Nested groups are exported separately, so you can type a fragment of the object
you build elsewhere.

```ts
import type { SelectFloatingOptions } from '@maas/mirror/vue/Select'

const floating: SelectFloatingOptions = { side: 'bottom', sideOffset: 8 }
```

`Select` and `Combobox` also export `*FocusOptions` and `*TransitionOptions`,
plus a `*DismissInteraction` union typing one entry of the `dismiss` array, and
`Select` adds `SelectTypeaheadOptions`.

## Module composables

A module keeps its state in an app-scoped store keyed by `id` and exposes one
composable as its public API. Each takes a `MaybeRefOrGetter<string>` and
returns a `UseMirror<Component>Return`.

```ts
import { useMirrorSelect, type UseMirrorSelectReturn } from '@maas/mirror/vue/Select'

const select = useMirrorSelect('framework')
```

Every module exports one composable named after it, `useMirrorSelect` for
Select and `useMirrorTabs` for Tabs. Checkbox exports a second,
`useMirrorCheckboxGroup`, for the group store. Toggle is the one exception to
the naming: a lone `Toggle.Root` holds its state locally, so its only
composable is `useMirrorToggleGroup`.

`Button`, `Input`, `Label`, `Separator` and `Textarea` have no store and
therefore no composable; they take props and nothing else.

## Instance IDs

An `id` is an ordinary string. Mirror generates one when you leave it out and
declares no enum of its own, so keep your instance IDs in a const object or an
enum your app owns.

```ts
export const FieldId = {
  Email: 'email',
  Password: 'password',
} as const
```

Pass one wherever something outside the subtree has to address the instance.

```vue
<template>
  <Field.Root :id="FieldId.Email" :options="{ name: 'email' }" />
</template>
```

## Context keys

Where a part needs to reach its parent’s context directly, the injection key is
exported.

```ts
import { inject } from 'vue'
import { FieldContextKey } from '@maas/mirror/vue/Field'
import { SelectItemContextKey } from '@maas/mirror/vue/Select'

const field = inject(FieldContextKey, undefined)
```

`AvatarContextKey`, `ComboboxContextKey`, `ComboboxItemContextKey`,
`FieldContextKey`, `ProgressContextKey`, `RadioItemContextKey`,
`SelectContextKey`, `SelectGroupContextKey`, `SelectItemContextKey`,
`SliderContextKey` and
`TabsContextKey` are all available, along with the `CheckboxInstanceId`,
`RadioInstanceId` and `SwitchInstanceId` id keys.

Prefer the composable where one exists, and reach for a context key when you
are building a part of your own that has to sit inside a Mirror subtree.

## Errors

Mirror fails loudly rather than rendering something inert. Every assertion
throws a `MagicError`, which is re-exported from `@maas/mirror/vue` so you can
narrow on it directly.

```ts
import { MagicError } from '@maas/mirror/vue'

try {
  render()
} catch (error) {
  if (error instanceof MagicError) {
    console.error(error.errorCode, error.source)
  }
}
```

::docs-table
---
columns:
  - label: Property
  - label: Type
rows:
  - items:
      - label: errorCode
        description: The code to branch on.
      - label: 'string | number'
  - items:
      - label: source
        description: The part that raised it, such as `CheckboxIndicator`.
      - label: string
  - items:
      - label: timestamp
        description: When it was raised.
      - label: number
---
::

The message is prefixed `[Mirror]: ` and is also written to `console.error`
before the throw, so catch by code rather than by message.

`MagicErrorCode` types the codes shared across every part.

```ts
import type { MagicErrorCode } from '@maas/mirror/vue'

// 'missing_context' | 'missing_instance_id' | 'missing_element'
function isRecoverable(code: MagicErrorCode) {
  return code === 'missing_element'
}
```

`missing_context` is the most common one, since any part whose only requirement
is an immediate parent raises it, such as `Select.ItemText` outside
a `Select.Item`. Component-specific codes are plain strings, documented one for
one with the assertion that raises them on each component page.

## Further reading

- [Composition](/components/composition): what to do with the interfaces once you
  have them.
- [Form Integration](/components/form-integration): `useMirrorField` and the
  field context in practice.
