@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,20 @@
1
+ import type { ReactNode } from 'react';
2
+ /** A React node to render into a DOM host owned by the editor engine (gutter marker, widget, panel). */
3
+ export interface PortalEntry {
4
+ id: number;
5
+ host: HTMLElement;
6
+ node: ReactNode;
7
+ }
8
+ /**
9
+ * External store connecting plain-DOM CodeMirror extensions to React.
10
+ * The engine registers `(host, node)` pairs; `PortalOutlet` renders each one with `createPortal`.
11
+ */
12
+ export interface PortalRegistry {
13
+ register(host: HTMLElement, node: ReactNode): number;
14
+ update(id: number, node: ReactNode): void;
15
+ unregister(id: number): void;
16
+ subscribe(listener: () => void): () => void;
17
+ /** Stable reference until a change */
18
+ getSnapshot(): readonly PortalEntry[];
19
+ }
20
+ export declare const createPortalRegistry: () => PortalRegistry;
@@ -0,0 +1,47 @@
1
+ const createPortalRegistry = ()=>{
2
+ let nextId = 1;
3
+ let entries = [];
4
+ const listeners = new Set();
5
+ const emit = (next)=>{
6
+ entries = next;
7
+ for (const listener of [
8
+ ...listeners
9
+ ])listener();
10
+ };
11
+ return {
12
+ register (host, node) {
13
+ const id = nextId++;
14
+ emit([
15
+ ...entries,
16
+ {
17
+ id,
18
+ host,
19
+ node
20
+ }
21
+ ]);
22
+ return id;
23
+ },
24
+ update (id, node) {
25
+ const current = entries.find((entry)=>entry.id === id);
26
+ if (!current || current.node === node) return;
27
+ emit(entries.map((entry)=>entry.id === id ? {
28
+ ...entry,
29
+ node
30
+ } : entry));
31
+ },
32
+ unregister (id) {
33
+ if (!entries.some((entry)=>entry.id === id)) return;
34
+ emit(entries.filter((entry)=>entry.id !== id));
35
+ },
36
+ subscribe (listener) {
37
+ listeners.add(listener);
38
+ return ()=>{
39
+ listeners.delete(listener);
40
+ };
41
+ },
42
+ getSnapshot () {
43
+ return entries;
44
+ }
45
+ };
46
+ };
47
+ export { createPortalRegistry };
@@ -0,0 +1,12 @@
1
+ export interface SplitContentProps<TWrapper> {
2
+ /** Attributes for the typing surface (`.cm-content`), as DOM attribute strings */
3
+ contentAttributes: Record<string, string>;
4
+ /** Everything else: className, style, event handlers, other wrapper attributes */
5
+ wrapperProps: TWrapper;
6
+ }
7
+ /**
8
+ * Attribute routing for `CodeEditorContent` (spec D10): `data-*`, `aria-*`,
9
+ * `id`, `title` and `tabIndex` go to the editor's typing surface; the rest stays
10
+ * on the wrapper div. `undefined` / `null` values are dropped, as React would.
11
+ */
12
+ export declare const splitContentProps: <TProps extends object>(props: TProps) => SplitContentProps<TProps>;
@@ -0,0 +1,28 @@
1
+ const isContentAttribute = (key)=>key.startsWith('data-') || key.startsWith('aria-') || 'id' === key || 'title' === key;
2
+ const toAttributeValue = (value)=>{
3
+ if ('string' == typeof value) return value;
4
+ if ('number' == typeof value || 'boolean' == typeof value) return String(value);
5
+ return null;
6
+ };
7
+ const splitContentProps = (props)=>{
8
+ const contentAttributes = {};
9
+ const wrapperProps = {};
10
+ for (const [key, value] of Object.entries(props)){
11
+ if ('tabIndex' === key) {
12
+ const attribute = toAttributeValue(value);
13
+ if (null !== attribute) contentAttributes.tabindex = attribute;
14
+ continue;
15
+ }
16
+ if (isContentAttribute(key)) {
17
+ const attribute = toAttributeValue(value);
18
+ if (null !== attribute) contentAttributes[key] = attribute;
19
+ continue;
20
+ }
21
+ wrapperProps[key] = value;
22
+ }
23
+ return {
24
+ contentAttributes,
25
+ wrapperProps: wrapperProps
26
+ };
27
+ };
28
+ export { splitContentProps };
@@ -0,0 +1,72 @@
1
+ import type { FoldRegion } from '../CodeSnippet/lib/foldUtils';
2
+ /** Language of the document. Passed verbatim to the syntax adapter and selects the structural parser. */
3
+ export type CodeEditorLanguage = 'http' | 'json' | 'yaml' | 'bash' | 'text' | 'javascript' | 'typescript' | 'python' | 'lua';
4
+ /** Imperative handle exposed through `apiRef` and `useCodeEditor()`. */
5
+ export interface CodeEditorApi {
6
+ focus(): void;
7
+ getValue(): string;
8
+ /** Replaces the selection, or inserts at the cursor. Undoable. */
9
+ insertText(text: string): void;
10
+ openSearch(): void;
11
+ foldAll(): void;
12
+ unfoldAll(): void;
13
+ }
14
+ export interface CodeEditorPosition {
15
+ /** Absolute line number (startingLineNumber-based) */
16
+ line: number;
17
+ /** 1-based column */
18
+ column: number;
19
+ }
20
+ export interface CodeEditorDiagnostic {
21
+ from: CodeEditorPosition;
22
+ /**
23
+ * Defaults to one character after `from` (a point when `from` is at the end of its line).
24
+ * A line past the document end is clamped to the document end; never before `from`.
25
+ */
26
+ to?: CodeEditorPosition;
27
+ severity: 'error' | 'warning' | 'info';
28
+ message: string;
29
+ /** 'syntax' | 'schema' for built-ins; free text for consumer diagnostics */
30
+ source?: string;
31
+ }
32
+ export interface CodeEditorCompletion {
33
+ label: string;
34
+ /** Text to insert; defaults to label */
35
+ apply?: string;
36
+ detail?: string;
37
+ info?: string;
38
+ kind?: 'keyword' | 'property' | 'value' | 'method' | 'header' | 'snippet' | 'text';
39
+ }
40
+ /** HTTP-specific location of the cursor, present when `language === 'http'`. */
41
+ export interface CodeEditorHttpContext {
42
+ section: 'start-line' | 'header-name' | 'header-value' | 'body';
43
+ headerName?: string;
44
+ messageKind: 'request' | 'response';
45
+ }
46
+ export interface CodeEditorCompletionContext {
47
+ value: string;
48
+ position: CodeEditorPosition;
49
+ lineText: string;
50
+ /** Word being typed before the cursor and where it starts */
51
+ word: {
52
+ text: string;
53
+ from: CodeEditorPosition;
54
+ };
55
+ /** true when opened with Ctrl-Space */
56
+ explicit: boolean;
57
+ /** Present when language === 'http' */
58
+ http?: CodeEditorHttpContext;
59
+ /** Present in a JSON document or JSON body: JSON pointer of the cursor location */
60
+ jsonPointer?: string;
61
+ }
62
+ export type CodeEditorCompletionSource = (ctx: CodeEditorCompletionContext) => CodeEditorCompletion[] | null | Promise<CodeEditorCompletion[] | null>;
63
+ /** Static fold regions, or a function re-run (debounced) after edits. */
64
+ export type CodeEditorFolds = FoldRegion[] | ((value: string, ctx: {
65
+ startingLineNumber: number;
66
+ }) => FoldRegion[]);
67
+ /**
68
+ * JSON Schema document (any draft supported by json-schema-library).
69
+ * Structural on purpose: the engine casts it to json-schema-library's own type,
70
+ * so the public API never depends on that module statically.
71
+ */
72
+ export type JsonSchema = Record<string, unknown> | boolean;
File without changes
@@ -1,9 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { cn } from "../../utils/cn.js";
3
2
  import { useTestId } from "../../utils/testId.js";
