@salemcobalt/ui 0.2.0-rc.20 → 0.2.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -37
- package/dist/{codemirror-D3Z4MYFA.js → codemirror-6REE3TUN.js} +8 -1
- package/dist/{codemirror-D3Z4MYFA.js.map → codemirror-6REE3TUN.js.map} +1 -1
- package/dist/editor.d.ts +12 -3
- package/dist/editor.js +27 -2
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +39 -2
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +110 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @salemcobalt/ui
|
|
2
2
|
|
|
3
|
-
React 19 components for the Salem Cobalt design system: `Button`, `IconButton`, `Icon` and a shipped icon set, `BroomIcon`, `Badge`, `Card`, `Stack`, `Inline`, `Heading`, `Text`, `Kbd`, `Dialog`, `FullScreenFlow`, `Tooltip`, `Menu`, `Popover`, `PriorityNav`, `Toolbar`, `Banner`, `Spinner`, `Progress`, `Tabs`, `Input`, `Textarea`, `Select`, `FileInput` and `FilePreviewDialog`, `Checkbox`, `Switch`, `Table` (with row selection), `SelectionBar`, `Toast`, `Stat`, `Chip`, `Disclosure`, `EmptyState`, `Skeleton`, `Score`, `ReasonList`, `WordDiff`, `Quote`. Requires `@salemcobalt/tokens`.
|
|
3
|
+
React 19 components for the Salem Cobalt design system: `Button`, `IconButton`, `Icon` and a shipped icon set, `Avatar` and `AvatarButton`, `BroomIcon`, `Badge`, `Card`, `Stack`, `Inline`, `Heading`, `Text`, `Kbd`, `Dialog`, `FullScreenFlow`, `Tooltip`, `Menu`, `Popover`, `PriorityNav`, `Toolbar`, `Banner`, `Spinner`, `Progress`, `Tabs`, `Input`, `Textarea`, `Select`, `FileInput` and `FilePreviewDialog`, `Checkbox`, `Switch`, `Table` (with row selection), `SelectionBar`, `Toast`, `Stat`, `Chip`, `Disclosure`, `EmptyState`, `Skeleton`, `Score`, `ReasonList`, `WordDiff`, `Quote`. Requires `@salemcobalt/tokens`.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import '@salemcobalt/tokens/css'; // custom properties, light + dark
|
|
@@ -23,42 +23,43 @@ import {
|
|
|
23
23
|
|
|
24
24
|
Styles ship as one stylesheet inside `@layer sc`, so an app's unlayered CSS always wins. Every value in it is a token custom property; theme with `data-theme="light|dark"` on `<html>` and density with `data-density="compact"` on any ancestor. Interactive behaviour (Dialog, Tooltip and Menu focus and keyboard handling, `render` composition) comes from Base UI; see `docs/adr/0001-headless-primitives.md`.
|
|
25
25
|
|
|
26
|
-
| Component
|
|
27
|
-
|
|
|
28
|
-
| `Button`
|
|
29
|
-
| `IconButton`
|
|
30
|
-
| `Icon`, `*Icon`
|
|
31
|
-
| `Badge`
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
26
|
+
| Component | Notes |
|
|
27
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `Button` | `variant` primary · secondary · ghost · danger; `size` sm · md · lg; `iconStart`/`iconEnd`; `loading` (keeps width, `aria-busy`); `fullWidth`. `shortcut={<Kbd>N</Kbd>}` shows a key hint after the label, tinted to the variant and hidden from the name (add `aria-keyshortcuts="n"`); `iconOnly` collapses to the icon in a square button without remounting (label kept as the name, shortcut hidden). The hint is not shown where there is no fine pointer (`not all and (any-pointer: fine)`: phones, tablets without a trackpad). |
|
|
29
|
+
| `IconButton` | Square Button for one icon. Required `label` (the accessible name); `variant` ghost (default) · secondary · primary · danger; `size`; `tooltip` (`true` shows `label`, or a string); `tooltipSide`; `round`; `loading`; `render={<a href />}` or a Next.js `Link` makes it a link. `dot` neutral · accent · success · warning · danger · info puts a status dot top-left with a ring in `--sc-icon-button-dot-ring` (default `surface-default`; set it to the header background); decorative, so say the status in `label`. |
|
|
30
|
+
| `Icon`, `*Icon` | 49 outline icons on a 16px grid in `currentColor`, one component each (`PlayIcon`, `ChevronDownIcon` …, listed in `iconNames`); `size` xs 12 · sm 14 · md 16 · lg 20 · xl 24 · em; decorative unless `title` is given. `Icon` wraps your own paths. |
|
|
31
|
+
| `Badge` | `tone` neutral · accent · success · warning · danger · info; `variant` soft · solid · outline; `size` sm · md; `dot`. Text only. |
|
|
32
|
+
| `Avatar`, `AvatarButton` | A person's picture, else initials on `color.avatar.fill` inside a 1px `color.avatar.ring` (the ring is the boundary: ≥ 3:1 on the fill and every surface; the tint alone is not). `name` (a name or an email; initials from the first and last words, or the email's local part; `avatarInitials` exported), `initials` override (up to 2 characters), `src` (initials until it loads and if it fails, on Base UI Avatar), `size` xs 24 · sm 32 · md 40 (default) · lg 48 (control heights, density-aware). Decorative unless `label` (`role="img"`). `AvatarButton`: required `label`, the round button for an account menu (`<Menu.Trigger render={<AvatarButton label="Account: Robin" name="Robin" size="sm" />} />`) or a link via `render`; ring 2px on hover and while open, system focus ring, 44px hit area on touch. |
|
|
33
|
+
| `Card` | `variant` outlined · elevated · filled · plain (a quiet row: no surface, a hover wash without border or shadow, selected is `accent.wash` with a 1px `accent.border`); `padding` none · sm · md · lg; `interactive`; `selected` (visual only: pair with `aria-pressed`/`aria-selected`/`aria-current`; also turns a Score inside solid); `as` or `render` to make it an `a`/`button`/`li`. |
|
|
34
|
+
| `Stack`, `Inline` | `gap` from the spacing scale (`none` … `4xl`); `align`; `justify`; `as`. `Inline` wraps unless `wrap={false}`. |
|
|
35
|
+
| `Kbd` | One key per `Kbd`; `Kbd.Group label="Command K"` groups a shortcut with a `+` (or `separator="space"`). |
|
|
36
|
+
| `Dialog` | `Root` (`open`, `defaultOpen`, `onOpenChange`, `dismissible`, `onRequestClose(reason)`: Esc, backdrop and every `Close` ask instead of closing; close by setting `open`), `Trigger`, `Content` (`size` sm · md · lg, every size clamps to the viewport minus a 16px gutter; `variant="sheet"` becomes a bottom sheet below 640px with a scrolling `Body`; `variant="fullscreen"` covers the viewport, inset 16px from 900px, with a scrolling `Body`), `Title`, `Description`, `Body`, `Actions`, `Close`. Menus, selects and tooltips opened inside render above it. |
|
|
37
|
+
| `FullScreenFlow` | A multi-step takeover on `Dialog variant="fullscreen"` (docs/adr/0006). `Root` (`open` default `true`, required `onRequestClose(reason)` for Esc and the ×, `onNext`/`onBack` for Enter and the arrow keys outside controls, `decoration`), `Header` (`title` names the dialog, `logo`, `compactTitle` below 760px, `status`, `closeLabel`; children: `Progress`), `Progress` (`chapters` `{ label, steps }[]`, `step` counted from 1, `label`; current chapter `aria-current="step"`), `Body`, `Rail` (from 1000px, `footer`) › `RailGroup` (`label`) › `RailItem` (`current`, `done`, `disabled`, `adornment`), `Viewport` (`direction` forward · back; one keyed `Step`), `Step` (`title` an `<h2>` that takes focus, `eyebrow`, `description`, `announcement` for the live region), `Footer` (`back`, `hint`, children; 44px buttons). The app owns the step state; the flow owns motion, focus and announcements. |
|
|
38
|
+
| `Toolbar` | `Root` (required `aria-label`, `wrap`, `gap`), `Group` (`aria-label`, `gap`), `Separator`, `Spacer`, `Button` (`render={<IconButton … />}` or `render={<Menu.Trigger render={…} />}` so the control joins the roving focus: Tab enters, arrows move, Tab leaves), `Link` (an anchor in the same navigation; `render={<IconButton label render={<a href />} />}` for an icon link). |
|
|
39
|
+
| `Banner` | `tone` info (default) · success · warning · danger · neutral; `icon` (or `false`); `action` slot; `dismissible` + `onDismiss` (`open` to control); warning and danger are `role="alert"`, the rest `role="status"`. |
|
|
40
|
+
| `Tabs` | `Root` (`value`, `defaultValue`, `onValueChange`), `List` (required `aria-label`, `fill`), `Tab` (`value`, `icon`, `count`, `disabled`), `Panel` (`value`, `keepMounted`). Arrow keys move, Enter/Space activates. `List variant="segmented"`: a quiet segmented control (sunken track, the active tab raised on `surface-default`, no accent, plain muted counts); `line` stays the default. |
|
|
41
|
+
| `Input`, `Textarea` | Labelled native controls on Base UI's Field: required `label` (`hideLabel` keeps it for assistive tech), `description`, `error` (sets invalid and announces), `invalid`, `required`, `disabled`, `name`, `size` sm · md · lg (Input), `iconStart`/`iconEnd` (Input), `rows`/`resize`/`variant="code"` (Textarea; monospace, tab-size 2, no spellcheck, add native `wrap="off"` for sideways scroll), `onValueChange(string)` beside native `onChange`. Every native attribute passes through: `type="date" \| "time" \| "number"` keep the same height, `list` + a `<datalist>` gives suggestions. |
|
|
42
|
+
| `Select` | Same field props; `options` as `{ value, label, disabled? }` or `{ label, options }` groups; `value`/`defaultValue`/`onValueChange` (`null` for none, shows `placeholder`; no sentinel option needed); `clearable` adds an × and Backspace/Delete on the trigger that call `onValueChange(null)`; `clearLabel`; `fullWidth`. Submits through `name` like a native select. |
|
|
43
|
+
| `FileInput` | Same field props (`label`, `hideLabel`, `description`, `error`, `invalid`, `required`, `disabled`, `name`, `size`) on a drop zone built on FilePond (docs/adr/0004): `accept` (`".pdf,.docx"`, MIME types, `image/*`, as the native attribute), `acceptLabel`, `maxFileSize` (bytes or `"10MB"`), `multiple` + `maxFiles`, `files`/`defaultFiles`/`onFilesChange(File[])`, `idleLabel`, `browseLabel`. No upload server: plain `File`s go to the callback, and with `name` to the form's `FormData` (Server Actions included). Rejected files never join the list; the reason shows in the error slot and is announced. Without `multiple`, a new file replaces the current one only once it passes. FilePond loads on mount, in its own chunk. `preview` (default `true`): the name of a PDF, plain text, Markdown or image file is a button that opens a `FilePreviewDialog` before upload; DOCX and other types keep a plain name. |
|
|
44
|
+
| `FilePreviewDialog` | Shows a local `File` before upload in a `lg` Dialog (`file`, `open`, `onOpenChange`, `finalFocus`): a PDF in the browser's viewer (an object URL re-typed `application/pdf`; at 48rem and below a link opens it in a new tab), plain text and Markdown as text, images in an `<img>`; anything else gets a note and is never framed. The object URL lives only while it is open. |
|
|
45
|
+
| `Checkbox`, `Switch` | Required `label`, `description`, `checked`/`defaultChecked`/`onCheckedChange(boolean)`, `name`/`value`, `disabled`, `required`; Checkbox adds `indeterminate`; Switch adds `labelPosition` start · end. |
|
|
46
|
+
| `Tooltip` | `content`; `children` is the trigger (an `IconButton`, `Button` or focusable `Badge`); `side` top · bottom · left · right; `align`; `delay` (300ms); `open`/`onOpenChange`; `disabled`. `Tooltip.Provider` around a row skips the delay between siblings. |
|
|
47
|
+
| `Chip` | A pill: `tone` neutral · accent · success · warning · danger · info; `size` sm · md; `icon`; `onClick` + `selected` make it a toggle button (`aria-pressed`); `onRemove` adds a labelled × (Backspace/Delete on the chip removes too); `removeLabel`; `disabled`. |
|
|
48
|
+
| `Disclosure` | A native `<details>` collapse: required `summary`, `summaryOpen`, `open`/`defaultOpen`/`onOpenChange`, `previewLines` (fades the first N lines while closed, summary moves below), `summaryPosition`; share `name` for an exclusive group. |
|
|
49
|
+
| `EmptyState` | Centred placeholder: required `title`, `description`, `icon` (24px in a muted circle), children are the actions, `size` sm · md, `as` div · section · li. |
|
|
50
|
+
| `Skeleton` | Loading placeholder, `aria-hidden`: `variant` text (default, one line) · rect · circle, `width`, `height`, `lines` (text, last line shorter), `still`. Put `aria-busy` on the region or a labelled `Spinner` beside it. |
|
|
51
|
+
| `Menu` | `Root` (`open`, `onOpenChange`, `modal`), `Trigger` (secondary Button with a chevron, or `render={<IconButton label="More" />}`), `Content` (`side`, `align`), `Item` (`icon`, `trailing`, `tone="danger"`, `disabled`), `LinkItem` (`href`), `CheckboxItem` (`checked`, `onCheckedChange`), `RadioGroup` (`value`, `onValueChange`, `label`) + `RadioItem`, `Group` (`label`), `Separator`. Arrow keys, Home/End, type-ahead, Esc and focus return come from Base UI. |
|
|
52
|
+
| `Table` selection | `useRowSelection(ids)` (ids in visible order; ids that disappear leave the selection): `selected`, `selectedIds`, `count`, `anchor`, `headerState` none · some · all, `isSelected`, `toggle`, `toggleRange`, `selectAll`, `clear`; a pure reducer underneath (`selection.*` events). `Table.Root selection` + `Table.SelectionHeader` (`label`, mixed when some) + `Table.SelectionCell label="Select …"` in a `Table.Row selectionId`. Shift+click (checkbox or row) and Shift+Space extend from the anchor; Cmd/Ctrl+click on a row toggles without reaching its `onClick`; Cmd/Ctrl+A in the table selects all, Esc clears. |
|
|
53
|
+
| `SelectionBar` | Floating bulk-action bar on Toolbar, centred at the bottom of the viewport (content width up to 48rem, gutters and the safe area respected, full width between the gutters under 640px, fade and slide-up, none under reduced motion): `count` (hidden at 0), `onClear`, children are `Toolbar.Button`s, `aria-label` ("Bulk actions"), `countLabel`, `clearLabel`. Mount it at the end of the scrolling content: while open it leaves a spacer as tall as itself so the last rows scroll clear. A polite status region announces the count; focus returns to where it came from when the bar leaves. |
|
|
54
|
+
| `Toast` | `Toast.Provider` (`timeout` 6000, `limit` 3) near the root, `useToast().add({ title, description, tone, action: { label, onClick }, timeout, priority })` below it; Base UI Toast: polite region, paused on hover and focus, F6 to reach it, bottom-end at `--sc-z-toast`. |
|
|
55
|
+
| `PriorityNav` | Priority+ primary navigation (docs/adr/0005): required `aria-label`; `items` as `{ href, label, id?, current?, badge?, badgeLabel? }` in priority order; links that fit stay inline and the rest go into a "More" disclosure (APG disclosure navigation: a `button` with `aria-expanded`, not a menu; Esc, click outside and focus leaving close it). The `current` link is never collapsed. `moreLabel`, `moreCountLabel(n)` (announced only: "More, 3 links"), `renderLink={(props) => <Link {...props} />}` for a router. Server-renders every link on one clipped line (scrolls sideways without JS), measures widths once (again after web fonts load or when keys/string labels change) and re-renders only when the inline count changes. Takes the free space in a flex row (`flex: 1 1 0%`); in a grid use a `minmax(0, 1fr)` track. The open panel sits in the top layer (Popover API), anchored to the toggle with CSS anchor positioning (a JS fallback elsewhere) and capped to the viewport. `data-overflowing` + `onOverflowChange(boolean)` let a header make room, with `overflowHysteresis` (px) so it cannot oscillate. Under 400px wide (container query) More shows only an ellipsis. `badge` (a count, or `'dot'`; `0` shows nothing) draws an accent pill or dot after the label, decorative, with `badgeLabel` ("2 suggestions to review") appended to the link's name; while the item is collapsed, More shows the collapsed items' summed count (a dot when they only have dots) and adds their `badgeLabel`s to its name ("More, 3 links, 2 suggestions to review"). Icon-only, the badge sits on More's corner. No motion. |
|
|
56
|
+
| `Popover` | Non-modal panel on Base UI Popover: `Root` (`open`, `defaultOpen`, `onOpenChange`, `modal` default false), `Trigger` (secondary Button, or `render={<IconButton label dot />}`), `Content` (`side`, `align`, `sideOffset`, `arrow`; Menu's surface, `--sc-z-dropdown`), `Title` (names the dialog), `Description`, `Close` (the × with no children, or wraps a Button). Esc and focus return come from Base UI. |
|
|
57
|
+
| `BroomIcon` | Animated 24px-grid icon for "a sweep is running": `sweeping`, `size` (Icon sizes, default `lg`, or `display` 72px with a 1.75px stroke), `title`. Inline SVG; strokes stay 1.25px at any size; holds still with the dust shown under `prefers-reduced-motion`. Inside an IconButton it takes the next icon size up. |
|
|
58
|
+
| `Score` | A number on a tinted tile or in a ring: required `value` (a number, or `null` for not scored yet: a muted dash on the neutral tile or in an empty ring, no tone, no arc, read as "Fit score: not scored"), `max` (100), `tone` neutral · accent (default) · success · warning · danger (no thresholds inside: map value to tone in the app), `variant` tile · ring (64px, decorative SVG, arc animates only on change, none under reduced motion), `size` sm 40 · md 48 (tile), `caption`, `label` ("Fit score"), `valueText`, `selected`. Assistive tech reads one visually hidden sentence, "Fit score 94 out of 100"; the visible number is `aria-hidden`, no `aria-label`. A tile turns solid when `selected` or inside `.sc-card[data-selected]`, `[aria-pressed="true"]` or `[aria-selected="true"]`. |
|
|
59
|
+
| `ReasonList` | `ReasonList` (`ul`) + `ReasonList.Item` (`li`): required `tone` for (filled square) · against (bar) · gap (hollow circle), so the marker shape carries the meaning as well as the colour; `size` sm · md; `divider` (dashed rule above). Name it with a heading via `aria-labelledby`; `<strong>` inside picks out key words. |
|
|
60
|
+
| `WordDiff` | Prose word diff: required `parts` (`{ kind: 'same' \| 'added' \| 'removed', text }[]`), `as` p · div · span, `size` sm · md, `addedLabel`/`removedLabel` ("added"/"removed"). Real `<del>`/`<ins>` with visually hidden "[removed: …]"/"[added: …]"; strike-through and underline carry the change (3:1 on the `diff.*` tints); whitespace kept exactly (`pre-wrap`), edge whitespace left outside the mark. |
|
|
61
|
+
| `Quote` | Someone's words echoed back with who said them: required `label` ("You") and `children`. A `figure` with the label as `figcaption` and the words in a `blockquote`, on `accent.wash` with an `accent.border`. |
|
|
62
|
+
| `Heading`, `Text` | `Heading size="overline"`: 12px uppercase mono with `tracking-wider`, muted unless `tone` is set; keep a real `level`. `Text family="mono"` for data next to prose (row meta, dates). |
|
|
62
63
|
|
|
63
64
|
```tsx
|
|
64
65
|
<Dialog.Root>
|
|
@@ -364,6 +364,13 @@ function mountEditor(parent, options, onChange) {
|
|
|
364
364
|
}
|
|
365
365
|
view.focus();
|
|
366
366
|
},
|
|
367
|
+
selectLine(line) {
|
|
368
|
+
if (current.disabled) return;
|
|
369
|
+
const doc = view.state.doc;
|
|
370
|
+
const { from, to } = doc.line(Math.min(Math.max(Math.trunc(line), 1), doc.lines));
|
|
371
|
+
view.dispatch({ selection: { anchor: from, head: to }, scrollIntoView: true });
|
|
372
|
+
view.focus();
|
|
373
|
+
},
|
|
367
374
|
destroy() {
|
|
368
375
|
destroyed = true;
|
|
369
376
|
view.destroy();
|
|
@@ -373,4 +380,4 @@ function mountEditor(parent, options, onChange) {
|
|
|
373
380
|
export {
|
|
374
381
|
mountEditor
|
|
375
382
|
};
|
|
376
|
-
//# sourceMappingURL=codemirror-
|
|
383
|
+
//# sourceMappingURL=codemirror-6REE3TUN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/TextEditor/codemirror.ts","../src/components/TextEditor/wordDiff.ts","../src/components/TextEditor/diffParts.ts"],"sourcesContent":["// The lazily loaded half of TextEditor: everything that imports CodeMirror. Each language is its own chunk too.\nimport { defaultKeymap, history, historyKeymap } from '@codemirror/commands';\nimport { HighlightStyle, syntaxHighlighting } from '@codemirror/language';\nimport { Compartment, EditorState, type Extension } from '@codemirror/state';\nimport { EditorView, keymap } from '@codemirror/view';\nimport { tags as t } from '@lezer/highlight';\nimport { setDiffFocus, wordDiff } from './wordDiff';\n\nexport type TextEditorLanguage = 'yaml' | 'json' | 'markdown' | 'text';\n\nexport interface EditorOptions {\n value: string;\n /** The text to diff against. No base, no diff. */\n base: string | undefined;\n /** Keep showing changes while focused, recomputed after a pause in typing. Off: hidden until blur. */\n liveDiff: boolean;\n language: TextEditorLanguage;\n disabled: boolean;\n readOnly: boolean;\n invalid: boolean;\n /** Attributes for the editable element: id and ARIA wiring from the React side. */\n attributes: Record<string, string>;\n}\n\nexport interface EditorHandle {\n update(next: EditorOptions): void;\n /** Focuses the editor, optionally with a selection (from the Textarea it replaced). */\n focus(anchor?: number, head?: number): void;\n destroy(): void;\n}\n\nconst languages: Record<Exclude<TextEditorLanguage, 'text'>, () => Promise<Extension>> = {\n yaml: () => import('@codemirror/lang-yaml').then((m) => m.yaml()),\n json: () => import('@codemirror/lang-json').then((m) => m.json()),\n markdown: () => import('./markdown').then((m) => m.markdown()),\n};\n\n/** Prose gets the phone's autocorrect, capitals and spellcheck; YAML and JSON don't (like Textarea variant=\"code\"). */\nconst isProse = (language: TextEditorLanguage) => language === 'markdown' || language === 'text';\n\n/** Touch screens: keep the caret line this far above the bottom of the viewport, clear of Safari's toolbar. */\nconst TOUCH_BOTTOM_MARGIN = 96;\nconst coarse = () => typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches;\n\n// One theme, all tokens: the box and its states match .sc-input.sc-textarea[data-variant='code'].\nconst theme = EditorView.theme({\n '&': {\n border: 'var(--sc-border-width-hairline) solid var(--sc-color-border-strong)',\n borderRadius: 'var(--sc-radius-md)',\n background: 'var(--sc-color-surface-default)',\n color: 'var(--sc-color-text-default)',\n transition:\n 'border-color var(--sc-duration-fast) var(--sc-ease-standard), background-color var(--sc-duration-fast) var(--sc-ease-standard)',\n },\n '&:hover:not([data-disabled])': { borderColor: 'var(--sc-color-text-muted)' },\n '&[data-invalid]': { borderColor: 'var(--sc-color-danger-solid)' },\n '&.cm-focused': {\n outline: 'var(--sc-border-width-focus) solid var(--sc-color-border-focus)',\n outlineOffset: '0',\n borderColor: 'var(--sc-color-border-focus)',\n },\n '&[data-disabled]': {\n background: 'var(--sc-color-surface-sunken)',\n color: 'var(--sc-color-text-subtle)',\n cursor: 'not-allowed',\n },\n '&[data-disabled] .cm-content': { cursor: 'not-allowed' },\n '.cm-scroller': {\n fontFamily: 'var(--sc-font-family-mono)',\n lineHeight: 'var(--sc-font-line-height-normal)',\n },\n '.cm-content': {\n padding: 'var(--sc-spacing-xs) 0',\n fontFamily: 'var(--sc-font-family-mono)',\n fontSize: 'calc(var(--sc-font-size-sm) * 0.95)',\n lineHeight: 'var(--sc-font-line-height-normal)',\n minBlockSize: 'calc(var(--_rows, 3) * 1lh)',\n caretColor: 'var(--sc-color-text-default)',\n },\n '.cm-line, .cm-sc-removed-lines': { paddingInline: 'var(--sc-spacing-sm)' },\n // Touch: iOS zooms the page into a focused field under 16px (as in field.css). Room under the last line, so a tap\n // there isn't near Safari's collapsed toolbar, which takes taps at the bottom edge of the screen.\n '@media (pointer: coarse)': {\n '.cm-content': {\n fontSize: 'max(16px, calc(var(--sc-font-size-sm) * 0.95))',\n paddingBlockEnd: 'var(--sc-spacing-xl)',\n },\n '.cm-scroller': { scrollPaddingBlockEnd: `${TOUCH_BOTTOM_MARGIN}px` },\n },\n});\n\nconst highlight = HighlightStyle.define([\n { tag: [t.propertyName, t.definition(t.propertyName)], color: 'var(--sc-color-accent-text)' },\n { tag: t.heading, color: 'var(--sc-color-accent-text)', fontWeight: 'var(--sc-font-weight-semibold)' },\n { tag: [t.string, t.special(t.string)], color: 'var(--sc-color-info-text)' },\n { tag: [t.number, t.bool, t.null, t.atom], color: 'var(--sc-color-warning-text)' },\n {\n tag: [t.comment, t.meta, t.punctuation, t.separator, t.processingInstruction],\n color: 'var(--sc-color-text-muted)',\n },\n { tag: [t.link, t.url], color: 'var(--sc-color-text-link)' },\n { tag: t.emphasis, fontStyle: 'italic' },\n { tag: t.strong, fontWeight: 'var(--sc-font-weight-semibold)' },\n]);\n\nconst diffExtension = (o: EditorOptions) => (o.base != null ? wordDiff(o.base, { live: o.liveDiff }) : []);\n\n/** Editable state, attributes on the editor and on the content: everything that follows the props but the diff. */\nfunction stateExtension(o: EditorOptions): Extension {\n const flag = (on: boolean) => (on ? 'true' : 'false');\n return [\n EditorView.editable.of(!o.disabled),\n EditorState.readOnly.of(o.disabled || o.readOnly),\n EditorView.editorAttributes.of({\n ...(o.invalid ? { 'data-invalid': '' } : {}),\n ...(o.disabled ? { 'data-disabled': '' } : {}),\n ...(o.readOnly ? { 'data-readonly': '' } : {}),\n }),\n EditorView.contentAttributes.of({\n spellcheck: flag(isProse(o.language)),\n autocorrect: isProse(o.language) ? 'on' : 'off',\n autocapitalize: isProse(o.language) ? 'sentences' : 'off',\n ...(o.invalid ? { 'aria-invalid': 'true' } : {}),\n ...(o.disabled ? { 'aria-disabled': 'true' } : {}),\n // Read-only stays focusable and selectable; inputmode none keeps the phone's keyboard down.\n ...(o.readOnly ? { 'aria-readonly': 'true', inputmode: 'none' } : {}),\n ...o.attributes,\n }),\n ];\n}\nconst stateKey = (o: EditorOptions) =>\n JSON.stringify([o.language, o.disabled, o.readOnly, o.invalid, o.attributes]);\n\n/** Mounts a CodeMirror view in `parent`. `onChange` gets the full text after every document change. */\nexport function mountEditor(\n parent: HTMLElement,\n options: EditorOptions,\n onChange: (value: string) => void,\n): EditorHandle {\n const diffSlot = new Compartment();\n const languageSlot = new Compartment();\n const stateSlot = new Compartment();\n let current = options;\n let destroyed = false;\n\n const loadLanguage = (language: TextEditorLanguage) => {\n if (language === 'text') return view.dispatch({ effects: languageSlot.reconfigure([]) });\n void languages[language]().then((extension) => {\n if (!destroyed && current.language === language)\n view.dispatch({ effects: languageSlot.reconfigure(extension) });\n });\n };\n\n const view = new EditorView({\n parent,\n state: EditorState.create({\n doc: options.value,\n extensions: [\n history(),\n keymap.of([...defaultKeymap, ...historyKeymap]),\n EditorView.lineWrapping,\n EditorState.tabSize.of(2),\n EditorView.scrollMargins.of(() => (coarse() ? { bottom: TOUCH_BOTTOM_MARGIN } : null)),\n theme,\n syntaxHighlighting(highlight),\n // WordDiff's own classes, so the marks and widgets look like WordDiff family=\"mono\".\n EditorView.contentAttributes.of({ class: 'sc-word-diff', 'data-family': 'mono' }),\n stateSlot.of(stateExtension(options)),\n languageSlot.of([]),\n diffSlot.of(diffExtension(options)),\n EditorView.updateListener.of((u) => {\n if (u.docChanged) onChange(u.state.doc.toString());\n }),\n ],\n }),\n });\n loadLanguage(options.language);\n\n return {\n update(next) {\n const prev = current;\n current = next;\n const effects = [];\n const diffAdded = next.base != null && prev.base == null;\n if (next.base !== prev.base || next.liveDiff !== prev.liveDiff)\n effects.push(diffSlot.reconfigure(diffExtension(next)));\n if (stateKey(next) !== stateKey(prev)) effects.push(stateSlot.reconfigure(stateExtension(next)));\n const doc = view.state.doc;\n const changes =\n next.value !== doc.toString() ? { from: 0, to: doc.length, insert: next.value } : undefined;\n if (effects.length || changes) view.dispatch(changes ? { effects, changes } : { effects });\n // A diff turned on while focused starts out believing it's blurred.\n if (diffAdded && view.hasFocus) view.dispatch({ effects: setDiffFocus.of(true) });\n if (next.language !== prev.language) loadLanguage(next.language);\n },\n focus(anchor, head) {\n if (current.disabled) return;\n if (anchor != null) {\n const max = view.state.doc.length;\n view.dispatch({ selection: { anchor: Math.min(anchor, max), head: Math.min(head ?? anchor, max) } });\n }\n view.focus();\n },\n destroy() {\n destroyed = true;\n view.destroy();\n },\n };\n}\n","/**\n * Inline word diffs inside CodeMirror 6 (job-radar docs/design/word-diff-editor.md, option (b), phase 2).\n *\n * The current document is diffed against a `base` string (`diffParts`), and the parts become decorations:\n * - added text is a mark (`<ins class=\"sc-word-diff__added\">`) over the document range, between visually hidden\n * \"[added: \" and \"]\", as WordDiff reads;\n * - each removed run is a widget, so it isn't part of the document: the caret steps over it, and it can't be edited\n * or copied. Inside a line it's an inline `<del>` at the position it was removed from. A run that holds a line\n * break is a block of `<del>` lines above the line it was removed from, so it never breaks a line in two.\n * The classes are WordDiff's, and the content carries `class=\"sc-word-diff\" data-family=\"mono\"`, so it looks the\n * same as WordDiff from `@salemcobalt/ui/styles.css`. Not `@codemirror/merge` (see the design doc).\n *\n * With `live` off (the default) the diff hides while the editor has focus, so nothing moves under the caret, and\n * shows again on blur. With `live` on it stays, mapped through each edit, and recomputes `DEBOUNCE_MS` after the\n * last one.\n */\nimport {\n Facet,\n StateEffect,\n StateField,\n type EditorState,\n type Extension,\n type Range,\n type Text,\n} from '@codemirror/state';\nimport {\n Decoration,\n EditorView,\n ViewPlugin,\n WidgetType,\n type DecorationSet,\n type ViewUpdate,\n} from '@codemirror/view';\nimport { diffParts } from './diffParts';\n\nconst edges = /^(\\s*)([\\s\\S]*?)(\\s*)$/;\n\nconst hidden = (text: string) =>\n Object.assign(document.createElement('span'), { className: 'sc-visually-hidden', textContent: text });\n\nclass RemovedWidget extends WidgetType {\n constructor(\n readonly text: string,\n readonly block: boolean,\n readonly label: string,\n ) {\n super();\n }\n override eq(other: RemovedWidget) {\n return other.text === this.text && other.block === this.block && other.label === this.label;\n }\n toDOM() {\n // Like WordDiff's Change: edge whitespace outside the <del>, so the tint hugs the words.\n const [, lead = '', core = '', trail = ''] = edges.exec(this.text) ?? [];\n const wrap = document.createElement(this.block ? 'div' : 'span');\n wrap.className = this.block ? 'cm-sc-removed-lines' : 'cm-sc-removed';\n const del = document.createElement('del');\n del.className = 'sc-word-diff__removed';\n del.append(hidden(`[${this.label}: `), core, hidden(']'));\n wrap.append(lead, del, trail);\n return wrap;\n }\n}\n\n/** Visually hidden \"[added: \" / \"]\" around an added mark, for screen readers. */\nclass LabelWidget extends WidgetType {\n constructor(readonly text: string) {\n super();\n }\n override eq(other: LabelWidget) {\n return other.text === this.text;\n }\n toDOM() {\n return hidden(this.text);\n }\n}\n\nexport interface DiffLabels {\n /** Screen reader word around added text: \"[added: …]\". Default \"added\". */\n added?: string | undefined;\n /** Screen reader word around removed text: \"[removed: …]\". Default \"removed\". */\n removed?: string | undefined;\n}\n\nconst addedMark = Decoration.mark({ tagName: 'ins', class: 'sc-word-diff__added' });\n\n/** The decorations for `doc` against `base`: added marks with their labels, and removed widgets, sorted. */\nexport function diffDecorations(doc: Text, base: string, labels: DiffLabels = {}): DecorationSet {\n const { added = 'added', removed = 'removed' } = labels;\n const ranges: Range<Decoration>[] = [];\n let pos = 0;\n for (const part of diffParts(base, doc.toString())) {\n const [, lead = '', core = ''] = edges.exec(part.text) ?? [];\n if (part.kind === 'same') pos += part.text.length;\n else if (part.kind === 'added') {\n if (core) {\n const from = pos + lead.length;\n const to = from + core.length;\n ranges.push(\n Decoration.widget({ widget: new LabelWidget(`[${added}: `), side: -1, kind: 'label' }).range(from),\n addedMark.range(from, to),\n Decoration.widget({ widget: new LabelWidget(']'), side: 1, kind: 'label' }).range(to),\n );\n }\n pos += part.text.length;\n } else if (core) {\n if (!part.text.includes('\\n')) {\n const widget = new RemovedWidget(part.text, false, removed);\n ranges.push(Decoration.widget({ widget, side: -1, kind: 'removed' }).range(pos));\n } else {\n // A run with a line break is whole lines next to the line it was removed from: below it when removed from\n // its end, above it otherwise. Never inline, so it can't break a line in two.\n const line = doc.lineAt(pos);\n const below = pos === line.to && line.to > line.from;\n const widget = new RemovedWidget(part.text.replace(/^\\n+|\\n+$/g, ''), true, removed);\n ranges.push(\n Decoration.widget({ widget, side: below ? 1 : -1, block: true, kind: 'removed' }).range(\n below ? line.to : line.from,\n ),\n );\n }\n }\n }\n return Decoration.set(ranges, true);\n}\n\ninterface DiffConfig {\n base: string;\n live: boolean;\n labels: DiffLabels;\n}\ninterface DiffState {\n decorations: DecorationSet;\n focused: boolean;\n /** Mapped through edits since the last diff; the debounce will recompute. */\n stale: boolean;\n}\n\n/** Quiet time after the last edit before a live diff recomputes. */\nexport const DEBOUNCE_MS = 300;\n\nconst config = Facet.define<DiffConfig, DiffConfig>({\n combine: (values) => values[0] ?? { base: '', live: false, labels: {} },\n});\n/** The editor gained (true) or lost focus. Exported so tests can focus a bare EditorState. */\nexport const setDiffFocus = StateEffect.define<boolean>();\nconst refresh = StateEffect.define<null>();\n\nconst compute = (state: EditorState, focused: boolean): DiffState => {\n const { base, labels } = state.facet(config);\n return { decorations: diffDecorations(state.doc, base, labels), focused, stale: false };\n};\n\nconst diffField = StateField.define<DiffState>({\n create: (state) => compute(state, false),\n update(value, tr) {\n let focused = value.focused;\n let refreshed = false;\n for (const e of tr.effects) {\n if (e.is(setDiffFocus)) focused = e.value;\n if (e.is(refresh)) refreshed = true;\n }\n const next = tr.state.facet(config);\n // Not live: nothing moves under the caret while typing. The diff shows again on blur.\n if (focused && !next.live) return { decorations: Decoration.none, focused, stale: false };\n const configChanged = next !== tr.startState.facet(config);\n // Live and typing: keep the old marks where they were; the debounce recomputes.\n if (focused && tr.docChanged && !refreshed && !configChanged)\n return { decorations: value.decorations.map(tr.changes), focused, stale: true };\n if (refreshed || configChanged || tr.docChanged || focused !== value.focused)\n return compute(tr.state, focused);\n return value;\n },\n // Block widgets have to come from state, not a view plugin.\n provide: (field) => EditorView.decorations.from(field, (value) => value.decorations),\n});\n\n// Schedules the recompute of a stale (live) diff, restarting on every edit.\nconst debounce = ViewPlugin.fromClass(\n class {\n timer: ReturnType<typeof setTimeout> | undefined;\n constructor(readonly view: EditorView) {}\n update(u: ViewUpdate) {\n if (!u.docChanged || !u.state.field(diffField).stale) return;\n clearTimeout(this.timer);\n this.timer = setTimeout(() => this.view.dispatch({ effects: refresh.of(null) }), DEBOUNCE_MS);\n }\n destroy() {\n clearTimeout(this.timer);\n }\n },\n);\n\nconst theme = EditorView.theme({\n '.cm-sc-removed-lines': { paddingInline: 'var(--sc-spacing-sm)', whiteSpace: 'pre-wrap' },\n});\n\n/**\n * Shows the document's changes against `base` inline. Reconfigure it (in a Compartment) to change the base, `live`\n * or the labels; a new configuration recomputes at once.\n */\nexport function wordDiff(base: string, options: { live?: boolean; labels?: DiffLabels } = {}): Extension {\n return [\n config.of({ base, live: options.live ?? false, labels: options.labels ?? {} }),\n diffField,\n debounce,\n EditorView.focusChangeEffect.of((_state, focusing) => setDiffFocus.of(focusing)),\n theme,\n ];\n}\n\n/** Test seam: the decorations in `state`, or none when the extension isn't on. */\nexport const wordDiffDecorations = (state: EditorState): DecorationSet =>\n state.field(diffField, false)?.decorations ?? Decoration.none;\n","/**\n * The word-diff engine (job-radar docs/design/word-diff-editor.md, \"Engine\"): lines first (Myers, `diffLines`),\n * then each run of changed lines paired line by line, with `diffWordsWithSpace` on each pair. A pair that changed\n * mostly, or is large, shows as the whole line removed and added; lines left over on either side are whole lines.\n * Pairing per line keeps every word-level change inside one line, so a removed run never spans a line break.\n * No CodeMirror here, so a plain Textarea + WordDiff can use it too.\n */\nimport { diffLines, diffWordsWithSpace } from 'diff';\nimport type { WordDiffPart } from '../WordDiff/WordDiff';\n\n/** Above this share of changed characters, a changed line shows as a whole line removed and added. */\nexport const WORD_DIFF_MAX_CHANGE = 0.6;\n/** Above this many characters (both sides), a changed line shows whole, without a word pass. */\nexport const WORD_DIFF_MAX_CHARS = 4000;\n\n/** One changed line against its replacement, as word parts, or whole when a word diff wouldn't help. */\nfunction pair(a: string, b: string): WordDiffPart[] {\n if (a.length + b.length <= WORD_DIFF_MAX_CHARS) {\n const parts = diffWordsWithSpace(a, b).map((p): WordDiffPart => ({\n kind: p.added ? 'added' : p.removed ? 'removed' : 'same',\n text: p.value,\n }));\n const changed = parts.reduce((n, p) => n + (p.kind === 'same' ? 0 : p.text.length), 0);\n if (changed / (a.length + b.length) <= WORD_DIFF_MAX_CHANGE) return parts;\n }\n return [\n { kind: 'removed', text: a },\n { kind: 'added', text: b },\n ];\n}\n\nconst lines = (text: string) => text.match(/[^\\n]*\\n|[^\\n]+$/g) ?? [];\n\n/** A run of changed lines: pairs first, then the lines left over on either side, whole. */\nfunction hunk(a: string, b: string): WordDiffPart[] {\n const removed = lines(a);\n const added = lines(b);\n const n = Math.min(removed.length, added.length);\n const out: WordDiffPart[] = [];\n for (let i = 0; i < n; i++) out.push(...pair(removed[i]!, added[i]!));\n out.push({ kind: 'removed', text: removed.slice(n).join('') });\n out.push({ kind: 'added', text: added.slice(n).join('') });\n return out;\n}\n\n/** `base` → `text` as WordDiff parts. Joining the `same` and `added` texts gives `text` exactly. */\nexport function diffParts(base: string, text: string): WordDiffPart[] {\n const out: WordDiffPart[] = [];\n const push = ({ kind, text }: WordDiffPart) => {\n if (!text) return;\n const last = out.at(-1);\n if (last?.kind === kind) last.text += text;\n else out.push({ kind, text });\n };\n const changes = diffLines(base, text);\n for (let i = 0; i < changes.length;) {\n const c = changes[i]!;\n if (!c.added && !c.removed) {\n push({ kind: 'same', text: c.value });\n i++;\n continue;\n }\n let removed = '';\n let added = '';\n for (; i < changes.length && (changes[i]!.added || changes[i]!.removed); i++) {\n if (changes[i]!.added) added += changes[i]!.value;\n else removed += changes[i]!.value;\n }\n hunk(removed, added).forEach(push);\n }\n return out;\n}\n"],"mappings":"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","names":["EditorView","text","theme","EditorView"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/TextEditor/codemirror.ts","../src/components/TextEditor/wordDiff.ts","../src/components/TextEditor/diffParts.ts"],"sourcesContent":["// The lazily loaded half of TextEditor: everything that imports CodeMirror. Each language is its own chunk too.\nimport { defaultKeymap, history, historyKeymap } from '@codemirror/commands';\nimport { HighlightStyle, syntaxHighlighting } from '@codemirror/language';\nimport { Compartment, EditorState, type Extension } from '@codemirror/state';\nimport { EditorView, keymap } from '@codemirror/view';\nimport { tags as t } from '@lezer/highlight';\nimport { setDiffFocus, wordDiff } from './wordDiff';\n\nexport type TextEditorLanguage = 'yaml' | 'json' | 'markdown' | 'text';\n\nexport interface EditorOptions {\n value: string;\n /** The text to diff against. No base, no diff. */\n base: string | undefined;\n /** Keep showing changes while focused, recomputed after a pause in typing. Off: hidden until blur. */\n liveDiff: boolean;\n language: TextEditorLanguage;\n disabled: boolean;\n readOnly: boolean;\n invalid: boolean;\n /** Attributes for the editable element: id and ARIA wiring from the React side. */\n attributes: Record<string, string>;\n}\n\nexport interface EditorHandle {\n update(next: EditorOptions): void;\n /** Focuses the editor, optionally with a selection (from the Textarea it replaced). */\n focus(anchor?: number, head?: number): void;\n /** Selects line `line` (1-based, clamped to the document), scrolls it into view and focuses the editor. */\n selectLine(line: number): void;\n destroy(): void;\n}\n\nconst languages: Record<Exclude<TextEditorLanguage, 'text'>, () => Promise<Extension>> = {\n yaml: () => import('@codemirror/lang-yaml').then((m) => m.yaml()),\n json: () => import('@codemirror/lang-json').then((m) => m.json()),\n markdown: () => import('./markdown').then((m) => m.markdown()),\n};\n\n/** Prose gets the phone's autocorrect, capitals and spellcheck; YAML and JSON don't (like Textarea variant=\"code\"). */\nconst isProse = (language: TextEditorLanguage) => language === 'markdown' || language === 'text';\n\n/** Touch screens: keep the caret line this far above the bottom of the viewport, clear of Safari's toolbar. */\nconst TOUCH_BOTTOM_MARGIN = 96;\nconst coarse = () => typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches;\n\n// One theme, all tokens: the box and its states match .sc-input.sc-textarea[data-variant='code'].\nconst theme = EditorView.theme({\n '&': {\n border: 'var(--sc-border-width-hairline) solid var(--sc-color-border-strong)',\n borderRadius: 'var(--sc-radius-md)',\n background: 'var(--sc-color-surface-default)',\n color: 'var(--sc-color-text-default)',\n transition:\n 'border-color var(--sc-duration-fast) var(--sc-ease-standard), background-color var(--sc-duration-fast) var(--sc-ease-standard)',\n },\n '&:hover:not([data-disabled])': { borderColor: 'var(--sc-color-text-muted)' },\n '&[data-invalid]': { borderColor: 'var(--sc-color-danger-solid)' },\n '&.cm-focused': {\n outline: 'var(--sc-border-width-focus) solid var(--sc-color-border-focus)',\n outlineOffset: '0',\n borderColor: 'var(--sc-color-border-focus)',\n },\n '&[data-disabled]': {\n background: 'var(--sc-color-surface-sunken)',\n color: 'var(--sc-color-text-subtle)',\n cursor: 'not-allowed',\n },\n '&[data-disabled] .cm-content': { cursor: 'not-allowed' },\n '.cm-scroller': {\n fontFamily: 'var(--sc-font-family-mono)',\n lineHeight: 'var(--sc-font-line-height-normal)',\n },\n '.cm-content': {\n padding: 'var(--sc-spacing-xs) 0',\n fontFamily: 'var(--sc-font-family-mono)',\n fontSize: 'calc(var(--sc-font-size-sm) * 0.95)',\n lineHeight: 'var(--sc-font-line-height-normal)',\n minBlockSize: 'calc(var(--_rows, 3) * 1lh)',\n caretColor: 'var(--sc-color-text-default)',\n },\n '.cm-line, .cm-sc-removed-lines': { paddingInline: 'var(--sc-spacing-sm)' },\n // Touch: iOS zooms the page into a focused field under 16px (as in field.css). Room under the last line, so a tap\n // there isn't near Safari's collapsed toolbar, which takes taps at the bottom edge of the screen.\n '@media (pointer: coarse)': {\n '.cm-content': {\n fontSize: 'max(16px, calc(var(--sc-font-size-sm) * 0.95))',\n paddingBlockEnd: 'var(--sc-spacing-xl)',\n },\n '.cm-scroller': { scrollPaddingBlockEnd: `${TOUCH_BOTTOM_MARGIN}px` },\n },\n});\n\nconst highlight = HighlightStyle.define([\n { tag: [t.propertyName, t.definition(t.propertyName)], color: 'var(--sc-color-accent-text)' },\n { tag: t.heading, color: 'var(--sc-color-accent-text)', fontWeight: 'var(--sc-font-weight-semibold)' },\n { tag: [t.string, t.special(t.string)], color: 'var(--sc-color-info-text)' },\n { tag: [t.number, t.bool, t.null, t.atom], color: 'var(--sc-color-warning-text)' },\n {\n tag: [t.comment, t.meta, t.punctuation, t.separator, t.processingInstruction],\n color: 'var(--sc-color-text-muted)',\n },\n { tag: [t.link, t.url], color: 'var(--sc-color-text-link)' },\n { tag: t.emphasis, fontStyle: 'italic' },\n { tag: t.strong, fontWeight: 'var(--sc-font-weight-semibold)' },\n]);\n\nconst diffExtension = (o: EditorOptions) => (o.base != null ? wordDiff(o.base, { live: o.liveDiff }) : []);\n\n/** Editable state, attributes on the editor and on the content: everything that follows the props but the diff. */\nfunction stateExtension(o: EditorOptions): Extension {\n const flag = (on: boolean) => (on ? 'true' : 'false');\n return [\n EditorView.editable.of(!o.disabled),\n EditorState.readOnly.of(o.disabled || o.readOnly),\n EditorView.editorAttributes.of({\n ...(o.invalid ? { 'data-invalid': '' } : {}),\n ...(o.disabled ? { 'data-disabled': '' } : {}),\n ...(o.readOnly ? { 'data-readonly': '' } : {}),\n }),\n EditorView.contentAttributes.of({\n spellcheck: flag(isProse(o.language)),\n autocorrect: isProse(o.language) ? 'on' : 'off',\n autocapitalize: isProse(o.language) ? 'sentences' : 'off',\n ...(o.invalid ? { 'aria-invalid': 'true' } : {}),\n ...(o.disabled ? { 'aria-disabled': 'true' } : {}),\n // Read-only stays focusable and selectable; inputmode none keeps the phone's keyboard down.\n ...(o.readOnly ? { 'aria-readonly': 'true', inputmode: 'none' } : {}),\n ...o.attributes,\n }),\n ];\n}\nconst stateKey = (o: EditorOptions) =>\n JSON.stringify([o.language, o.disabled, o.readOnly, o.invalid, o.attributes]);\n\n/** Mounts a CodeMirror view in `parent`. `onChange` gets the full text after every document change. */\nexport function mountEditor(\n parent: HTMLElement,\n options: EditorOptions,\n onChange: (value: string) => void,\n): EditorHandle {\n const diffSlot = new Compartment();\n const languageSlot = new Compartment();\n const stateSlot = new Compartment();\n let current = options;\n let destroyed = false;\n\n const loadLanguage = (language: TextEditorLanguage) => {\n if (language === 'text') return view.dispatch({ effects: languageSlot.reconfigure([]) });\n void languages[language]().then((extension) => {\n if (!destroyed && current.language === language)\n view.dispatch({ effects: languageSlot.reconfigure(extension) });\n });\n };\n\n const view = new EditorView({\n parent,\n state: EditorState.create({\n doc: options.value,\n extensions: [\n history(),\n keymap.of([...defaultKeymap, ...historyKeymap]),\n EditorView.lineWrapping,\n EditorState.tabSize.of(2),\n EditorView.scrollMargins.of(() => (coarse() ? { bottom: TOUCH_BOTTOM_MARGIN } : null)),\n theme,\n syntaxHighlighting(highlight),\n // WordDiff's own classes, so the marks and widgets look like WordDiff family=\"mono\".\n EditorView.contentAttributes.of({ class: 'sc-word-diff', 'data-family': 'mono' }),\n stateSlot.of(stateExtension(options)),\n languageSlot.of([]),\n diffSlot.of(diffExtension(options)),\n EditorView.updateListener.of((u) => {\n if (u.docChanged) onChange(u.state.doc.toString());\n }),\n ],\n }),\n });\n loadLanguage(options.language);\n\n return {\n update(next) {\n const prev = current;\n current = next;\n const effects = [];\n const diffAdded = next.base != null && prev.base == null;\n if (next.base !== prev.base || next.liveDiff !== prev.liveDiff)\n effects.push(diffSlot.reconfigure(diffExtension(next)));\n if (stateKey(next) !== stateKey(prev)) effects.push(stateSlot.reconfigure(stateExtension(next)));\n const doc = view.state.doc;\n const changes =\n next.value !== doc.toString() ? { from: 0, to: doc.length, insert: next.value } : undefined;\n if (effects.length || changes) view.dispatch(changes ? { effects, changes } : { effects });\n // A diff turned on while focused starts out believing it's blurred.\n if (diffAdded && view.hasFocus) view.dispatch({ effects: setDiffFocus.of(true) });\n if (next.language !== prev.language) loadLanguage(next.language);\n },\n focus(anchor, head) {\n if (current.disabled) return;\n if (anchor != null) {\n const max = view.state.doc.length;\n view.dispatch({ selection: { anchor: Math.min(anchor, max), head: Math.min(head ?? anchor, max) } });\n }\n view.focus();\n },\n selectLine(line) {\n if (current.disabled) return;\n const doc = view.state.doc;\n const { from, to } = doc.line(Math.min(Math.max(Math.trunc(line), 1), doc.lines));\n view.dispatch({ selection: { anchor: from, head: to }, scrollIntoView: true });\n view.focus();\n },\n destroy() {\n destroyed = true;\n view.destroy();\n },\n };\n}\n","/**\n * Inline word diffs inside CodeMirror 6 (job-radar docs/design/word-diff-editor.md, option (b), phase 2).\n *\n * The current document is diffed against a `base` string (`diffParts`), and the parts become decorations:\n * - added text is a mark (`<ins class=\"sc-word-diff__added\">`) over the document range, between visually hidden\n * \"[added: \" and \"]\", as WordDiff reads;\n * - each removed run is a widget, so it isn't part of the document: the caret steps over it, and it can't be edited\n * or copied. Inside a line it's an inline `<del>` at the position it was removed from. A run that holds a line\n * break is a block of `<del>` lines above the line it was removed from, so it never breaks a line in two.\n * The classes are WordDiff's, and the content carries `class=\"sc-word-diff\" data-family=\"mono\"`, so it looks the\n * same as WordDiff from `@salemcobalt/ui/styles.css`. Not `@codemirror/merge` (see the design doc).\n *\n * With `live` off (the default) the diff hides while the editor has focus, so nothing moves under the caret, and\n * shows again on blur. With `live` on it stays, mapped through each edit, and recomputes `DEBOUNCE_MS` after the\n * last one.\n */\nimport {\n Facet,\n StateEffect,\n StateField,\n type EditorState,\n type Extension,\n type Range,\n type Text,\n} from '@codemirror/state';\nimport {\n Decoration,\n EditorView,\n ViewPlugin,\n WidgetType,\n type DecorationSet,\n type ViewUpdate,\n} from '@codemirror/view';\nimport { diffParts } from './diffParts';\n\nconst edges = /^(\\s*)([\\s\\S]*?)(\\s*)$/;\n\nconst hidden = (text: string) =>\n Object.assign(document.createElement('span'), { className: 'sc-visually-hidden', textContent: text });\n\nclass RemovedWidget extends WidgetType {\n constructor(\n readonly text: string,\n readonly block: boolean,\n readonly label: string,\n ) {\n super();\n }\n override eq(other: RemovedWidget) {\n return other.text === this.text && other.block === this.block && other.label === this.label;\n }\n toDOM() {\n // Like WordDiff's Change: edge whitespace outside the <del>, so the tint hugs the words.\n const [, lead = '', core = '', trail = ''] = edges.exec(this.text) ?? [];\n const wrap = document.createElement(this.block ? 'div' : 'span');\n wrap.className = this.block ? 'cm-sc-removed-lines' : 'cm-sc-removed';\n const del = document.createElement('del');\n del.className = 'sc-word-diff__removed';\n del.append(hidden(`[${this.label}: `), core, hidden(']'));\n wrap.append(lead, del, trail);\n return wrap;\n }\n}\n\n/** Visually hidden \"[added: \" / \"]\" around an added mark, for screen readers. */\nclass LabelWidget extends WidgetType {\n constructor(readonly text: string) {\n super();\n }\n override eq(other: LabelWidget) {\n return other.text === this.text;\n }\n toDOM() {\n return hidden(this.text);\n }\n}\n\nexport interface DiffLabels {\n /** Screen reader word around added text: \"[added: …]\". Default \"added\". */\n added?: string | undefined;\n /** Screen reader word around removed text: \"[removed: …]\". Default \"removed\". */\n removed?: string | undefined;\n}\n\nconst addedMark = Decoration.mark({ tagName: 'ins', class: 'sc-word-diff__added' });\n\n/** The decorations for `doc` against `base`: added marks with their labels, and removed widgets, sorted. */\nexport function diffDecorations(doc: Text, base: string, labels: DiffLabels = {}): DecorationSet {\n const { added = 'added', removed = 'removed' } = labels;\n const ranges: Range<Decoration>[] = [];\n let pos = 0;\n for (const part of diffParts(base, doc.toString())) {\n const [, lead = '', core = ''] = edges.exec(part.text) ?? [];\n if (part.kind === 'same') pos += part.text.length;\n else if (part.kind === 'added') {\n if (core) {\n const from = pos + lead.length;\n const to = from + core.length;\n ranges.push(\n Decoration.widget({ widget: new LabelWidget(`[${added}: `), side: -1, kind: 'label' }).range(from),\n addedMark.range(from, to),\n Decoration.widget({ widget: new LabelWidget(']'), side: 1, kind: 'label' }).range(to),\n );\n }\n pos += part.text.length;\n } else if (core) {\n if (!part.text.includes('\\n')) {\n const widget = new RemovedWidget(part.text, false, removed);\n ranges.push(Decoration.widget({ widget, side: -1, kind: 'removed' }).range(pos));\n } else {\n // A run with a line break is whole lines next to the line it was removed from: below it when removed from\n // its end, above it otherwise. Never inline, so it can't break a line in two.\n const line = doc.lineAt(pos);\n const below = pos === line.to && line.to > line.from;\n const widget = new RemovedWidget(part.text.replace(/^\\n+|\\n+$/g, ''), true, removed);\n ranges.push(\n Decoration.widget({ widget, side: below ? 1 : -1, block: true, kind: 'removed' }).range(\n below ? line.to : line.from,\n ),\n );\n }\n }\n }\n return Decoration.set(ranges, true);\n}\n\ninterface DiffConfig {\n base: string;\n live: boolean;\n labels: DiffLabels;\n}\ninterface DiffState {\n decorations: DecorationSet;\n focused: boolean;\n /** Mapped through edits since the last diff; the debounce will recompute. */\n stale: boolean;\n}\n\n/** Quiet time after the last edit before a live diff recomputes. */\nexport const DEBOUNCE_MS = 300;\n\nconst config = Facet.define<DiffConfig, DiffConfig>({\n combine: (values) => values[0] ?? { base: '', live: false, labels: {} },\n});\n/** The editor gained (true) or lost focus. Exported so tests can focus a bare EditorState. */\nexport const setDiffFocus = StateEffect.define<boolean>();\nconst refresh = StateEffect.define<null>();\n\nconst compute = (state: EditorState, focused: boolean): DiffState => {\n const { base, labels } = state.facet(config);\n return { decorations: diffDecorations(state.doc, base, labels), focused, stale: false };\n};\n\nconst diffField = StateField.define<DiffState>({\n create: (state) => compute(state, false),\n update(value, tr) {\n let focused = value.focused;\n let refreshed = false;\n for (const e of tr.effects) {\n if (e.is(setDiffFocus)) focused = e.value;\n if (e.is(refresh)) refreshed = true;\n }\n const next = tr.state.facet(config);\n // Not live: nothing moves under the caret while typing. The diff shows again on blur.\n if (focused && !next.live) return { decorations: Decoration.none, focused, stale: false };\n const configChanged = next !== tr.startState.facet(config);\n // Live and typing: keep the old marks where they were; the debounce recomputes.\n if (focused && tr.docChanged && !refreshed && !configChanged)\n return { decorations: value.decorations.map(tr.changes), focused, stale: true };\n if (refreshed || configChanged || tr.docChanged || focused !== value.focused)\n return compute(tr.state, focused);\n return value;\n },\n // Block widgets have to come from state, not a view plugin.\n provide: (field) => EditorView.decorations.from(field, (value) => value.decorations),\n});\n\n// Schedules the recompute of a stale (live) diff, restarting on every edit.\nconst debounce = ViewPlugin.fromClass(\n class {\n timer: ReturnType<typeof setTimeout> | undefined;\n constructor(readonly view: EditorView) {}\n update(u: ViewUpdate) {\n if (!u.docChanged || !u.state.field(diffField).stale) return;\n clearTimeout(this.timer);\n this.timer = setTimeout(() => this.view.dispatch({ effects: refresh.of(null) }), DEBOUNCE_MS);\n }\n destroy() {\n clearTimeout(this.timer);\n }\n },\n);\n\nconst theme = EditorView.theme({\n '.cm-sc-removed-lines': { paddingInline: 'var(--sc-spacing-sm)', whiteSpace: 'pre-wrap' },\n});\n\n/**\n * Shows the document's changes against `base` inline. Reconfigure it (in a Compartment) to change the base, `live`\n * or the labels; a new configuration recomputes at once.\n */\nexport function wordDiff(base: string, options: { live?: boolean; labels?: DiffLabels } = {}): Extension {\n return [\n config.of({ base, live: options.live ?? false, labels: options.labels ?? {} }),\n diffField,\n debounce,\n EditorView.focusChangeEffect.of((_state, focusing) => setDiffFocus.of(focusing)),\n theme,\n ];\n}\n\n/** Test seam: the decorations in `state`, or none when the extension isn't on. */\nexport const wordDiffDecorations = (state: EditorState): DecorationSet =>\n state.field(diffField, false)?.decorations ?? Decoration.none;\n","/**\n * The word-diff engine (job-radar docs/design/word-diff-editor.md, \"Engine\"): lines first (Myers, `diffLines`),\n * then each run of changed lines paired line by line, with `diffWordsWithSpace` on each pair. A pair that changed\n * mostly, or is large, shows as the whole line removed and added; lines left over on either side are whole lines.\n * Pairing per line keeps every word-level change inside one line, so a removed run never spans a line break.\n * No CodeMirror here, so a plain Textarea + WordDiff can use it too.\n */\nimport { diffLines, diffWordsWithSpace } from 'diff';\nimport type { WordDiffPart } from '../WordDiff/WordDiff';\n\n/** Above this share of changed characters, a changed line shows as a whole line removed and added. */\nexport const WORD_DIFF_MAX_CHANGE = 0.6;\n/** Above this many characters (both sides), a changed line shows whole, without a word pass. */\nexport const WORD_DIFF_MAX_CHARS = 4000;\n\n/** One changed line against its replacement, as word parts, or whole when a word diff wouldn't help. */\nfunction pair(a: string, b: string): WordDiffPart[] {\n if (a.length + b.length <= WORD_DIFF_MAX_CHARS) {\n const parts = diffWordsWithSpace(a, b).map((p): WordDiffPart => ({\n kind: p.added ? 'added' : p.removed ? 'removed' : 'same',\n text: p.value,\n }));\n const changed = parts.reduce((n, p) => n + (p.kind === 'same' ? 0 : p.text.length), 0);\n if (changed / (a.length + b.length) <= WORD_DIFF_MAX_CHANGE) return parts;\n }\n return [\n { kind: 'removed', text: a },\n { kind: 'added', text: b },\n ];\n}\n\nconst lines = (text: string) => text.match(/[^\\n]*\\n|[^\\n]+$/g) ?? [];\n\n/** A run of changed lines: pairs first, then the lines left over on either side, whole. */\nfunction hunk(a: string, b: string): WordDiffPart[] {\n const removed = lines(a);\n const added = lines(b);\n const n = Math.min(removed.length, added.length);\n const out: WordDiffPart[] = [];\n for (let i = 0; i < n; i++) out.push(...pair(removed[i]!, added[i]!));\n out.push({ kind: 'removed', text: removed.slice(n).join('') });\n out.push({ kind: 'added', text: added.slice(n).join('') });\n return out;\n}\n\n/** `base` → `text` as WordDiff parts. Joining the `same` and `added` texts gives `text` exactly. */\nexport function diffParts(base: string, text: string): WordDiffPart[] {\n const out: WordDiffPart[] = [];\n const push = ({ kind, text }: WordDiffPart) => {\n if (!text) return;\n const last = out.at(-1);\n if (last?.kind === kind) last.text += text;\n else out.push({ kind, text });\n };\n const changes = diffLines(base, text);\n for (let i = 0; i < changes.length;) {\n const c = changes[i]!;\n if (!c.added && !c.removed) {\n push({ kind: 'same', text: c.value });\n i++;\n continue;\n }\n let removed = '';\n let added = '';\n for (; i < changes.length && (changes[i]!.added || changes[i]!.removed); i++) {\n if (changes[i]!.added) added += changes[i]!.value;\n else removed += changes[i]!.value;\n }\n hunk(removed, added).forEach(push);\n }\n return out;\n}\n"],"mappings":";;;AACA,SAAS,eAAe,SAAS,qBAAqB;AACtD,SAAS,gBAAgB,0BAA0B;AACnD,SAAS,aAAa,mBAAmC;AACzD,SAAS,cAAAA,aAAY,cAAc;AACnC,SAAS,QAAQ,SAAS;;;ACW1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;ACzBP,SAAS,WAAW,0BAA0B;AAIvC,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB;AAGnC,SAAS,KAAK,GAAW,GAA2B;AAClD,MAAI,EAAE,SAAS,EAAE,UAAU,qBAAqB;AAC9C,UAAM,QAAQ,mBAAmB,GAAG,CAAC,EAAE,IAAI,CAAC,OAAqB;AAAA,MAC/D,MAAM,EAAE,QAAQ,UAAU,EAAE,UAAU,YAAY;AAAA,MAClD,MAAM,EAAE;AAAA,IACV,EAAE;AACF,UAAM,UAAU,MAAM,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,SAAS,SAAS,IAAI,EAAE,KAAK,SAAS,CAAC;AACrF,QAAI,WAAW,EAAE,SAAS,EAAE,WAAW,qBAAsB,QAAO;AAAA,EACtE;AACA,SAAO;AAAA,IACL,EAAE,MAAM,WAAW,MAAM,EAAE;AAAA,IAC3B,EAAE,MAAM,SAAS,MAAM,EAAE;AAAA,EAC3B;AACF;AAEA,IAAM,QAAQ,CAAC,SAAiB,KAAK,MAAM,mBAAmB,KAAK,CAAC;AAGpE,SAAS,KAAK,GAAW,GAA2B;AAClD,QAAM,UAAU,MAAM,CAAC;AACvB,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,IAAI,KAAK,IAAI,QAAQ,QAAQ,MAAM,MAAM;AAC/C,QAAM,MAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAI,KAAK,GAAG,KAAK,QAAQ,CAAC,GAAI,MAAM,CAAC,CAAE,CAAC;AACpE,MAAI,KAAK,EAAE,MAAM,WAAW,MAAM,QAAQ,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;AAC7D,MAAI,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;AACzD,SAAO;AACT;AAGO,SAAS,UAAU,MAAc,MAA8B;AACpE,QAAM,MAAsB,CAAC;AAC7B,QAAM,OAAO,CAAC,EAAE,MAAM,MAAAC,MAAK,MAAoB;AAC7C,QAAI,CAACA,MAAM;AACX,UAAM,OAAO,IAAI,GAAG,EAAE;AACtB,QAAI,MAAM,SAAS,KAAM,MAAK,QAAQA;AAAA,QACjC,KAAI,KAAK,EAAE,MAAM,MAAAA,MAAK,CAAC;AAAA,EAC9B;AACA,QAAM,UAAU,UAAU,MAAM,IAAI;AACpC,WAAS,IAAI,GAAG,IAAI,QAAQ,UAAS;AACnC,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS;AAC1B,WAAK,EAAE,MAAM,QAAQ,MAAM,EAAE,MAAM,CAAC;AACpC;AACA;AAAA,IACF;AACA,QAAI,UAAU;AACd,QAAI,QAAQ;AACZ,WAAO,IAAI,QAAQ,WAAW,QAAQ,CAAC,EAAG,SAAS,QAAQ,CAAC,EAAG,UAAU,KAAK;AAC5E,UAAI,QAAQ,CAAC,EAAG,MAAO,UAAS,QAAQ,CAAC,EAAG;AAAA,UACvC,YAAW,QAAQ,CAAC,EAAG;AAAA,IAC9B;AACA,SAAK,SAAS,KAAK,EAAE,QAAQ,IAAI;AAAA,EACnC;AACA,SAAO;AACT;;;ADpCA,IAAM,QAAQ;AAEd,IAAM,SAAS,CAAC,SACd,OAAO,OAAO,SAAS,cAAc,MAAM,GAAG,EAAE,WAAW,sBAAsB,aAAa,KAAK,CAAC;AAEtG,IAAM,gBAAN,cAA4B,WAAW;AAAA,EACrC,YACW,MACA,OACA,OACT;AACA,UAAM;AAJG;AACA;AACA;AAAA,EAGX;AAAA,EALW;AAAA,EACA;AAAA,EACA;AAAA,EAIF,GAAG,OAAsB;AAChC,WAAO,MAAM,SAAS,KAAK,QAAQ,MAAM,UAAU,KAAK,SAAS,MAAM,UAAU,KAAK;AAAA,EACxF;AAAA,EACA,QAAQ;AAEN,UAAM,CAAC,EAAE,OAAO,IAAI,OAAO,IAAI,QAAQ,EAAE,IAAI,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC;AACvE,UAAM,OAAO,SAAS,cAAc,KAAK,QAAQ,QAAQ,MAAM;AAC/D,SAAK,YAAY,KAAK,QAAQ,wBAAwB;AACtD,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,YAAY;AAChB,QAAI,OAAO,OAAO,IAAI,KAAK,KAAK,IAAI,GAAG,MAAM,OAAO,GAAG,CAAC;AACxD,SAAK,OAAO,MAAM,KAAK,KAAK;AAC5B,WAAO;AAAA,EACT;AACF;AAGA,IAAM,cAAN,cAA0B,WAAW;AAAA,EACnC,YAAqB,MAAc;AACjC,UAAM;AADa;AAAA,EAErB;AAAA,EAFqB;AAAA,EAGZ,GAAG,OAAoB;AAC9B,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B;AAAA,EACA,QAAQ;AACN,WAAO,OAAO,KAAK,IAAI;AAAA,EACzB;AACF;AASA,IAAM,YAAY,WAAW,KAAK,EAAE,SAAS,OAAO,OAAO,sBAAsB,CAAC;AAG3E,SAAS,gBAAgB,KAAW,MAAc,SAAqB,CAAC,GAAkB;AAC/F,QAAM,EAAE,QAAQ,SAAS,UAAU,UAAU,IAAI;AACjD,QAAM,SAA8B,CAAC;AACrC,MAAI,MAAM;AACV,aAAW,QAAQ,UAAU,MAAM,IAAI,SAAS,CAAC,GAAG;AAClD,UAAM,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,IAAI,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC;AAC3D,QAAI,KAAK,SAAS,OAAQ,QAAO,KAAK,KAAK;AAAA,aAClC,KAAK,SAAS,SAAS;AAC9B,UAAI,MAAM;AACR,cAAM,OAAO,MAAM,KAAK;AACxB,cAAM,KAAK,OAAO,KAAK;AACvB,eAAO;AAAA,UACL,WAAW,OAAO,EAAE,QAAQ,IAAI,YAAY,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,MAAM,QAAQ,CAAC,EAAE,MAAM,IAAI;AAAA,UACjG,UAAU,MAAM,MAAM,EAAE;AAAA,UACxB,WAAW,OAAO,EAAE,QAAQ,IAAI,YAAY,GAAG,GAAG,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE;AAAA,QACtF;AAAA,MACF;AACA,aAAO,KAAK,KAAK;AAAA,IACnB,WAAW,MAAM;AACf,UAAI,CAAC,KAAK,KAAK,SAAS,IAAI,GAAG;AAC7B,cAAM,SAAS,IAAI,cAAc,KAAK,MAAM,OAAO,OAAO;AAC1D,eAAO,KAAK,WAAW,OAAO,EAAE,QAAQ,MAAM,IAAI,MAAM,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC;AAAA,MACjF,OAAO;AAGL,cAAM,OAAO,IAAI,OAAO,GAAG;AAC3B,cAAM,QAAQ,QAAQ,KAAK,MAAM,KAAK,KAAK,KAAK;AAChD,cAAM,SAAS,IAAI,cAAc,KAAK,KAAK,QAAQ,cAAc,EAAE,GAAG,MAAM,OAAO;AACnF,eAAO;AAAA,UACL,WAAW,OAAO,EAAE,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,MAAM,MAAM,UAAU,CAAC,EAAE;AAAA,YAChF,QAAQ,KAAK,KAAK,KAAK;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,WAAW,IAAI,QAAQ,IAAI;AACpC;AAeO,IAAM,cAAc;AAE3B,IAAM,SAAS,MAAM,OAA+B;AAAA,EAClD,SAAS,CAAC,WAAW,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,MAAM,OAAO,QAAQ,CAAC,EAAE;AACxE,CAAC;AAEM,IAAM,eAAe,YAAY,OAAgB;AACxD,IAAM,UAAU,YAAY,OAAa;AAEzC,IAAM,UAAU,CAAC,OAAoB,YAAgC;AACnE,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM,MAAM,MAAM;AAC3C,SAAO,EAAE,aAAa,gBAAgB,MAAM,KAAK,MAAM,MAAM,GAAG,SAAS,OAAO,MAAM;AACxF;AAEA,IAAM,YAAY,WAAW,OAAkB;AAAA,EAC7C,QAAQ,CAAC,UAAU,QAAQ,OAAO,KAAK;AAAA,EACvC,OAAO,OAAO,IAAI;AAChB,QAAI,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,eAAW,KAAK,GAAG,SAAS;AAC1B,UAAI,EAAE,GAAG,YAAY,EAAG,WAAU,EAAE;AACpC,UAAI,EAAE,GAAG,OAAO,EAAG,aAAY;AAAA,IACjC;AACA,UAAM,OAAO,GAAG,MAAM,MAAM,MAAM;AAElC,QAAI,WAAW,CAAC,KAAK,KAAM,QAAO,EAAE,aAAa,WAAW,MAAM,SAAS,OAAO,MAAM;AACxF,UAAM,gBAAgB,SAAS,GAAG,WAAW,MAAM,MAAM;AAEzD,QAAI,WAAW,GAAG,cAAc,CAAC,aAAa,CAAC;AAC7C,aAAO,EAAE,aAAa,MAAM,YAAY,IAAI,GAAG,OAAO,GAAG,SAAS,OAAO,KAAK;AAChF,QAAI,aAAa,iBAAiB,GAAG,cAAc,YAAY,MAAM;AACnE,aAAO,QAAQ,GAAG,OAAO,OAAO;AAClC,WAAO;AAAA,EACT;AAAA;AAAA,EAEA,SAAS,CAAC,UAAU,WAAW,YAAY,KAAK,OAAO,CAAC,UAAU,MAAM,WAAW;AACrF,CAAC;AAGD,IAAM,WAAW,WAAW;AAAA,EAC1B,MAAM;AAAA,IAEJ,YAAqB,MAAkB;AAAlB;AAAA,IAAmB;AAAA,IAAnB;AAAA,IADrB;AAAA,IAEA,OAAO,GAAe;AACpB,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,SAAS,EAAE,MAAO;AACtD,mBAAa,KAAK,KAAK;AACvB,WAAK,QAAQ,WAAW,MAAM,KAAK,KAAK,SAAS,EAAE,SAAS,QAAQ,GAAG,IAAI,EAAE,CAAC,GAAG,WAAW;AAAA,IAC9F;AAAA,IACA,UAAU;AACR,mBAAa,KAAK,KAAK;AAAA,IACzB;AAAA,EACF;AACF;AAEA,IAAM,QAAQ,WAAW,MAAM;AAAA,EAC7B,wBAAwB,EAAE,eAAe,wBAAwB,YAAY,WAAW;AAC1F,CAAC;AAMM,SAAS,SAAS,MAAc,UAAmD,CAAC,GAAc;AACvG,SAAO;AAAA,IACL,OAAO,GAAG,EAAE,MAAM,MAAM,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,UAAU,CAAC,EAAE,CAAC;AAAA,IAC7E;AAAA,IACA;AAAA,IACA,WAAW,kBAAkB,GAAG,CAAC,QAAQ,aAAa,aAAa,GAAG,QAAQ,CAAC;AAAA,IAC/E;AAAA,EACF;AACF;;;ADhLA,IAAM,YAAmF;AAAA,EACvF,MAAM,MAAM,OAAO,uBAAuB,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,EAChE,MAAM,MAAM,OAAO,uBAAuB,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,EAChE,UAAU,MAAM,OAAO,wBAAY,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/D;AAGA,IAAM,UAAU,CAAC,aAAiC,aAAa,cAAc,aAAa;AAG1F,IAAM,sBAAsB;AAC5B,IAAM,SAAS,MAAM,OAAO,eAAe,cAAc,WAAW,mBAAmB,EAAE;AAGzF,IAAMC,SAAQC,YAAW,MAAM;AAAA,EAC7B,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,YACE;AAAA,EACJ;AAAA,EACA,gCAAgC,EAAE,aAAa,6BAA6B;AAAA,EAC5E,mBAAmB,EAAE,aAAa,+BAA+B;AAAA,EACjE,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,eAAe;AAAA,IACf,aAAa;AAAA,EACf;AAAA,EACA,oBAAoB;AAAA,IAClB,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,gCAAgC,EAAE,QAAQ,cAAc;AAAA,EACxD,gBAAgB;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AAAA,EACA,eAAe;AAAA,IACb,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,YAAY;AAAA,EACd;AAAA,EACA,kCAAkC,EAAE,eAAe,uBAAuB;AAAA;AAAA;AAAA,EAG1E,4BAA4B;AAAA,IAC1B,eAAe;AAAA,MACb,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB;AAAA,IACA,gBAAgB,EAAE,uBAAuB,GAAG,mBAAmB,KAAK;AAAA,EACtE;AACF,CAAC;AAED,IAAM,YAAY,eAAe,OAAO;AAAA,EACtC,EAAE,KAAK,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,8BAA8B;AAAA,EAC5F,EAAE,KAAK,EAAE,SAAS,OAAO,+BAA+B,YAAY,iCAAiC;AAAA,EACrG,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,OAAO,4BAA4B;AAAA,EAC3E,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAG,OAAO,+BAA+B;AAAA,EACjF;AAAA,IACE,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,qBAAqB;AAAA,IAC5E,OAAO;AAAA,EACT;AAAA,EACA,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,4BAA4B;AAAA,EAC3D,EAAE,KAAK,EAAE,UAAU,WAAW,SAAS;AAAA,EACvC,EAAE,KAAK,EAAE,QAAQ,YAAY,iCAAiC;AAChE,CAAC;AAED,IAAM,gBAAgB,CAAC,MAAsB,EAAE,QAAQ,OAAO,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC;AAGxG,SAAS,eAAe,GAA6B;AACnD,QAAM,OAAO,CAAC,OAAiB,KAAK,SAAS;AAC7C,SAAO;AAAA,IACLA,YAAW,SAAS,GAAG,CAAC,EAAE,QAAQ;AAAA,IAClC,YAAY,SAAS,GAAG,EAAE,YAAY,EAAE,QAAQ;AAAA,IAChDA,YAAW,iBAAiB,GAAG;AAAA,MAC7B,GAAI,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;AAAA,MAC1C,GAAI,EAAE,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;AAAA,MAC5C,GAAI,EAAE,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;AAAA,IAC9C,CAAC;AAAA,IACDA,YAAW,kBAAkB,GAAG;AAAA,MAC9B,YAAY,KAAK,QAAQ,EAAE,QAAQ,CAAC;AAAA,MACpC,aAAa,QAAQ,EAAE,QAAQ,IAAI,OAAO;AAAA,MAC1C,gBAAgB,QAAQ,EAAE,QAAQ,IAAI,cAAc;AAAA,MACpD,GAAI,EAAE,UAAU,EAAE,gBAAgB,OAAO,IAAI,CAAC;AAAA,MAC9C,GAAI,EAAE,WAAW,EAAE,iBAAiB,OAAO,IAAI,CAAC;AAAA;AAAA,MAEhD,GAAI,EAAE,WAAW,EAAE,iBAAiB,QAAQ,WAAW,OAAO,IAAI,CAAC;AAAA,MACnE,GAAG,EAAE;AAAA,IACP,CAAC;AAAA,EACH;AACF;AACA,IAAM,WAAW,CAAC,MAChB,KAAK,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC;AAGvE,SAAS,YACd,QACA,SACA,UACc;AACd,QAAM,WAAW,IAAI,YAAY;AACjC,QAAM,eAAe,IAAI,YAAY;AACrC,QAAM,YAAY,IAAI,YAAY;AAClC,MAAI,UAAU;AACd,MAAI,YAAY;AAEhB,QAAM,eAAe,CAAC,aAAiC;AACrD,QAAI,aAAa,OAAQ,QAAO,KAAK,SAAS,EAAE,SAAS,aAAa,YAAY,CAAC,CAAC,EAAE,CAAC;AACvF,SAAK,UAAU,QAAQ,EAAE,EAAE,KAAK,CAAC,cAAc;AAC7C,UAAI,CAAC,aAAa,QAAQ,aAAa;AACrC,aAAK,SAAS,EAAE,SAAS,aAAa,YAAY,SAAS,EAAE,CAAC;AAAA,IAClE,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,IAAIA,YAAW;AAAA,IAC1B;AAAA,IACA,OAAO,YAAY,OAAO;AAAA,MACxB,KAAK,QAAQ;AAAA,MACb,YAAY;AAAA,QACV,QAAQ;AAAA,QACR,OAAO,GAAG,CAAC,GAAG,eAAe,GAAG,aAAa,CAAC;AAAA,QAC9CA,YAAW;AAAA,QACX,YAAY,QAAQ,GAAG,CAAC;AAAA,QACxBA,YAAW,cAAc,GAAG,MAAO,OAAO,IAAI,EAAE,QAAQ,oBAAoB,IAAI,IAAK;AAAA,QACrFD;AAAA,QACA,mBAAmB,SAAS;AAAA;AAAA,QAE5BC,YAAW,kBAAkB,GAAG,EAAE,OAAO,gBAAgB,eAAe,OAAO,CAAC;AAAA,QAChF,UAAU,GAAG,eAAe,OAAO,CAAC;AAAA,QACpC,aAAa,GAAG,CAAC,CAAC;AAAA,QAClB,SAAS,GAAG,cAAc,OAAO,CAAC;AAAA,QAClCA,YAAW,eAAe,GAAG,CAAC,MAAM;AAClC,cAAI,EAAE,WAAY,UAAS,EAAE,MAAM,IAAI,SAAS,CAAC;AAAA,QACnD,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,eAAa,QAAQ,QAAQ;AAE7B,SAAO;AAAA,IACL,OAAO,MAAM;AACX,YAAM,OAAO;AACb,gBAAU;AACV,YAAM,UAAU,CAAC;AACjB,YAAM,YAAY,KAAK,QAAQ,QAAQ,KAAK,QAAQ;AACpD,UAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,aAAa,KAAK;AACpD,gBAAQ,KAAK,SAAS,YAAY,cAAc,IAAI,CAAC,CAAC;AACxD,UAAI,SAAS,IAAI,MAAM,SAAS,IAAI,EAAG,SAAQ,KAAK,UAAU,YAAY,eAAe,IAAI,CAAC,CAAC;AAC/F,YAAM,MAAM,KAAK,MAAM;AACvB,YAAM,UACJ,KAAK,UAAU,IAAI,SAAS,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,QAAQ,QAAQ,KAAK,MAAM,IAAI;AACpF,UAAI,QAAQ,UAAU,QAAS,MAAK,SAAS,UAAU,EAAE,SAAS,QAAQ,IAAI,EAAE,QAAQ,CAAC;AAEzF,UAAI,aAAa,KAAK,SAAU,MAAK,SAAS,EAAE,SAAS,aAAa,GAAG,IAAI,EAAE,CAAC;AAChF,UAAI,KAAK,aAAa,KAAK,SAAU,cAAa,KAAK,QAAQ;AAAA,IACjE;AAAA,IACA,MAAM,QAAQ,MAAM;AAClB,UAAI,QAAQ,SAAU;AACtB,UAAI,UAAU,MAAM;AAClB,cAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,aAAK,SAAS,EAAE,WAAW,EAAE,QAAQ,KAAK,IAAI,QAAQ,GAAG,GAAG,MAAM,KAAK,IAAI,QAAQ,QAAQ,GAAG,EAAE,EAAE,CAAC;AAAA,MACrG;AACA,WAAK,MAAM;AAAA,IACb;AAAA,IACA,WAAW,MAAM;AACf,UAAI,QAAQ,SAAU;AACtB,YAAM,MAAM,KAAK,MAAM;AACvB,YAAM,EAAE,MAAM,GAAG,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,KAAK,CAAC;AAChF,WAAK,SAAS,EAAE,WAAW,EAAE,QAAQ,MAAM,MAAM,GAAG,GAAG,gBAAgB,KAAK,CAAC;AAC7E,WAAK,MAAM;AAAA,IACb;AAAA,IACA,UAAU;AACR,kBAAY;AACZ,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AACF;","names":["EditorView","text","theme","EditorView"]}
|