@gntik-ai/editor 0.0.0-stage → 0.11.0
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 +247 -2
- package/dist/index.d.ts +164 -0
- package/dist/index.js +592 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -3
- package/src/styles.css +10 -0
package/README.md
CHANGED
|
@@ -1,3 +1,248 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gntik-ai/editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Monaco (the VS Code editor) themed by the gntik-ui brand tokens. Ships three Monaco
|
|
4
|
+
themes — `gntik-dark`, `gntik-light`, `gntik-hc` — built at runtime from the live CSS
|
|
5
|
+
variables of `@gntik-ai/tokens`, and two React components that follow the `<html>`
|
|
6
|
+
theme class automatically.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import '@gntik-ai/tokens/brand.css';
|
|
10
|
+
import { CodeEditor, DiffEditor } from '@gntik-ai/editor';
|
|
11
|
+
|
|
12
|
+
<CodeEditor value={code} onChange={setCode} language="typescript" height={360} aria-label="Agent source" />
|
|
13
|
+
<DiffEditor original={before} modified={after} language="yaml" aria-label="Manifest changes" />
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Monaco is loaded with a dynamic `import('monaco-editor')`, so it is split into its own
|
|
17
|
+
chunk and only downloaded when an editor mounts. A token-styled skeleton shows until then.
|
|
18
|
+
|
|
19
|
+
## Workers (required)
|
|
20
|
+
|
|
21
|
+
Monaco runs its language services in web workers. The package does not import them —
|
|
22
|
+
each bundler resolves workers differently — so wire them once at app start-up.
|
|
23
|
+
|
|
24
|
+
### Vite
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// src/monaco-workers.ts — import this once, before any editor renders.
|
|
28
|
+
import { configureMonacoWorkers } from '@gntik-ai/editor';
|
|
29
|
+
import EditorWorker from 'monaco-editor/editor/editor.worker?worker';
|
|
30
|
+
import JsonWorker from 'monaco-editor/languages/features/json/json.worker?worker';
|
|
31
|
+
import CssWorker from 'monaco-editor/languages/features/css/css.worker?worker';
|
|
32
|
+
import HtmlWorker from 'monaco-editor/languages/features/html/html.worker?worker';
|
|
33
|
+
import TsWorker from 'monaco-editor/languages/features/typescript/ts.worker?worker';
|
|
34
|
+
|
|
35
|
+
configureMonacoWorkers((_id, label) => {
|
|
36
|
+
if (label === 'json') return new JsonWorker();
|
|
37
|
+
if (label === 'css' || label === 'scss' || label === 'less') return new CssWorker();
|
|
38
|
+
if (label === 'html' || label === 'handlebars' || label === 'razor') return new HtmlWorker();
|
|
39
|
+
if (label === 'typescript' || label === 'javascript') return new TsWorker();
|
|
40
|
+
return new EditorWorker();
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Only need syntax highlighting? Return `new EditorWorker()` for every label and skip the rest.
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
| Export | Purpose |
|
|
49
|
+
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `CodeEditor` | `value` / `defaultValue`, `onChange(value, event)`, `language`, `readOnly`, `height`, `aria-label`, `options`, `className`, `loader`, `onMount`. |
|
|
51
|
+
| `DiffEditor` | `original`, `modified`, `language`, `readOnly` (default `true`), `sideBySide`, `onChange(modified)`, `height`, `aria-label`, `options`, `className`, `loader`, `onMount`. |
|
|
52
|
+
| `defineBrandThemes(monaco)` | Registers the three brand themes from the live tokens. Call again after a theme switch (the components do). |
|
|
53
|
+
| `brandThemeFor(theme?)` | `'dark' \| 'light' \| 'high_contrast'` → Monaco theme name (defaults to the active theme). |
|
|
54
|
+
| `brandThemeData(theme, el?)` | The raw `IStandaloneThemeData` for one theme. |
|
|
55
|
+
| `configureMonacoWorkers(getWorker)` | Sets `self.MonacoEnvironment.getWorker`. |
|
|
56
|
+
| `loadMonaco`, `useMonaco`, `useBrandTheme`, `BASE_OPTIONS`, `EditorSkeleton` | Building blocks for custom Monaco surfaces. |
|
|
57
|
+
|
|
58
|
+
### Custom loader
|
|
59
|
+
|
|
60
|
+
Pass `loader` to use a preconfigured or self-hosted Monaco (or a mock in tests):
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
const loader = () => import('monaco-editor').then((m) => {
|
|
64
|
+
m.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true });
|
|
65
|
+
return m;
|
|
66
|
+
});
|
|
67
|
+
<CodeEditor loader={loader} … />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Keep the loader identity stable (module scope or `useCallback`); a new function reloads Monaco.
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- Monaco themes only accept hex, so colours are converted with `tokenHex` from
|
|
75
|
+
`@gntik-ai/tokens/runtime`; alpha is appended as a hex suffix. No colour literals.
|
|
76
|
+
- `monaco.editor.setTheme` is global: every Monaco instance on the page shares one theme.
|
|
77
|
+
- Light tokens live on `:root`, so `gntik-light` is only exact while light is active; the
|
|
78
|
+
components redefine all themes on every switch, so the active theme is always correct.
|
|
79
|
+
|
|
80
|
+
## Consumer-injected YAML diagnostics (Monaco 0.57)
|
|
81
|
+
|
|
82
|
+
The kit does not depend on `monaco-yaml` and never imports a worker. Install a compatible
|
|
83
|
+
`monaco-yaml` in the consumer (the docs example uses 5.5.1 with `monaco-editor` 0.57.0).
|
|
84
|
+
The integration lives behind a stable lazy loader. Do not import these worker constructors
|
|
85
|
+
from your app entry if you want to defer their chunks until the editor opens.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// yaml-workers.ts: loaded only by yaml-loader.ts
|
|
89
|
+
import { configureMonacoWorkers } from '@gntik-ai/editor';
|
|
90
|
+
import EditorWorker from 'monaco-editor/editor/editor.worker?worker';
|
|
91
|
+
import YamlWorker from 'monaco-yaml/yaml.worker?worker';
|
|
92
|
+
|
|
93
|
+
configureMonacoWorkers((_id, label) => {
|
|
94
|
+
if (label === 'yaml') return new YamlWorker();
|
|
95
|
+
return new EditorWorker();
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// yaml-loader.ts: keep this module's loader identity stable
|
|
101
|
+
import type { MonacoLoader } from '@gntik-ai/editor';
|
|
102
|
+
let configured: ReturnType<MonacoLoader> | undefined;
|
|
103
|
+
export const yamlLoader: MonacoLoader = () => {
|
|
104
|
+
configured ??= Promise.all([import('./yaml-workers'), import('monaco-editor'), import('monaco-yaml')]).then(
|
|
105
|
+
([, monaco, { configureMonacoYaml }]) => {
|
|
106
|
+
configureMonacoYaml(yamlMonaco(monaco), {
|
|
107
|
+
enableSchemaRequest: false,
|
|
108
|
+
validate: true,
|
|
109
|
+
schemas: [
|
|
110
|
+
{
|
|
111
|
+
uri: 'inmemory://schemas/workflow.json',
|
|
112
|
+
fileMatch: ['*'],
|
|
113
|
+
schema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
required: ['count'],
|
|
116
|
+
properties: { count: { type: 'integer' } },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
return monaco;
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
return configured;
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This adapter is pinned to `monaco-yaml` 5.5.1 and its `monaco-worker-manager` 2.0.1
|
|
129
|
+
dependency: it relies on their internal two-message bootstrap. Revalidate the production
|
|
130
|
+
browser spec before upgrading either dependency. Put this consumer adapter in `yaml-loader.ts`. Monaco 0.57 requires a `worker` instance in
|
|
131
|
+
`createWebWorker`; the older worker manager supplies `moduleId`, `label` and
|
|
132
|
+
`createData` instead. Passing those options directly silently selects Monaco's generic
|
|
133
|
+
worker, which cannot run YAML validation. The adapter requests the YAML worker through
|
|
134
|
+
`getWorker`, initializes its legacy bootstrap with the schema options, and passes it to
|
|
135
|
+
Monaco's current API. It only adapts the Monaco facade given to `configureMonacoYaml`.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import type { Monaco } from '@gntik-ai/editor';
|
|
139
|
+
|
|
140
|
+
function yamlMonaco(monaco: Monaco): Monaco {
|
|
141
|
+
return {
|
|
142
|
+
...monaco,
|
|
143
|
+
editor: {
|
|
144
|
+
...monaco.editor,
|
|
145
|
+
createWebWorker<T extends object>(options: {
|
|
146
|
+
createData?: unknown;
|
|
147
|
+
label?: string;
|
|
148
|
+
host?: Parameters<Monaco['editor']['createWebWorker']>[0]['host'];
|
|
149
|
+
keepIdleModels?: boolean;
|
|
150
|
+
}) {
|
|
151
|
+
const worker = Promise.resolve(
|
|
152
|
+
globalThis.MonacoEnvironment!.getWorker!('workerMain.js', options.label ?? 'yaml'),
|
|
153
|
+
).then((worker) => {
|
|
154
|
+
worker.postMessage('-please-ignore-');
|
|
155
|
+
worker.postMessage(options.createData);
|
|
156
|
+
return worker;
|
|
157
|
+
});
|
|
158
|
+
return monaco.editor.createWebWorker<T>({
|
|
159
|
+
worker,
|
|
160
|
+
host: options.host,
|
|
161
|
+
keepIdleModels: options.keepIdleModels,
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The running example is `apps/docs/src/examples/yaml-loader.js` and its sibling
|
|
170
|
+
`yaml-monaco.js`. After building the docs, `pnpm --filter @gntik-ai/docs test:yaml-worker`
|
|
171
|
+
checks the real bundled worker's schema diagnostics without an HTTP server. The browser
|
|
172
|
+
test additionally checks the Problems list and production CSP.
|
|
173
|
+
|
|
174
|
+
Monaco 0.57 exports shorter public subpaths. `monaco-worker-manager` 2.0.1, used by
|
|
175
|
+
`monaco-yaml` 5.5.1, still imports the legacy `monaco-editor/esm/vs/` path. Keep the alias
|
|
176
|
+
in the consumer Vite config for that dependency, including worker builds:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { fileURLToPath } from 'node:url';
|
|
180
|
+
export default {
|
|
181
|
+
resolve: {
|
|
182
|
+
alias: [
|
|
183
|
+
{
|
|
184
|
+
find: /^monaco-editor\/esm\/vs\//,
|
|
185
|
+
replacement: fileURLToPath(new URL('./node_modules/monaco-editor/esm/vs/', import.meta.url)),
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
worker: { format: 'es' },
|
|
190
|
+
build: { assetsInlineLimit: 0 },
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Vite's `?worker` constructors load module workers from bundled same-origin assets. Serve
|
|
195
|
+
production with `script-src 'self'; worker-src 'self' blob:; connect-src 'self'`. Monaco
|
|
196
|
+
also needs its generated inline styles (`style-src 'self' 'unsafe-inline'`); fonts and
|
|
197
|
+
stylesheets must be bundled on the same origin. Always supply the schema inline and set
|
|
198
|
+
`enableSchemaRequest: false`. Do not configure remote `$ref` dependencies, schema stores,
|
|
199
|
+
or CDN loaders. The kit's default `loadMonaco` is an ESM import, never a CDN request.
|
|
200
|
+
|
|
201
|
+
With `font-src 'self'`, set `build.assetsInlineLimit: 0` so Vite emits small fonts as
|
|
202
|
+
same-origin files instead of CSP-blocked data URLs. After building the docs, run
|
|
203
|
+
`node --test --test-isolation=none apps/docs/scripts/check-csp-fonts.test.mjs` to check
|
|
204
|
+
the emitted font sources without a browser.
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<CodePanel
|
|
208
|
+
files={[{ path: 'flow.yaml', language: 'yaml', content: 'count: wrong\n' }]}
|
|
209
|
+
problems={[]}
|
|
210
|
+
loader={yamlLoader}
|
|
211
|
+
markers={[
|
|
212
|
+
{
|
|
213
|
+
id: 'semantic',
|
|
214
|
+
owner: 'semantic-worker',
|
|
215
|
+
file: 'flow.yaml',
|
|
216
|
+
line: 1,
|
|
217
|
+
severity: 'warning',
|
|
218
|
+
message: 'Review the terminal step.',
|
|
219
|
+
},
|
|
220
|
+
]}
|
|
221
|
+
readOnly={runMode}
|
|
222
|
+
onValidityChange={(valid) => setCanSave(valid)}
|
|
223
|
+
/>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Set `markers` (even an empty list), `onMonacoReady`, or `onValidityChange` to opt
|
|
227
|
+
into model diagnostics. `readOnly` controls editing independently. Omitting diagnostics
|
|
228
|
+
extensions preserves the caller-supplied Problems list. Collection covers both the code editor and the modified diff model.
|
|
229
|
+
|
|
230
|
+
`CodePanel` subscribes to the public Monaco marker API for every mounted file model and
|
|
231
|
+
merges every owner's markers into its Problems list. It reserves each `markers[].owner`
|
|
232
|
+
for that consumer marker group, writes those line markers to the model, and clears them
|
|
233
|
+
when removed or unmounted. Consumer markers also appear for files not yet opened. Keep
|
|
234
|
+
owners distinct from `yaml`; owner groups supplied through `markers` replace that group's
|
|
235
|
+
model diagnostics, so they are listed once.
|
|
236
|
+
|
|
237
|
+
`onMonacoReady(monaco)` is an alternative setup hook for already-loaded consumer services;
|
|
238
|
+
it runs once per Monaco instance in the panel and may return a disposer. Prefer the loader
|
|
239
|
+
for language registration before editor creation. Validity is based on the merged error
|
|
240
|
+
count: no notification for an initially clean panel, `false` when errors become nonzero,
|
|
241
|
+
and `true` when the last error clears. Warnings do not change validity. `readOnly` reaches
|
|
242
|
+
both CodeEditor and DiffEditor; leaving it undefined preserves each editor's defaults.
|
|
243
|
+
|
|
244
|
+
The production docs example is `/builders.html?item=code-panel&mode=edit&theme=dark`.
|
|
245
|
+
`apps/docs/e2e/builders.a11y.spec.js` checks Worker response evidence for `getWorker('yaml')`,
|
|
246
|
+
inline-schema errors alongside consumer markers, no CSP violations or external requests,
|
|
247
|
+
and axe in dark, light and high_contrast for the code panel, flow builder and workflow page
|
|
248
|
+
in edit and run modes. Build the docs before running that Playwright file.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
|
|
2
|
+
import "react";
|
|
3
|
+
import { Theme } from "@gntik-ai/tokens/runtime";
|
|
4
|
+
import * as MonacoNamespace from "monaco-editor";
|
|
5
|
+
import { editor } from "monaco-editor";
|
|
6
|
+
//#region src/loader.d.ts
|
|
7
|
+
/** The `monaco-editor` ESM module. */
|
|
8
|
+
type Monaco = typeof MonacoNamespace;
|
|
9
|
+
/** Returns the Monaco module; inject one to self-host, preconfigure or mock Monaco. */
|
|
10
|
+
type MonacoLoader = () => Promise<Monaco>;
|
|
11
|
+
/** Default loader: a dynamic `import('monaco-editor')`, so Monaco lands in its own chunk. */
|
|
12
|
+
export declare const loadMonaco: MonacoLoader;
|
|
13
|
+
type MonacoState = {
|
|
14
|
+
status: 'loading';
|
|
15
|
+
monaco: null;
|
|
16
|
+
} | {
|
|
17
|
+
status: 'ready';
|
|
18
|
+
monaco: Monaco;
|
|
19
|
+
} | {
|
|
20
|
+
status: 'error';
|
|
21
|
+
monaco: null;
|
|
22
|
+
error: unknown;
|
|
23
|
+
};
|
|
24
|
+
/** Loads Monaco once per loader and reports its state. */
|
|
25
|
+
export declare function useMonaco(loader?: MonacoLoader): MonacoState;
|
|
26
|
+
/** Registers the brand themes, applies the active one and re-skins on every <html> theme switch. */
|
|
27
|
+
export declare function useBrandTheme(monaco: Monaco | null): void;
|
|
28
|
+
/** Brand defaults shared by both editors (Geist Mono, flat chrome, no overview ruler). */
|
|
29
|
+
export declare const BASE_OPTIONS: {
|
|
30
|
+
readonly automaticLayout: true;
|
|
31
|
+
readonly editContext: false;
|
|
32
|
+
readonly fontFamily: "Geist Mono, ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
33
|
+
readonly fontSize: 13;
|
|
34
|
+
readonly lineHeight: 21;
|
|
35
|
+
readonly fontLigatures: false;
|
|
36
|
+
readonly letterSpacing: 0.2;
|
|
37
|
+
readonly padding: {
|
|
38
|
+
readonly top: 14;
|
|
39
|
+
readonly bottom: 14;
|
|
40
|
+
};
|
|
41
|
+
readonly scrollBeyondLastLine: false;
|
|
42
|
+
readonly smoothScrolling: true;
|
|
43
|
+
readonly cursorBlinking: "smooth";
|
|
44
|
+
readonly cursorSmoothCaretAnimation: "on";
|
|
45
|
+
readonly renderLineHighlight: "all";
|
|
46
|
+
readonly roundedSelection: true;
|
|
47
|
+
readonly tabSize: 2;
|
|
48
|
+
readonly insertSpaces: true;
|
|
49
|
+
readonly guides: {
|
|
50
|
+
readonly indentation: true;
|
|
51
|
+
readonly bracketPairs: true;
|
|
52
|
+
};
|
|
53
|
+
readonly scrollbar: {
|
|
54
|
+
readonly verticalScrollbarSize: 10;
|
|
55
|
+
readonly horizontalScrollbarSize: 10;
|
|
56
|
+
readonly useShadows: false;
|
|
57
|
+
};
|
|
58
|
+
readonly overviewRulerLanes: 0;
|
|
59
|
+
readonly overviewRulerBorder: false;
|
|
60
|
+
readonly fixedOverflowWidgets: true;
|
|
61
|
+
readonly minimap: {
|
|
62
|
+
readonly enabled: false;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/CodeEditor.d.ts
|
|
67
|
+
interface CodeEditorProps {
|
|
68
|
+
/** Controlled text. When it changes, the editor is updated (cursor kept when equal). */
|
|
69
|
+
value?: string;
|
|
70
|
+
/** Initial text for an uncontrolled editor. */
|
|
71
|
+
defaultValue?: string;
|
|
72
|
+
/** Called with the full text on every user edit. */
|
|
73
|
+
onChange?: (value: string, event: editor.IModelContentChangedEvent) => void;
|
|
74
|
+
/** Monaco language id, e.g. "typescript", "json", "yaml". */
|
|
75
|
+
language?: string;
|
|
76
|
+
readOnly?: boolean;
|
|
77
|
+
/** CSS height of the editor box. */
|
|
78
|
+
height?: number | string;
|
|
79
|
+
/** Accessible name of the editor's text area. */
|
|
80
|
+
'aria-label'?: string;
|
|
81
|
+
/** Extra Monaco options, merged over the brand defaults. */
|
|
82
|
+
options?: editor.IStandaloneEditorConstructionOptions;
|
|
83
|
+
className?: string;
|
|
84
|
+
/** Override how Monaco is loaded (self-hosting, preconfigured instance, tests). */
|
|
85
|
+
loader?: MonacoLoader;
|
|
86
|
+
/** Called once the editor instance exists. */
|
|
87
|
+
onMount?: (instance: editor.IStandaloneCodeEditor, monaco: Monaco) => void;
|
|
88
|
+
}
|
|
89
|
+
/** Monaco code editor themed by the brand tokens; loads Monaco lazily. */
|
|
90
|
+
export declare function CodeEditor({ value, defaultValue, onChange, language, readOnly, height, 'aria-label': ariaLabel, options, className, loader, onMount }: CodeEditorProps): import("react").JSX.Element;
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/DiffEditor.d.ts
|
|
93
|
+
interface DiffEditorProps {
|
|
94
|
+
/** Left-hand (before) text. */
|
|
95
|
+
original: string;
|
|
96
|
+
/** Right-hand (after) text. */
|
|
97
|
+
modified: string;
|
|
98
|
+
/** Monaco language id for both sides. */
|
|
99
|
+
language?: string;
|
|
100
|
+
/** Read-only by default; set false to let users edit the modified side. */
|
|
101
|
+
readOnly?: boolean;
|
|
102
|
+
/** Side by side (default) or inline. */
|
|
103
|
+
sideBySide?: boolean;
|
|
104
|
+
height?: number | string;
|
|
105
|
+
/** Accessible name; the sides are announced as "<label>, original" / "<label>, modified". */
|
|
106
|
+
'aria-label'?: string;
|
|
107
|
+
/** Called with the modified text when the user edits it (requires readOnly={false}). */
|
|
108
|
+
onChange?: (modified: string) => void;
|
|
109
|
+
/** Extra Monaco diff options, merged over the brand defaults. */
|
|
110
|
+
options?: editor.IStandaloneDiffEditorConstructionOptions;
|
|
111
|
+
className?: string;
|
|
112
|
+
loader?: MonacoLoader;
|
|
113
|
+
onMount?: (instance: editor.IStandaloneDiffEditor, monaco: Monaco) => void;
|
|
114
|
+
}
|
|
115
|
+
/** Monaco diff editor themed by the brand tokens; loads Monaco lazily. */
|
|
116
|
+
export declare function DiffEditor({ original, modified, language, readOnly, sideBySide, height, 'aria-label': ariaLabel, onChange, options, className, loader, onMount }: DiffEditorProps): import("react").JSX.Element;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/EditorFrame.d.ts
|
|
119
|
+
/** Token-styled placeholder shown until Monaco has loaded. */
|
|
120
|
+
export declare function EditorSkeleton({ label }: {
|
|
121
|
+
label: string;
|
|
122
|
+
}): import("react").JSX.Element;
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/theme.d.ts
|
|
125
|
+
/** Monaco theme names registered by `defineBrandThemes`. */
|
|
126
|
+
export declare const BRAND_THEMES: {
|
|
127
|
+
readonly dark: "gntik-dark";
|
|
128
|
+
readonly light: "gntik-light";
|
|
129
|
+
readonly high_contrast: "gntik-hc";
|
|
130
|
+
};
|
|
131
|
+
type BrandThemeName = (typeof BRAND_THEMES)[Theme];
|
|
132
|
+
/** The subset of the Monaco API the theme helpers touch (keeps them mock-friendly). */
|
|
133
|
+
interface MonacoThemeApi {
|
|
134
|
+
editor: Pick<typeof editor, 'defineTheme'>;
|
|
135
|
+
}
|
|
136
|
+
/** Monaco theme name for a gntik-ui theme. Defaults to the active <html> theme. */
|
|
137
|
+
export declare function brandThemeFor(theme?: Theme): BrandThemeName;
|
|
138
|
+
/**
|
|
139
|
+
* Builds Monaco theme data from the live brand tokens read on `el`
|
|
140
|
+
* (default: <html>). Every colour comes from `tokenHex`; alpha is a hex suffix.
|
|
141
|
+
*/
|
|
142
|
+
export declare function brandThemeData(theme: Theme, el?: Element): editor.IStandaloneThemeData;
|
|
143
|
+
/**
|
|
144
|
+
* Registers `gntik-dark`, `gntik-light` and `gntik-hc` from the live brand tokens.
|
|
145
|
+
* The active theme is read from <html>; dark and high contrast are resolved on a
|
|
146
|
+
* hidden probe carrying their class. Light lives on `:root`, so while another
|
|
147
|
+
* theme is active it cannot be resolved exactly — call again after every theme
|
|
148
|
+
* switch (the components do this through `observeTheme`).
|
|
149
|
+
*/
|
|
150
|
+
export declare function defineBrandThemes(monaco: MonacoThemeApi): void;
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/workers.d.ts
|
|
153
|
+
/** Worker factory Monaco calls per language service (`label` is e.g. "json", "typescript", "yaml"). */
|
|
154
|
+
type MonacoWorkerFactory = (workerId: string, label: string) => Worker | Promise<Worker>;
|
|
155
|
+
/**
|
|
156
|
+
* Sets `self.MonacoEnvironment.getWorker`. Call once, before the first editor mounts.
|
|
157
|
+
* The package never imports workers itself: bundlers resolve them differently
|
|
158
|
+
* including consumer-injected YAML workers. Labels are passed through unchanged
|
|
159
|
+
* (see README for the Vite `?worker` recipe).
|
|
160
|
+
*/
|
|
161
|
+
export declare function configureMonacoWorkers(getWorker: MonacoWorkerFactory): void;
|
|
162
|
+
//#endregion
|
|
163
|
+
export type { BrandThemeName, CodeEditorProps, DiffEditorProps, Monaco, MonacoLoader, MonacoState, MonacoThemeApi, MonacoWorkerFactory };
|
|
164
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { currentTheme, observeTheme, tokenHex } from "@gntik-ai/tokens/runtime";
|
|
5
|
+
//#region src/EditorFrame.tsx
|
|
6
|
+
const SKELETON_LINES = [
|
|
7
|
+
"w-2/5",
|
|
8
|
+
"w-3/4",
|
|
9
|
+
"w-1/2",
|
|
10
|
+
"w-2/3",
|
|
11
|
+
"w-1/3",
|
|
12
|
+
"w-3/5"
|
|
13
|
+
];
|
|
14
|
+
/** Token-styled placeholder shown until Monaco has loaded. */
|
|
15
|
+
function EditorSkeleton({ label }) {
|
|
16
|
+
return /* @__PURE__ */ jsx("div", {
|
|
17
|
+
role: "status",
|
|
18
|
+
"aria-label": `Loading ${label}`,
|
|
19
|
+
"data-slot": "editor-skeleton",
|
|
20
|
+
className: "absolute inset-0 flex flex-col gap-3 bg-card px-4 py-3.5",
|
|
21
|
+
children: SKELETON_LINES.map((width, i) => /* @__PURE__ */ jsxs("div", {
|
|
22
|
+
className: "flex items-center gap-4",
|
|
23
|
+
children: [/* @__PURE__ */ jsx("span", { className: "h-2.5 w-4 rounded-sm bg-muted motion-safe:animate-pulse" }), /* @__PURE__ */ jsx("span", { className: `h-2.5 ${width} rounded-sm bg-muted motion-safe:animate-pulse` })]
|
|
24
|
+
}, i))
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function EditorError({ label }) {
|
|
28
|
+
return /* @__PURE__ */ jsx("div", {
|
|
29
|
+
role: "alert",
|
|
30
|
+
"data-slot": "editor-error",
|
|
31
|
+
className: "absolute inset-0 grid place-items-center border border-dashed border-border bg-card px-8 text-center",
|
|
32
|
+
children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("p", {
|
|
33
|
+
className: "font-sans text-sm font-semibold text-foreground",
|
|
34
|
+
children: [
|
|
35
|
+
"The ",
|
|
36
|
+
label,
|
|
37
|
+
" could not load"
|
|
38
|
+
]
|
|
39
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
40
|
+
className: "mt-1 font-sans text-xs text-muted-foreground",
|
|
41
|
+
children: [
|
|
42
|
+
"Check that ",
|
|
43
|
+
/* @__PURE__ */ jsx("span", {
|
|
44
|
+
className: "font-mono",
|
|
45
|
+
children: "monaco-editor"
|
|
46
|
+
}),
|
|
47
|
+
" is installed and its workers are configured."
|
|
48
|
+
]
|
|
49
|
+
})] })
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Sized host for a Monaco instance plus its loading and error states. */
|
|
53
|
+
function EditorFrame({ state, height, label, className, hostRef }) {
|
|
54
|
+
const style = { height };
|
|
55
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
56
|
+
"data-slot": "editor",
|
|
57
|
+
"data-state": state.status,
|
|
58
|
+
"aria-busy": state.status === "loading" || void 0,
|
|
59
|
+
className: ["relative w-full overflow-hidden bg-card", className].filter(Boolean).join(" "),
|
|
60
|
+
style,
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx("div", {
|
|
63
|
+
ref: hostRef,
|
|
64
|
+
className: "absolute inset-0"
|
|
65
|
+
}),
|
|
66
|
+
state.status === "loading" && /* @__PURE__ */ jsx(EditorSkeleton, { label }),
|
|
67
|
+
state.status === "error" && /* @__PURE__ */ jsx(EditorError, { label })
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/theme.ts
|
|
73
|
+
/** Monaco theme names registered by `defineBrandThemes`. */
|
|
74
|
+
const BRAND_THEMES = {
|
|
75
|
+
dark: "gntik-dark",
|
|
76
|
+
light: "gntik-light",
|
|
77
|
+
high_contrast: "gntik-hc"
|
|
78
|
+
};
|
|
79
|
+
const BASE = {
|
|
80
|
+
dark: "vs-dark",
|
|
81
|
+
light: "vs",
|
|
82
|
+
high_contrast: "hc-black"
|
|
83
|
+
};
|
|
84
|
+
/** Monaco theme name for a gntik-ui theme. Defaults to the active <html> theme. */
|
|
85
|
+
function brandThemeFor(theme = currentTheme()) {
|
|
86
|
+
return BRAND_THEMES[theme];
|
|
87
|
+
}
|
|
88
|
+
/** Syntax token → brand token. Keywords use the brand green; the rest, categorical accents. */
|
|
89
|
+
const RULES = [
|
|
90
|
+
["", "foreground"],
|
|
91
|
+
[
|
|
92
|
+
"comment",
|
|
93
|
+
"muted-foreground",
|
|
94
|
+
"italic"
|
|
95
|
+
],
|
|
96
|
+
["keyword", "primary"],
|
|
97
|
+
["keyword.json", "primary"],
|
|
98
|
+
["operator", "primary"],
|
|
99
|
+
["operators", "primary"],
|
|
100
|
+
["tag", "primary"],
|
|
101
|
+
["string", "category-cyan"],
|
|
102
|
+
["string.escape", "category-amber"],
|
|
103
|
+
["string.key.json", "primary"],
|
|
104
|
+
["string.value.json", "category-cyan"],
|
|
105
|
+
["attribute.value", "category-cyan"],
|
|
106
|
+
["attribute.name", "category-violet"],
|
|
107
|
+
["number", "category-amber"],
|
|
108
|
+
["constant", "category-amber"],
|
|
109
|
+
["regexp", "category-rose"],
|
|
110
|
+
["type", "category-violet"],
|
|
111
|
+
["type.identifier", "category-violet"],
|
|
112
|
+
["identifier", "foreground"],
|
|
113
|
+
["delimiter", "muted-foreground"],
|
|
114
|
+
["delimiter.bracket", "muted-foreground"],
|
|
115
|
+
["annotation", "category-rose"],
|
|
116
|
+
["metatag", "category-rose"]
|
|
117
|
+
];
|
|
118
|
+
const channel = (hex, i) => parseInt(hex.slice(1 + 2 * i, 3 + 2 * i), 16);
|
|
119
|
+
const luminance = (hex) => {
|
|
120
|
+
const [r, g, b] = [
|
|
121
|
+
0,
|
|
122
|
+
1,
|
|
123
|
+
2
|
|
124
|
+
].map((i) => {
|
|
125
|
+
const v = channel(hex, i) / 255;
|
|
126
|
+
return v <= .03928 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
|
|
127
|
+
});
|
|
128
|
+
return .2126 * r + .7152 * g + .0722 * b;
|
|
129
|
+
};
|
|
130
|
+
const contrastRatio = (a, b) => {
|
|
131
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
|
132
|
+
return (hi + .05) / (lo + .05);
|
|
133
|
+
};
|
|
134
|
+
/** Syntax text target: AA (4.5:1) with headroom for the current-line highlight. */
|
|
135
|
+
const SYNTAX_CONTRAST = 4.7;
|
|
136
|
+
/**
|
|
137
|
+
* Keeps a syntax colour's hue but mixes it toward `fg` in 10% steps until it reaches
|
|
138
|
+
* SYNTAX_CONTRAST on `bg` (green, amber and cyan on the light theme's white card otherwise
|
|
139
|
+
* fall to 2–3:1). Token values never change; only the mix is derived from them.
|
|
140
|
+
*/
|
|
141
|
+
function readableOn(color, bg, fg) {
|
|
142
|
+
for (let step = 0; step <= 10; step++) {
|
|
143
|
+
const hex = mixHex(color, fg, step / 10);
|
|
144
|
+
if (contrastRatio(hex, bg) >= SYNTAX_CONTRAST) return hex;
|
|
145
|
+
}
|
|
146
|
+
return fg;
|
|
147
|
+
}
|
|
148
|
+
/** `a` mixed with `b` by `t` (0–1), per sRGB channel. */
|
|
149
|
+
function mixHex(a, b, t) {
|
|
150
|
+
return `#${[
|
|
151
|
+
0,
|
|
152
|
+
1,
|
|
153
|
+
2
|
|
154
|
+
].map((i) => Math.round(channel(a, i) * (1 - t) + channel(b, i) * t).toString(16).padStart(2, "0")).join("")}`;
|
|
155
|
+
}
|
|
156
|
+
/** Alpha of the current-line highlight (`${fg}0d`): syntax text must also read on it. */
|
|
157
|
+
const LINE_HIGHLIGHT_ALPHA = 13;
|
|
158
|
+
/**
|
|
159
|
+
* Builds Monaco theme data from the live brand tokens read on `el`
|
|
160
|
+
* (default: <html>). Every colour comes from `tokenHex`; alpha is a hex suffix.
|
|
161
|
+
*/
|
|
162
|
+
function brandThemeData(theme, el) {
|
|
163
|
+
const c = (name) => tokenHex(name, el);
|
|
164
|
+
const primary = c("primary");
|
|
165
|
+
const muted = c("muted-foreground");
|
|
166
|
+
const border = c("border");
|
|
167
|
+
const fg = c("foreground");
|
|
168
|
+
const card = c("card");
|
|
169
|
+
const popover = c("popover");
|
|
170
|
+
const destructive = c("destructive");
|
|
171
|
+
const transparent = `${card}00`;
|
|
172
|
+
const lineBg = mixHex(card, fg, LINE_HIGHLIGHT_ALPHA / 255);
|
|
173
|
+
const syntax = (color) => readableOn(color, lineBg, fg);
|
|
174
|
+
return {
|
|
175
|
+
base: BASE[theme],
|
|
176
|
+
inherit: true,
|
|
177
|
+
rules: RULES.map(([token, color, fontStyle]) => ({
|
|
178
|
+
token,
|
|
179
|
+
foreground: syntax(c(color)).slice(1),
|
|
180
|
+
...fontStyle ? { fontStyle } : {}
|
|
181
|
+
})),
|
|
182
|
+
colors: {
|
|
183
|
+
"editor.background": card,
|
|
184
|
+
"editor.foreground": fg,
|
|
185
|
+
"editorLineNumber.foreground": `${muted}59`,
|
|
186
|
+
"editorLineNumber.activeForeground": primary,
|
|
187
|
+
"editorCursor.foreground": primary,
|
|
188
|
+
"editor.selectionBackground": `${primary}33`,
|
|
189
|
+
"editor.inactiveSelectionBackground": `${primary}1f`,
|
|
190
|
+
"editor.selectionHighlightBackground": `${primary}1f`,
|
|
191
|
+
"editor.lineHighlightBackground": `${fg}${LINE_HIGHLIGHT_ALPHA.toString(16).padStart(2, "0")}`,
|
|
192
|
+
"editor.lineHighlightBorder": transparent,
|
|
193
|
+
"editorIndentGuide.background": `${border}99`,
|
|
194
|
+
"editorIndentGuide.activeBackground": `${primary}80`,
|
|
195
|
+
"editorGutter.background": card,
|
|
196
|
+
"editorWhitespace.foreground": border,
|
|
197
|
+
"editorBracketMatch.background": `${primary}26`,
|
|
198
|
+
"editorBracketMatch.border": `${primary}99`,
|
|
199
|
+
"editorBracketHighlight.foreground1": syntax(primary),
|
|
200
|
+
"editorBracketHighlight.foreground2": syntax(c("category-violet")),
|
|
201
|
+
"editorBracketHighlight.foreground3": syntax(c("category-cyan")),
|
|
202
|
+
"editorOverviewRuler.border": transparent,
|
|
203
|
+
"scrollbarSlider.background": `${muted}2e`,
|
|
204
|
+
"scrollbarSlider.hoverBackground": `${muted}4d`,
|
|
205
|
+
"scrollbarSlider.activeBackground": `${muted}70`,
|
|
206
|
+
"editorWidget.background": popover,
|
|
207
|
+
"editorWidget.border": border,
|
|
208
|
+
"editorSuggestWidget.background": popover,
|
|
209
|
+
"editorSuggestWidget.border": border,
|
|
210
|
+
"editorSuggestWidget.selectedBackground": `${primary}26`,
|
|
211
|
+
"editorSuggestWidget.highlightForeground": primary,
|
|
212
|
+
"editorHoverWidget.background": popover,
|
|
213
|
+
"editorHoverWidget.border": border,
|
|
214
|
+
"editorGutter.modifiedBackground": c("info"),
|
|
215
|
+
"editorGutter.addedBackground": primary,
|
|
216
|
+
"editorGutter.deletedBackground": destructive,
|
|
217
|
+
"minimap.background": card,
|
|
218
|
+
"minimapSlider.background": `${muted}24`,
|
|
219
|
+
"diffEditor.insertedTextBackground": `${primary}26`,
|
|
220
|
+
"diffEditor.removedTextBackground": `${destructive}2e`,
|
|
221
|
+
"diffEditor.insertedLineBackground": `${primary}14`,
|
|
222
|
+
"diffEditor.removedLineBackground": `${destructive}14`,
|
|
223
|
+
"diffEditorGutter.insertedLineBackground": `${primary}24`,
|
|
224
|
+
"diffEditorGutter.removedLineBackground": `${destructive}24`,
|
|
225
|
+
"diffEditor.diagonalFill": `${border}80`
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Theme class a probe element needs to resolve a theme's tokens. Light is `:root`. */
|
|
230
|
+
const PROBE_CLASS = {
|
|
231
|
+
dark: "dark",
|
|
232
|
+
light: "",
|
|
233
|
+
high_contrast: "high_contrast"
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Registers `gntik-dark`, `gntik-light` and `gntik-hc` from the live brand tokens.
|
|
237
|
+
* The active theme is read from <html>; dark and high contrast are resolved on a
|
|
238
|
+
* hidden probe carrying their class. Light lives on `:root`, so while another
|
|
239
|
+
* theme is active it cannot be resolved exactly — call again after every theme
|
|
240
|
+
* switch (the components do this through `observeTheme`).
|
|
241
|
+
*/
|
|
242
|
+
function defineBrandThemes(monaco) {
|
|
243
|
+
const active = currentTheme();
|
|
244
|
+
const doc = typeof document === "undefined" ? void 0 : document;
|
|
245
|
+
for (const theme of Object.keys(BRAND_THEMES)) {
|
|
246
|
+
let probe;
|
|
247
|
+
if (theme !== active && doc && PROBE_CLASS[theme]) {
|
|
248
|
+
probe = doc.createElement("div");
|
|
249
|
+
probe.className = PROBE_CLASS[theme];
|
|
250
|
+
probe.hidden = true;
|
|
251
|
+
doc.body.appendChild(probe);
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
monaco.editor.defineTheme(BRAND_THEMES[theme], brandThemeData(theme, probe));
|
|
255
|
+
} finally {
|
|
256
|
+
probe?.remove();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/loader.ts
|
|
262
|
+
let cached;
|
|
263
|
+
/** Default loader: a dynamic `import('monaco-editor')`, so Monaco lands in its own chunk. */
|
|
264
|
+
const loadMonaco = () => {
|
|
265
|
+
cached ??= import("monaco-editor").then((m) => {
|
|
266
|
+
if (typeof document !== "undefined" && document.fonts?.ready) document.fonts.ready.then(() => m.editor.remeasureFonts());
|
|
267
|
+
return m;
|
|
268
|
+
});
|
|
269
|
+
cached.catch(() => {
|
|
270
|
+
cached = void 0;
|
|
271
|
+
});
|
|
272
|
+
return cached;
|
|
273
|
+
};
|
|
274
|
+
/** Loads Monaco once per loader and reports its state. */
|
|
275
|
+
function useMonaco(loader = loadMonaco) {
|
|
276
|
+
const [entry, setEntry] = useState(null);
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
let alive = true;
|
|
279
|
+
loader().then((monaco) => alive && setEntry({
|
|
280
|
+
loader,
|
|
281
|
+
state: {
|
|
282
|
+
status: "ready",
|
|
283
|
+
monaco
|
|
284
|
+
}
|
|
285
|
+
}), (error) => alive && setEntry({
|
|
286
|
+
loader,
|
|
287
|
+
state: {
|
|
288
|
+
status: "error",
|
|
289
|
+
monaco: null,
|
|
290
|
+
error
|
|
291
|
+
}
|
|
292
|
+
}));
|
|
293
|
+
return () => {
|
|
294
|
+
alive = false;
|
|
295
|
+
};
|
|
296
|
+
}, [loader]);
|
|
297
|
+
return entry && entry.loader === loader ? entry.state : {
|
|
298
|
+
status: "loading",
|
|
299
|
+
monaco: null
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** Registers the brand themes, applies the active one and re-skins on every <html> theme switch. */
|
|
303
|
+
function useBrandTheme(monaco) {
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (!monaco) return void 0;
|
|
306
|
+
const apply = () => {
|
|
307
|
+
defineBrandThemes(monaco);
|
|
308
|
+
monaco.editor.setTheme(brandThemeFor());
|
|
309
|
+
};
|
|
310
|
+
apply();
|
|
311
|
+
return observeTheme(apply);
|
|
312
|
+
}, [monaco]);
|
|
313
|
+
}
|
|
314
|
+
/** Brand defaults shared by both editors (Geist Mono, flat chrome, no overview ruler). */
|
|
315
|
+
const BASE_OPTIONS = {
|
|
316
|
+
automaticLayout: true,
|
|
317
|
+
editContext: false,
|
|
318
|
+
fontFamily: "Geist Mono, ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
319
|
+
fontSize: 13,
|
|
320
|
+
lineHeight: 21,
|
|
321
|
+
fontLigatures: false,
|
|
322
|
+
letterSpacing: .2,
|
|
323
|
+
padding: {
|
|
324
|
+
top: 14,
|
|
325
|
+
bottom: 14
|
|
326
|
+
},
|
|
327
|
+
scrollBeyondLastLine: false,
|
|
328
|
+
smoothScrolling: true,
|
|
329
|
+
cursorBlinking: "smooth",
|
|
330
|
+
cursorSmoothCaretAnimation: "on",
|
|
331
|
+
renderLineHighlight: "all",
|
|
332
|
+
roundedSelection: true,
|
|
333
|
+
tabSize: 2,
|
|
334
|
+
insertSpaces: true,
|
|
335
|
+
guides: {
|
|
336
|
+
indentation: true,
|
|
337
|
+
bracketPairs: true
|
|
338
|
+
},
|
|
339
|
+
scrollbar: {
|
|
340
|
+
verticalScrollbarSize: 10,
|
|
341
|
+
horizontalScrollbarSize: 10,
|
|
342
|
+
useShadows: false
|
|
343
|
+
},
|
|
344
|
+
overviewRulerLanes: 0,
|
|
345
|
+
overviewRulerBorder: false,
|
|
346
|
+
fixedOverflowWidgets: true,
|
|
347
|
+
minimap: { enabled: false }
|
|
348
|
+
};
|
|
349
|
+
/** Forces a few layout passes: containers and webfonts often settle after creation. */
|
|
350
|
+
function kickLayout(layout) {
|
|
351
|
+
const run = () => {
|
|
352
|
+
try {
|
|
353
|
+
layout();
|
|
354
|
+
} catch {}
|
|
355
|
+
};
|
|
356
|
+
const raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame(run) : 0;
|
|
357
|
+
const timers = [setTimeout(run, 70), setTimeout(run, 240)];
|
|
358
|
+
return () => {
|
|
359
|
+
if (raf && typeof cancelAnimationFrame === "function") cancelAnimationFrame(raf);
|
|
360
|
+
timers.forEach(clearTimeout);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/CodeEditor.tsx
|
|
365
|
+
/** Monaco code editor themed by the brand tokens; loads Monaco lazily. */
|
|
366
|
+
function CodeEditor({ value, defaultValue = "", onChange, language = "plaintext", readOnly = false, height = 320, "aria-label": ariaLabel = "Code editor", options, className, loader = loadMonaco, onMount }) {
|
|
367
|
+
const state = useMonaco(loader);
|
|
368
|
+
const monaco = state.monaco;
|
|
369
|
+
const host = useRef(null);
|
|
370
|
+
const instance = useRef(null);
|
|
371
|
+
const syncing = useRef(false);
|
|
372
|
+
const latest = useRef({
|
|
373
|
+
value,
|
|
374
|
+
defaultValue,
|
|
375
|
+
language,
|
|
376
|
+
readOnly,
|
|
377
|
+
ariaLabel,
|
|
378
|
+
options,
|
|
379
|
+
onChange,
|
|
380
|
+
onMount
|
|
381
|
+
});
|
|
382
|
+
useLayoutEffect(() => {
|
|
383
|
+
latest.current = {
|
|
384
|
+
value,
|
|
385
|
+
defaultValue,
|
|
386
|
+
language,
|
|
387
|
+
readOnly,
|
|
388
|
+
ariaLabel,
|
|
389
|
+
options,
|
|
390
|
+
onChange,
|
|
391
|
+
onMount
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
useBrandTheme(monaco);
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
if (!monaco || !host.current) return void 0;
|
|
397
|
+
const p = latest.current;
|
|
398
|
+
const ed = monaco.editor.create(host.current, {
|
|
399
|
+
...BASE_OPTIONS,
|
|
400
|
+
...p.options,
|
|
401
|
+
value: p.value ?? p.defaultValue,
|
|
402
|
+
language: p.language,
|
|
403
|
+
readOnly: p.readOnly,
|
|
404
|
+
ariaLabel: p.ariaLabel,
|
|
405
|
+
theme: brandThemeFor()
|
|
406
|
+
});
|
|
407
|
+
instance.current = ed;
|
|
408
|
+
const sub = ed.onDidChangeModelContent((event) => {
|
|
409
|
+
if (!syncing.current) latest.current.onChange?.(ed.getValue(), event);
|
|
410
|
+
});
|
|
411
|
+
p.onMount?.(ed, monaco);
|
|
412
|
+
const stopLayout = kickLayout(() => ed.layout());
|
|
413
|
+
return () => {
|
|
414
|
+
stopLayout();
|
|
415
|
+
sub.dispose();
|
|
416
|
+
ed.dispose();
|
|
417
|
+
instance.current = null;
|
|
418
|
+
};
|
|
419
|
+
}, [monaco]);
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
const ed = instance.current;
|
|
422
|
+
if (!ed || value === void 0 || ed.getValue() === value) return;
|
|
423
|
+
syncing.current = true;
|
|
424
|
+
try {
|
|
425
|
+
ed.setValue(value);
|
|
426
|
+
} finally {
|
|
427
|
+
syncing.current = false;
|
|
428
|
+
}
|
|
429
|
+
}, [value, monaco]);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
const model = instance.current?.getModel();
|
|
432
|
+
if (monaco && model) monaco.editor.setModelLanguage(model, language);
|
|
433
|
+
}, [language, monaco]);
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
instance.current?.updateOptions({
|
|
436
|
+
...options,
|
|
437
|
+
readOnly,
|
|
438
|
+
ariaLabel
|
|
439
|
+
});
|
|
440
|
+
}, [
|
|
441
|
+
options,
|
|
442
|
+
readOnly,
|
|
443
|
+
ariaLabel,
|
|
444
|
+
monaco
|
|
445
|
+
]);
|
|
446
|
+
return /* @__PURE__ */ jsx(EditorFrame, {
|
|
447
|
+
state,
|
|
448
|
+
height,
|
|
449
|
+
label: "code editor",
|
|
450
|
+
className,
|
|
451
|
+
hostRef: host
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/DiffEditor.tsx
|
|
456
|
+
/** Monaco diff editor themed by the brand tokens; loads Monaco lazily. */
|
|
457
|
+
function DiffEditor({ original, modified, language = "plaintext", readOnly = true, sideBySide = true, height = 320, "aria-label": ariaLabel = "Diff editor", onChange, options, className, loader = loadMonaco, onMount }) {
|
|
458
|
+
const state = useMonaco(loader);
|
|
459
|
+
const monaco = state.monaco;
|
|
460
|
+
const host = useRef(null);
|
|
461
|
+
const instance = useRef(null);
|
|
462
|
+
const syncing = useRef(false);
|
|
463
|
+
const latest = useRef({
|
|
464
|
+
original,
|
|
465
|
+
modified,
|
|
466
|
+
language,
|
|
467
|
+
readOnly,
|
|
468
|
+
sideBySide,
|
|
469
|
+
ariaLabel,
|
|
470
|
+
options,
|
|
471
|
+
onChange,
|
|
472
|
+
onMount
|
|
473
|
+
});
|
|
474
|
+
useLayoutEffect(() => {
|
|
475
|
+
latest.current = {
|
|
476
|
+
original,
|
|
477
|
+
modified,
|
|
478
|
+
language,
|
|
479
|
+
readOnly,
|
|
480
|
+
sideBySide,
|
|
481
|
+
ariaLabel,
|
|
482
|
+
options,
|
|
483
|
+
onChange,
|
|
484
|
+
onMount
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
useBrandTheme(monaco);
|
|
488
|
+
useEffect(() => {
|
|
489
|
+
if (!monaco || !host.current) return void 0;
|
|
490
|
+
const p = latest.current;
|
|
491
|
+
const ed = monaco.editor.createDiffEditor(host.current, {
|
|
492
|
+
...BASE_OPTIONS,
|
|
493
|
+
ignoreTrimWhitespace: false,
|
|
494
|
+
renderOverviewRuler: false,
|
|
495
|
+
renderIndicators: true,
|
|
496
|
+
renderMarginRevertIcon: false,
|
|
497
|
+
useInlineViewWhenSpaceIsLimited: false,
|
|
498
|
+
...p.options,
|
|
499
|
+
readOnly: p.readOnly,
|
|
500
|
+
renderSideBySide: p.sideBySide,
|
|
501
|
+
originalAriaLabel: `${p.ariaLabel}, original`,
|
|
502
|
+
modifiedAriaLabel: `${p.ariaLabel}, modified`,
|
|
503
|
+
theme: brandThemeFor()
|
|
504
|
+
});
|
|
505
|
+
const originalModel = monaco.editor.createModel(p.original, p.language);
|
|
506
|
+
const modifiedModel = monaco.editor.createModel(p.modified, p.language);
|
|
507
|
+
ed.setModel({
|
|
508
|
+
original: originalModel,
|
|
509
|
+
modified: modifiedModel
|
|
510
|
+
});
|
|
511
|
+
instance.current = ed;
|
|
512
|
+
const sub = modifiedModel.onDidChangeContent(() => {
|
|
513
|
+
if (!syncing.current) latest.current.onChange?.(modifiedModel.getValue());
|
|
514
|
+
});
|
|
515
|
+
p.onMount?.(ed, monaco);
|
|
516
|
+
const fit = () => {
|
|
517
|
+
const el = host.current;
|
|
518
|
+
if (el) ed.layout({
|
|
519
|
+
width: el.clientWidth,
|
|
520
|
+
height: el.clientHeight
|
|
521
|
+
});
|
|
522
|
+
};
|
|
523
|
+
const stopLayout = kickLayout(fit);
|
|
524
|
+
return () => {
|
|
525
|
+
stopLayout();
|
|
526
|
+
sub.dispose();
|
|
527
|
+
ed.dispose();
|
|
528
|
+
originalModel.dispose();
|
|
529
|
+
modifiedModel.dispose();
|
|
530
|
+
instance.current = null;
|
|
531
|
+
};
|
|
532
|
+
}, [monaco]);
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
const model = instance.current?.getModel();
|
|
535
|
+
if (!model) return;
|
|
536
|
+
syncing.current = true;
|
|
537
|
+
try {
|
|
538
|
+
if (model.original.getValue() !== original) model.original.setValue(original);
|
|
539
|
+
if (model.modified.getValue() !== modified) model.modified.setValue(modified);
|
|
540
|
+
} finally {
|
|
541
|
+
syncing.current = false;
|
|
542
|
+
}
|
|
543
|
+
}, [
|
|
544
|
+
original,
|
|
545
|
+
modified,
|
|
546
|
+
monaco
|
|
547
|
+
]);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
const model = instance.current?.getModel();
|
|
550
|
+
if (!monaco || !model) return;
|
|
551
|
+
monaco.editor.setModelLanguage(model.original, language);
|
|
552
|
+
monaco.editor.setModelLanguage(model.modified, language);
|
|
553
|
+
}, [language, monaco]);
|
|
554
|
+
useEffect(() => {
|
|
555
|
+
instance.current?.updateOptions({
|
|
556
|
+
...options,
|
|
557
|
+
readOnly,
|
|
558
|
+
renderSideBySide: sideBySide
|
|
559
|
+
});
|
|
560
|
+
}, [
|
|
561
|
+
options,
|
|
562
|
+
readOnly,
|
|
563
|
+
sideBySide,
|
|
564
|
+
monaco
|
|
565
|
+
]);
|
|
566
|
+
return /* @__PURE__ */ jsx(EditorFrame, {
|
|
567
|
+
state,
|
|
568
|
+
height,
|
|
569
|
+
label: "diff editor",
|
|
570
|
+
className,
|
|
571
|
+
hostRef: host
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/workers.ts
|
|
576
|
+
/**
|
|
577
|
+
* Sets `self.MonacoEnvironment.getWorker`. Call once, before the first editor mounts.
|
|
578
|
+
* The package never imports workers itself: bundlers resolve them differently
|
|
579
|
+
* including consumer-injected YAML workers. Labels are passed through unchanged
|
|
580
|
+
* (see README for the Vite `?worker` recipe).
|
|
581
|
+
*/
|
|
582
|
+
function configureMonacoWorkers(getWorker) {
|
|
583
|
+
const scope = globalThis;
|
|
584
|
+
scope.MonacoEnvironment = {
|
|
585
|
+
...scope.MonacoEnvironment,
|
|
586
|
+
getWorker
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
export { BASE_OPTIONS, BRAND_THEMES, CodeEditor, DiffEditor, EditorSkeleton, brandThemeData, brandThemeFor, configureMonacoWorkers, defineBrandThemes, loadMonaco, useBrandTheme, useMonaco };
|
|
591
|
+
|
|
592
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/EditorFrame.tsx","../src/theme.ts","../src/loader.ts","../src/CodeEditor.tsx","../src/DiffEditor.tsx","../src/workers.ts"],"sourcesContent":["import type { CSSProperties, ReactNode, Ref } from 'react';\nimport type { MonacoState } from './loader';\n\nexport interface EditorFrameProps {\n state: MonacoState;\n height: number | string;\n label: string;\n className?: string;\n hostRef: Ref<HTMLDivElement>;\n}\n\nconst SKELETON_LINES = ['w-2/5', 'w-3/4', 'w-1/2', 'w-2/3', 'w-1/3', 'w-3/5'];\n\n/** Token-styled placeholder shown until Monaco has loaded. */\nexport function EditorSkeleton({ label }: { label: string }) {\n return (\n <div\n role=\"status\"\n aria-label={`Loading ${label}`}\n data-slot=\"editor-skeleton\"\n className=\"absolute inset-0 flex flex-col gap-3 bg-card px-4 py-3.5\"\n >\n {SKELETON_LINES.map((width, i) => (\n <div key={i} className=\"flex items-center gap-4\">\n <span className=\"h-2.5 w-4 rounded-sm bg-muted motion-safe:animate-pulse\" />\n <span className={`h-2.5 ${width} rounded-sm bg-muted motion-safe:animate-pulse`} />\n </div>\n ))}\n </div>\n );\n}\n\nfunction EditorError({ label }: { label: string }): ReactNode {\n return (\n <div\n role=\"alert\"\n data-slot=\"editor-error\"\n className=\"absolute inset-0 grid place-items-center border border-dashed border-border bg-card px-8 text-center\"\n >\n <div>\n <p className=\"font-sans text-sm font-semibold text-foreground\">The {label} could not load</p>\n <p className=\"mt-1 font-sans text-xs text-muted-foreground\">\n Check that <span className=\"font-mono\">monaco-editor</span> is installed and its workers are configured.\n </p>\n </div>\n </div>\n );\n}\n\n/** Sized host for a Monaco instance plus its loading and error states. */\nexport function EditorFrame({ state, height, label, className, hostRef }: EditorFrameProps) {\n const style: CSSProperties = { height };\n return (\n <div\n data-slot=\"editor\"\n data-state={state.status}\n aria-busy={state.status === 'loading' || undefined}\n className={['relative w-full overflow-hidden bg-card', className].filter(Boolean).join(' ')}\n style={style}\n >\n <div ref={hostRef} className=\"absolute inset-0\" />\n {state.status === 'loading' && <EditorSkeleton label={label} />}\n {state.status === 'error' && <EditorError label={label} />}\n </div>\n );\n}\n","import type { editor } from 'monaco-editor';\nimport { currentTheme, tokenHex, type Theme } from '@gntik-ai/tokens/runtime';\n\n/** Monaco theme names registered by `defineBrandThemes`. */\nexport const BRAND_THEMES = {\n dark: 'gntik-dark',\n light: 'gntik-light',\n high_contrast: 'gntik-hc',\n} as const satisfies Record<Theme, string>;\n\nexport type BrandThemeName = (typeof BRAND_THEMES)[Theme];\n\n/** The subset of the Monaco API the theme helpers touch (keeps them mock-friendly). */\nexport interface MonacoThemeApi {\n editor: Pick<typeof editor, 'defineTheme'>;\n}\n\nconst BASE: Record<Theme, editor.BuiltinTheme> = {\n dark: 'vs-dark',\n light: 'vs',\n high_contrast: 'hc-black',\n};\n\n/** Monaco theme name for a gntik-ui theme. Defaults to the active <html> theme. */\nexport function brandThemeFor(theme: Theme = currentTheme()): BrandThemeName {\n return BRAND_THEMES[theme];\n}\n\n/** Syntax token → brand token. Keywords use the brand green; the rest, categorical accents. */\nconst RULES: ReadonlyArray<readonly [token: string, color: string, fontStyle?: string]> = [\n ['', 'foreground'],\n ['comment', 'muted-foreground', 'italic'],\n ['keyword', 'primary'],\n ['keyword.json', 'primary'],\n ['operator', 'primary'],\n ['operators', 'primary'],\n ['tag', 'primary'],\n ['string', 'category-cyan'],\n ['string.escape', 'category-amber'],\n ['string.key.json', 'primary'],\n ['string.value.json', 'category-cyan'],\n ['attribute.value', 'category-cyan'],\n ['attribute.name', 'category-violet'],\n ['number', 'category-amber'],\n ['constant', 'category-amber'],\n ['regexp', 'category-rose'],\n ['type', 'category-violet'],\n ['type.identifier', 'category-violet'],\n ['identifier', 'foreground'],\n ['delimiter', 'muted-foreground'],\n ['delimiter.bracket', 'muted-foreground'],\n ['annotation', 'category-rose'],\n ['metatag', 'category-rose'],\n];\n\nconst channel = (hex: string, i: number) => parseInt(hex.slice(1 + 2 * i, 3 + 2 * i), 16);\nconst luminance = (hex: string) => {\n const [r, g, b] = [0, 1, 2].map((i) => {\n const v = channel(hex, i) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r! + 0.7152 * g! + 0.0722 * b!;\n};\nexport const contrastRatio = (a: string, b: string) => {\n const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);\n return (hi! + 0.05) / (lo! + 0.05);\n};\n/** Syntax text target: AA (4.5:1) with headroom for the current-line highlight. */\nconst SYNTAX_CONTRAST = 4.7;\n\n/**\n * Keeps a syntax colour's hue but mixes it toward `fg` in 10% steps until it reaches\n * SYNTAX_CONTRAST on `bg` (green, amber and cyan on the light theme's white card otherwise\n * fall to 2–3:1). Token values never change; only the mix is derived from them.\n */\nexport function readableOn(color: string, bg: string, fg: string): string {\n for (let step = 0; step <= 10; step++) {\n const hex = mixHex(color, fg, step / 10);\n if (contrastRatio(hex, bg) >= SYNTAX_CONTRAST) return hex;\n }\n return fg;\n}\n\n/** `a` mixed with `b` by `t` (0–1), per sRGB channel. */\nexport function mixHex(a: string, b: string, t: number): string {\n return `#${[0, 1, 2]\n .map((i) =>\n Math.round(channel(a, i) * (1 - t) + channel(b, i) * t)\n .toString(16)\n .padStart(2, '0'),\n )\n .join('')}`;\n}\n\n/** Alpha of the current-line highlight (`${fg}0d`): syntax text must also read on it. */\nconst LINE_HIGHLIGHT_ALPHA = 0x0d;\n\n/**\n * Builds Monaco theme data from the live brand tokens read on `el`\n * (default: <html>). Every colour comes from `tokenHex`; alpha is a hex suffix.\n */\nexport function brandThemeData(theme: Theme, el?: Element): editor.IStandaloneThemeData {\n const c = (name: string) => tokenHex(name, el);\n const primary = c('primary');\n const muted = c('muted-foreground');\n const border = c('border');\n const fg = c('foreground');\n const card = c('card');\n const popover = c('popover');\n const destructive = c('destructive');\n const transparent = `${card}00`;\n // Darkest surface syntax text sits on: the card under the current-line highlight.\n const lineBg = mixHex(card, fg, LINE_HIGHLIGHT_ALPHA / 255);\n const syntax = (color: string) => readableOn(color, lineBg, fg);\n\n return {\n base: BASE[theme],\n inherit: true,\n rules: RULES.map(([token, color, fontStyle]) => ({\n token,\n foreground: syntax(c(color)).slice(1),\n ...(fontStyle ? { fontStyle } : {}),\n })),\n colors: {\n 'editor.background': card,\n 'editor.foreground': fg,\n 'editorLineNumber.foreground': `${muted}59`,\n 'editorLineNumber.activeForeground': primary,\n 'editorCursor.foreground': primary,\n 'editor.selectionBackground': `${primary}33`,\n 'editor.inactiveSelectionBackground': `${primary}1f`,\n 'editor.selectionHighlightBackground': `${primary}1f`,\n 'editor.lineHighlightBackground': `${fg}${LINE_HIGHLIGHT_ALPHA.toString(16).padStart(2, '0')}`,\n 'editor.lineHighlightBorder': transparent,\n 'editorIndentGuide.background': `${border}99`,\n 'editorIndentGuide.activeBackground': `${primary}80`,\n 'editorGutter.background': card,\n 'editorWhitespace.foreground': border,\n 'editorBracketMatch.background': `${primary}26`,\n 'editorBracketMatch.border': `${primary}99`,\n 'editorBracketHighlight.foreground1': syntax(primary),\n 'editorBracketHighlight.foreground2': syntax(c('category-violet')),\n 'editorBracketHighlight.foreground3': syntax(c('category-cyan')),\n 'editorOverviewRuler.border': transparent,\n 'scrollbarSlider.background': `${muted}2e`,\n 'scrollbarSlider.hoverBackground': `${muted}4d`,\n 'scrollbarSlider.activeBackground': `${muted}70`,\n 'editorWidget.background': popover,\n 'editorWidget.border': border,\n 'editorSuggestWidget.background': popover,\n 'editorSuggestWidget.border': border,\n 'editorSuggestWidget.selectedBackground': `${primary}26`,\n 'editorSuggestWidget.highlightForeground': primary,\n 'editorHoverWidget.background': popover,\n 'editorHoverWidget.border': border,\n 'editorGutter.modifiedBackground': c('info'),\n 'editorGutter.addedBackground': primary,\n 'editorGutter.deletedBackground': destructive,\n 'minimap.background': card,\n 'minimapSlider.background': `${muted}24`,\n 'diffEditor.insertedTextBackground': `${primary}26`,\n 'diffEditor.removedTextBackground': `${destructive}2e`,\n 'diffEditor.insertedLineBackground': `${primary}14`,\n 'diffEditor.removedLineBackground': `${destructive}14`,\n 'diffEditorGutter.insertedLineBackground': `${primary}24`,\n 'diffEditorGutter.removedLineBackground': `${destructive}24`,\n 'diffEditor.diagonalFill': `${border}80`,\n },\n };\n}\n\n/** Theme class a probe element needs to resolve a theme's tokens. Light is `:root`. */\nconst PROBE_CLASS: Record<Theme, string> = { dark: 'dark', light: '', high_contrast: 'high_contrast' };\n\n/**\n * Registers `gntik-dark`, `gntik-light` and `gntik-hc` from the live brand tokens.\n * The active theme is read from <html>; dark and high contrast are resolved on a\n * hidden probe carrying their class. Light lives on `:root`, so while another\n * theme is active it cannot be resolved exactly — call again after every theme\n * switch (the components do this through `observeTheme`).\n */\nexport function defineBrandThemes(monaco: MonacoThemeApi): void {\n const active = currentTheme();\n const doc = typeof document === 'undefined' ? undefined : document;\n for (const theme of Object.keys(BRAND_THEMES) as Theme[]) {\n let probe: HTMLElement | undefined;\n if (theme !== active && doc && PROBE_CLASS[theme]) {\n probe = doc.createElement('div');\n probe.className = PROBE_CLASS[theme];\n probe.hidden = true;\n doc.body.appendChild(probe);\n }\n try {\n monaco.editor.defineTheme(BRAND_THEMES[theme], brandThemeData(theme, probe));\n } finally {\n probe?.remove();\n }\n }\n}\n","import { useEffect, useState } from 'react';\nimport { observeTheme } from '@gntik-ai/tokens/runtime';\nimport type * as MonacoNamespace from 'monaco-editor';\nimport { brandThemeFor, defineBrandThemes } from './theme';\n\n/** The `monaco-editor` ESM module. */\nexport type Monaco = typeof MonacoNamespace;\n/** Returns the Monaco module; inject one to self-host, preconfigure or mock Monaco. */\nexport type MonacoLoader = () => Promise<Monaco>;\n\nlet cached: Promise<Monaco> | undefined;\n\n/** Default loader: a dynamic `import('monaco-editor')`, so Monaco lands in its own chunk. */\nexport const loadMonaco: MonacoLoader = () => {\n cached ??= import('monaco-editor').then((m) => {\n // Webfonts load late: re-measure glyph metrics once they are ready.\n if (typeof document !== 'undefined' && document.fonts?.ready) {\n void document.fonts.ready.then(() => m.editor.remeasureFonts());\n }\n return m;\n });\n cached.catch(() => {\n cached = undefined;\n });\n return cached;\n};\n\nexport type MonacoState =\n | { status: 'loading'; monaco: null }\n | { status: 'ready'; monaco: Monaco }\n | { status: 'error'; monaco: null; error: unknown };\n\n/** Loads Monaco once per loader and reports its state. */\nexport function useMonaco(loader: MonacoLoader = loadMonaco): MonacoState {\n // State is keyed by loader, so a new loader reads as \"loading\" without a sync setState.\n const [entry, setEntry] = useState<{ loader: MonacoLoader; state: MonacoState } | null>(null);\n useEffect(() => {\n let alive = true;\n loader().then(\n (monaco) => alive && setEntry({ loader, state: { status: 'ready', monaco } }),\n (error: unknown) => alive && setEntry({ loader, state: { status: 'error', monaco: null, error } }),\n );\n return () => {\n alive = false;\n };\n }, [loader]);\n return entry && entry.loader === loader ? entry.state : { status: 'loading', monaco: null };\n}\n\n/** Registers the brand themes, applies the active one and re-skins on every <html> theme switch. */\nexport function useBrandTheme(monaco: Monaco | null): void {\n useEffect(() => {\n if (!monaco) return undefined;\n const apply = () => {\n defineBrandThemes(monaco);\n monaco.editor.setTheme(brandThemeFor());\n };\n apply();\n return observeTheme(apply);\n }, [monaco]);\n}\n\n/** Brand defaults shared by both editors (Geist Mono, flat chrome, no overview ruler). */\nexport const BASE_OPTIONS = {\n automaticLayout: true,\n // Keep the labelled input focusable before the first click in Chromium.\n // Monaco 0.57's native EditContext can expose a zero-size textbox instead.\n editContext: false,\n fontFamily: 'Geist Mono, ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 13,\n lineHeight: 21,\n fontLigatures: false,\n letterSpacing: 0.2,\n padding: { top: 14, bottom: 14 },\n scrollBeyondLastLine: false,\n smoothScrolling: true,\n cursorBlinking: 'smooth',\n cursorSmoothCaretAnimation: 'on',\n renderLineHighlight: 'all',\n roundedSelection: true,\n tabSize: 2,\n insertSpaces: true,\n guides: { indentation: true, bracketPairs: true },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10, useShadows: false },\n overviewRulerLanes: 0,\n overviewRulerBorder: false,\n fixedOverflowWidgets: true,\n minimap: { enabled: false },\n} as const satisfies MonacoNamespace.editor.IStandaloneEditorConstructionOptions;\n\n/** Forces a few layout passes: containers and webfonts often settle after creation. */\nexport function kickLayout(layout: () => void): () => void {\n const run = () => {\n try {\n layout();\n } catch {\n /* editor already disposed */\n }\n };\n const raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame(run) : 0;\n const timers = [setTimeout(run, 70), setTimeout(run, 240)];\n return () => {\n if (raf && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(raf);\n timers.forEach(clearTimeout);\n };\n}\n","import { useEffect, useRef, useLayoutEffect } from 'react';\nimport type { editor } from 'monaco-editor';\nimport { EditorFrame } from './EditorFrame';\nimport {\n BASE_OPTIONS,\n kickLayout,\n loadMonaco,\n useBrandTheme,\n useMonaco,\n type Monaco,\n type MonacoLoader,\n} from './loader';\nimport { brandThemeFor } from './theme';\n\nexport interface CodeEditorProps {\n /** Controlled text. When it changes, the editor is updated (cursor kept when equal). */\n value?: string;\n /** Initial text for an uncontrolled editor. */\n defaultValue?: string;\n /** Called with the full text on every user edit. */\n onChange?: (value: string, event: editor.IModelContentChangedEvent) => void;\n /** Monaco language id, e.g. \"typescript\", \"json\", \"yaml\". */\n language?: string;\n readOnly?: boolean;\n /** CSS height of the editor box. */\n height?: number | string;\n /** Accessible name of the editor's text area. */\n 'aria-label'?: string;\n /** Extra Monaco options, merged over the brand defaults. */\n options?: editor.IStandaloneEditorConstructionOptions;\n className?: string;\n /** Override how Monaco is loaded (self-hosting, preconfigured instance, tests). */\n loader?: MonacoLoader;\n /** Called once the editor instance exists. */\n onMount?: (instance: editor.IStandaloneCodeEditor, monaco: Monaco) => void;\n}\n\n/** Monaco code editor themed by the brand tokens; loads Monaco lazily. */\nexport function CodeEditor({\n value,\n defaultValue = '',\n onChange,\n language = 'plaintext',\n readOnly = false,\n height = 320,\n 'aria-label': ariaLabel = 'Code editor',\n options,\n className,\n loader = loadMonaco,\n onMount,\n}: CodeEditorProps) {\n const state = useMonaco(loader);\n const monaco = state.monaco;\n const host = useRef<HTMLDivElement>(null);\n const instance = useRef<editor.IStandaloneCodeEditor | null>(null);\n const syncing = useRef(false);\n const latest = useRef({ value, defaultValue, language, readOnly, ariaLabel, options, onChange, onMount });\n useLayoutEffect(() => {\n latest.current = { value, defaultValue, language, readOnly, ariaLabel, options, onChange, onMount };\n });\n\n useBrandTheme(monaco);\n\n useEffect(() => {\n if (!monaco || !host.current) return undefined;\n const p = latest.current;\n const ed = monaco.editor.create(host.current, {\n ...BASE_OPTIONS,\n ...p.options,\n value: p.value ?? p.defaultValue,\n language: p.language,\n readOnly: p.readOnly,\n ariaLabel: p.ariaLabel,\n theme: brandThemeFor(),\n });\n instance.current = ed;\n const sub = ed.onDidChangeModelContent((event) => {\n if (!syncing.current) latest.current.onChange?.(ed.getValue(), event);\n });\n p.onMount?.(ed, monaco);\n const stopLayout = kickLayout(() => ed.layout());\n return () => {\n stopLayout();\n sub.dispose();\n ed.dispose();\n instance.current = null;\n };\n }, [monaco]);\n\n useEffect(() => {\n const ed = instance.current;\n if (!ed || value === undefined || ed.getValue() === value) return;\n syncing.current = true;\n try {\n ed.setValue(value);\n } finally {\n syncing.current = false;\n }\n }, [value, monaco]);\n\n useEffect(() => {\n const model = instance.current?.getModel();\n if (monaco && model) monaco.editor.setModelLanguage(model, language);\n }, [language, monaco]);\n\n useEffect(() => {\n instance.current?.updateOptions({ ...options, readOnly, ariaLabel });\n }, [options, readOnly, ariaLabel, monaco]);\n\n return <EditorFrame state={state} height={height} label=\"code editor\" className={className} hostRef={host} />;\n}\n","import { useEffect, useRef, useLayoutEffect } from 'react';\nimport type { editor } from 'monaco-editor';\nimport { EditorFrame } from './EditorFrame';\nimport {\n BASE_OPTIONS,\n kickLayout,\n loadMonaco,\n useBrandTheme,\n useMonaco,\n type Monaco,\n type MonacoLoader,\n} from './loader';\nimport { brandThemeFor } from './theme';\n\nexport interface DiffEditorProps {\n /** Left-hand (before) text. */\n original: string;\n /** Right-hand (after) text. */\n modified: string;\n /** Monaco language id for both sides. */\n language?: string;\n /** Read-only by default; set false to let users edit the modified side. */\n readOnly?: boolean;\n /** Side by side (default) or inline. */\n sideBySide?: boolean;\n height?: number | string;\n /** Accessible name; the sides are announced as \"<label>, original\" / \"<label>, modified\". */\n 'aria-label'?: string;\n /** Called with the modified text when the user edits it (requires readOnly={false}). */\n onChange?: (modified: string) => void;\n /** Extra Monaco diff options, merged over the brand defaults. */\n options?: editor.IStandaloneDiffEditorConstructionOptions;\n className?: string;\n loader?: MonacoLoader;\n onMount?: (instance: editor.IStandaloneDiffEditor, monaco: Monaco) => void;\n}\n\n/** Monaco diff editor themed by the brand tokens; loads Monaco lazily. */\nexport function DiffEditor({\n original,\n modified,\n language = 'plaintext',\n readOnly = true,\n sideBySide = true,\n height = 320,\n 'aria-label': ariaLabel = 'Diff editor',\n onChange,\n options,\n className,\n loader = loadMonaco,\n onMount,\n}: DiffEditorProps) {\n const state = useMonaco(loader);\n const monaco = state.monaco;\n const host = useRef<HTMLDivElement>(null);\n const instance = useRef<editor.IStandaloneDiffEditor | null>(null);\n const syncing = useRef(false);\n const latest = useRef({ original, modified, language, readOnly, sideBySide, ariaLabel, options, onChange, onMount });\n useLayoutEffect(() => {\n latest.current = { original, modified, language, readOnly, sideBySide, ariaLabel, options, onChange, onMount };\n });\n\n useBrandTheme(monaco);\n\n useEffect(() => {\n if (!monaco || !host.current) return undefined;\n const p = latest.current;\n const ed = monaco.editor.createDiffEditor(host.current, {\n ...BASE_OPTIONS,\n ignoreTrimWhitespace: false,\n renderOverviewRuler: false,\n renderIndicators: true,\n renderMarginRevertIcon: false,\n useInlineViewWhenSpaceIsLimited: false,\n ...p.options,\n readOnly: p.readOnly,\n renderSideBySide: p.sideBySide,\n originalAriaLabel: `${p.ariaLabel}, original`,\n modifiedAriaLabel: `${p.ariaLabel}, modified`,\n theme: brandThemeFor(),\n });\n const originalModel = monaco.editor.createModel(p.original, p.language);\n const modifiedModel = monaco.editor.createModel(p.modified, p.language);\n ed.setModel({ original: originalModel, modified: modifiedModel });\n instance.current = ed;\n const sub = modifiedModel.onDidChangeContent(() => {\n if (!syncing.current) latest.current.onChange?.(modifiedModel.getValue());\n });\n p.onMount?.(ed, monaco);\n const fit = () => {\n const el = host.current;\n if (el) ed.layout({ width: el.clientWidth, height: el.clientHeight });\n };\n const stopLayout = kickLayout(fit);\n return () => {\n stopLayout();\n sub.dispose();\n ed.dispose();\n originalModel.dispose();\n modifiedModel.dispose();\n instance.current = null;\n };\n }, [monaco]);\n\n useEffect(() => {\n const model = instance.current?.getModel();\n if (!model) return;\n syncing.current = true;\n try {\n if (model.original.getValue() !== original) model.original.setValue(original);\n if (model.modified.getValue() !== modified) model.modified.setValue(modified);\n } finally {\n syncing.current = false;\n }\n }, [original, modified, monaco]);\n\n useEffect(() => {\n const model = instance.current?.getModel();\n if (!monaco || !model) return;\n monaco.editor.setModelLanguage(model.original, language);\n monaco.editor.setModelLanguage(model.modified, language);\n }, [language, monaco]);\n\n useEffect(() => {\n instance.current?.updateOptions({ ...options, readOnly, renderSideBySide: sideBySide });\n }, [options, readOnly, sideBySide, monaco]);\n\n return <EditorFrame state={state} height={height} label=\"diff editor\" className={className} hostRef={host} />;\n}\n","/** Worker factory Monaco calls per language service (`label` is e.g. \"json\", \"typescript\", \"yaml\"). */\nexport type MonacoWorkerFactory = (workerId: string, label: string) => Worker | Promise<Worker>;\n\ninterface MonacoEnvironmentLike {\n getWorker?: MonacoWorkerFactory;\n [key: string]: unknown;\n}\n\n/**\n * Sets `self.MonacoEnvironment.getWorker`. Call once, before the first editor mounts.\n * The package never imports workers itself: bundlers resolve them differently\n * including consumer-injected YAML workers. Labels are passed through unchanged\n * (see README for the Vite `?worker` recipe).\n */\nexport function configureMonacoWorkers(getWorker: MonacoWorkerFactory): void {\n const scope = globalThis as unknown as { MonacoEnvironment?: MonacoEnvironmentLike };\n scope.MonacoEnvironment = { ...scope.MonacoEnvironment, getWorker };\n}\n"],"mappings":";;;;;AAWA,MAAM,iBAAiB;CAAC;CAAS;CAAS;CAAS;CAAS;CAAS;AAAO;;AAG5E,SAAgB,eAAe,EAAE,SAA4B;CAC3D,OACE,oBAAC,OAAD;EACE,MAAK;EACL,cAAY,WAAW;EACvB,aAAU;EACV,WAAU;EAET,UAAA,eAAe,KAAK,OAAO,MAC1B,qBAAC,OAAD;GAAa,WAAU;GAAvB,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,0DAA2D,CAAA,GAC3E,oBAAC,QAAD,EAAM,WAAW,SAAS,MAAM,gDAAkD,CAAA,CAC/E;EAHK,GAAA,CAGL,CACN;CACE,CAAA;AAET;AAEA,SAAS,YAAY,EAAE,SAAuC;CAC5D,OACE,oBAAC,OAAD;EACE,MAAK;EACL,aAAU;EACV,WAAU;EAEV,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,qBAAC,KAAD;GAAG,WAAU;GAAb,UAAA;IAA+D;IAAK;IAAM;GAAkB;EAC5F,CAAA,GAAA,qBAAC,KAAD;GAAG,WAAU;GAAb,UAAA;IAA4D;IAC/C,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA;IAAmB,CAAA;IAAC;GAC1D;EACA,CAAA,CAAA,EAAA,CAAA;CACF,CAAA;AAET;;AAGA,SAAgB,YAAY,EAAE,OAAO,QAAQ,OAAO,WAAW,WAA6B;CAC1F,MAAM,QAAuB,EAAE,OAAO;CACtC,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY,MAAM;EAClB,aAAW,MAAM,WAAW,aAAa,KAAA;EACzC,WAAW,CAAC,2CAA2C,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACnF;EALT,UAAA;GAOE,oBAAC,OAAD;IAAK,KAAK;IAAS,WAAU;GAAoB,CAAA;GAChD,MAAM,WAAW,aAAa,oBAAC,gBAAD,EAAuB,MAAQ,CAAA;GAC7D,MAAM,WAAW,WAAW,oBAAC,aAAD,EAAoB,MAAQ,CAAA;EACtD;;AAET;;;;AC7DA,MAAa,eAAe;CAC1B,MAAM;CACN,OAAO;CACP,eAAe;AACjB;AASA,MAAM,OAA2C;CAC/C,MAAM;CACN,OAAO;CACP,eAAe;AACjB;;AAGA,SAAgB,cAAc,QAAe,aAAa,GAAmB;CAC3E,OAAO,aAAa;AACtB;;AAGA,MAAM,QAAoF;CACxF,CAAC,IAAI,YAAY;CACjB;EAAC;EAAW;EAAoB;CAAQ;CACxC,CAAC,WAAW,SAAS;CACrB,CAAC,gBAAgB,SAAS;CAC1B,CAAC,YAAY,SAAS;CACtB,CAAC,aAAa,SAAS;CACvB,CAAC,OAAO,SAAS;CACjB,CAAC,UAAU,eAAe;CAC1B,CAAC,iBAAiB,gBAAgB;CAClC,CAAC,mBAAmB,SAAS;CAC7B,CAAC,qBAAqB,eAAe;CACrC,CAAC,mBAAmB,eAAe;CACnC,CAAC,kBAAkB,iBAAiB;CACpC,CAAC,UAAU,gBAAgB;CAC3B,CAAC,YAAY,gBAAgB;CAC7B,CAAC,UAAU,eAAe;CAC1B,CAAC,QAAQ,iBAAiB;CAC1B,CAAC,mBAAmB,iBAAiB;CACrC,CAAC,cAAc,YAAY;CAC3B,CAAC,aAAa,kBAAkB;CAChC,CAAC,qBAAqB,kBAAkB;CACxC,CAAC,cAAc,eAAe;CAC9B,CAAC,WAAW,eAAe;AAC7B;AAEA,MAAM,WAAW,KAAa,MAAc,SAAS,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AACxF,MAAM,aAAa,QAAgB;CACjC,MAAM,CAAC,GAAG,GAAG,KAAK;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM;EACrC,MAAM,IAAI,QAAQ,KAAK,CAAC,IAAI;EAC5B,OAAO,KAAK,SAAU,IAAI,UAAU,IAAI,QAAS,UAAU;CAC7D,CAAC;CACD,OAAO,QAAS,IAAK,QAAS,IAAK,QAAS;AAC9C;AACA,MAAa,iBAAiB,GAAW,MAAc;CACrD,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAClE,QAAQ,KAAM,QAAS,KAAM;AAC/B;;AAEA,MAAM,kBAAkB;;;;;;AAOxB,SAAgB,WAAW,OAAe,IAAY,IAAoB;CACxE,KAAK,IAAI,OAAO,GAAG,QAAQ,IAAI,QAAQ;EACrC,MAAM,MAAM,OAAO,OAAO,IAAI,OAAO,EAAE;EACvC,IAAI,cAAc,KAAK,EAAE,KAAK,iBAAiB,OAAO;CACxD;CACA,OAAO;AACT;;AAGA,SAAgB,OAAO,GAAW,GAAW,GAAmB;CAC9D,OAAO,IAAI;EAAC;EAAG;EAAG;CAAC,CAAC,CACjB,KAAK,MACJ,KAAK,MAAM,QAAQ,GAAG,CAAC,KAAK,IAAI,KAAK,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC,CACpD,SAAS,EAAE,CAAC,CACZ,SAAS,GAAG,GAAG,CACpB,CAAC,CACA,KAAK,EAAE;AACZ;;AAGA,MAAM,uBAAuB;;;;;AAM7B,SAAgB,eAAe,OAAc,IAA2C;CACtF,MAAM,KAAK,SAAiB,SAAS,MAAM,EAAE;CAC7C,MAAM,UAAU,EAAE,SAAS;CAC3B,MAAM,QAAQ,EAAE,kBAAkB;CAClC,MAAM,SAAS,EAAE,QAAQ;CACzB,MAAM,KAAK,EAAE,YAAY;CACzB,MAAM,OAAO,EAAE,MAAM;CACrB,MAAM,UAAU,EAAE,SAAS;CAC3B,MAAM,cAAc,EAAE,aAAa;CACnC,MAAM,cAAc,GAAG,KAAK;CAE5B,MAAM,SAAS,OAAO,MAAM,IAAI,uBAAuB,GAAG;CAC1D,MAAM,UAAU,UAAkB,WAAW,OAAO,QAAQ,EAAE;CAE9D,OAAO;EACL,MAAM,KAAK;EACX,SAAS;EACT,OAAO,MAAM,KAAK,CAAC,OAAO,OAAO,gBAAgB;GAC/C;GACA,YAAY,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;GACpC,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACnC,EAAE;EACF,QAAQ;GACN,qBAAqB;GACrB,qBAAqB;GACrB,+BAA+B,GAAG,MAAM;GACxC,qCAAqC;GACrC,2BAA2B;GAC3B,8BAA8B,GAAG,QAAQ;GACzC,sCAAsC,GAAG,QAAQ;GACjD,uCAAuC,GAAG,QAAQ;GAClD,kCAAkC,GAAG,KAAK,qBAAqB,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;GAC3F,8BAA8B;GAC9B,gCAAgC,GAAG,OAAO;GAC1C,sCAAsC,GAAG,QAAQ;GACjD,2BAA2B;GAC3B,+BAA+B;GAC/B,iCAAiC,GAAG,QAAQ;GAC5C,6BAA6B,GAAG,QAAQ;GACxC,sCAAsC,OAAO,OAAO;GACpD,sCAAsC,OAAO,EAAE,iBAAiB,CAAC;GACjE,sCAAsC,OAAO,EAAE,eAAe,CAAC;GAC/D,8BAA8B;GAC9B,8BAA8B,GAAG,MAAM;GACvC,mCAAmC,GAAG,MAAM;GAC5C,oCAAoC,GAAG,MAAM;GAC7C,2BAA2B;GAC3B,uBAAuB;GACvB,kCAAkC;GAClC,8BAA8B;GAC9B,0CAA0C,GAAG,QAAQ;GACrD,2CAA2C;GAC3C,gCAAgC;GAChC,4BAA4B;GAC5B,mCAAmC,EAAE,MAAM;GAC3C,gCAAgC;GAChC,kCAAkC;GAClC,sBAAsB;GACtB,4BAA4B,GAAG,MAAM;GACrC,qCAAqC,GAAG,QAAQ;GAChD,oCAAoC,GAAG,YAAY;GACnD,qCAAqC,GAAG,QAAQ;GAChD,oCAAoC,GAAG,YAAY;GACnD,2CAA2C,GAAG,QAAQ;GACtD,0CAA0C,GAAG,YAAY;GACzD,2BAA2B,GAAG,OAAO;EACvC;CACF;AACF;;AAGA,MAAM,cAAqC;CAAE,MAAM;CAAQ,OAAO;CAAI,eAAe;AAAgB;;;;;;;;AASrG,SAAgB,kBAAkB,QAA8B;CAC9D,MAAM,SAAS,aAAa;CAC5B,MAAM,MAAM,OAAO,aAAa,cAAc,KAAA,IAAY;CAC1D,KAAK,MAAM,SAAS,OAAO,KAAK,YAAY,GAAc;EACxD,IAAI;EACJ,IAAI,UAAU,UAAU,OAAO,YAAY,QAAQ;GACjD,QAAQ,IAAI,cAAc,KAAK;GAC/B,MAAM,YAAY,YAAY;GAC9B,MAAM,SAAS;GACf,IAAI,KAAK,YAAY,KAAK;EAC5B;EACA,IAAI;GACF,OAAO,OAAO,YAAY,aAAa,QAAQ,eAAe,OAAO,KAAK,CAAC;EAC7E,UAAU;GACR,OAAO,OAAO;EAChB;CACF;AACF;;;AC5LA,IAAI;;AAGJ,MAAa,mBAAiC;CAC5C,WAAW,OAAO,gBAAgB,CAAC,MAAM,MAAM;EAE7C,IAAI,OAAO,aAAa,eAAe,SAAS,OAAO,OACrD,SAAc,MAAM,MAAM,WAAW,EAAE,OAAO,eAAe,CAAC;EAEhE,OAAO;CACT,CAAC;CACD,OAAO,YAAY;EACjB,SAAS,KAAA;CACX,CAAC;CACD,OAAO;AACT;;AAQA,SAAgB,UAAU,SAAuB,YAAyB;CAExE,MAAM,CAAC,OAAO,YAAY,SAA8D,IAAI;CAC5F,gBAAgB;EACd,IAAI,QAAQ;EACZ,OAAO,CAAC,CAAC,MACN,WAAW,SAAS,SAAS;GAAE;GAAQ,OAAO;IAAE,QAAQ;IAAS;GAAO;EAAE,CAAC,IAC3E,UAAmB,SAAS,SAAS;GAAE;GAAQ,OAAO;IAAE,QAAQ;IAAS,QAAQ;IAAM;GAAM;EAAE,CAAC,CACnG;EACA,aAAa;GACX,QAAQ;EACV;CACF,GAAG,CAAC,MAAM,CAAC;CACX,OAAO,SAAS,MAAM,WAAW,SAAS,MAAM,QAAQ;EAAE,QAAQ;EAAW,QAAQ;CAAK;AAC5F;;AAGA,SAAgB,cAAc,QAA6B;CACzD,gBAAgB;EACd,IAAI,CAAC,QAAQ,OAAO,KAAA;EACpB,MAAM,cAAc;GAClB,kBAAkB,MAAM;GACxB,OAAO,OAAO,SAAS,cAAc,CAAC;EACxC;EACA,MAAM;EACN,OAAO,aAAa,KAAK;CAC3B,GAAG,CAAC,MAAM,CAAC;AACb;;AAGA,MAAa,eAAe;CAC1B,iBAAiB;CAGjB,aAAa;CACb,YAAY;CACZ,UAAU;CACV,YAAY;CACZ,eAAe;CACf,eAAe;CACf,SAAS;EAAE,KAAK;EAAI,QAAQ;CAAG;CAC/B,sBAAsB;CACtB,iBAAiB;CACjB,gBAAgB;CAChB,4BAA4B;CAC5B,qBAAqB;CACrB,kBAAkB;CAClB,SAAS;CACT,cAAc;CACd,QAAQ;EAAE,aAAa;EAAM,cAAc;CAAK;CAChD,WAAW;EAAE,uBAAuB;EAAI,yBAAyB;EAAI,YAAY;CAAM;CACvF,oBAAoB;CACpB,qBAAqB;CACrB,sBAAsB;CACtB,SAAS,EAAE,SAAS,MAAM;AAC5B;;AAGA,SAAgB,WAAW,QAAgC;CACzD,MAAM,YAAY;EAChB,IAAI;GACF,OAAO;EACT,QAAQ,CAER;CACF;CACA,MAAM,MAAM,OAAO,0BAA0B,aAAa,sBAAsB,GAAG,IAAI;CACvF,MAAM,SAAS,CAAC,WAAW,KAAK,EAAE,GAAG,WAAW,KAAK,GAAG,CAAC;CACzD,aAAa;EACX,IAAI,OAAO,OAAO,yBAAyB,YAAY,qBAAqB,GAAG;EAC/E,OAAO,QAAQ,YAAY;CAC7B;AACF;;;;ACnEA,SAAgB,WAAW,EACzB,OACA,eAAe,IACf,UACA,WAAW,aACX,WAAW,OACX,SAAS,KACT,cAAc,YAAY,eAC1B,SACA,WACA,SAAS,YACT,WACkB;CAClB,MAAM,QAAQ,UAAU,MAAM;CAC9B,MAAM,SAAS,MAAM;CACrB,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,WAAW,OAA4C,IAAI;CACjE,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,SAAS,OAAO;EAAE;EAAO;EAAc;EAAU;EAAU;EAAW;EAAS;EAAU;CAAQ,CAAC;CACxG,sBAAsB;EACpB,OAAO,UAAU;GAAE;GAAO;GAAc;GAAU;GAAU;GAAW;GAAS;GAAU;EAAQ;CACpG,CAAC;CAED,cAAc,MAAM;CAEpB,gBAAgB;EACd,IAAI,CAAC,UAAU,CAAC,KAAK,SAAS,OAAO,KAAA;EACrC,MAAM,IAAI,OAAO;EACjB,MAAM,KAAK,OAAO,OAAO,OAAO,KAAK,SAAS;GAC5C,GAAG;GACH,GAAG,EAAE;GACL,OAAO,EAAE,SAAS,EAAE;GACpB,UAAU,EAAE;GACZ,UAAU,EAAE;GACZ,WAAW,EAAE;GACb,OAAO,cAAc;EACvB,CAAC;EACD,SAAS,UAAU;EACnB,MAAM,MAAM,GAAG,yBAAyB,UAAU;GAChD,IAAI,CAAC,QAAQ,SAAS,OAAO,QAAQ,WAAW,GAAG,SAAS,GAAG,KAAK;EACtE,CAAC;EACD,EAAE,UAAU,IAAI,MAAM;EACtB,MAAM,aAAa,iBAAiB,GAAG,OAAO,CAAC;EAC/C,aAAa;GACX,WAAW;GACX,IAAI,QAAQ;GACZ,GAAG,QAAQ;GACX,SAAS,UAAU;EACrB;CACF,GAAG,CAAC,MAAM,CAAC;CAEX,gBAAgB;EACd,MAAM,KAAK,SAAS;EACpB,IAAI,CAAC,MAAM,UAAU,KAAA,KAAa,GAAG,SAAS,MAAM,OAAO;EAC3D,QAAQ,UAAU;EAClB,IAAI;GACF,GAAG,SAAS,KAAK;EACnB,UAAU;GACR,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,OAAO,MAAM,CAAC;CAElB,gBAAgB;EACd,MAAM,QAAQ,SAAS,SAAS,SAAS;EACzC,IAAI,UAAU,OAAO,OAAO,OAAO,iBAAiB,OAAO,QAAQ;CACrE,GAAG,CAAC,UAAU,MAAM,CAAC;CAErB,gBAAgB;EACd,SAAS,SAAS,cAAc;GAAE,GAAG;GAAS;GAAU;EAAU,CAAC;CACrE,GAAG;EAAC;EAAS;EAAU;EAAW;CAAM,CAAC;CAEzC,OAAO,oBAAC,aAAD;EAAoB;EAAe;EAAQ,OAAM;EAAyB;EAAW,SAAS;CAAO,CAAA;AAC9G;;;;ACxEA,SAAgB,WAAW,EACzB,UACA,UACA,WAAW,aACX,WAAW,MACX,aAAa,MACb,SAAS,KACT,cAAc,YAAY,eAC1B,UACA,SACA,WACA,SAAS,YACT,WACkB;CAClB,MAAM,QAAQ,UAAU,MAAM;CAC9B,MAAM,SAAS,MAAM;CACrB,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,WAAW,OAA4C,IAAI;CACjE,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,SAAS,OAAO;EAAE;EAAU;EAAU;EAAU;EAAU;EAAY;EAAW;EAAS;EAAU;CAAQ,CAAC;CACnH,sBAAsB;EACpB,OAAO,UAAU;GAAE;GAAU;GAAU;GAAU;GAAU;GAAY;GAAW;GAAS;GAAU;EAAQ;CAC/G,CAAC;CAED,cAAc,MAAM;CAEpB,gBAAgB;EACd,IAAI,CAAC,UAAU,CAAC,KAAK,SAAS,OAAO,KAAA;EACrC,MAAM,IAAI,OAAO;EACjB,MAAM,KAAK,OAAO,OAAO,iBAAiB,KAAK,SAAS;GACtD,GAAG;GACH,sBAAsB;GACtB,qBAAqB;GACrB,kBAAkB;GAClB,wBAAwB;GACxB,iCAAiC;GACjC,GAAG,EAAE;GACL,UAAU,EAAE;GACZ,kBAAkB,EAAE;GACpB,mBAAmB,GAAG,EAAE,UAAU;GAClC,mBAAmB,GAAG,EAAE,UAAU;GAClC,OAAO,cAAc;EACvB,CAAC;EACD,MAAM,gBAAgB,OAAO,OAAO,YAAY,EAAE,UAAU,EAAE,QAAQ;EACtE,MAAM,gBAAgB,OAAO,OAAO,YAAY,EAAE,UAAU,EAAE,QAAQ;EACtE,GAAG,SAAS;GAAE,UAAU;GAAe,UAAU;EAAc,CAAC;EAChE,SAAS,UAAU;EACnB,MAAM,MAAM,cAAc,yBAAyB;GACjD,IAAI,CAAC,QAAQ,SAAS,OAAO,QAAQ,WAAW,cAAc,SAAS,CAAC;EAC1E,CAAC;EACD,EAAE,UAAU,IAAI,MAAM;EACtB,MAAM,YAAY;GAChB,MAAM,KAAK,KAAK;GAChB,IAAI,IAAI,GAAG,OAAO;IAAE,OAAO,GAAG;IAAa,QAAQ,GAAG;GAAa,CAAC;EACtE;EACA,MAAM,aAAa,WAAW,GAAG;EACjC,aAAa;GACX,WAAW;GACX,IAAI,QAAQ;GACZ,GAAG,QAAQ;GACX,cAAc,QAAQ;GACtB,cAAc,QAAQ;GACtB,SAAS,UAAU;EACrB;CACF,GAAG,CAAC,MAAM,CAAC;CAEX,gBAAgB;EACd,MAAM,QAAQ,SAAS,SAAS,SAAS;EACzC,IAAI,CAAC,OAAO;EACZ,QAAQ,UAAU;EAClB,IAAI;GACF,IAAI,MAAM,SAAS,SAAS,MAAM,UAAU,MAAM,SAAS,SAAS,QAAQ;GAC5E,IAAI,MAAM,SAAS,SAAS,MAAM,UAAU,MAAM,SAAS,SAAS,QAAQ;EAC9E,UAAU;GACR,QAAQ,UAAU;EACpB;CACF,GAAG;EAAC;EAAU;EAAU;CAAM,CAAC;CAE/B,gBAAgB;EACd,MAAM,QAAQ,SAAS,SAAS,SAAS;EACzC,IAAI,CAAC,UAAU,CAAC,OAAO;EACvB,OAAO,OAAO,iBAAiB,MAAM,UAAU,QAAQ;EACvD,OAAO,OAAO,iBAAiB,MAAM,UAAU,QAAQ;CACzD,GAAG,CAAC,UAAU,MAAM,CAAC;CAErB,gBAAgB;EACd,SAAS,SAAS,cAAc;GAAE,GAAG;GAAS;GAAU,kBAAkB;EAAW,CAAC;CACxF,GAAG;EAAC;EAAS;EAAU;EAAY;CAAM,CAAC;CAE1C,OAAO,oBAAC,aAAD;EAAoB;EAAe;EAAQ,OAAM;EAAyB;EAAW,SAAS;CAAO,CAAA;AAC9G;;;;;;;;;AClHA,SAAgB,uBAAuB,WAAsC;CAC3E,MAAM,QAAQ;CACd,MAAM,oBAAoB;EAAE,GAAG,MAAM;EAAmB;CAAU;AACpE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gntik-ai/editor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Monaco code editor themed by the gntik-ui brand tokens (dark, light, high contrast).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"*.css"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src/styles.css"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
"./styles.css": "./src/styles.css"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org/",
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/gntik-ai/gntik-ui.git",
|
|
31
|
+
"directory": "packages/editor"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "^19.0.0",
|
|
35
|
+
"react-dom": "^19.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@gntik-ai/tokens": "0.3.0",
|
|
39
|
+
"monaco-editor": "^0.57.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
43
|
+
"@testing-library/react": "^16.3.3",
|
|
44
|
+
"@types/react": "^19.3.0",
|
|
45
|
+
"@types/react-dom": "^19.3.0",
|
|
46
|
+
"jsdom": "^30.1.1",
|
|
47
|
+
"react": "^19.3.0",
|
|
48
|
+
"react-dom": "^19.3.0",
|
|
49
|
+
"tsdown": "^0.23.0",
|
|
50
|
+
"typescript": "~6.0.3",
|
|
51
|
+
"vitest": "^5.0.2"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsdown",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
|
+
"test": "vitest run"
|
|
57
|
+
}
|
|
6
58
|
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* @gntik-ai/editor — lets Tailwind v4 see this package's classes. Import after "@gntik-ai/ui/styles.css". */
|
|
2
|
+
@source "../dist";
|
|
3
|
+
|
|
4
|
+
/* Keep the labelled input available to pointer users above the editor surface.
|
|
5
|
+
Leave Monaco's visible IME composition layer in control of its own stacking. */
|
|
6
|
+
[data-slot="editor"] .monaco-editor .inputarea:not(.ime-input) {
|
|
7
|
+
min-width: 1px;
|
|
8
|
+
min-height: 1px;
|
|
9
|
+
z-index: 1;
|
|
10
|
+
}
|