4
3
  import { useCodeSnippet } from "./hooks/index.js";
5
- import { CodeContent, CodeLine, TokenizedCodeLine } from "./internal/index.js";
6
- import { getFoldSummaryLabel } from "./lib/foldUtils.js";
4
+ import { CodeContent, CodeLine, FoldSummary, TokenizedCodeLine } from "./internal/index.js";
7
5
  import { SIZE_LINE_HEIGHT_CLASSES } from "./lib/lineStyles.js";
8
6
  import { splitTextByRanges } from "./lib/lineUtils.js";
9
7
  const CodeSnippetCode = ({ className, ...props })=>{
@@ -21,14 +19,7 @@ const CodeSnippetCode = ({ className, ...props })=>{
21
19
  hasFolds: true
22
20
  };
23
21
  };
24
- const renderFoldSummary = (item)=>{
25
- const label = getFoldSummaryLabel(item.fold, item.lineCount);
26
- const { className: summaryClassName, onClick: consumerOnClick, ...summaryProps } = item.fold.summaryProps ?? {};
27
- const handleSummaryClick = (event)=>{
28
- toggleFold(item.fold.id);
29
- consumerOnClick?.(event);
30
- };
31
- return /*#__PURE__*/ jsx(CodeLine, {
22
+ const renderFoldSummary = (item)=>/*#__PURE__*/ jsx(CodeLine, {
32
23
  lineConfig: void 0,
33
24
  lineHeightClass: lineHeightClass,
34
25
  showInlineGutter: inlineGutter,
@@ -37,20 +28,12 @@ const CodeSnippetCode = ({ className, ...props })=>{
37
28
  isFoldCollapsed: inlineGutter || void 0,
38
29
  onFoldToggle: inlineGutter ? ()=>toggleFold(item.fold.id) : void 0,
39
30
  hasFolds: inlineGutter && hasFolds,
40
- children: /*#__PURE__*/ jsx("button", {
41
- type: "button",
42
- className: cn('inline-flex items-center cursor-pointer select-none', summaryClassName),
43
- "aria-expanded": false,
44
- "aria-label": `Collapsed region: ${label}, ${item.lineCount} lines`,
45
- ...summaryProps,
46
- onClick: handleSummaryClick,
47
- children: /*#__PURE__*/ jsx("span", {
48
- className: "inline-flex items-center italic text-text-secondary hover:text-text-primary transition-colors",
49
- children: label
50
- })
31
+ children: /*#__PURE__*/ jsx(FoldSummary, {
32
+ fold: item.fold,
33
+ lineCount: item.lineCount,
34
+ onToggle: ()=>toggleFold(item.fold.id)
51
35
  })
52
36
  }, `fold-${item.fold.id}`);
53
- };
54
37
  if (isLoading || !tokens) {
55
38
  const codeLines = code.split('\n');
56
39
  return /*#__PURE__*/ jsx(CodeContent, {
@@ -2,12 +2,13 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useTestId } from "../../utils/testId.js";
3
3
  import { Button } from "../Button/index.js";
4
4
  import { Copyable, CopyableIcon } from "../Copyable/index.js";
5
- import { useCodeSnippet } from "./hooks/index.js";
5
+ import { useCodeSnippetFrame } from "./hooks/index.js";
6
6
  const CodeSnippetCopyButton = ({ ref, ...props })=>{
7
7
  const testId = useTestId('copy-button');
8
- const { code } = useCodeSnippet();
8
+ const { getCode, notifyCopied } = useCodeSnippetFrame();
9
9
  return /*#__PURE__*/ jsx(Copyable, {
10
- text: code,
10
+ text: getCode,
11
+ onCopied: notifyCopied,
11
12
  tooltip: true,
12
13
  children: /*#__PURE__*/ jsx(Button, {
13
14
  ref: ref,
@@ -0,0 +1,25 @@
1
+ import type { CodeSnippetSize } from './CodeSnippetContext';
2
+ /**
3
+ * Frame-level state shared by the toolbar/show-more buttons.
4
+ *
5
+ * Provided by `CodeSnippetRoot` (and by `CodeEditorRoot`), so the frame
6
+ * buttons work under either root without depending on the snippet's
7
+ * render pipeline (`CodeSnippetContext`).
8
+ */
9
+ export type CodeSnippetFrameContextValue = {
10
+ size: CodeSnippetSize;
11
+ /** Lazily reads the current text (snippet: `code` prop; editor: live document) */
12
+ getCode: () => string;
13
+ /** Called by the copy button after a copy; roots forward it to their `onCopy` */
14
+ notifyCopied: () => void;
15
+ wrapLines: boolean;
16
+ setWrapLines: (wrap: boolean) => void;
17
+ isFullscreen: boolean;
18
+ setIsFullscreen: (fullscreen: boolean) => void;
19
+ maxLines: number;
20
+ isExpanded: boolean;
21
+ setIsExpanded: (expanded: boolean) => void;
22
+ /** max(0, totalRows - maxLines) — independent of isExpanded; 0 when maxLines <= 0 */
23
+ hiddenLineCount: number;
24
+ };
25
+ export declare const CodeSnippetFrameContext: import("react").Context<CodeSnippetFrameContextValue | null>;
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+ const CodeSnippetFrameContext = createContext(null);
3
+ export { CodeSnippetFrameContext };
@@ -5,10 +5,10 @@ import { useTestId } from "../../utils/testId.js";
5
5
  import { Button } from "../Button/index.js";
6
6
  import { Kbd } from "../Kbd/index.js";
7
7
  import { Tooltip, TooltipContent, TooltipTrigger } from "../Tooltip/index.js";
8
- import { useCodeSnippet } from "./hooks/index.js";
8
+ import { useCodeSnippetFrame } from "./hooks/index.js";
9
9
  const CodeSnippetFullscreenButton = ({ onClick, ref, ...props })=>{
10
10
  const testId = useTestId('fullscreen-button');
11
- const { isFullscreen, setIsFullscreen } = useCodeSnippet();
11
+ const { isFullscreen, setIsFullscreen } = useCodeSnippetFrame();
12
12
  const handleClick = (event)=>{
13
13
  setIsFullscreen(!isFullscreen);
14
14
  onClick?.(event);
@@ -1,11 +1,9 @@
1
1
  import type { HTMLAttributes, ReactNode, Ref } from 'react';
2
- import { type VariantProps } from 'class-variance-authority';
2
+ import type { VariantProps } from 'class-variance-authority';
3
3
  import { type TestableProps } from '../../utils/testId';
4
4
  import { type LineConfig } from './CodeSnippetContext';
5
+ import { codeSnippetRootVariants } from './classes';
5
6
  import { type FoldRegion } from './lib/foldUtils';
6
- declare const codeSnippetRootVariants: (props?: ({
7
- size?: "lg" | "md" | "sm" | null | undefined;
8
- } & import("class-variance-authority/types").ClassProp) | undefined) => string;
9
7
  type CodeSnippetRootVariantProps = VariantProps<typeof codeSnippetRootVariants>;
10
8
  type CodeSnippetRootNativeProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'>;
11
9
  export type CodeSnippetRootProps<TLanguage extends string = string> = CodeSnippetRootNativeProps & CodeSnippetRootVariantProps & TestableProps & {
@@ -33,7 +31,7 @@ export type CodeSnippetRootProps<TLanguage extends string = string> = CodeSnippe
33
31
  /** Child components */
34
32
  children?: ReactNode;
35
33
  };
36
- export declare function CodeSnippetRoot<TLanguage extends string = string>({ code, language, size, lines, startingLineNumber, wrapLines: initialWrapLines, maxLines, folds: foldsProp, onCopy, className, children, 'data-testid': testId, ...props }: CodeSnippetRootProps<TLanguage>): import("react").JSX.Element;
34
+ export declare function CodeSnippetRoot<TLanguage extends string = string>({ code, language, size, lines, startingLineNumber, wrapLines: initialWrapLines, maxLines, folds: foldsProp, onCopy, className, children, ref, 'data-testid': testId, ...props }: CodeSnippetRootProps<TLanguage>): import("react").JSX.Element;
37
35
  export declare namespace CodeSnippetRoot {
38
36
  var displayName: string;
39
37
  }
@@ -1,30 +1,19 @@
1
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Children, isValidElement, useCallback, useEffect, useMemo, useState } from "react";
3
- import { createPortal } from "react-dom";
4
- import { cva } from "class-variance-authority";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
5
3
  import { cn } from "../../utils/cn.js";
6
4
  import { copyText } from "../../utils/copyText.js";
7
5
  import { TestIdProvider } from "../../utils/testId.js";
8
6
  import { plainAdapter } from "./adapters/plain.js";
9
- import { CodeSnippetContext, MIN_HIDDEN_LINES_THRESHOLD } from "./CodeSnippetContext.js";
7
+ import { CodeSnippetContext } from "./CodeSnippetContext.js";
8
+ import { CodeSnippetFrameContext } from "./CodeSnippetFrameContext.js";
10
9
  import { CodeSnippetShowMoreButton } from "./CodeSnippetShowMoreButton.js";
10
+ import { codeSnippetRootVariants } from "./classes.js";
11
11
  import { useAdapter } from "./hooks/index.js";
12
+ import { CodeSnippetFrame } from "./internal/CodeSnippetFrame.js";
12
13
  import { buildDisplayItems, validateFolds } from "./lib/foldUtils.js";
13
- const codeSnippetRootVariants = cva("relative code-snippet-bg rounded-6 font-mono text-syntax-no-syntax overflow-hidden flex flex-col [&::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&::selection]:text-[var(--color-syntax-highlight-selected-code)] [&_*::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&_*::selection]:text-[var(--color-syntax-highlight-selected-code)] [&>[data-slot=code-snippet-actions]]:absolute [&>[data-slot=code-snippet-actions]]:right-0 [&>[data-slot=code-snippet-actions]]:top-0 [&>[data-slot=code-snippet-actions]]:z-30 [&>[data-slot=code-snippet-actions]]:p-6 [&>[data-slot=code-snippet-actions]]:rounded-br-6 [&>[data-slot=code-snippet-actions]]:rounded-tl-6", {
14
- variants: {
15
- size: {
16
- sm: 'text-xs leading-sm',
17
- md: 'text-sm',
18
- lg: 'text-base leading-sm'
19
- }
20
- },
21
- defaultVariants: {
22
- size: 'sm'
23
- }
24
- });
14
+ import { getHiddenLineCount, hasExplicitShowMoreButton, isClamped } from "./lib/showMore.js";
25
15
  const EMPTY_LINES = {};
26
- const isCodeSnippetShowMoreButton = (child)=>/*#__PURE__*/ isValidElement(child) && child.type?.displayName === CodeSnippetShowMoreButton.displayName;
27
- const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_LINES, startingLineNumber = 1, wrapLines: initialWrapLines = false, maxLines = 0, folds: foldsProp, onCopy, className, children, 'data-testid': testId, ...props })=>{
16
+ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_LINES, startingLineNumber = 1, wrapLines: initialWrapLines = false, maxLines = 0, folds: foldsProp, onCopy, className, children, ref, 'data-testid': testId, ...props })=>{
28
17
  const adapterContext = useAdapter();
29
18
  const [wrapLines, setWrapLines] = useState(initialWrapLines);
30
19
  const [isExpanded, setIsExpanded] = useState(false);
@@ -65,16 +54,6 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_L
65
54
  language,
66
55
  adapter
67
56
  ]);
68
- useEffect(()=>{
69
- if (!isFullscreen) return;
70
- const handleKeyDown = (e)=>{
71
- if ('Escape' === e.key) setIsFullscreen(false);
72
- };
73
- document.addEventListener('keydown', handleKeyDown);
74
- return ()=>document.removeEventListener('keydown', handleKeyDown);
75
- }, [
76
- isFullscreen
77
- ]);
78
57
  const copyToClipboard = useCallback(async ()=>{
79
58
  await copyText(code);
80
59
  onCopy?.(code);
@@ -82,6 +61,15 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_L
82
61
  code,
83
62
  onCopy
84
63
  ]);
64
+ const getCode = useCallback(()=>code, [
65
+ code
66
+ ]);
67
+ const notifyCopied = useCallback(()=>{
68
+ onCopy?.(code);
69
+ }, [
70
+ code,
71
+ onCopy
72
+ ]);
85
73
  const totalLines = tokens?.length ?? code.split('\n').length;
86
74
  const validatedFolds = useMemo(()=>foldsProp ? validateFolds(foldsProp, totalLines, startingLineNumber) : [], [
87
75
  foldsProp,
@@ -119,13 +107,11 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_L
119
107
  collapsedFolds,
120
108
  startingLineNumber
121
109
  ]);
122
- const hasExplicitShowMoreButton = Children.toArray(children).some(isCodeSnippetShowMoreButton);
123
- const visibleDisplayItems = useMemo(()=>{
124
- const hiddenRows = displayItems.length - maxLines;
125
- const shouldClip = maxLines > 0 && !isExpanded && hiddenRows >= MIN_HIDDEN_LINES_THRESHOLD;
126
- return shouldClip ? displayItems.slice(0, maxLines) : displayItems;
127
- }, [
110
+ const hasExplicitShowMore = hasExplicitShowMoreButton(children);
111
+ const hiddenLineCount = getHiddenLineCount(displayItems.length, maxLines);
112
+ const visibleDisplayItems = useMemo(()=>isClamped(hiddenLineCount, isExpanded) ? displayItems.slice(0, maxLines) : displayItems, [
128
113
  displayItems,
114
+ hiddenLineCount,
129
115
  maxLines,
130
116
  isExpanded
131
117
  ]);
@@ -180,31 +166,51 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_L
180
166
  copyToClipboard,
181
167
  adapter
182
168
  ]);
183
- const snippet = /*#__PURE__*/ jsxs("div", {
169
+ const frameValue = useMemo(()=>({
170
+ size: size ?? 'sm',
171
+ getCode,
172
+ notifyCopied,
173
+ wrapLines,
174
+ setWrapLines,
175
+ isFullscreen,
176
+ setIsFullscreen,
177
+ maxLines,
178
+ isExpanded,
179
+ setIsExpanded,
180
+ hiddenLineCount
181
+ }), [
182
+ size,
183
+ getCode,
184
+ notifyCopied,
185
+ wrapLines,
186
+ isFullscreen,
187
+ maxLines,
188
+ isExpanded,
189
+ hiddenLineCount
190
+ ]);
191
+ const snippet = /*#__PURE__*/ jsxs(CodeSnippetFrame, {
184
192
  "data-slot": "code-snippet",
185
193
  "data-testid": testId,
194
+ ...props,
195
+ ref: ref,
186
196
  className: cn(codeSnippetRootVariants({
187
197
  size
188
- }), isFullscreen ? 'fixed inset-16 z-50' : className),
189
- ...!isFullscreen ? props : {},
198
+ }), className),
199
+ isFullscreen: isFullscreen,
200
+ setIsFullscreen: setIsFullscreen,
190
201
  children: [
191
202
  children,
192
- maxLines > 0 && !hasExplicitShowMoreButton && /*#__PURE__*/ jsx(CodeSnippetShowMoreButton, {})
203
+ maxLines > 0 && !hasExplicitShowMore && /*#__PURE__*/ jsx(CodeSnippetShowMoreButton, {})
193
204
  ]
194
205
  });
195
206
  return /*#__PURE__*/ jsx(TestIdProvider, {
196
207
  value: testId,
197
- children: /*#__PURE__*/ jsx(CodeSnippetContext.Provider, {
198
- value: contextValue,
199
- children: isFullscreen ? /*#__PURE__*/ createPortal(/*#__PURE__*/ jsxs(Fragment, {
200
- children: [
201
- /*#__PURE__*/ jsx("div", {
202
- className: "fixed inset-0 z-40 backdrop-blur-xs bg-component-dialog-overlay",
203
- onClick: ()=>setIsFullscreen(false)
204
- }),
205
- snippet
206
- ]
207
- }), document.body) : snippet
208
+ children: /*#__PURE__*/ jsx(CodeSnippetFrameContext.Provider, {
209
+ value: frameValue,
210
+ children: /*#__PURE__*/ jsx(CodeSnippetContext.Provider, {
211
+ value: contextValue,
212
+ children: snippet
213
+ })
208
214
  })
209
215
  });
210
216
  };
@@ -1,13 +1,14 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { ChevronDown } from "../../icons/ChevronDown.js";
3
3
  import { ChevronUp } from "../../icons/ChevronUp.js";
4
+ import { useTestId } from "../../utils/testId.js";
4
5
  import { Button } from "../Button/index.js";
5
6
  import { MIN_HIDDEN_LINES_THRESHOLD } from "./CodeSnippetContext.js";
6
- import { useCodeSnippet } from "./hooks/index.js";
7
+ import { useCodeSnippetFrame } from "./hooks/index.js";
7
8
  const CodeSnippetShowMoreButton = ({ onClick, ref, ...props })=>{
8
- const { displayItems, maxLines, isExpanded, setIsExpanded } = useCodeSnippet();
9
- const hiddenLines = displayItems.length - maxLines;
10
- if (maxLines <= 0 || hiddenLines < MIN_HIDDEN_LINES_THRESHOLD) return null;
9
+ const testId = useTestId('show-more-button');
10
+ const { maxLines, isExpanded, setIsExpanded, hiddenLineCount } = useCodeSnippetFrame();
11
+ if (maxLines <= 0 || hiddenLineCount < MIN_HIDDEN_LINES_THRESHOLD) return null;
11
12
  const handleClick = (event)=>{
12
13
  setIsExpanded(!isExpanded);
13
14
  onClick?.(event);
@@ -21,6 +22,7 @@ const CodeSnippetShowMoreButton = ({ onClick, ref, ...props })=>{
21
22
  color: "neutral",
22
23
  size: "small",
23
24
  fullWidth: true,
25
+ "data-testid": testId,
24
26
  ...props,
25
27
  onClick: handleClick,
26
28
  children: isExpanded ? /*#__PURE__*/ jsxs(Fragment, {
@@ -31,7 +33,7 @@ const CodeSnippetShowMoreButton = ({ onClick, ref, ...props })=>{
31
33
  }) : /*#__PURE__*/ jsxs(Fragment, {
32
34
  children: [
33
35
  "Show more (",
34
- hiddenLines,
36
+ hiddenLineCount,
35
37
  " lines)",
36
38
  /*#__PURE__*/ jsx(ChevronDown, {})
37
39
  ]
@@ -3,10 +3,10 @@ import { WrapText } from "../../icons/WrapText.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
4
  import { ToggleButton } from "../ToggleButton/index.js";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../Tooltip/index.js";
6
- import { useCodeSnippet } from "./hooks/index.js";
6
+ import { useCodeSnippetFrame } from "./hooks/index.js";
7
7
  const CodeSnippetWrapButton = ({ onToggle, ref, ...props })=>{
8
8
  const testId = useTestId('wrap-button');
9
- const { wrapLines, setWrapLines } = useCodeSnippet();
9
+ const { wrapLines, setWrapLines } = useCodeSnippetFrame();
10
10
  const handleToggle = (active, event)=>{
11
11
  setWrapLines(active);
12
12
  onToggle?.(active, event);
@@ -10,6 +10,7 @@ const SUPPORTED_LANGUAGES = [
10
10
  'python',
11
11
  'go',
12
12
  'sql',
13
+ 'lua',
13
14
  'http'
14
15
  ];
15
16
  const HLJS_CLASS_MAP = {
@@ -4,7 +4,8 @@ let languagesPromise = null;
4
4
  async function loadAdditionalLanguages() {
5
5
  if (!languagesPromise) languagesPromise = Promise.all([
6
6
  import("prismjs/components/prism-bash"),
7
- import("prismjs/components/prism-http")
7
+ import("prismjs/components/prism-http"),
8
+ import("prismjs/components/prism-lua")
8
9
  ]);
9
10
  return languagesPromise;
10
11
  }
@@ -24,6 +25,7 @@ const SUPPORTED_LANGUAGES = [
24
25
  'sql',
25
26
  'yaml',
26
27
  'markdown',
28
+ 'lua',
27
29
  'http'
28
30
  ];
29
31
  const PRISM_TOKEN_MAP = {
@@ -15,6 +15,7 @@ const SUPPORTED_LANGUAGES = [
15
15
  'sql',
16
16
  'yaml',
17
17
  'markdown',
18
+ 'lua',
18
19
  'rust',
19
20
  'java',
20
21
  'c',
@@ -1,7 +1,7 @@
1
1
  /** Supported language union types per adapter */
2
- export type PrismLanguage = 'javascript' | 'typescript' | 'jsx' | 'tsx' | 'bash' | 'shell' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'yaml' | 'markdown' | 'http';
3
- export type ShikiLanguage = 'javascript' | 'typescript' | 'jsx' | 'tsx' | 'bash' | 'shell' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'yaml' | 'markdown' | 'rust' | 'java' | 'c' | 'cpp' | 'http';
4
- export type HighlightJsLanguage = 'javascript' | 'typescript' | 'bash' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'http';
2
+ export type PrismLanguage = 'javascript' | 'typescript' | 'jsx' | 'tsx' | 'bash' | 'shell' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'yaml' | 'markdown' | 'lua' | 'http';
3
+ export type ShikiLanguage = 'javascript' | 'typescript' | 'jsx' | 'tsx' | 'bash' | 'shell' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'yaml' | 'markdown' | 'lua' | 'rust' | 'java' | 'c' | 'cpp' | 'http';
4
+ export type HighlightJsLanguage = 'javascript' | 'typescript' | 'bash' | 'json' | 'html' | 'xml' | 'css' | 'python' | 'go' | 'sql' | 'lua' | 'http';
5
5
  export type PlainLanguage = 'text' | 'plain';
6
6
  /** Strict token types for syntax highlighting */
7
7
  export type TokenType = 'plain' | 'keyword' | 'string' | 'comment' | 'function' | 'number' | 'operator' | 'punctuation' | 'class-name' | 'property' | 'tag' | 'attr-name' | 'attr-value' | 'boolean' | 'builtin' | 'constant' | 'regex' | 'variable';
@@ -0,0 +1,3 @@
1
+ export declare const codeSnippetRootVariants: (props?: ({
2
+ size?: "lg" | "md" | "sm" | null | undefined;
3
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -0,0 +1,14 @@
1
+ import { cva } from "class-variance-authority";
2
+ const codeSnippetRootVariants = cva("relative code-snippet-bg rounded-6 font-mono text-syntax-no-syntax overflow-hidden flex flex-col [&::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&::selection]:text-[var(--color-syntax-highlight-selected-code)] [&_*::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&_*::selection]:text-[var(--color-syntax-highlight-selected-code)] [&>[data-slot=code-snippet-actions]]:absolute [&>[data-slot=code-snippet-actions]]:right-0 [&>[data-slot=code-snippet-actions]]:top-0 [&>[data-slot=code-snippet-actions]]:z-30 [&>[data-slot=code-snippet-actions]]:p-6 [&>[data-slot=code-snippet-actions]]:rounded-br-6 [&>[data-slot=code-snippet-actions]]:rounded-tl-6", {
3
+ variants: {
4
+ size: {
5
+ sm: 'text-xs leading-sm',
6
+ md: 'text-sm',
7
+ lg: 'text-base leading-sm'
8
+ }
9
+ },
10
+ defaultVariants: {
11
+ size: 'sm'
12
+ }
13
+ });
14
+ export { codeSnippetRootVariants };
@@ -1,2 +1,3 @@
1
1
  export { useAdapter } from './useAdapter';
2
2
  export { useCodeSnippet } from './useCodeSnippet';
3
+ export { useCodeSnippetFrame } from './useCodeSnippetFrame';
@@ -1,2 +1,3 @@
1
1
  export { useAdapter } from "./useAdapter.js";
2
2
  export { useCodeSnippet } from "./useCodeSnippet.js";
3
+ export { useCodeSnippetFrame } from "./useCodeSnippetFrame.js";
@@ -0,0 +1,2 @@
1
+ import { type CodeSnippetFrameContextValue } from '../CodeSnippetFrameContext';
2
+ export declare const useCodeSnippetFrame: () => CodeSnippetFrameContextValue;
@@ -0,0 +1,8 @@
1
+ import { useContext } from "react";
2
+ import { CodeSnippetFrameContext } from "../CodeSnippetFrameContext.js";
3
+ const useCodeSnippetFrame = ()=>{
4
+ const context = useContext(CodeSnippetFrameContext);
5
+ if (!context) throw new Error('useCodeSnippetFrame must be used within CodeSnippetRoot or CodeEditorRoot');
6
+ return context;
7
+ };
8
+ export { useCodeSnippetFrame };