@wallarm-org/design-system 1.30.0 → 1.31.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/dist/components/CodeEditor/CodeEditorContent.d.ts +17 -0
- package/dist/components/CodeEditor/CodeEditorContent.js +197 -0
- package/dist/components/CodeEditor/CodeEditorContext.d.ts +17 -0
- package/dist/components/CodeEditor/CodeEditorContext.js +8 -0
- package/dist/components/CodeEditor/CodeEditorRoot.d.ts +77 -0
- package/dist/components/CodeEditor/CodeEditorRoot.js +203 -0
- package/dist/components/CodeEditor/classes.d.ts +21 -0
- package/dist/components/CodeEditor/classes.js +41 -0
- package/dist/components/CodeEditor/engine/SearchPanel.d.ts +13 -0
- package/dist/components/CodeEditor/engine/SearchPanel.js +201 -0
- package/dist/components/CodeEditor/engine/adapterPainter.d.ts +23 -0
- package/dist/components/CodeEditor/engine/adapterPainter.js +110 -0
- package/dist/components/CodeEditor/engine/babelSyntax.d.ts +15 -0
- package/dist/components/CodeEditor/engine/babelSyntax.js +54 -0
- package/dist/components/CodeEditor/engine/completion.d.ts +17 -0
- package/dist/components/CodeEditor/engine/completion.js +129 -0
- package/dist/components/CodeEditor/engine/contentAttributes.d.ts +12 -0
- package/dist/components/CodeEditor/engine/contentAttributes.js +24 -0
- package/dist/components/CodeEditor/engine/diagnostics.d.ts +73 -0
- package/dist/components/CodeEditor/engine/diagnostics.js +327 -0
- package/dist/components/CodeEditor/engine/diff.d.ts +37 -0
- package/dist/components/CodeEditor/engine/diff.js +145 -0
- package/dist/components/CodeEditor/engine/folds.d.ts +25 -0
- package/dist/components/CodeEditor/engine/folds.js +458 -0
- package/dist/components/CodeEditor/engine/gutters.d.ts +22 -0
- package/dist/components/CodeEditor/engine/gutters.js +160 -0
- package/dist/components/CodeEditor/engine/index.d.ts +12 -0
- package/dist/components/CodeEditor/engine/index.js +387 -0
- package/dist/components/CodeEditor/engine/languages/http/index.d.ts +20 -0
- package/dist/components/CodeEditor/engine/languages/http/index.js +69 -0
- package/dist/components/CodeEditor/engine/languages/http/parser.d.ts +2 -0
- package/dist/components/CodeEditor/engine/languages/http/parser.js +31 -0
- package/dist/components/CodeEditor/engine/languages/http/parser.terms.d.ts +1 -0
- package/dist/components/CodeEditor/engine/languages/http/parser.terms.js +2 -0
- package/dist/components/CodeEditor/engine/languages/index.d.ts +8 -0
- package/dist/components/CodeEditor/engine/languages/index.js +41 -0
- package/dist/components/CodeEditor/engine/languages/json.d.ts +8 -0
- package/dist/components/CodeEditor/engine/languages/json.js +11 -0
- package/dist/components/CodeEditor/engine/languages/jsonPointers.d.ts +47 -0
- package/dist/components/CodeEditor/engine/languages/jsonPointers.js +125 -0
- package/dist/components/CodeEditor/engine/languages/yaml.d.ts +4 -0
- package/dist/components/CodeEditor/engine/languages/yaml.js +11 -0
- package/dist/components/CodeEditor/engine/lines.d.ts +22 -0
- package/dist/components/CodeEditor/engine/lines.js +131 -0
- package/dist/components/CodeEditor/engine/luaSyntax.d.ts +14 -0
- package/dist/components/CodeEditor/engine/luaSyntax.js +36 -0
- package/dist/components/CodeEditor/engine/positions.d.ts +19 -0
- package/dist/components/CodeEditor/engine/positions.js +22 -0
- package/dist/components/CodeEditor/engine/schema/complete.d.ts +12 -0
- package/dist/components/CodeEditor/engine/schema/complete.js +211 -0
- package/dist/components/CodeEditor/engine/schema/hover.d.ts +12 -0
- package/dist/components/CodeEditor/engine/schema/hover.js +73 -0
- package/dist/components/CodeEditor/engine/schema/loadSchema.d.ts +19 -0
- package/dist/components/CodeEditor/engine/schema/loadSchema.js +32 -0
- package/dist/components/CodeEditor/engine/schema/validate.d.ts +9 -0
- package/dist/components/CodeEditor/engine/schema/validate.js +67 -0
- package/dist/components/CodeEditor/engine/search.d.ts +13 -0
- package/dist/components/CodeEditor/engine/search.js +84 -0
- package/dist/components/CodeEditor/engine/searchMatches.d.ts +8 -0
- package/dist/components/CodeEditor/engine/searchMatches.js +15 -0
- package/dist/components/CodeEditor/engine/theme.d.ts +8 -0
- package/dist/components/CodeEditor/engine/theme.js +89 -0
- package/dist/components/CodeEditor/engine/types.d.ts +44 -0
- package/dist/components/CodeEditor/engine/types.js +0 -0
- package/dist/components/CodeEditor/hooks/useCodeEditor.d.ts +6 -0
- package/dist/components/CodeEditor/hooks/useCodeEditor.js +3 -0
- package/dist/components/CodeEditor/index.d.ts +6 -0
- package/dist/components/CodeEditor/index.js +5 -0
- package/dist/components/CodeEditor/lib/PortalOutlet.d.ts +11 -0
- package/dist/components/CodeEditor/lib/PortalOutlet.js +11 -0
- package/dist/components/CodeEditor/lib/httpCompletions.d.ts +7 -0
- package/dist/components/CodeEditor/lib/httpCompletions.js +102 -0
- package/dist/components/CodeEditor/lib/httpCompletionsData.d.ts +29 -0
- package/dist/components/CodeEditor/lib/httpCompletionsData.js +650 -0
- package/dist/components/CodeEditor/lib/keyboardHint.d.ts +5 -0
- package/dist/components/CodeEditor/lib/keyboardHint.js +2 -0
- package/dist/components/CodeEditor/lib/loadEngine.d.ts +7 -0
- package/dist/components/CodeEditor/lib/loadEngine.js +9 -0
- package/dist/components/CodeEditor/lib/portalRegistry.d.ts +20 -0
- package/dist/components/CodeEditor/lib/portalRegistry.js +47 -0
- package/dist/components/CodeEditor/lib/splitContentProps.d.ts +12 -0
- package/dist/components/CodeEditor/lib/splitContentProps.js +28 -0
- package/dist/components/CodeEditor/types.d.ts +72 -0
- package/dist/components/CodeEditor/types.js +0 -0
- package/dist/components/CodeSnippet/CodeSnippetCode.js +6 -23
- package/dist/components/CodeSnippet/CodeSnippetCopyButton.js +4 -3
- package/dist/components/CodeSnippet/CodeSnippetFrameContext.d.ts +25 -0
- package/dist/components/CodeSnippet/CodeSnippetFrameContext.js +3 -0
- package/dist/components/CodeSnippet/CodeSnippetFullscreenButton.js +2 -2
- package/dist/components/CodeSnippet/CodeSnippetRoot.d.ts +3 -5
- package/dist/components/CodeSnippet/CodeSnippetRoot.js +56 -50
- package/dist/components/CodeSnippet/CodeSnippetShowMoreButton.js +7 -5
- package/dist/components/CodeSnippet/CodeSnippetWrapButton.js +2 -2
- package/dist/components/CodeSnippet/adapters/highlightjs.js +1 -0
- package/dist/components/CodeSnippet/adapters/prism.js +3 -1
- package/dist/components/CodeSnippet/adapters/shiki.js +1 -0
- package/dist/components/CodeSnippet/adapters/types.d.ts +3 -3
- package/dist/components/CodeSnippet/classes.d.ts +3 -0
- package/dist/components/CodeSnippet/classes.js +14 -0
- package/dist/components/CodeSnippet/hooks/index.d.ts +1 -0
- package/dist/components/CodeSnippet/hooks/index.js +1 -0
- package/dist/components/CodeSnippet/hooks/useCodeSnippetFrame.d.ts +2 -0
- package/dist/components/CodeSnippet/hooks/useCodeSnippetFrame.js +8 -0
- package/dist/components/CodeSnippet/index.d.ts +2 -1
- package/dist/components/CodeSnippet/index.js +1 -1
- package/dist/components/CodeSnippet/internal/CodeSnippetFrame.d.ts +22 -0
- package/dist/components/CodeSnippet/internal/CodeSnippetFrame.js +81 -0
- package/dist/components/CodeSnippet/internal/CodeToken.d.ts +2 -1
- package/dist/components/CodeSnippet/internal/CodeToken.js +1 -1
- package/dist/components/CodeSnippet/internal/FoldSummary.d.ts +11 -0
- package/dist/components/CodeSnippet/internal/FoldSummary.js +27 -0
- package/dist/components/CodeSnippet/internal/FoldToggle.d.ts +2 -0
- package/dist/components/CodeSnippet/internal/FoldToggle.js +2 -1
- package/dist/components/CodeSnippet/internal/index.d.ts +1 -0
- package/dist/components/CodeSnippet/internal/index.js +1 -0
- package/dist/components/CodeSnippet/lib/foldUtils.d.ts +9 -2
- package/dist/components/CodeSnippet/lib/foldUtils.js +2 -2
- package/dist/components/CodeSnippet/lib/showMore.d.ts +7 -0
- package/dist/components/CodeSnippet/lib/showMore.js +7 -0
- package/dist/components/Copyable/Copyable.d.ts +5 -2
- package/dist/components/Copyable/Copyable.js +1 -1
- package/dist/metadata/components.json +1235 -5
- package/package.json +24 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { hoverTooltip } from "@codemirror/view";
|
|
2
|
+
import { getJsonPointers, readRegionData } from "../languages/jsonPointers.js";
|
|
3
|
+
import { getCompiledSchema } from "./loadSchema.js";
|
|
4
|
+
const CONTAINER_START = /^[[{]/;
|
|
5
|
+
const hoverTargetAt = (state, pos, region)=>{
|
|
6
|
+
let best = null;
|
|
7
|
+
const consider = (candidate)=>{
|
|
8
|
+
if (pos < candidate.from || pos > candidate.to) return;
|
|
9
|
+
if (!best || candidate.to - candidate.from < best.to - best.from) best = candidate;
|
|
10
|
+
};
|
|
11
|
+
for (const entry of getJsonPointers(state, region).values()){
|
|
12
|
+
if (void 0 !== entry.keyFrom && void 0 !== entry.keyTo) consider({
|
|
13
|
+
pointer: entry.pointer,
|
|
14
|
+
from: entry.keyFrom,
|
|
15
|
+
to: entry.keyTo
|
|
16
|
+
});
|
|
17
|
+
const isContainer = CONTAINER_START.test(state.sliceDoc(entry.valueFrom, entry.valueFrom + 1));
|
|
18
|
+
if (!isContainer) consider({
|
|
19
|
+
pointer: entry.pointer,
|
|
20
|
+
from: entry.valueFrom,
|
|
21
|
+
to: entry.valueTo
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return best;
|
|
25
|
+
};
|
|
26
|
+
const textOf = (value)=>'string' == typeof value && '' !== value.trim() ? value : void 0;
|
|
27
|
+
const renderHover = (title, description)=>{
|
|
28
|
+
const dom = document.createElement('div');
|
|
29
|
+
dom.className = 'flex max-w-[320px] flex-col gap-4 px-8 py-4 text-xs text-text-primary-alt';
|
|
30
|
+
dom.setAttribute('data-schema-hover', '');
|
|
31
|
+
if (title) {
|
|
32
|
+
const heading = document.createElement('div');
|
|
33
|
+
heading.className = 'font-medium';
|
|
34
|
+
heading.textContent = title;
|
|
35
|
+
dom.append(heading);
|
|
36
|
+
}
|
|
37
|
+
if (description) {
|
|
38
|
+
const body = document.createElement('div');
|
|
39
|
+
body.className = 'whitespace-pre-wrap text-text-primary-alt';
|
|
40
|
+
body.textContent = description;
|
|
41
|
+
dom.append(body);
|
|
42
|
+
}
|
|
43
|
+
return dom;
|
|
44
|
+
};
|
|
45
|
+
const hoverFor = async (state, pos, getRegion, schema)=>{
|
|
46
|
+
const region = getRegion(state);
|
|
47
|
+
if (!region || pos < region.from || pos > region.to) return null;
|
|
48
|
+
const target = hoverTargetAt(state, pos, region);
|
|
49
|
+
if (!target) return null;
|
|
50
|
+
const root = await getCompiledSchema(schema);
|
|
51
|
+
const { node } = root.getNode(target.pointer, readRegionData(state, region));
|
|
52
|
+
if (!node) return null;
|
|
53
|
+
const title = textOf(node.schema.title);
|
|
54
|
+
const description = textOf(node.schema.description);
|
|
55
|
+
if (!title && !description) return null;
|
|
56
|
+
return {
|
|
57
|
+
pos: target.from,
|
|
58
|
+
end: target.to,
|
|
59
|
+
above: true,
|
|
60
|
+
create: ()=>({
|
|
61
|
+
dom: renderHover(title, description)
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const schemaHoverSource = (getSchema, getRegion)=>(view, pos)=>{
|
|
66
|
+
const schema = getSchema();
|
|
67
|
+
if (void 0 === schema) return Promise.resolve(null);
|
|
68
|
+
return hoverFor(view.state, pos, getRegion, schema).catch(()=>null);
|
|
69
|
+
};
|
|
70
|
+
const schemaHover = (getSchema, getRegion)=>[
|
|
71
|
+
hoverTooltip(schemaHoverSource(getSchema, getRegion))
|
|
72
|
+
];
|
|
73
|
+
export { schemaHover, schemaHoverSource };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SchemaNode } from 'json-schema-library';
|
|
2
|
+
import type { JsonSchema } from '../../types';
|
|
3
|
+
/** Library types for the other schema modules, so this file stays the single import site. */
|
|
4
|
+
export type { JsonError, JsonSchema as LibraryJsonSchema, SchemaNode, } from 'json-schema-library';
|
|
5
|
+
type SchemaLibrary = typeof import('json-schema-library');
|
|
6
|
+
/**
|
|
7
|
+
* The only place `json-schema-library` is imported (spec D8): a separate chunk that
|
|
8
|
+
* loads on first use. The promise is cached; a failed load is retried on the next call.
|
|
9
|
+
*/
|
|
10
|
+
export declare const loadSchemaLibrary: () => Promise<SchemaLibrary>;
|
|
11
|
+
/** `compileSchema(schema)` from the lazily loaded library, cached per schema identity. */
|
|
12
|
+
export declare const getCompiledSchema: (schema: JsonSchema) => Promise<SchemaNode>;
|
|
13
|
+
/** `JSON.parse` without throwing: `{ ok: false }` for invalid JSON. */
|
|
14
|
+
export declare const parseJson: (text: string) => {
|
|
15
|
+
ok: true;
|
|
16
|
+
value: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
let libraryPromise = null;
|
|
2
|
+
const loadSchemaLibrary = ()=>{
|
|
3
|
+
libraryPromise ??= import("json-schema-library").catch((error)=>{
|
|
4
|
+
libraryPromise = null;
|
|
5
|
+
throw error;
|
|
6
|
+
});
|
|
7
|
+
return libraryPromise;
|
|
8
|
+
};
|
|
9
|
+
const compiledObjects = new WeakMap();
|
|
10
|
+
const compiledBooleans = new Map();
|
|
11
|
+
const getCompiledSchema = async (schema)=>{
|
|
12
|
+
const cached = 'boolean' == typeof schema ? compiledBooleans.get(schema) : compiledObjects.get(schema);
|
|
13
|
+
if (cached) return cached;
|
|
14
|
+
const { compileSchema } = await loadSchemaLibrary();
|
|
15
|
+
const node = compileSchema(schema);
|
|
16
|
+
if ('boolean' == typeof schema) compiledBooleans.set(schema, node);
|
|
17
|
+
else compiledObjects.set(schema, node);
|
|
18
|
+
return node;
|
|
19
|
+
};
|
|
20
|
+
const parseJson = (text)=>{
|
|
21
|
+
try {
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
value: JSON.parse(text)
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return {
|
|
28
|
+
ok: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export { getCompiledSchema, loadSchemaLibrary, parseJson };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Diagnostic } from '@codemirror/lint';
|
|
2
|
+
import type { EditorState } from '@codemirror/state';
|
|
3
|
+
import type { JsonSchema } from '../../types';
|
|
4
|
+
import { type JsonRegion } from '../languages/jsonPointers';
|
|
5
|
+
/**
|
|
6
|
+
* JSON Schema errors for the JSON in `region` (spec §7.13). Returns `[]` when the slice
|
|
7
|
+
* is not valid JSON — syntax errors are reported by the syntax source (Task 12).
|
|
8
|
+
*/
|
|
9
|
+
export declare const validateAgainstSchema: (state: EditorState, region: JsonRegion, schema: JsonSchema) => Promise<Diagnostic[]>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getJsonPointers } from "../languages/jsonPointers.js";
|
|
2
|
+
import { getCompiledSchema, parseJson } from "./loadSchema.js";
|
|
3
|
+
const REQUIRED_ERROR = 'required-property-error';
|
|
4
|
+
const ADDITIONAL_PROPERTY_ERROR = 'no-additional-properties-error';
|
|
5
|
+
const normalizeLibraryPointer = (pointer)=>pointer.startsWith('#') ? pointer.slice(1) : pointer;
|
|
6
|
+
const unescapeSegment = (segment)=>segment.replaceAll('~1', '/').replaceAll('~0', '~');
|
|
7
|
+
const toUnescapedPointer = (pointer)=>pointer.split('/').map(unescapeSegment).join('/');
|
|
8
|
+
const parentPointer = (pointer)=>{
|
|
9
|
+
const slash = pointer.lastIndexOf('/');
|
|
10
|
+
return slash < 0 ? null : pointer.slice(0, slash);
|
|
11
|
+
};
|
|
12
|
+
const findEntry = (pointers, unescaped, libraryPointer)=>{
|
|
13
|
+
for(let pointer = normalizeLibraryPointer(libraryPointer); null !== pointer; pointer = parentPointer(pointer)){
|
|
14
|
+
const entry = pointers.get(pointer) ?? unescaped.get(pointer);
|
|
15
|
+
if (entry) return entry;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const rangeFor = (error, entry, region)=>{
|
|
19
|
+
if (!entry) return {
|
|
20
|
+
from: region.from,
|
|
21
|
+
to: Math.min(region.from + 1, region.to)
|
|
22
|
+
};
|
|
23
|
+
if (error.code === REQUIRED_ERROR) return {
|
|
24
|
+
from: entry.valueFrom,
|
|
25
|
+
to: entry.valueFrom + 1
|
|
26
|
+
};
|
|
27
|
+
if (error.code === ADDITIONAL_PROPERTY_ERROR && void 0 !== entry.keyFrom && void 0 !== entry.keyTo) return {
|
|
28
|
+
from: entry.keyFrom,
|
|
29
|
+
to: entry.keyTo
|
|
30
|
+
};
|
|
31
|
+
if (entry.valueTo > entry.valueFrom) return {
|
|
32
|
+
from: entry.valueFrom,
|
|
33
|
+
to: entry.valueTo
|
|
34
|
+
};
|
|
35
|
+
if (void 0 !== entry.keyFrom && void 0 !== entry.keyTo) return {
|
|
36
|
+
from: entry.keyFrom,
|
|
37
|
+
to: entry.keyTo
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
from: region.from,
|
|
41
|
+
to: Math.min(region.from + 1, region.to)
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const validateAgainstSchema = async (state, region, schema)=>{
|
|
45
|
+
const parsed = parseJson(state.sliceDoc(region.from, region.to));
|
|
46
|
+
if (!parsed.ok) return [];
|
|
47
|
+
const node = await getCompiledSchema(schema);
|
|
48
|
+
const { errors } = node.validate(parsed.value);
|
|
49
|
+
if (0 === errors.length) return [];
|
|
50
|
+
const pointers = getJsonPointers(state, region);
|
|
51
|
+
const unescaped = new Map();
|
|
52
|
+
for (const [pointer, entry] of pointers){
|
|
53
|
+
const key = toUnescapedPointer(pointer);
|
|
54
|
+
if (!unescaped.has(key)) unescaped.set(key, entry);
|
|
55
|
+
}
|
|
56
|
+
return errors.map((error)=>{
|
|
57
|
+
const { from, to } = rangeFor(error, findEntry(pointers, unescaped, error.data.pointer), region);
|
|
58
|
+
return {
|
|
59
|
+
from,
|
|
60
|
+
to,
|
|
61
|
+
severity: 'error',
|
|
62
|
+
source: 'schema',
|
|
63
|
+
message: error.message
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
export { validateAgainstSchema };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Extension, Facet } from '@codemirror/state';
|
|
2
|
+
import type { PortalRegistry } from '../lib/portalRegistry';
|
|
3
|
+
/**
|
|
4
|
+
* `true` once a search extension is configured. `api.openSearch` checks it so
|
|
5
|
+
* `openSearchPanel` never falls back to CodeMirror's own un-themed panel.
|
|
6
|
+
*/
|
|
7
|
+
export declare const searchConfigured: Facet<boolean, boolean>;
|
|
8
|
+
/** Find / replace panel (spec §7.11): DS components rendered into CodeMirror's top panel via portals. */
|
|
9
|
+
export declare const searchExtension: (config: {
|
|
10
|
+
portals: PortalRegistry;
|
|
11
|
+
readOnly: boolean;
|
|
12
|
+
testId: string | undefined;
|
|
13
|
+
}) => Extension;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { getSearchQuery, gotoLine, search, searchKeymap } from "@codemirror/search";
|
|
3
|
+
import { EditorState, Facet } from "@codemirror/state";
|
|
4
|
+
import { EditorView, keymap } from "@codemirror/view";
|
|
5
|
+
import { SearchPanel } from "./SearchPanel.js";
|
|
6
|
+
import { countMatches } from "./searchMatches.js";
|
|
7
|
+
const searchConfigured = Facet.define({
|
|
8
|
+
combine: (values)=>values.some(Boolean)
|
|
9
|
+
});
|
|
10
|
+
const searchPanelConfig = Facet.define({
|
|
11
|
+
combine: (values)=>values[0] ?? null
|
|
12
|
+
});
|
|
13
|
+
const renderPanel = (view, state, config)=>{
|
|
14
|
+
const query = getSearchQuery(state);
|
|
15
|
+
return createElement(SearchPanel, {
|
|
16
|
+
view,
|
|
17
|
+
query,
|
|
18
|
+
matchCount: query.valid ? countMatches(state, query) : null,
|
|
19
|
+
readOnly: config.readOnly || state.readOnly,
|
|
20
|
+
testId: config.testId
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const needsRender = (update)=>update.docChanged || update.state.readOnly !== update.startState.readOnly || update.state.facet(searchPanelConfig) !== update.startState.facet(searchPanelConfig) || !getSearchQuery(update.state).eq(getSearchQuery(update.startState));
|
|
24
|
+
const createSearchPanel = (view)=>{
|
|
25
|
+
const dom = document.createElement('div');
|
|
26
|
+
let registered = null;
|
|
27
|
+
return {
|
|
28
|
+
dom,
|
|
29
|
+
top: true,
|
|
30
|
+
mount () {
|
|
31
|
+
const config = view.state.facet(searchPanelConfig);
|
|
32
|
+
if (!config) return;
|
|
33
|
+
registered = {
|
|
34
|
+
id: config.portals.register(dom, renderPanel(view, view.state, config)),
|
|
35
|
+
portals: config.portals
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
update (update) {
|
|
39
|
+
const config = update.state.facet(searchPanelConfig);
|
|
40
|
+
if (!registered || !config || !needsRender(update)) return;
|
|
41
|
+
registered.portals.update(registered.id, renderPanel(view, update.state, config));
|
|
42
|
+
},
|
|
43
|
+
destroy () {
|
|
44
|
+
registered?.portals.unregister(registered.id);
|
|
45
|
+
registered = null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const SEARCH_PHRASES = {
|
|
50
|
+
'replaced $ matches': 'Occurrences replaced: $'
|
|
51
|
+
};
|
|
52
|
+
const dsSearchKeymap = searchKeymap.filter((binding)=>binding.run !== gotoLine);
|
|
53
|
+
const searchTheme = EditorView.theme({
|
|
54
|
+
'.cm-panels': {
|
|
55
|
+
backgroundColor: 'transparent',
|
|
56
|
+
color: 'inherit'
|
|
57
|
+
},
|
|
58
|
+
'.cm-panels.cm-panels-top': {
|
|
59
|
+
borderBottom: 'none'
|
|
60
|
+
},
|
|
61
|
+
'.cm-searchMatch': {
|
|
62
|
+
backgroundColor: 'color-mix(in oklab, var(--color-syntax-highlight-warning-indicator) 24%, transparent)'
|
|
63
|
+
},
|
|
64
|
+
'.cm-searchMatch.cm-searchMatch-selected': {
|
|
65
|
+
backgroundColor: 'color-mix(in oklab, var(--color-syntax-highlight-warning-indicator) 48%, transparent)',
|
|
66
|
+
outline: '1px solid var(--color-syntax-highlight-warning-indicator)'
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const searchExtension = (config)=>[
|
|
70
|
+
search({
|
|
71
|
+
top: true,
|
|
72
|
+
createPanel: createSearchPanel
|
|
73
|
+
}),
|
|
74
|
+
searchPanelConfig.of({
|
|
75
|
+
portals: config.portals,
|
|
76
|
+
readOnly: config.readOnly,
|
|
77
|
+
testId: config.testId
|
|
78
|
+
}),
|
|
79
|
+
searchConfigured.of(true),
|
|
80
|
+
keymap.of(dsSearchKeymap),
|
|
81
|
+
EditorState.phrases.of(SEARCH_PHRASES),
|
|
82
|
+
searchTheme
|
|
83
|
+
];
|
|
84
|
+
export { searchConfigured, searchExtension };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SearchQuery } from '@codemirror/search';
|
|
2
|
+
import type { EditorState } from '@codemirror/state';
|
|
3
|
+
/** Match counting stops here; the panel then shows `1000+ matches`. */
|
|
4
|
+
export declare const MATCH_COUNT_LIMIT = 1000;
|
|
5
|
+
/** Number of matches of `query` in the document, capped at `MATCH_COUNT_LIMIT + 1`. */
|
|
6
|
+
export declare const countMatches: (state: EditorState, query: SearchQuery) => number;
|
|
7
|
+
/** Screen-reader and visible text for a match count. */
|
|
8
|
+
export declare const matchCountMessage: (count: number) => string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const MATCH_COUNT_LIMIT = 1000;
|
|
2
|
+
const countMatches = (state, query)=>{
|
|
3
|
+
if (!query.valid) return 0;
|
|
4
|
+
const cursor = query.getCursor(state);
|
|
5
|
+
let count = 0;
|
|
6
|
+
while(count <= MATCH_COUNT_LIMIT && !cursor.next().done)count++;
|
|
7
|
+
return count;
|
|
8
|
+
};
|
|
9
|
+
const matchCountMessage = (count)=>{
|
|
10
|
+
if (0 === count) return 'No matches';
|
|
11
|
+
if (1 === count) return '1 match';
|
|
12
|
+
if (count > MATCH_COUNT_LIMIT) return `${MATCH_COUNT_LIMIT}+ matches`;
|
|
13
|
+
return `${count} matches`;
|
|
14
|
+
};
|
|
15
|
+
export { MATCH_COUNT_LIMIT, countMatches, matchCountMessage };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Extension } from '@codemirror/state';
|
|
2
|
+
export declare const editorTheme: Extension;
|
|
3
|
+
/**
|
|
4
|
+
* Height clamp for maxLines / Show more (spec §7.8); `null` = no clamp. Memoised by height:
|
|
5
|
+
* every `EditorView.theme` call mounts a new style module, so toggling the clamp would
|
|
6
|
+
* otherwise pile up CSS rules.
|
|
7
|
+
*/
|
|
8
|
+
export declare const maxHeightTheme: (maxHeight: number | null) => Extension;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Decoration, EditorView, ViewPlugin } from "@codemirror/view";
|
|
2
|
+
const CODE_SNIPPET_BG = 'linear-gradient(var(--color-component-code-snippet-bg), var(--color-component-code-snippet-bg)), var(--color-bg-page-bg)';
|
|
3
|
+
const snippetTheme = EditorView.theme({
|
|
4
|
+
'&': {
|
|
5
|
+
color: 'var(--color-syntax-no-syntax)',
|
|
6
|
+
backgroundColor: 'transparent'
|
|
7
|
+
},
|
|
8
|
+
'&.cm-focused': {
|
|
9
|
+
outline: 'none'
|
|
10
|
+
},
|
|
11
|
+
'.cm-scroller': {
|
|
12
|
+
fontFamily: 'inherit',
|
|
13
|
+
lineHeight: '20px'
|
|
14
|
+
},
|
|
15
|
+
'.cm-content': {
|
|
16
|
+
padding: '8px 0',
|
|
17
|
+
caretColor: 'var(--color-syntax-no-syntax)'
|
|
18
|
+
},
|
|
19
|
+
'.cm-line': {
|
|
20
|
+
padding: '0 12px'
|
|
21
|
+
},
|
|
22
|
+
'&:has(.cm-gutters) .cm-line': {
|
|
23
|
+
paddingLeft: '8px'
|
|
24
|
+
},
|
|
25
|
+
'.cm-lineWrapping': {
|
|
26
|
+
wordBreak: 'break-all'
|
|
27
|
+
},
|
|
28
|
+
'.cm-gutters': {
|
|
29
|
+
background: CODE_SNIPPET_BG,
|
|
30
|
+
color: 'inherit',
|
|
31
|
+
border: 'none'
|
|
32
|
+
},
|
|
33
|
+
'.cm-gutters.cm-gutters-before': {
|
|
34
|
+
borderRightWidth: '0'
|
|
35
|
+
},
|
|
36
|
+
'.cm-cursor, .cm-dropCursor': {
|
|
37
|
+
borderLeftColor: 'var(--color-syntax-no-syntax)'
|
|
38
|
+
},
|
|
39
|
+
'.cm-selectionBackground': {
|
|
40
|
+
background: 'var(--color-syntax-highlight-selected-highlight)'
|
|
41
|
+
},
|
|
42
|
+
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
|
43
|
+
background: 'var(--color-syntax-highlight-selected-highlight)'
|
|
44
|
+
},
|
|
45
|
+
'.cm-matchingBracket': {
|
|
46
|
+
backgroundColor: 'var(--color-syntax-highlight-neutral-highlight)'
|
|
47
|
+
},
|
|
48
|
+
'&.cm-focused .cm-matchingBracket': {
|
|
49
|
+
backgroundColor: 'var(--color-syntax-highlight-neutral-highlight)'
|
|
50
|
+
},
|
|
51
|
+
'&.cm-focused .cm-nonmatchingBracket': {
|
|
52
|
+
backgroundColor: 'var(--color-syntax-highlight-error-highlight)'
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const selectedTextMark = Decoration.mark({
|
|
56
|
+
class: 'text-syntax-highlight-selected-code! [&_*]:text-syntax-highlight-selected-code!'
|
|
57
|
+
});
|
|
58
|
+
const buildSelectedText = (state)=>Decoration.set(state.selection.ranges.filter((range)=>!range.empty).map((range)=>selectedTextMark.range(range.from, range.to)), true);
|
|
59
|
+
const selectedTextColor = ViewPlugin.fromClass(class {
|
|
60
|
+
decorations;
|
|
61
|
+
constructor(view){
|
|
62
|
+
this.decorations = buildSelectedText(view.state);
|
|
63
|
+
}
|
|
64
|
+
update(update) {
|
|
65
|
+
if (update.selectionSet || update.docChanged) this.decorations = buildSelectedText(update.state);
|
|
66
|
+
}
|
|
67
|
+
}, {
|
|
68
|
+
decorations: (plugin)=>plugin.decorations
|
|
69
|
+
});
|
|
70
|
+
const editorTheme = [
|
|
71
|
+
snippetTheme,
|
|
72
|
+
selectedTextColor
|
|
73
|
+
];
|
|
74
|
+
const maxHeightThemes = new Map();
|
|
75
|
+
const maxHeightTheme = (maxHeight)=>{
|
|
76
|
+
if (null === maxHeight) return [];
|
|
77
|
+
let theme = maxHeightThemes.get(maxHeight);
|
|
78
|
+
if (!theme) {
|
|
79
|
+
theme = EditorView.theme({
|
|
80
|
+
'.cm-scroller': {
|
|
81
|
+
maxHeight: `${maxHeight}px`,
|
|
82
|
+
overflowY: 'auto'
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
maxHeightThemes.set(maxHeight, theme);
|
|
86
|
+
}
|
|
87
|
+
return theme;
|
|
88
|
+
};
|
|
89
|
+
export { editorTheme, maxHeightTheme };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EditorView } from '@codemirror/view';
|
|
2
|
+
import type { SyntaxAdapter } from '../../CodeSnippet/adapters/types';
|
|
3
|
+
import type { LineConfig } from '../../CodeSnippet/CodeSnippetContext';
|
|
4
|
+
import type { PortalRegistry } from '../lib/portalRegistry';
|
|
5
|
+
import type { CodeEditorApi, CodeEditorCompletionSource, CodeEditorDiagnostic, CodeEditorFolds, CodeEditorLanguage, JsonSchema } from '../types';
|
|
6
|
+
/** Everything the engine needs to build or reconfigure an editor. Plain data, no CM types. */
|
|
7
|
+
export interface EngineOptions {
|
|
8
|
+
value: string;
|
|
9
|
+
documentId: string | undefined;
|
|
10
|
+
language: CodeEditorLanguage;
|
|
11
|
+
readOnly: boolean;
|
|
12
|
+
wrapLines: boolean;
|
|
13
|
+
startingLineNumber: number;
|
|
14
|
+
lineNumbers: boolean;
|
|
15
|
+
lines: Record<number, LineConfig>;
|
|
16
|
+
folds: CodeEditorFolds | undefined;
|
|
17
|
+
adapter: SyntaxAdapter<string>;
|
|
18
|
+
original: string | undefined;
|
|
19
|
+
schema: JsonSchema | undefined;
|
|
20
|
+
completions: readonly CodeEditorCompletionSource[];
|
|
21
|
+
diagnostics: readonly CodeEditorDiagnostic[];
|
|
22
|
+
contentAttributes: Record<string, string>;
|
|
23
|
+
/** Root data-testid base; engine derives `${testId}--editor`, `--gutter`, `--search`, `--fold-toggle`, `--fold-summary` */
|
|
24
|
+
testId: string | undefined;
|
|
25
|
+
cspNonce: string | undefined;
|
|
26
|
+
/** px, null = no clamp */
|
|
27
|
+
maxHeight: number | null;
|
|
28
|
+
}
|
|
29
|
+
/** Callbacks from the engine back into React. */
|
|
30
|
+
export interface EngineCallbacks {
|
|
31
|
+
onChange: (value: string) => void;
|
|
32
|
+
onDiagnosticsChange: (diagnostics: CodeEditorDiagnostic[]) => void;
|
|
33
|
+
onVisibleRowCountChange: (rows: number) => void;
|
|
34
|
+
portals: PortalRegistry;
|
|
35
|
+
}
|
|
36
|
+
/** Returned by `createEditor()`. */
|
|
37
|
+
export interface EditorHandle {
|
|
38
|
+
/** Compares with previous options, reconfigures changed compartments, syncs value/documentId */
|
|
39
|
+
update: (options: EngineOptions) => void;
|
|
40
|
+
api: CodeEditorApi;
|
|
41
|
+
/** For tests only */
|
|
42
|
+
view: EditorView;
|
|
43
|
+
destroy: () => void;
|
|
44
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CodeEditorContent, type CodeEditorContentProps } from './CodeEditorContent';
|
|
2
|
+
export { CodeEditorRoot, type CodeEditorRootProps, type CodeEditorRootProps as CodeEditorProps, } from './CodeEditorRoot';
|
|
3
|
+
export { useCodeEditor } from './hooks/useCodeEditor';
|
|
4
|
+
export { httpCompletions } from './lib/httpCompletions';
|
|
5
|
+
export { CODE_EDITOR_KEYBOARD_HINT } from './lib/keyboardHint';
|
|
6
|
+
export type { CodeEditorApi, CodeEditorCompletion, CodeEditorCompletionContext, CodeEditorCompletionSource, CodeEditorDiagnostic, CodeEditorFolds, CodeEditorHttpContext, CodeEditorLanguage, CodeEditorPosition, JsonSchema, } from './types';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { CodeEditorContent } from "./CodeEditorContent.js";
|
|
2
|
+
export { CodeEditorRoot } from "./CodeEditorRoot.js";
|
|
3
|
+
export { useCodeEditor } from "./hooks/useCodeEditor.js";
|
|
4
|
+
export { httpCompletions } from "./lib/httpCompletions.js";
|
|
5
|
+
export { CODE_EDITOR_KEYBOARD_HINT } from "./lib/keyboardHint.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { PortalRegistry } from './portalRegistry';
|
|
3
|
+
export interface PortalOutletProps {
|
|
4
|
+
registry: PortalRegistry;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Renders every registry entry into its engine-owned host with `createPortal`.
|
|
8
|
+
* Renders no DOM of its own (so no `data-slot`); portals keep React context,
|
|
9
|
+
* so DS components inside hosts see the surrounding providers.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PortalOutlet: FC<PortalOutletProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
const PortalOutlet = ({ registry })=>{
|
|
5
|
+
const entries = useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot);
|
|
6
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
7
|
+
children: entries.map((entry)=>/*#__PURE__*/ createPortal(entry.node, entry.host, String(entry.id)))
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
PortalOutlet.displayName = 'PortalOutlet';
|
|
11
|
+
export { PortalOutlet };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CodeEditorCompletionSource } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Built-in HTTP completion source (spec §7.14): methods and versions on the start line,
|
|
4
|
+
* header names on header lines, common values for well-known headers. Pure — reads only
|
|
5
|
+
* the `CodeEditorCompletionContext`, so consumers can wrap or filter it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const httpCompletions: CodeEditorCompletionSource;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { HTTP_COMPLETION_METHODS, HTTP_HEADERS, HTTP_HEADER_VALUES, HTTP_MEDIA_TYPE_PARAMETERS, HTTP_VERSIONS } from "./httpCompletionsData.js";
|
|
2
|
+
const BLANK_LINE = /^[ \t]*$/;
|
|
3
|
+
const WHITESPACE = /[ \t]+/;
|
|
4
|
+
const MEDIA_TYPE_HEADERS = new Set([
|
|
5
|
+
'content-type',
|
|
6
|
+
'accept'
|
|
7
|
+
]);
|
|
8
|
+
const DIRECTION_DETAIL = {
|
|
9
|
+
request: 'request header',
|
|
10
|
+
response: 'response header',
|
|
11
|
+
both: 'header'
|
|
12
|
+
};
|
|
13
|
+
const textBeforeCursor = (ctx)=>ctx.lineText.slice(0, Math.max(0, ctx.position.column - 1));
|
|
14
|
+
const headerNameOf = (line)=>{
|
|
15
|
+
const colon = line.indexOf(':');
|
|
16
|
+
return (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase();
|
|
17
|
+
};
|
|
18
|
+
const countHeaderNames = (value)=>{
|
|
19
|
+
const counts = new Map();
|
|
20
|
+
const lines = value.split('\n');
|
|
21
|
+
for(let index = 1; index < lines.length; index++){
|
|
22
|
+
const line = lines[index] ?? '';
|
|
23
|
+
if (BLANK_LINE.test(line)) break;
|
|
24
|
+
const name = headerNameOf(line);
|
|
25
|
+
if (name) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
return counts;
|
|
28
|
+
};
|
|
29
|
+
const methodCompletions = ()=>HTTP_COMPLETION_METHODS.map((method)=>({
|
|
30
|
+
label: method,
|
|
31
|
+
apply: `${method} `,
|
|
32
|
+
kind: 'method'
|
|
33
|
+
}));
|
|
34
|
+
const versionCompletions = (suffix)=>HTTP_VERSIONS.map((version)=>({
|
|
35
|
+
label: version,
|
|
36
|
+
apply: `${version}${suffix}`,
|
|
37
|
+
kind: 'keyword'
|
|
38
|
+
}));
|
|
39
|
+
const valueCompletions = (values)=>values.map(({ label, apply, info })=>({
|
|
40
|
+
label,
|
|
41
|
+
...void 0 === apply ? {} : {
|
|
42
|
+
apply
|
|
43
|
+
},
|
|
44
|
+
...void 0 === info ? {} : {
|
|
45
|
+
info
|
|
46
|
+
},
|
|
47
|
+
kind: 'value'
|
|
48
|
+
}));
|
|
49
|
+
const startLineCompletions = (ctx, http)=>{
|
|
50
|
+
const before = textBeforeCursor(ctx);
|
|
51
|
+
if (WHITESPACE.test(before.charAt(0))) return null;
|
|
52
|
+
const tokenIndex = before.split(WHITESPACE).length - 1;
|
|
53
|
+
if (0 === tokenIndex) {
|
|
54
|
+
if (!ctx.explicit && '' === ctx.word.text) return null;
|
|
55
|
+
return [
|
|
56
|
+
...methodCompletions(),
|
|
57
|
+
...versionCompletions(' ')
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
if (2 === tokenIndex && 'request' === http.messageKind) return versionCompletions('');
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
const headerNameCompletions = (ctx, http)=>{
|
|
64
|
+
if (textBeforeCursor(ctx) !== ctx.word.text) return null;
|
|
65
|
+
if (!ctx.explicit && '' === ctx.word.text) return null;
|
|
66
|
+
const present = countHeaderNames(ctx.value);
|
|
67
|
+
const current = headerNameOf(ctx.lineText);
|
|
68
|
+
const currentCount = present.get(current);
|
|
69
|
+
if (void 0 !== currentCount) present.set(current, currentCount - 1);
|
|
70
|
+
return HTTP_HEADERS.filter((header)=>('both' === header.direction || header.direction === http.messageKind) && (true === header.repeatable || (present.get(header.name.toLowerCase()) ?? 0) === 0)).map((header)=>({
|
|
71
|
+
label: header.name,
|
|
72
|
+
apply: `${header.name}: `,
|
|
73
|
+
detail: DIRECTION_DETAIL[header.direction],
|
|
74
|
+
info: header.description,
|
|
75
|
+
kind: 'header'
|
|
76
|
+
}));
|
|
77
|
+
};
|
|
78
|
+
const headerValueCompletions = (ctx, http)=>{
|
|
79
|
+
if (ctx.word.text.includes(':')) return null;
|
|
80
|
+
const name = http.headerName?.trim().toLowerCase() ?? '';
|
|
81
|
+
const values = HTTP_HEADER_VALUES[name];
|
|
82
|
+
if (!values) return null;
|
|
83
|
+
const before = textBeforeCursor(ctx);
|
|
84
|
+
const valueSoFar = before.slice(before.indexOf(':') + 1);
|
|
85
|
+
const typedBeforeWord = valueSoFar.slice(0, valueSoFar.length - ctx.word.text.length);
|
|
86
|
+
if (before.slice(0, before.length - ctx.word.text.length).endsWith('=')) return null;
|
|
87
|
+
if (MEDIA_TYPE_HEADERS.has(name) && typedBeforeWord.includes(';')) {
|
|
88
|
+
if (/;\s*charset\s*=/i.test(typedBeforeWord)) return null;
|
|
89
|
+
return valueCompletions(HTTP_MEDIA_TYPE_PARAMETERS);
|
|
90
|
+
}
|
|
91
|
+
if ('' !== typedBeforeWord.trim()) return ctx.explicit ? valueCompletions(values) : null;
|
|
92
|
+
return valueCompletions(values);
|
|
93
|
+
};
|
|
94
|
+
const httpCompletions = (ctx)=>{
|
|
95
|
+
const { http } = ctx;
|
|
96
|
+
if (!http) return null;
|
|
97
|
+
if ('start-line' === http.section) return startLineCompletions(ctx, http);
|
|
98
|
+
if ('header-name' === http.section) return headerNameCompletions(ctx, http);
|
|
99
|
+
if ('header-value' === http.section) return headerValueCompletions(ctx, http);
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
export { httpCompletions };
|