<!-- GENERATED by scripts/build-llms.mjs from llms/forms.md — do not edit this file. -->

# `lr-code-editor`

- **Import** `import '@aceshooting/lyra-ui/components/lr-code-editor.js';` (stable tag alias; registers the tag)
- **Class** `LyraCodeEditor`, also available unregistered from `@aceshooting/lyra-ui/components/forms/code-editor/code-editor.class.js`
- **Family** `components/forms/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 8 parts, 14 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-code-editor`

Long translated form chrome wraps within the host, while long source stays reachable through the
editor's internal scroll extent instead of widening the page. The `Narrow RTL long content
(320px)` story covers both boundaries together.

`form.reset()` restores the default value and pristine interaction feedback. Required and custom
validity constraints remain; a required-empty editor is still invalid. Removing `label`, `hint`, or
`error-text` treats the removed value as absent for rendering while retaining native
attribute-removal property readback.

Host `aria-describedby` references resolve in the host root onto the native textarea before its
local error/hint guidance. Target replacement, removal, reinsertion, unresolved IDs, reconnect, and
adoption keep that relationship current.

Dependency-free, form-associated multiline code editor built around a native textarea, with an
optional line-number gutter. No syntax highlighting: `language` is metadata only.

**Properties:**

- `language: string = ''` — reflected on the host and projected onto the `editor` part as
  `data-language`; purely a consumer-reachable styling/metadata hook, nothing tokenizes the text
- `lineNumbers: boolean = true` (attribute `line-numbers`, reflected) — renders the `gutter` part,
  one row per `\n`-separated line
- `tabSize: number = 2` (attribute `tab-size`) — spaces inserted per Tab press and the tab width
  shared by the native textarea and text measurement. Explicit property/attribute assignment wins
  over `--lr-code-editor-tab-size`; otherwise the token controls the rendered tab width. Sanitized
  on assignment to a finite integer clamped to `1..16`, so a `NaN`/`Infinity` value can neither
  empty the insert nor throw out of `String.repeat()`.
- `label: string = ''`, `hint: string = ''`, `errorText: string = ''` (attribute `error-text`),
  `placeholder: string = ''`
- `readonly: boolean = false` (reflected) — also disables Tab indentation
- `rows: number = 4`, `cols: number = 20`, `minlength?: number`, `maxlength?: number` — native
  textarea geometry and code-unit length constraints. Programmatic/restored values receive the
  same supplemental length validity as user edits.
- `resize: 'none' | 'both' | 'horizontal' | 'vertical' | 'auto' = 'both'` — written as the
  textarea's inline `resize`; `auto` grows the owned surface to its content without a manual drag
  handle. A consumer `max-block-size` on the `textarea` part caps that growth and restores vertical
  scrolling; changing away from `auto` clears its inline block-size and overflow state. An invalid
  runtime value falls back to `'both'`
- `size: LyraSize = 'm'` (reflected) — visual size on the shared control ladder, the same scale as
  `lr-textarea`/`lr-input`/`lr-select`, accepting both spellings of every tier (`2xs`/`xs`/`s`/`m`/
  `l`/`xl` and `small`/`medium`/`large`). Governs the gutter's and textarea's padding and font size,
  plus the editor frame's minimum block size.
- `wrap: 'off' | 'soft' | 'hard' = 'off'` — native textarea wrapping. `'off'` (the default) makes
  the `editor` part the single horizontal scroll viewport. Soft/hard text wraps within the editor
  allocation, with the caret and logical line-number gutter following the wrapped lines as the
  allocation changes; the editor frame owns scrolling. `hard` uses the owner realm's native
  textarea serializer, so FormData receives platform-equivalent `cols` wrapping while the live
  `value` remains unwrapped.
- `spellcheck: boolean = false` — off by default for code, and parsed with a string-aware converter
  so `spellcheck="false"` really is `false`
- `autofocus: boolean = false`, `title: string = ''`, `autocomplete: string = ''`,
  `inputMode`/`inputmode: string = ''`, `enterKeyHint`/`enterkeyhint: string = ''`,
  `autocapitalize: string = 'off'`, and `autocorrect: boolean = false` (attribute vocabulary
  `on`/`off`; boolean and string writes normalize through the shared native converter)
- `accessibleLabel: string = ''` (attribute `aria-label`) — wins over `label`/the localized
  `codeEditorLabel` fallback on the internal textarea
- The shared form surface adds `value`, `defaultValue`, `customError` (`custom-error`), `name`,
  `disabled`, `required`, `form`, `getForm()`, `checkValidity()`, `reportValidity()`, and
  `setCustomValidity()` / `resetValidity()`. The latter clears only consumer custom validity and
  restores current intrinsic constraints; it does not change the value/default or prior
  interaction state.

**Methods:** `focus(options?)`, `blur()`, `select()`, `setSelectionRange(start, end, direction?)`,
`setRangeText(replacement, start?, end?, selectMode?)` (writes the result back into `value` without
emitting an event), and `scrollPosition()` / `scrollPosition({top?,left?})`. The `input` getter
returns the owned native textarea after render. `selectionStart`, `selectionEnd`, and
`selectionDirection` use native nullable sentinels before that surface exists.
`scrollPosition()` reads or writes `[part="editor"]`'s top/left offsets: the gutter and caret share
that single scroll owner rather than a private textarea scroll position.
The native textarea receives the actual `required` state. Its `aria-invalid` is true whenever
visible property/slotted error chrome exists, or after interaction while native validity fails;
showing error chrome alone does not mutate `ElementInternals` validity.

**Events:** exactly one realm-correct native `input`, `change`, `focus`, and `blur` is relayed from
the internal textarea; native payload such as `InputEvent.inputType` and
`FocusEvent.relatedTarget` is preserved. Typed `lr-input`/`lr-change` aliases carry
`detail: { value }`. `lr-invalid`
(no detail) fires once when validity fails.

**Slots:** `label`, `hint`, `error`.

**CSS parts:** `form-control`, `label` / `form-control-label` (both tokens sit on the same `<label>`
element — `label` is the historical name, `form-control-label` the one every other form component
in this family uses), `editor` (the bordered frame and the single scroll viewport), `gutter` (line
numbers, `aria-hidden`, only when `lineNumbers`), `textarea`, `hint`, `error`.

**The required marker.** `required` with a non-empty `label` paints the library's shared marker on
that label element — the one `::after` rule described under "The required-field marker" above, not
a copy of it, so `--lr-form-control-required-content`, `--lr-form-control-required-color` and
`--lr-form-control-required-offset` retune or suppress it here exactly as they do on `lr-input`.
With no label text the element is hidden and no glyph is painted.

**Themeable custom properties:** `--lr-code-editor-min-block-size` (default `--lr-size-8rem`, the
frame's and textarea's height floor), `--lr-code-editor-padding` (default `--lr-space-s`, the
gutter's block-side padding and the textarea's all-side padding), and `--lr-code-editor-font-size`
(default `--lr-font-size-m`, the gutter's and textarea's font size) — all three come from the active
`size` tier by default, and assigning one directly overrides that tier's value. Also
`--lr-code-editor-line-height` (default `1.5`, applied to both gutter and textarea so line numbers
stay aligned with their lines).
`--lr-code-editor-tab-size` (default `2`) is read by the `textarea` part's rule and drives both the
rendered tab stops and the number of spaces Tab inserts. Precedence, highest first: an explicitly
assigned `tabSize` (property or `tab-size` attribute) > a host-level `--lr-code-editor-tab-size` >
the `:host` default of `2`. The component writes the token inline on the `textarea` part only while
`tabSize` has been assigned, so an untouched `tabSize` leaves your override in charge; removing the
`tab-size` attribute hands control back to the token. A length-valued override (`40px`, `2ch`, …)
still sets the visual tab stops for literal tab characters, but is not reinterpreted as a count of
spaces — the Tab key keeps inserting `tabSize` spaces in that case.
`--lr-code-editor-border` (default `var(--lr-color-border)`) and `--lr-code-editor-fill` (default
`var(--lr-color-surface)`) retint the frame's resting border and background, independent of the
hover and invalid states below.
`--lr-code-editor-hover-border` (default `var(--lr-color-brand)`) and
`--lr-code-editor-invalid-border` (default `var(--lr-color-danger)`) retint those frame states
without changing brand/danger paint in sibling components.
The `editor` scroll frame also honors the opt-in theme-level
`--lr-theme-scrollbar-width`/`--lr-theme-scrollbar-gutter` hooks (defaults `auto`/`auto`, matching
its previous unconditional `scrollbar-width: auto`) — set either on `:root` or any ancestor for one
declaration to retheme every internal scroll container in the library, including `lr-table`,
`lr-virtual-list`, `lr-scroller`, `lr-carousel`, and `lr-code-block`.

**Known gotchas:**

- Keyboard contract (no keyboard trap, WCAG 2.1.2): Tab inserts one indent unit of spaces at the
  caret (see the tab-width precedence above);
  Shift+Tab is never captured, so reverse focus traversal always works; pressing Escape releases
  the _next_ Tab for forward traversal instead, and any other keypress (or focus leaving the
  editor) re-arms Tab indentation.
- The host gets a `data-invalid` attribute once the field has been blurred at least once and
  validity fails; the styles hang the danger border off it.
- Public/default/restored CR and CRLF sequences normalize once to LF, matching the native
  textarea's value, gutter line count, selection offsets and ordinary FormData state.

**Additional API surface:** `click()` activates the internal editing surface.
