@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 +14 -0
- package/README.md +46 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +24 -6
- package/package.json +3 -3
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
|
-
|
|
4
|
-
|
|
5
|
-
Server
|
|
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
|
-
|
|
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.
|
|
27
|
-
|
|
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](
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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?:
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
58
|
+
"@vortexlabs/kirari": "1.0.5",
|
|
59
59
|
"react": "^19.3.1",
|
|
60
60
|
"react-dom": "^19.3.1"
|
|
61
61
|
},
|