@vortexlabs/kirari-react 1.0.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @vortexlabs/kirari-react
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 52ad674: Accept text children in `<Code>` the way React renders text. `<Code>let x = {count}</Code>` now highlights the joined source instead of misaligned tokens. Numbers and bigints are kept instead of dropped, and `null`, `undefined`, and booleans render nothing instead of throwing. Element children throw a `TypeError`. The `children` prop type widens from `string` to the new exported `CodeText` type.
8
+
9
+ Stop `lines` and `highlightLines` from adding an empty last line wrapper after a trailing newline. The newline still follows the last wrapper, so the source text is unchanged, and blank lines before it keep their wrappers.
10
+
11
+ ## 1.0.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 3ee99c0: Update the package homepage and documentation links to https://kirari.sh.
16
+
3
17
  ## 1.0.0
4
18
 
5
19
  ### Major Changes
package/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # @vortexlabs/kirari-react
2
2
 
3
- Kirari highlighting as React elements. `<Code>` renders tokens as `<span>`
4
- elements, with no `dangerouslySetInnerHTML` and no hooks, so it works as a
5
- Server Component.
3
+ Syntax highlighting for React 19, built on
4
+ [`@vortexlabs/kirari`](https://github.com/vortexkit/kirari/blob/main/packages/kirari/README.md).
5
+ `<Code>` renders code as React elements and works in Server Components. Use the
6
+ optional streaming hook for code that arrives in chunks.
7
+
8
+ [Documentation](https://kirari.sh/docs/) · [Playground](https://kirari.sh/playground/)
6
9
 
7
10
  ## Install
8
11
 
@@ -19,19 +22,30 @@ import { Code } from '@vortexlabs/kirari-react'
19
22
  import { typescript } from '@vortexlabs/kirari/languages/typescript'
20
23
  import '@vortexlabs/kirari/theme.css'
21
24
 
22
- <Code language={typescript} highlightLines={[1, [3, 4]]}>{source}</Code>
25
+ export const CodeExample = () => (
26
+ <Code language={typescript}>
27
+ {'const greeting: string = "Hello"'}
28
+ </Code>
29
+ )
23
30
  ```
24
31
 
25
32
  Output is `<pre class="kirari"><code>…</code></pre>` with the standard
26
- `kirari-*` classes, so every Kirari theme works. `language` defaults to
27
- JavaScript. Other props go to the `<pre>`.
33
+ `kirari-*` classes, so every Kirari theme works. Code text is escaped by React;
34
+ the component does not use `dangerouslySetInnerHTML`. Text children are joined
35
+ as React joins text, so `{value}` interpolation works; elements throw. `language`
36
+ defaults to JavaScript. Other props go to the `<pre>`.
37
+
38
+ ## Lines and customization
39
+
40
+ Use `highlightLines={[1, [3, 4]]}` to mark line 1 and lines 3–4. Style
41
+ `.kirari-line-highlighted` to choose the highlight's appearance.
28
42
 
29
43
  | Prop | Effect |
30
44
  | --- | --- |
31
45
  | `lines` | Wrap each line in `<span class="kirari-line" data-line="n">`. |
32
46
  | `highlightLines` | Lines or `[first, last]` ranges get `kirari-line-highlighted`. Implies `lines`. |
33
47
  | `lineClassName` | `(line) => class` for a line wrapper. `line` is `{ number, start, end }`. |
34
- | `decorations` | `{ start, end, className }` source ranges, as in [decorations](../../docs/decorations.md). |
48
+ | `decorations` | `{ start, end, className }` source ranges, as in [decorations](https://github.com/vortexkit/kirari/blob/main/docs/decorations.md). |
35
49
  | `classes` | Extra classes per token type, e.g. `{ keyword: 'kw' }`. |
36
50
  | `tokenClassName` | `(token) => class` per token. |
37
51
  | `tokens` | Pre-scanned tokens instead of `children`. |
@@ -46,15 +60,36 @@ Line numbers are CSS:
46
60
 
47
61
  ```tsx
48
62
  'use client'
63
+
64
+ import { Code } from '@vortexlabs/kirari-react'
49
65
  import { useTokenStream } from '@vortexlabs/kirari-react/stream'
66
+ import { javascript } from '@vortexlabs/kirari/languages/javascript'
67
+ import '@vortexlabs/kirari/theme.css'
68
+
69
+ export const StreamExample = () => {
70
+ const { tokens, write, reset } = useTokenStream(javascript)
50
71
 
51
- const { tokens, write, end, reset } = useTokenStream(javascript)
52
- // write(chunk) as text arrives, then end()
53
- return <Code tokens={tokens} />
72
+ return (
73
+ <>
74
+ <button type="button" onClick={() => write('const count = 42\n')}>
75
+ Append a line
76
+ </button>
77
+ <button type="button" onClick={reset}>Reset</button>
78
+ <Code tokens={tokens} />
79
+ </>
80
+ )
81
+ }
54
82
  ```
55
83
 
56
- - Rescans only the unfinished tail. See [token streaming](../../docs/streaming.md).
84
+ This example adds one chunk per click. In your app, call `write(chunk)` as text
85
+ arrives. The hook also returns `end()`, which closes the stream when it is complete.
86
+
87
+ - Built-in JavaScript, TypeScript, JSX, TSX, JSON, JSONC, and Python reuse completed scanning work. Other languages rescan the full source. Long unfinished tokens can still be expensive; see [token streaming](https://github.com/vortexkit/kirari/blob/main/docs/streaming.md).
57
88
  - Changing `language` starts a new, empty stream.
58
89
  - After `end()`, call `reset()` before writing again.
59
90
  - `/stream` is a client module; `<Code>` is not.
60
91
  - Managing tokens yourself? Use `createTokenStream` from `@vortexlabs/kirari/stream` with `applyPatch(tokens, patch)`.
92
+
93
+ ## License
94
+
95
+ [MIT](https://github.com/vortexkit/kirari/blob/main/packages/react/LICENSE) © 2026 [Ugi](https://ugi.dev)
package/dist/index.d.ts CHANGED
@@ -7,9 +7,11 @@ export interface Line {
7
7
  readonly start: number;
8
8
  readonly end: number;
9
9
  }
10
+ /** Text children, joined like React text: `null`, `undefined`, and booleans render nothing. */
11
+ export type CodeText = string | number | bigint | boolean | null | undefined | readonly CodeText[];
10
12
  export interface CodeProps extends Omit<ComponentPropsWithoutRef<'pre'>, 'children'> {
11
13
  /** Source to highlight. Ignored when `tokens` is set. */
12
- readonly children?: string;
14
+ readonly children?: CodeText;
13
15
  /** Pre-scanned tokens, such as the output of `useTokenStream`. */
14
16
  readonly tokens?: readonly Token[];
15
17
  readonly language?: Language;
package/dist/index.js CHANGED
@@ -4,6 +4,15 @@ import { javascript } from '@vortexlabs/kirari/languages/javascript';
4
4
  const join = (...names) => {
5
5
  return names.filter(Boolean).join(' ');
6
6
  };
7
+ const sourceText = (children) => {
8
+ if (children == null || typeof children === 'boolean')
9
+ return '';
10
+ if (typeof children === 'string' || typeof children === 'number' || typeof children === 'bigint')
11
+ return String(children);
12
+ if (Array.isArray(children))
13
+ return children.map(sourceText).join('');
14
+ throw new TypeError('Kirari: <Code> children must be text');
15
+ };
7
16
  const splitsSurrogate = (source, offset) => {
8
17
  const before = source.charCodeAt(offset - 1);
9
18
  const after = source.charCodeAt(offset);
@@ -13,7 +22,7 @@ const highlighted = (ranges) => {
13
22
  const pairs = ranges.map(range => typeof range === 'number' ? [range, range] : range);
14
23
  return line => pairs.some(([first, last]) => first <= line && line <= last);
15
24
  };
16
- const codeNodes = (tokens, options = {}) => {
25
+ const assertContiguous = (tokens) => {
17
26
  let offset = 0;
18
27
  for (const token of tokens) {
19
28
  if (token.start !== offset || token.end !== offset + token.value.length) {
@@ -21,8 +30,9 @@ const codeNodes = (tokens, options = {}) => {
21
30
  }
22
31
  offset = token.end;
23
32
  }
24
- const source = tokens.map(token => token.value).join('');
25
- const decorations = options.decorations ?? [];
33
+ };
34
+ /** Validated enter/leave points for each decoration, in source order. */
35
+ const decorationEvents = (decorations, source) => {
26
36
  const events = [];
27
37
  for (const [index, decoration] of decorations.entries()) {
28
38
  if (!Number.isInteger(decoration.start) || !Number.isInteger(decoration.end)
@@ -34,7 +44,13 @@ const codeNodes = (tokens, options = {}) => {
34
44
  }
35
45
  events.push({ offset: decoration.start, index, entering: true }, { offset: decoration.end, index, entering: false });
36
46
  }
37
- events.sort((a, b) => a.offset - b.offset);
47
+ return events.sort((a, b) => a.offset - b.offset);
48
+ };
49
+ const codeNodes = (tokens, options = {}) => {
50
+ assertContiguous(tokens);
51
+ const source = tokens.map(token => token.value).join('');
52
+ const decorations = options.decorations ?? [];
53
+ const events = decorationEvents(decorations, source);
38
54
  // Resolve callbacks once so tokens split by lines or decorations see them a single time.
39
55
  const tokenClasses = tokens.map(token => join(options.classes && Object.hasOwn(options.classes, token.type) ? options.classes[token.type] : undefined, options.tokenClassName?.(token)));
40
56
  const active = new Set();
@@ -85,13 +101,15 @@ const codeNodes = (tokens, options = {}) => {
85
101
  break;
86
102
  nodes.push(match[0]);
87
103
  start = end + match[0].length;
104
+ if (start === source.length)
105
+ break;
88
106
  number++;
89
107
  } while (true);
90
108
  return nodes;
91
109
  };
92
110
  /** Highlighted code as React elements. Uses no hooks, so it also renders as a Server Component. */
93
- export const Code = ({ children = '', tokens, language = javascript, classes, tokenClassName, decorations, lines, lineClassName, highlightLines, className, ...props }) => {
94
- const nodes = codeNodes(tokens ?? tokenize(children, language), {
111
+ export const Code = ({ children, tokens, language = javascript, classes, tokenClassName, decorations, lines, lineClassName, highlightLines, className, ...props }) => {
112
+ const nodes = codeNodes(tokens ?? tokenize(sourceText(children), language), {
95
113
  ...(classes && { classes }),
96
114
  ...(tokenClassName && { tokenClassName }),
97
115
  ...(decorations && { decorations }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vortexlabs/kirari-react",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Kirari syntax highlighting as React elements.",
5
5
  "keywords": [
6
6
  "react",
@@ -10,7 +10,7 @@
10
10
  ],
11
11
  "author": "Ugi (https://ugi.dev)",
12
12
  "license": "MIT",
13
- "homepage": "https://kirari.vortex.design",
13
+ "homepage": "https://kirari.sh",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "git+https://github.com/vortexkit/kirari.git",
@@ -55,7 +55,7 @@
55
55
  "@happy-dom/global-registrator": "^20.14.6",
56
56
  "@types/react": "^19.3.0",
57
57
  "@types/react-dom": "^19.3.0",
58
- "@vortexlabs/kirari": "1.0.0",
58
+ "@vortexlabs/kirari": "1.0.5",
59
59
  "react": "^19.3.1",
60
60
  "react-dom": "^19.3.1"
61
61
  },