@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.
Files changed (123) hide show
  1. package/dist/components/CodeEditor/CodeEditorContent.d.ts +17 -0
  2. package/dist/components/CodeEditor/CodeEditorContent.js +197 -0
  3. package/dist/components/CodeEditor/CodeEditorContext.d.ts +17 -0
  4. package/dist/components/CodeEditor/CodeEditorContext.js +8 -0
  5. package/dist/components/CodeEditor/CodeEditorRoot.d.ts +77 -0
  6. package/dist/components/CodeEditor/CodeEditorRoot.js +203 -0
  7. package/dist/components/CodeEditor/classes.d.ts +21 -0
  8. package/dist/components/CodeEditor/classes.js +41 -0
  9. package/dist/components/CodeEditor/engine/SearchPanel.d.ts +13 -0
  10. package/dist/components/CodeEditor/engine/SearchPanel.js +201 -0
  11. package/dist/components/CodeEditor/engine/adapterPainter.d.ts +23 -0
  12. package/dist/components/CodeEditor/engine/adapterPainter.js +110 -0
  13. package/dist/components/CodeEditor/engine/babelSyntax.d.ts +15 -0
  14. package/dist/components/CodeEditor/engine/babelSyntax.js +54 -0
  15. package/dist/components/CodeEditor/engine/completion.d.ts +17 -0
  16. package/dist/components/CodeEditor/engine/completion.js +129 -0
  17. package/dist/components/CodeEditor/engine/contentAttributes.d.ts +12 -0
  18. package/dist/components/CodeEditor/engine/contentAttributes.js +24 -0
  19. package/dist/components/CodeEditor/engine/diagnostics.d.ts +73 -0
  20. package/dist/components/CodeEditor/engine/diagnostics.js +327 -0
  21. package/dist/components/CodeEditor/engine/diff.d.ts +37 -0
  22. package/dist/components/CodeEditor/engine/diff.js +145 -0
  23. package/dist/components/CodeEditor/engine/folds.d.ts +25 -0
  24. package/dist/components/CodeEditor/engine/folds.js +458 -0
  25. package/dist/components/CodeEditor/engine/gutters.d.ts +22 -0
  26. package/dist/components/CodeEditor/engine/gutters.js +160 -0
  27. package/dist/components/CodeEditor/engine/index.d.ts +12 -0
  28. package/dist/components/CodeEditor/engine/index.js +387 -0
  29. package/dist/components/CodeEditor/engine/languages/http/index.d.ts +20 -0
  30. package/dist/components/CodeEditor/engine/languages/http/index.js +69 -0
  31. package/dist/components/CodeEditor/engine/languages/http/parser.d.ts +2 -0
  32. package/dist/components/CodeEditor/engine/languages/http/parser.js +31 -0
  33. package/dist/components/CodeEditor/engine/languages/http/parser.terms.d.ts +1 -0
  34. package/dist/components/CodeEditor/engine/languages/http/parser.terms.js +2 -0
  35. package/dist/components/CodeEditor/engine/languages/index.d.ts +8 -0
  36. package/dist/components/CodeEditor/engine/languages/index.js +41 -0
  37. package/dist/components/CodeEditor/engine/languages/json.d.ts +8 -0
  38. package/dist/components/CodeEditor/engine/languages/json.js +11 -0
  39. package/dist/components/CodeEditor/engine/languages/jsonPointers.d.ts +47 -0
  40. package/dist/components/CodeEditor/engine/languages/jsonPointers.js +125 -0
  41. package/dist/components/CodeEditor/engine/languages/yaml.d.ts +4 -0
  42. package/dist/components/CodeEditor/engine/languages/yaml.js +11 -0
  43. package/dist/components/CodeEditor/engine/lines.d.ts +22 -0
  44. package/dist/components/CodeEditor/engine/lines.js +131 -0
  45. package/dist/components/CodeEditor/engine/luaSyntax.d.ts +14 -0
  46. package/dist/components/CodeEditor/engine/luaSyntax.js +36 -0
  47. package/dist/components/CodeEditor/engine/positions.d.ts +19 -0
  48. package/dist/components/CodeEditor/engine/positions.js +22 -0
  49. package/dist/components/CodeEditor/engine/schema/complete.d.ts +12 -0
  50. package/dist/components/CodeEditor/engine/schema/complete.js +211 -0
  51. package/dist/components/CodeEditor/engine/schema/hover.d.ts +12 -0
  52. package/dist/components/CodeEditor/engine/schema/hover.js +73 -0
  53. package/dist/components/CodeEditor/engine/schema/loadSchema.d.ts +19 -0
  54. package/dist/components/CodeEditor/engine/schema/loadSchema.js +32 -0
  55. package/dist/components/CodeEditor/engine/schema/validate.d.ts +9 -0
  56. package/dist/components/CodeEditor/engine/schema/validate.js +67 -0
  57. package/dist/components/CodeEditor/engine/search.d.ts +13 -0
  58. package/dist/components/CodeEditor/engine/search.js +84 -0
  59. package/dist/components/CodeEditor/engine/searchMatches.d.ts +8 -0
  60. package/dist/components/CodeEditor/engine/searchMatches.js +15 -0
  61. package/dist/components/CodeEditor/engine/theme.d.ts +8 -0
  62. package/dist/components/CodeEditor/engine/theme.js +89 -0
  63. package/dist/components/CodeEditor/engine/types.d.ts +44 -0
  64. package/dist/components/CodeEditor/engine/types.js +0 -0
  65. package/dist/components/CodeEditor/hooks/useCodeEditor.d.ts +6 -0
  66. package/dist/components/CodeEditor/hooks/useCodeEditor.js +3 -0
  67. package/dist/components/CodeEditor/index.d.ts +6 -0
  68. package/dist/components/CodeEditor/index.js +5 -0
  69. package/dist/components/CodeEditor/lib/PortalOutlet.d.ts +11 -0
  70. package/dist/components/CodeEditor/lib/PortalOutlet.js +11 -0
  71. package/dist/components/CodeEditor/lib/httpCompletions.d.ts +7 -0
  72. package/dist/components/CodeEditor/lib/httpCompletions.js +102 -0
  73. package/dist/components/CodeEditor/lib/httpCompletionsData.d.ts +29 -0
  74. package/dist/components/CodeEditor/lib/httpCompletionsData.js +650 -0
  75. package/dist/components/CodeEditor/lib/keyboardHint.d.ts +5 -0
  76. package/dist/components/CodeEditor/lib/keyboardHint.js +2 -0
  77. package/dist/components/CodeEditor/lib/loadEngine.d.ts +7 -0
  78. package/dist/components/CodeEditor/lib/loadEngine.js +9 -0
  79. package/dist/components/CodeEditor/lib/portalRegistry.d.ts +20 -0
  80. package/dist/components/CodeEditor/lib/portalRegistry.js +47 -0
  81. package/dist/components/CodeEditor/lib/splitContentProps.d.ts +12 -0
  82. package/dist/components/CodeEditor/lib/splitContentProps.js +28 -0
  83. package/dist/components/CodeEditor/types.d.ts +72 -0
  84. package/dist/components/CodeEditor/types.js +0 -0
  85. package/dist/components/CodeSnippet/CodeSnippetCode.js +6 -23
  86. package/dist/components/CodeSnippet/CodeSnippetCopyButton.js +4 -3
  87. package/dist/components/CodeSnippet/CodeSnippetFrameContext.d.ts +25 -0
  88. package/dist/components/CodeSnippet/CodeSnippetFrameContext.js +3 -0
  89. package/dist/components/CodeSnippet/CodeSnippetFullscreenButton.js +2 -2
  90. package/dist/components/CodeSnippet/CodeSnippetRoot.d.ts +3 -5
  91. package/dist/components/CodeSnippet/CodeSnippetRoot.js +56 -50
  92. package/dist/components/CodeSnippet/CodeSnippetShowMoreButton.js +7 -5
  93. package/dist/components/CodeSnippet/CodeSnippetWrapButton.js +2 -2
  94. package/dist/components/CodeSnippet/adapters/highlightjs.js +1 -0
  95. package/dist/components/CodeSnippet/adapters/prism.js +3 -1
  96. package/dist/components/CodeSnippet/adapters/shiki.js +1 -0
  97. package/dist/components/CodeSnippet/adapters/types.d.ts +3 -3
  98. package/dist/components/CodeSnippet/classes.d.ts +3 -0
  99. package/dist/components/CodeSnippet/classes.js +14 -0
  100. package/dist/components/CodeSnippet/hooks/index.d.ts +1 -0
  101. package/dist/components/CodeSnippet/hooks/index.js +1 -0
  102. package/dist/components/CodeSnippet/hooks/useCodeSnippetFrame.d.ts +2 -0
  103. package/dist/components/CodeSnippet/hooks/useCodeSnippetFrame.js +8 -0
  104. package/dist/components/CodeSnippet/index.d.ts +2 -1
  105. package/dist/components/CodeSnippet/index.js +1 -1
  106. package/dist/components/CodeSnippet/internal/CodeSnippetFrame.d.ts +22 -0
  107. package/dist/components/CodeSnippet/internal/CodeSnippetFrame.js +81 -0
  108. package/dist/components/CodeSnippet/internal/CodeToken.d.ts +2 -1
  109. package/dist/components/CodeSnippet/internal/CodeToken.js +1 -1
  110. package/dist/components/CodeSnippet/internal/FoldSummary.d.ts +11 -0
  111. package/dist/components/CodeSnippet/internal/FoldSummary.js +27 -0
  112. package/dist/components/CodeSnippet/internal/FoldToggle.d.ts +2 -0
  113. package/dist/components/CodeSnippet/internal/FoldToggle.js +2 -1
  114. package/dist/components/CodeSnippet/internal/index.d.ts +1 -0
  115. package/dist/components/CodeSnippet/internal/index.js +1 -0
  116. package/dist/components/CodeSnippet/lib/foldUtils.d.ts +9 -2
  117. package/dist/components/CodeSnippet/lib/foldUtils.js +2 -2
  118. package/dist/components/CodeSnippet/lib/showMore.d.ts +7 -0
  119. package/dist/components/CodeSnippet/lib/showMore.js +7 -0
  120. package/dist/components/Copyable/Copyable.d.ts +5 -2
  121. package/dist/components/Copyable/Copyable.js +1 -1
  122. package/dist/metadata/components.json +1235 -5
  123. 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
+ import type { CodeEditorApi } from '../types';
2
+ /**
3
+ * Returns the editor's imperative API from inside `CodeEditorRoot`
4
+ * (e.g. in custom header actions). Same object as `apiRef`.
5
+ */
6
+ export declare const useCodeEditor: () => CodeEditorApi;
@@ -0,0 +1,3 @@
1
+ import { useCodeEditorContext } from "../CodeEditorContext.js";
2
+ const useCodeEditor = ()=>useCodeEditorContext().api;
3
+ export { useCodeEditor };
@@ -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 };