# 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.

Everything public is exported from `@maas/mirror/vue`, types and values from
the same entry point.

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

## 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,
  FieldRootProps,
  RadioGroupProps,
  SelectContentProps,
  SliderThumbProps,
  TabsContentProps,
} from '@maas/mirror/vue'
```

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'
```

`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'

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

`Select` and `Combobox` also export `*DismissOptions`, `*FocusOptions` and
`*TransitionOptions`, 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'

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, SelectItemContextKey } from '@maas/mirror/vue'

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.
