@unfold-mdx/react 0.0.1

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.
@@ -0,0 +1,65 @@
1
+ import { SentenceDiff } from './diff/index.js';
2
+ export { sentenceDiff, tokenize, tokenizeCodeLine, tokenizeLines } from './diff/index.js';
3
+ import { C as CodeLineDiff } from './codeDiff-BVwZfdt9.js';
4
+ export { a as CodeToken, c as codeDiff } from './codeDiff-BVwZfdt9.js';
5
+ import * as React from 'react';
6
+ import React__default from 'react';
7
+ import { ShikiHighlighter } from './shiki/index.js';
8
+
9
+ interface DepthProps {
10
+ selectedIndex?: number;
11
+ defaultIndex?: number;
12
+ onChange?: (i: number) => void;
13
+ orientation?: "horizontal" | "vertical";
14
+ ratio?: number;
15
+ show?: "both" | "prose" | "code";
16
+ indicators?: boolean;
17
+ buttonVariant?: "text" | "arrow" | "chevron" | "minimal";
18
+ animate?: boolean;
19
+ highlight?: boolean;
20
+ highlighter?: ShikiHighlighter;
21
+ children: React__default.ReactNode;
22
+ }
23
+ declare function Depth({ selectedIndex, defaultIndex, onChange, orientation, ratio, show, indicators, buttonVariant, animate, highlight, highlighter, children, }: DepthProps): React__default.JSX.Element;
24
+
25
+ interface DepthLevelProps {
26
+ label: string;
27
+ children: React__default.ReactNode;
28
+ [key: string]: any;
29
+ }
30
+ /**
31
+ * DepthLevel acts as a slot component to hold a level's label and prose snapshot.
32
+ *
33
+ * - Inside <Depth> (after mounting/hydration), it is parsed and not rendered directly.
34
+ * - Outside <Depth> (or during SSR / before hydration), it falls back to rendering
35
+ * its children wrapped in a container with a data-depth-level attribute.
36
+ */
37
+ declare function DepthLevel({ label, children, ...props }: DepthLevelProps): React__default.JSX.Element;
38
+ declare namespace DepthLevel {
39
+ var unfoldType: string;
40
+ }
41
+
42
+ interface DepthCodeProps extends React__default.HTMLAttributes<HTMLPreElement> {
43
+ lang?: string;
44
+ label?: string;
45
+ children: string;
46
+ }
47
+ declare function DepthCode({ lang, label, children, ...rest }: DepthCodeProps): React__default.JSX.Element;
48
+ declare namespace DepthCode {
49
+ var unfoldType: string;
50
+ }
51
+
52
+ interface DepthPaneProps {
53
+ show: "both" | "prose" | "code";
54
+ proseDiff: SentenceDiff[];
55
+ codeDiff: CodeLineDiff[];
56
+ justEntered: boolean;
57
+ animate: boolean;
58
+ codeLang?: string;
59
+ codeText?: string;
60
+ highlighter?: ShikiHighlighter;
61
+ highlight?: boolean;
62
+ }
63
+ declare function DepthPane({ show, proseDiff, codeDiff, justEntered, animate, codeLang, codeText, highlighter, highlight, }: DepthPaneProps): React.JSX.Element;
64
+
65
+ export { CodeLineDiff, Depth, DepthCode, type DepthCodeProps, DepthLevel, type DepthLevelProps, DepthPane, type DepthProps, SentenceDiff };
package/dist/index.js ADDED
@@ -0,0 +1,378 @@
1
+ import {
2
+ codeDiff,
3
+ sentenceDiff,
4
+ tokenize,
5
+ tokenizeCodeLine,
6
+ tokenizeLines
7
+ } from "./chunk-OX5XKYQT.js";
8
+
9
+ // src/components/Depth.tsx
10
+ import React, { useState, useEffect, useMemo, useRef } from "react";
11
+
12
+ // src/context/DepthContext.tsx
13
+ import { createContext } from "react";
14
+ var DepthContext = createContext(null);
15
+
16
+ // src/components/DepthLevel.tsx
17
+ import { jsx } from "react/jsx-runtime";
18
+ function DepthLevel({ label, children, ...props }) {
19
+ return /* @__PURE__ */ jsx("div", { "data-depth-level": true, ...props, children });
20
+ }
21
+ DepthLevel.unfoldType = "level";
22
+
23
+ // src/components/DepthCode.tsx
24
+ import { jsx as jsx2 } from "react/jsx-runtime";
25
+ function DepthCode({ lang, label, children, ...rest }) {
26
+ return /* @__PURE__ */ jsx2("pre", { "data-lang": lang, ...rest, children: /* @__PURE__ */ jsx2("code", { children }) });
27
+ }
28
+ DepthCode.unfoldType = "code";
29
+
30
+ // src/components/DepthPane.tsx
31
+ import { useSyncExternalStore } from "react";
32
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
33
+ var noopSubscribe = () => () => {
34
+ };
35
+ function DepthPane({
36
+ show,
37
+ proseDiff,
38
+ codeDiff: codeDiff2,
39
+ justEntered,
40
+ animate,
41
+ codeLang,
42
+ codeText,
43
+ highlighter,
44
+ highlight = true
45
+ }) {
46
+ const showProse = show === "both" || show === "prose";
47
+ const showCode = show === "both" || show === "code";
48
+ const enterState = animate && justEntered ? "true" : void 0;
49
+ const getStatus = (itemKind) => {
50
+ if (!highlight || itemKind === "equal") return "equal";
51
+ return "added";
52
+ };
53
+ useSyncExternalStore(
54
+ highlighter?.subscribe ?? noopSubscribe,
55
+ () => highlighter?.getSnapshot() ?? 0,
56
+ () => highlighter?.getSnapshot() ?? 0
57
+ );
58
+ let finalCodeDiff = codeDiff2;
59
+ if (highlighter && codeText !== void 0 && codeLang !== void 0) {
60
+ const highlighted = highlighter.highlight(codeText, codeLang, codeDiff2);
61
+ if (highlighted) {
62
+ finalCodeDiff = highlighted;
63
+ }
64
+ }
65
+ return /* @__PURE__ */ jsxs("div", { "data-unfold-panes": true, children: [
66
+ showProse && /* @__PURE__ */ jsx3("div", { "data-unfold-pane": "prose", "aria-live": "polite", children: proseDiff.filter((item) => item.kind !== "removed").map((item, index) => {
67
+ const status = getStatus(item.kind);
68
+ const sentenceText = item.kind === "modified" ? item.after : item.sentence;
69
+ return /* @__PURE__ */ jsxs(
70
+ "span",
71
+ {
72
+ "data-sentence": status,
73
+ "data-enter": status === "added" ? enterState : void 0,
74
+ children: [
75
+ sentenceText,
76
+ " "
77
+ ]
78
+ },
79
+ `prose-${index}`
80
+ );
81
+ }) }),
82
+ showCode && /* @__PURE__ */ jsx3("div", { "data-unfold-pane": "code", "aria-live": "polite", children: /* @__PURE__ */ jsx3("pre", { "data-lang": codeLang, children: /* @__PURE__ */ jsx3("code", { children: finalCodeDiff.map((lineDiff, lineIndex) => {
83
+ const lineText = lineDiff.tokens ? lineDiff.tokens.map((t) => t.text).join("") : lineDiff.line || "";
84
+ const isWhitespaceOnly = lineText.trim() === "";
85
+ const lineStatus = isWhitespaceOnly ? "equal" : getStatus(lineDiff.kind);
86
+ if (lineDiff.kind === "equal" && !lineDiff.tokens) {
87
+ return /* @__PURE__ */ jsxs("span", { "data-code-line": "equal", children: [
88
+ lineDiff.line,
89
+ "\n"
90
+ ] }, `line-${lineIndex}`);
91
+ }
92
+ return /* @__PURE__ */ jsxs(
93
+ "span",
94
+ {
95
+ "data-code-line": lineStatus,
96
+ "data-enter": lineStatus === "added" ? enterState : void 0,
97
+ children: [
98
+ lineDiff.tokens?.map((token, tokenIndex) => {
99
+ const tokenStatus = getStatus(token.kind);
100
+ return /* @__PURE__ */ jsx3(
101
+ "span",
102
+ {
103
+ "data-code-token": isWhitespaceOnly ? "equal" : tokenStatus,
104
+ style: token.color ? { color: token.color } : void 0,
105
+ children: token.text
106
+ },
107
+ `token-${tokenIndex}`
108
+ );
109
+ }),
110
+ "\n"
111
+ ]
112
+ },
113
+ `line-${lineIndex}`
114
+ );
115
+ }) }) }) })
116
+ ] });
117
+ }
118
+
119
+ // src/components/controls/Controls.tsx
120
+ import { useContext } from "react";
121
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
122
+ var BUTTON_LABELS = {
123
+ text: { prev: "Prev", next: "Next" },
124
+ arrow: { prev: "\u2190 Prev", next: "Next \u2192" },
125
+ chevron: { prev: "\u2039 Prev", next: "Next \u203A" },
126
+ minimal: { prev: "\u2039", next: "\u203A" }
127
+ };
128
+ function Controls({ buttonVariant, indicators }) {
129
+ const context = useContext(DepthContext);
130
+ if (!context) return null;
131
+ const { selectedIndex, totalSteps, advance, back, goTo, labels } = context;
132
+ const isFirst = selectedIndex === 0;
133
+ const isLast = selectedIndex === totalSteps - 1;
134
+ const btnText = BUTTON_LABELS[buttonVariant] || BUTTON_LABELS.text;
135
+ return /* @__PURE__ */ jsxs2("div", { "data-unfold-controls": true, "data-button-variant": buttonVariant, children: [
136
+ /* @__PURE__ */ jsx4(
137
+ "button",
138
+ {
139
+ "data-unfold-prev": true,
140
+ "aria-disabled": isFirst ? "true" : "false",
141
+ "aria-label": !isFirst ? `Go back to step: ${labels[selectedIndex - 1]}` : void 0,
142
+ onClick: back,
143
+ children: btnText.prev
144
+ }
145
+ ),
146
+ indicators && /* @__PURE__ */ jsx4("div", { "data-unfold-indicators": true, role: "tablist", children: Array.from({ length: totalSteps }).map((_, i) => /* @__PURE__ */ jsx4(
147
+ "button",
148
+ {
149
+ role: "tab",
150
+ "aria-selected": i === selectedIndex,
151
+ "data-unfold-dot": true,
152
+ "data-active": i === selectedIndex ? "true" : void 0,
153
+ "aria-label": `Go to step: ${labels[i] || i + 1}`,
154
+ onClick: () => goTo(i)
155
+ },
156
+ i
157
+ )) }),
158
+ /* @__PURE__ */ jsx4(
159
+ "button",
160
+ {
161
+ "data-unfold-next": true,
162
+ "aria-disabled": isLast ? "true" : "false",
163
+ "aria-label": !isLast ? `Advance to step: ${labels[selectedIndex + 1]}` : void 0,
164
+ onClick: advance,
165
+ children: btnText.next
166
+ }
167
+ )
168
+ ] });
169
+ }
170
+
171
+ // src/components/Depth.tsx
172
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
173
+ function getTextContent(node) {
174
+ if (node === null || node === void 0) {
175
+ return "";
176
+ }
177
+ if (typeof node === "string" || typeof node === "number") {
178
+ return String(node);
179
+ }
180
+ if (Array.isArray(node)) {
181
+ return node.map(getTextContent).join("");
182
+ }
183
+ if (React.isValidElement(node)) {
184
+ return getTextContent(node.props.children);
185
+ }
186
+ return "";
187
+ }
188
+ function Depth({
189
+ selectedIndex,
190
+ defaultIndex,
191
+ onChange,
192
+ orientation = "horizontal",
193
+ ratio = 0.5,
194
+ show = "both",
195
+ indicators = false,
196
+ buttonVariant = "text",
197
+ animate = true,
198
+ highlight = true,
199
+ highlighter,
200
+ children
201
+ }) {
202
+ const isControlled = selectedIndex !== void 0 && onChange !== void 0;
203
+ const [internalIndex, setInternalIndex] = useState(defaultIndex ?? 0);
204
+ const currentIndex = isControlled ? selectedIndex : internalIndex;
205
+ const [isMounted, setIsMounted] = useState(false);
206
+ useEffect(() => {
207
+ setIsMounted(true);
208
+ }, []);
209
+ const parsedChildren = useMemo(() => {
210
+ const proseLevels = [];
211
+ const codeLevels = [];
212
+ React.Children.forEach(children, (child) => {
213
+ if (React.isValidElement(child)) {
214
+ const type = child.type?.unfoldType || child.type;
215
+ if (type === "level" || type === DepthLevel) {
216
+ proseLevels.push({
217
+ label: child.props.label || "",
218
+ text: getTextContent(child.props.children)
219
+ });
220
+ } else if (type === "code" || type === DepthCode) {
221
+ codeLevels.push({
222
+ lang: child.props.lang || "",
223
+ label: child.props.label || "",
224
+ text: getTextContent(child.props.children)
225
+ });
226
+ }
227
+ }
228
+ });
229
+ return { proseLevels, codeLevels };
230
+ }, [children]);
231
+ const proseDiffTable = useMemo(() => {
232
+ const table = [];
233
+ if (parsedChildren.proseLevels.length > 0) {
234
+ table.push(sentenceDiff("", parsedChildren.proseLevels[0].text));
235
+ for (let i = 1; i < parsedChildren.proseLevels.length; i++) {
236
+ table.push(sentenceDiff(parsedChildren.proseLevels[i - 1].text, parsedChildren.proseLevels[i].text));
237
+ }
238
+ }
239
+ return table;
240
+ }, [parsedChildren.proseLevels]);
241
+ const codeDiffTable = useMemo(() => {
242
+ const table = [];
243
+ if (parsedChildren.codeLevels.length > 0) {
244
+ table.push(codeDiff("", parsedChildren.codeLevels[0].text));
245
+ for (let i = 1; i < parsedChildren.codeLevels.length; i++) {
246
+ table.push(codeDiff(parsedChildren.codeLevels[i - 1].text, parsedChildren.codeLevels[i].text));
247
+ }
248
+ }
249
+ return table;
250
+ }, [parsedChildren.codeLevels]);
251
+ const [justEntered, setJustEntered] = useState(false);
252
+ const prevIndexRef = useRef(null);
253
+ useEffect(() => {
254
+ if (prevIndexRef.current !== null && currentIndex !== prevIndexRef.current) {
255
+ setJustEntered(true);
256
+ }
257
+ prevIndexRef.current = currentIndex;
258
+ }, [currentIndex]);
259
+ useEffect(() => {
260
+ if (justEntered) {
261
+ const animFrameId = requestAnimationFrame(() => {
262
+ setJustEntered(false);
263
+ });
264
+ return () => cancelAnimationFrame(animFrameId);
265
+ }
266
+ }, [justEntered]);
267
+ const totalSteps = Math.max(parsedChildren.proseLevels.length, parsedChildren.codeLevels.length, 1);
268
+ const goTo = (index) => {
269
+ if (index >= 0 && index < totalSteps) {
270
+ if (!isControlled) {
271
+ setInternalIndex(index);
272
+ }
273
+ onChange?.(index);
274
+ }
275
+ };
276
+ const handleBack = () => goTo(currentIndex - 1);
277
+ const handleNext = () => goTo(currentIndex + 1);
278
+ const maxLabels = Math.max(parsedChildren.proseLevels.length, parsedChildren.codeLevels.length);
279
+ const labels = Array.from({ length: maxLabels }).map((_, i) => {
280
+ return parsedChildren.proseLevels[i]?.label || parsedChildren.codeLevels[i]?.label || `Step ${i + 1}`;
281
+ });
282
+ const contextValue = {
283
+ selectedIndex: currentIndex,
284
+ totalSteps,
285
+ labels,
286
+ show,
287
+ orientation,
288
+ goTo,
289
+ advance: handleNext,
290
+ back: handleBack
291
+ };
292
+ if (!isMounted) {
293
+ const defaultLastIndex = totalSteps - 1;
294
+ let proseCount = 0;
295
+ let codeCount = 0;
296
+ const maxProse = parsedChildren.proseLevels.length;
297
+ const maxCode = parsedChildren.codeLevels.length;
298
+ return /* @__PURE__ */ jsx5(DepthContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx5(
299
+ "div",
300
+ {
301
+ "data-unfold-root": true,
302
+ "data-orientation": orientation,
303
+ "data-show": show,
304
+ style: { "--unfold-ratio": ratio },
305
+ "data-level": defaultLastIndex,
306
+ "data-total-levels": totalSteps,
307
+ role: "region",
308
+ "aria-label": labels[defaultLastIndex],
309
+ children: React.Children.map(children, (child) => {
310
+ if (React.isValidElement(child)) {
311
+ const type = child.type?.unfoldType || child.type;
312
+ if (type === "level" || type === DepthLevel) {
313
+ const isDeepest = proseCount === Math.max(0, maxProse - 1);
314
+ proseCount++;
315
+ return React.cloneElement(child, {
316
+ "data-unfold-active": isDeepest ? "true" : void 0
317
+ });
318
+ } else if (type === "code" || type === DepthCode) {
319
+ const isDeepest = codeCount === Math.max(0, maxCode - 1);
320
+ codeCount++;
321
+ return React.cloneElement(child, {
322
+ "data-unfold-active": isDeepest ? "true" : void 0
323
+ });
324
+ }
325
+ }
326
+ return child;
327
+ })
328
+ }
329
+ ) });
330
+ }
331
+ const clampedProseIndex = Math.min(currentIndex, proseDiffTable.length - 1);
332
+ const clampedCodeIndex = Math.min(currentIndex, codeDiffTable.length - 1);
333
+ const currentProseDiff = clampedProseIndex >= 0 ? proseDiffTable[clampedProseIndex] : [];
334
+ const currentCodeDiff = clampedCodeIndex >= 0 ? codeDiffTable[clampedCodeIndex] : [];
335
+ const currentCodeLang = clampedCodeIndex >= 0 ? parsedChildren.codeLevels[clampedCodeIndex]?.lang : void 0;
336
+ const currentCodeText = clampedCodeIndex >= 0 ? parsedChildren.codeLevels[clampedCodeIndex]?.text : void 0;
337
+ return /* @__PURE__ */ jsx5(DepthContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs3(
338
+ "div",
339
+ {
340
+ "data-unfold-root": true,
341
+ "data-orientation": orientation,
342
+ "data-show": show,
343
+ style: { "--unfold-ratio": ratio },
344
+ "data-level": currentIndex,
345
+ "data-total-levels": totalSteps,
346
+ role: "region",
347
+ "aria-label": labels[currentIndex],
348
+ children: [
349
+ /* @__PURE__ */ jsx5(
350
+ DepthPane,
351
+ {
352
+ show,
353
+ proseDiff: currentProseDiff,
354
+ codeDiff: currentCodeDiff,
355
+ justEntered,
356
+ animate,
357
+ codeLang: currentCodeLang,
358
+ codeText: currentCodeText,
359
+ highlighter,
360
+ highlight
361
+ }
362
+ ),
363
+ /* @__PURE__ */ jsx5(Controls, { buttonVariant, indicators })
364
+ ]
365
+ }
366
+ ) });
367
+ }
368
+ export {
369
+ Depth,
370
+ DepthCode,
371
+ DepthLevel,
372
+ DepthPane,
373
+ codeDiff,
374
+ sentenceDiff,
375
+ tokenize,
376
+ tokenizeCodeLine,
377
+ tokenizeLines
378
+ };
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/shiki/index.ts
21
+ var shiki_exports = {};
22
+ __export(shiki_exports, {
23
+ createShikiHighlighter: () => createShikiHighlighter
24
+ });
25
+ module.exports = __toCommonJS(shiki_exports);
26
+ var import_shiki = require("shiki");
27
+ function mergeTokens(diffTokens, syntaxTokens) {
28
+ const chars = [];
29
+ for (const dt of diffTokens) {
30
+ for (const char of dt.text) {
31
+ chars.push({ char, kind: dt.kind });
32
+ }
33
+ }
34
+ let charIdx = 0;
35
+ for (const st of syntaxTokens) {
36
+ const len = st.content.length;
37
+ for (let i = 0; i < len; i++) {
38
+ if (charIdx < chars.length) {
39
+ chars[charIdx].color = st.color;
40
+ }
41
+ charIdx++;
42
+ }
43
+ }
44
+ const merged = [];
45
+ if (chars.length === 0) return merged;
46
+ let currentKind = chars[0].kind;
47
+ let currentColor = chars[0].color;
48
+ let currentText = chars[0].char;
49
+ for (let i = 1; i < chars.length; i++) {
50
+ const c = chars[i];
51
+ if (c.kind === currentKind && c.color === currentColor) {
52
+ currentText += c.char;
53
+ } else {
54
+ merged.push({ kind: currentKind, color: currentColor, text: currentText });
55
+ currentKind = c.kind;
56
+ currentColor = c.color;
57
+ currentText = c.char;
58
+ }
59
+ }
60
+ merged.push({ kind: currentKind, color: currentColor, text: currentText });
61
+ return merged;
62
+ }
63
+ function createShikiHighlighter(options = {}) {
64
+ let highlighter = null;
65
+ let isReady = false;
66
+ let tick = 0;
67
+ const listeners = /* @__PURE__ */ new Set();
68
+ const notify = () => {
69
+ tick++;
70
+ listeners.forEach((fn) => fn());
71
+ };
72
+ const defaultTheme = options.theme || "vitesse-dark";
73
+ const defaultLangs = options.langs || ["javascript", "typescript", "jsx", "tsx", "css", "html", "json", "markdown"];
74
+ (0, import_shiki.getHighlighter)({
75
+ themes: [defaultTheme],
76
+ langs: defaultLangs
77
+ }).then((h) => {
78
+ highlighter = h;
79
+ isReady = true;
80
+ notify();
81
+ }).catch((err) => {
82
+ console.error("[unfold-mdx] Failed to initialize Shiki highlighter:", err);
83
+ });
84
+ return {
85
+ subscribe: (onStoreChange) => {
86
+ listeners.add(onStoreChange);
87
+ return () => {
88
+ listeners.delete(onStoreChange);
89
+ };
90
+ },
91
+ getSnapshot: () => tick,
92
+ highlight: (code, lang, diffLines) => {
93
+ if (!isReady || !highlighter) {
94
+ return void 0;
95
+ }
96
+ let syntaxLines;
97
+ try {
98
+ syntaxLines = highlighter.codeToTokensBase(code, {
99
+ lang,
100
+ theme: defaultTheme
101
+ });
102
+ } catch (e) {
103
+ return void 0;
104
+ }
105
+ return diffLines.map((lineDiff, i) => {
106
+ const syntaxTokens = syntaxLines[i] || [];
107
+ let diffTokens;
108
+ if (lineDiff.kind === "equal" && !lineDiff.tokens) {
109
+ diffTokens = [{ kind: "equal", text: lineDiff.line }];
110
+ } else {
111
+ diffTokens = lineDiff.tokens || [];
112
+ }
113
+ const mergedTokens = mergeTokens(diffTokens, syntaxTokens);
114
+ return {
115
+ ...lineDiff,
116
+ tokens: mergedTokens
117
+ };
118
+ });
119
+ }
120
+ };
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ createShikiHighlighter
125
+ });
@@ -0,0 +1,14 @@
1
+ import { C as CodeLineDiff } from '../codeDiff-BVwZfdt9.cjs';
2
+
3
+ interface ShikiAdapterOptions {
4
+ theme?: string;
5
+ langs?: string[];
6
+ }
7
+ interface ShikiHighlighter {
8
+ subscribe: (onStoreChange: () => void) => () => void;
9
+ getSnapshot: () => number;
10
+ highlight: (code: string, lang: string, diffLines: CodeLineDiff[]) => CodeLineDiff[] | undefined;
11
+ }
12
+ declare function createShikiHighlighter(options?: ShikiAdapterOptions): ShikiHighlighter;
13
+
14
+ export { type ShikiAdapterOptions, type ShikiHighlighter, createShikiHighlighter };
@@ -0,0 +1,14 @@
1
+ import { C as CodeLineDiff } from '../codeDiff-BVwZfdt9.js';
2
+
3
+ interface ShikiAdapterOptions {
4
+ theme?: string;
5
+ langs?: string[];
6
+ }
7
+ interface ShikiHighlighter {
8
+ subscribe: (onStoreChange: () => void) => () => void;
9
+ getSnapshot: () => number;
10
+ highlight: (code: string, lang: string, diffLines: CodeLineDiff[]) => CodeLineDiff[] | undefined;
11
+ }
12
+ declare function createShikiHighlighter(options?: ShikiAdapterOptions): ShikiHighlighter;
13
+
14
+ export { type ShikiAdapterOptions, type ShikiHighlighter, createShikiHighlighter };
@@ -0,0 +1,100 @@
1
+ // src/shiki/index.ts
2
+ import { getHighlighter } from "shiki";
3
+ function mergeTokens(diffTokens, syntaxTokens) {
4
+ const chars = [];
5
+ for (const dt of diffTokens) {
6
+ for (const char of dt.text) {
7
+ chars.push({ char, kind: dt.kind });
8
+ }
9
+ }
10
+ let charIdx = 0;
11
+ for (const st of syntaxTokens) {
12
+ const len = st.content.length;
13
+ for (let i = 0; i < len; i++) {
14
+ if (charIdx < chars.length) {
15
+ chars[charIdx].color = st.color;
16
+ }
17
+ charIdx++;
18
+ }
19
+ }
20
+ const merged = [];
21
+ if (chars.length === 0) return merged;
22
+ let currentKind = chars[0].kind;
23
+ let currentColor = chars[0].color;
24
+ let currentText = chars[0].char;
25
+ for (let i = 1; i < chars.length; i++) {
26
+ const c = chars[i];
27
+ if (c.kind === currentKind && c.color === currentColor) {
28
+ currentText += c.char;
29
+ } else {
30
+ merged.push({ kind: currentKind, color: currentColor, text: currentText });
31
+ currentKind = c.kind;
32
+ currentColor = c.color;
33
+ currentText = c.char;
34
+ }
35
+ }
36
+ merged.push({ kind: currentKind, color: currentColor, text: currentText });
37
+ return merged;
38
+ }
39
+ function createShikiHighlighter(options = {}) {
40
+ let highlighter = null;
41
+ let isReady = false;
42
+ let tick = 0;
43
+ const listeners = /* @__PURE__ */ new Set();
44
+ const notify = () => {
45
+ tick++;
46
+ listeners.forEach((fn) => fn());
47
+ };
48
+ const defaultTheme = options.theme || "vitesse-dark";
49
+ const defaultLangs = options.langs || ["javascript", "typescript", "jsx", "tsx", "css", "html", "json", "markdown"];
50
+ getHighlighter({
51
+ themes: [defaultTheme],
52
+ langs: defaultLangs
53
+ }).then((h) => {
54
+ highlighter = h;
55
+ isReady = true;
56
+ notify();
57
+ }).catch((err) => {
58
+ console.error("[unfold-mdx] Failed to initialize Shiki highlighter:", err);
59
+ });
60
+ return {
61
+ subscribe: (onStoreChange) => {
62
+ listeners.add(onStoreChange);
63
+ return () => {
64
+ listeners.delete(onStoreChange);
65
+ };
66
+ },
67
+ getSnapshot: () => tick,
68
+ highlight: (code, lang, diffLines) => {
69
+ if (!isReady || !highlighter) {
70
+ return void 0;
71
+ }
72
+ let syntaxLines;
73
+ try {
74
+ syntaxLines = highlighter.codeToTokensBase(code, {
75
+ lang,
76
+ theme: defaultTheme
77
+ });
78
+ } catch (e) {
79
+ return void 0;
80
+ }
81
+ return diffLines.map((lineDiff, i) => {
82
+ const syntaxTokens = syntaxLines[i] || [];
83
+ let diffTokens;
84
+ if (lineDiff.kind === "equal" && !lineDiff.tokens) {
85
+ diffTokens = [{ kind: "equal", text: lineDiff.line }];
86
+ } else {
87
+ diffTokens = lineDiff.tokens || [];
88
+ }
89
+ const mergedTokens = mergeTokens(diffTokens, syntaxTokens);
90
+ return {
91
+ ...lineDiff,
92
+ tokens: mergedTokens
93
+ };
94
+ });
95
+ }
96
+ };
97
+ }
98
+ export {
99
+ createShikiHighlighter
100
+ };