@mihirsarya/manim-scroll-next 0.1.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,293 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.extractAnimations = extractAnimations;
40
+ exports.extractAnimationsFromFile = extractAnimationsFromFile;
41
+ const fs = __importStar(require("fs"));
42
+ const parser_1 = require("@babel/parser");
43
+ const traverse_1 = __importDefault(require("@babel/traverse"));
44
+ const t = __importStar(require("@babel/types"));
45
+ const glob_1 = require("glob");
46
+ /**
47
+ * Extract the text content from JSX children.
48
+ * Handles string literals and JSX text nodes.
49
+ */
50
+ function extractChildrenText(children) {
51
+ const textParts = [];
52
+ for (const child of children) {
53
+ if (t.isJSXText(child)) {
54
+ const trimmed = child.value.trim();
55
+ if (trimmed) {
56
+ textParts.push(trimmed);
57
+ }
58
+ }
59
+ else if (t.isStringLiteral(child)) {
60
+ textParts.push(child.value);
61
+ }
62
+ else if (t.isJSXExpressionContainer(child)) {
63
+ if (t.isStringLiteral(child.expression)) {
64
+ textParts.push(child.expression.value);
65
+ }
66
+ else if (t.isTemplateLiteral(child.expression)) {
67
+ // Handle simple template literals without expressions
68
+ const parts = child.expression.quasis.map((q) => q.value.cooked || q.value.raw);
69
+ textParts.push(parts.join(""));
70
+ }
71
+ }
72
+ }
73
+ return textParts.length > 0 ? textParts.join(" ") : null;
74
+ }
75
+ /**
76
+ * Extract a literal value from a JSX attribute value.
77
+ */
78
+ function extractAttributeValue(value) {
79
+ if (value === null) {
80
+ return true; // Boolean attribute like `disabled`
81
+ }
82
+ if (t.isStringLiteral(value)) {
83
+ return value.value;
84
+ }
85
+ if (t.isJSXExpressionContainer(value)) {
86
+ const expr = value.expression;
87
+ if (t.isStringLiteral(expr)) {
88
+ return expr.value;
89
+ }
90
+ if (t.isNumericLiteral(expr)) {
91
+ return expr.value;
92
+ }
93
+ if (t.isBooleanLiteral(expr)) {
94
+ return expr.value;
95
+ }
96
+ if (t.isNullLiteral(expr)) {
97
+ return null;
98
+ }
99
+ if (t.isObjectExpression(expr)) {
100
+ return extractObjectExpression(expr);
101
+ }
102
+ if (t.isArrayExpression(expr)) {
103
+ return extractArrayExpression(expr);
104
+ }
105
+ }
106
+ // Cannot statically extract, return undefined
107
+ return undefined;
108
+ }
109
+ /**
110
+ * Extract an object expression into a plain object.
111
+ */
112
+ function extractObjectExpression(expr) {
113
+ const result = {};
114
+ for (const prop of expr.properties) {
115
+ if (t.isObjectProperty(prop)) {
116
+ let key = null;
117
+ if (t.isIdentifier(prop.key)) {
118
+ key = prop.key.name;
119
+ }
120
+ else if (t.isStringLiteral(prop.key)) {
121
+ key = prop.key.value;
122
+ }
123
+ if (key !== null) {
124
+ if (t.isStringLiteral(prop.value)) {
125
+ result[key] = prop.value.value;
126
+ }
127
+ else if (t.isNumericLiteral(prop.value)) {
128
+ result[key] = prop.value.value;
129
+ }
130
+ else if (t.isBooleanLiteral(prop.value)) {
131
+ result[key] = prop.value.value;
132
+ }
133
+ else if (t.isNullLiteral(prop.value)) {
134
+ result[key] = null;
135
+ }
136
+ else if (t.isObjectExpression(prop.value)) {
137
+ result[key] = extractObjectExpression(prop.value);
138
+ }
139
+ else if (t.isArrayExpression(prop.value)) {
140
+ result[key] = extractArrayExpression(prop.value);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ return result;
146
+ }
147
+ /**
148
+ * Extract an array expression into a plain array.
149
+ */
150
+ function extractArrayExpression(expr) {
151
+ const result = [];
152
+ for (const element of expr.elements) {
153
+ if (element === null) {
154
+ result.push(null);
155
+ }
156
+ else if (t.isStringLiteral(element)) {
157
+ result.push(element.value);
158
+ }
159
+ else if (t.isNumericLiteral(element)) {
160
+ result.push(element.value);
161
+ }
162
+ else if (t.isBooleanLiteral(element)) {
163
+ result.push(element.value);
164
+ }
165
+ else if (t.isNullLiteral(element)) {
166
+ result.push(null);
167
+ }
168
+ else if (t.isObjectExpression(element)) {
169
+ result.push(extractObjectExpression(element));
170
+ }
171
+ else if (t.isArrayExpression(element)) {
172
+ result.push(extractArrayExpression(element));
173
+ }
174
+ }
175
+ return result;
176
+ }
177
+ /**
178
+ * Check if a JSX element is a ManimScroll component.
179
+ */
180
+ function isManimScrollComponent(node) {
181
+ if (t.isJSXIdentifier(node.name)) {
182
+ return node.name.name === "ManimScroll";
183
+ }
184
+ if (t.isJSXMemberExpression(node.name)) {
185
+ // Handle cases like `Components.ManimScroll`
186
+ if (t.isJSXIdentifier(node.name.property)) {
187
+ return node.name.property.name === "ManimScroll";
188
+ }
189
+ }
190
+ return false;
191
+ }
192
+ /**
193
+ * Extract ManimScroll component data from a JSX element.
194
+ */
195
+ function extractManimScroll(jsxElement, filePath) {
196
+ var _a, _b;
197
+ const openingElement = jsxElement.openingElement;
198
+ if (!isManimScrollComponent(openingElement)) {
199
+ return null;
200
+ }
201
+ const props = {};
202
+ let scene = "TextScene";
203
+ // Extract attributes
204
+ for (const attr of openingElement.attributes) {
205
+ if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
206
+ const name = attr.name.name;
207
+ const value = extractAttributeValue(attr.value);
208
+ if (name === "scene" && typeof value === "string") {
209
+ scene = value;
210
+ }
211
+ else if (name === "manifestUrl") {
212
+ // Skip manifestUrl - this is for explicit mode
213
+ continue;
214
+ }
215
+ else if (value !== undefined) {
216
+ props[name] = value;
217
+ }
218
+ }
219
+ }
220
+ // Extract children as text prop
221
+ const childrenText = extractChildrenText(jsxElement.children);
222
+ if (childrenText) {
223
+ props.text = childrenText;
224
+ }
225
+ const line = (_b = (_a = openingElement.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 0;
226
+ return {
227
+ filePath,
228
+ line,
229
+ scene,
230
+ props,
231
+ };
232
+ }
233
+ /**
234
+ * Parse a source file and extract all ManimScroll components.
235
+ */
236
+ function extractFromFile(filePath) {
237
+ const source = fs.readFileSync(filePath, "utf-8");
238
+ const animations = [];
239
+ let ast;
240
+ try {
241
+ ast = (0, parser_1.parse)(source, {
242
+ sourceType: "module",
243
+ plugins: ["jsx", "typescript"],
244
+ });
245
+ }
246
+ catch {
247
+ // Skip files that cannot be parsed
248
+ return [];
249
+ }
250
+ (0, traverse_1.default)(ast, {
251
+ JSXElement(nodePath) {
252
+ const extracted = extractManimScroll(nodePath.node, filePath);
253
+ if (extracted) {
254
+ // Create a unique ID based on file path and line number
255
+ const relativePath = filePath.replace(/\\/g, "/");
256
+ const id = `${relativePath}:${extracted.line}`;
257
+ animations.push({
258
+ id,
259
+ ...extracted,
260
+ });
261
+ }
262
+ },
263
+ });
264
+ return animations;
265
+ }
266
+ /**
267
+ * Scan a directory for ManimScroll components and extract their configurations.
268
+ */
269
+ async function extractAnimations(options) {
270
+ const { rootDir, include = ["**/*.tsx", "**/*.jsx"], exclude = ["node_modules/**", ".next/**", "dist/**"], } = options;
271
+ const allAnimations = [];
272
+ for (const pattern of include) {
273
+ const files = await (0, glob_1.glob)(pattern, {
274
+ cwd: rootDir,
275
+ ignore: exclude,
276
+ absolute: true,
277
+ });
278
+ for (const file of files) {
279
+ const animations = extractFromFile(file);
280
+ allAnimations.push(...animations);
281
+ }
282
+ }
283
+ return allAnimations;
284
+ }
285
+ /**
286
+ * Extract animations from a single file (useful for watch mode).
287
+ */
288
+ function extractAnimationsFromFile(filePath) {
289
+ if (!fs.existsSync(filePath)) {
290
+ return [];
291
+ }
292
+ return extractFromFile(filePath);
293
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const fs = __importStar(require("fs"));
38
+ const extractor_1 = require("./extractor");
39
+ // Mock fs module
40
+ vitest_1.vi.mock("fs");
41
+ // Mock glob module
42
+ vitest_1.vi.mock("glob", () => ({
43
+ glob: vitest_1.vi.fn().mockResolvedValue([]),
44
+ }));
45
+ (0, vitest_1.describe)("extractAnimationsFromFile", () => {
46
+ (0, vitest_1.beforeEach)(() => {
47
+ vitest_1.vi.resetAllMocks();
48
+ });
49
+ (0, vitest_1.it)("should return empty array for non-existent file", () => {
50
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(false);
51
+ const result = (0, extractor_1.extractAnimationsFromFile)("/path/to/missing.tsx");
52
+ (0, vitest_1.expect)(result).toEqual([]);
53
+ });
54
+ (0, vitest_1.it)("should extract basic ManimScroll component", () => {
55
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
56
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
57
+ import { ManimScroll } from "@mihirsarya/manim-scroll";
58
+
59
+ export default function Page() {
60
+ return (
61
+ <ManimScroll fontSize={72} color="#ffffff">
62
+ Hello World
63
+ </ManimScroll>
64
+ );
65
+ }
66
+ `);
67
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
68
+ (0, vitest_1.expect)(result).toHaveLength(1);
69
+ (0, vitest_1.expect)(result[0].scene).toBe("TextScene"); // default
70
+ (0, vitest_1.expect)(result[0].props.fontSize).toBe(72);
71
+ (0, vitest_1.expect)(result[0].props.color).toBe("#ffffff");
72
+ (0, vitest_1.expect)(result[0].props.text).toBe("Hello World");
73
+ });
74
+ (0, vitest_1.it)("should extract custom scene name", () => {
75
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
76
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
77
+ <ManimScroll scene="CustomScene" value={42} />
78
+ `);
79
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
80
+ (0, vitest_1.expect)(result).toHaveLength(1);
81
+ (0, vitest_1.expect)(result[0].scene).toBe("CustomScene");
82
+ (0, vitest_1.expect)(result[0].props.value).toBe(42);
83
+ });
84
+ (0, vitest_1.it)("should extract multiple ManimScroll components", () => {
85
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
86
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
87
+ function Page() {
88
+ return (
89
+ <div>
90
+ <ManimScroll fontSize={48}>First</ManimScroll>
91
+ <ManimScroll fontSize={72}>Second</ManimScroll>
92
+ </div>
93
+ );
94
+ }
95
+ `);
96
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
97
+ (0, vitest_1.expect)(result).toHaveLength(2);
98
+ (0, vitest_1.expect)(result[0].props.text).toBe("First");
99
+ (0, vitest_1.expect)(result[1].props.text).toBe("Second");
100
+ });
101
+ (0, vitest_1.it)("should skip components with manifestUrl (explicit mode)", () => {
102
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
103
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
104
+ <ManimScroll manifestUrl="/assets/scene/manifest.json">
105
+ This should be skipped
106
+ </ManimScroll>
107
+ `);
108
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
109
+ (0, vitest_1.expect)(result).toHaveLength(1);
110
+ // manifestUrl should not be in props
111
+ (0, vitest_1.expect)(result[0].props.manifestUrl).toBeUndefined();
112
+ });
113
+ (0, vitest_1.it)("should extract boolean attributes", () => {
114
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
115
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
116
+ <ManimScroll enabled loop />
117
+ `);
118
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
119
+ (0, vitest_1.expect)(result).toHaveLength(1);
120
+ (0, vitest_1.expect)(result[0].props.enabled).toBe(true);
121
+ (0, vitest_1.expect)(result[0].props.loop).toBe(true);
122
+ });
123
+ (0, vitest_1.it)("should extract object props", () => {
124
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
125
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
126
+ <ManimScroll config={{ theme: "dark", size: 10 }} />
127
+ `);
128
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
129
+ (0, vitest_1.expect)(result).toHaveLength(1);
130
+ (0, vitest_1.expect)(result[0].props.config).toEqual({ theme: "dark", size: 10 });
131
+ });
132
+ (0, vitest_1.it)("should extract array props", () => {
133
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
134
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
135
+ <ManimScroll items={[1, 2, 3]} />
136
+ `);
137
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
138
+ (0, vitest_1.expect)(result).toHaveLength(1);
139
+ (0, vitest_1.expect)(result[0].props.items).toEqual([1, 2, 3]);
140
+ });
141
+ (0, vitest_1.it)("should extract null props", () => {
142
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
143
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
144
+ <ManimScroll value={null} />
145
+ `);
146
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
147
+ (0, vitest_1.expect)(result).toHaveLength(1);
148
+ (0, vitest_1.expect)(result[0].props.value).toBeNull();
149
+ });
150
+ (0, vitest_1.it)("should handle template literals in children", () => {
151
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
152
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
153
+ <ManimScroll>{\`Hello World\`}</ManimScroll>
154
+ `);
155
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
156
+ (0, vitest_1.expect)(result).toHaveLength(1);
157
+ (0, vitest_1.expect)(result[0].props.text).toBe("Hello World");
158
+ });
159
+ (0, vitest_1.it)("should handle JSX expression with string literal in children", () => {
160
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
161
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
162
+ <ManimScroll>{"Hello World"}</ManimScroll>
163
+ `);
164
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
165
+ (0, vitest_1.expect)(result).toHaveLength(1);
166
+ (0, vitest_1.expect)(result[0].props.text).toBe("Hello World");
167
+ });
168
+ (0, vitest_1.it)("should handle ManimScroll as member expression", () => {
169
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
170
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
171
+ import * as Components from "@mihirsarya/manim-scroll";
172
+
173
+ <Components.ManimScroll fontSize={72}>Test</Components.ManimScroll>
174
+ `);
175
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
176
+ (0, vitest_1.expect)(result).toHaveLength(1);
177
+ (0, vitest_1.expect)(result[0].scene).toBe("TextScene");
178
+ (0, vitest_1.expect)(result[0].props.fontSize).toBe(72);
179
+ });
180
+ (0, vitest_1.it)("should return empty array for files with no ManimScroll", () => {
181
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
182
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
183
+ function Page() {
184
+ return <div>No animations here</div>;
185
+ }
186
+ `);
187
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
188
+ (0, vitest_1.expect)(result).toEqual([]);
189
+ });
190
+ (0, vitest_1.it)("should return empty array for unparseable files", () => {
191
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
192
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
193
+ this is not valid javascript/typescript
194
+ <ManimScroll>Test</ManimScroll>
195
+ !!!syntax error!!!
196
+ `);
197
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
198
+ // Should return empty array, not throw
199
+ (0, vitest_1.expect)(result).toEqual([]);
200
+ });
201
+ (0, vitest_1.it)("should include file path and line number", () => {
202
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
203
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
204
+ // Line 1
205
+ // Line 2
206
+ <ManimScroll>Test</ManimScroll>
207
+ `);
208
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
209
+ (0, vitest_1.expect)(result).toHaveLength(1);
210
+ (0, vitest_1.expect)(result[0].filePath).toBe("/app/page.tsx");
211
+ (0, vitest_1.expect)(result[0].line).toBeGreaterThan(0);
212
+ (0, vitest_1.expect)(result[0].id).toContain("/app/page.tsx:");
213
+ });
214
+ (0, vitest_1.it)("should handle nested objects in props", () => {
215
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
216
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
217
+ <ManimScroll
218
+ config={{
219
+ theme: { primary: "#fff", secondary: "#000" },
220
+ sizes: [10, 20, 30]
221
+ }}
222
+ />
223
+ `);
224
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
225
+ (0, vitest_1.expect)(result).toHaveLength(1);
226
+ (0, vitest_1.expect)(result[0].props.config).toEqual({
227
+ theme: { primary: "#fff", secondary: "#000" },
228
+ sizes: [10, 20, 30],
229
+ });
230
+ });
231
+ (0, vitest_1.it)("should handle numeric string keys in objects", () => {
232
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
233
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
234
+ <ManimScroll data={{ "123": "value" }} />
235
+ `);
236
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
237
+ (0, vitest_1.expect)(result).toHaveLength(1);
238
+ (0, vitest_1.expect)(result[0].props.data).toEqual({ "123": "value" });
239
+ });
240
+ });
241
+ (0, vitest_1.describe)("extractChildrenText (via extractor)", () => {
242
+ (0, vitest_1.beforeEach)(() => {
243
+ vitest_1.vi.resetAllMocks();
244
+ });
245
+ (0, vitest_1.it)("should combine multiple JSX text nodes", () => {
246
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
247
+ // When text spans multiple lines, JSX parser treats it as a single text node
248
+ // with internal whitespace preserved (only leading/trailing trimmed)
249
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
250
+ <ManimScroll>Hello World</ManimScroll>
251
+ `);
252
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
253
+ (0, vitest_1.expect)(result).toHaveLength(1);
254
+ (0, vitest_1.expect)(result[0].props.text).toBe("Hello World");
255
+ });
256
+ (0, vitest_1.it)("should handle multiline text (preserves internal whitespace)", () => {
257
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
258
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
259
+ <ManimScroll>
260
+ Hello
261
+ World
262
+ </ManimScroll>
263
+ `);
264
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
265
+ (0, vitest_1.expect)(result).toHaveLength(1);
266
+ // Multiline JSX text preserves internal newlines after trimming
267
+ (0, vitest_1.expect)(result[0].props.text).toContain("Hello");
268
+ (0, vitest_1.expect)(result[0].props.text).toContain("World");
269
+ });
270
+ (0, vitest_1.it)("should handle whitespace-only children as no text", () => {
271
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
272
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
273
+ <ManimScroll> </ManimScroll>
274
+ `);
275
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
276
+ (0, vitest_1.expect)(result).toHaveLength(1);
277
+ (0, vitest_1.expect)(result[0].props.text).toBeUndefined();
278
+ });
279
+ (0, vitest_1.it)("should trim whitespace from text", () => {
280
+ vitest_1.vi.mocked(fs.existsSync).mockReturnValue(true);
281
+ vitest_1.vi.mocked(fs.readFileSync).mockReturnValue(`
282
+ <ManimScroll> Hello World </ManimScroll>
283
+ `);
284
+ const result = (0, extractor_1.extractAnimationsFromFile)("/app/page.tsx");
285
+ (0, vitest_1.expect)(result).toHaveLength(1);
286
+ (0, vitest_1.expect)(result[0].props.text).toBe("Hello World");
287
+ });
288
+ });
@@ -0,0 +1,55 @@
1
+ import type { NextConfig } from "next";
2
+ export interface ManimScrollConfig {
3
+ /** Path to the Python executable (default: "python3") */
4
+ pythonPath?: string;
5
+ /** Path to the render CLI script */
6
+ cliPath?: string;
7
+ /** Path to the scene templates directory */
8
+ templatesDir?: string;
9
+ /** Maximum number of parallel renders (default: CPU count - 1) */
10
+ concurrency?: number;
11
+ /** FPS for the animation (default: 30) */
12
+ fps?: number;
13
+ /** Resolution in "WIDTHxHEIGHT" format (default: "1920x1080") */
14
+ resolution?: string;
15
+ /** Manim quality preset: l, m, h, k (default: "h") */
16
+ quality?: string;
17
+ /** Output format: frames, video, both (default: "both") */
18
+ format?: string;
19
+ /** Glob patterns to include (default: ["**\/*.tsx", "**\/*.jsx"]) */
20
+ include?: string[];
21
+ /** Glob patterns to exclude (default: ["node_modules/**", ".next/**"]) */
22
+ exclude?: string[];
23
+ /** Clean up orphaned cache entries (default: true) */
24
+ cleanOrphans?: boolean;
25
+ /** Verbose logging (default: false) */
26
+ verbose?: boolean;
27
+ }
28
+ export interface NextConfigWithManimScroll extends NextConfig {
29
+ manimScroll?: ManimScrollConfig;
30
+ }
31
+ /**
32
+ * Process ManimScroll components: extract, cache, and render.
33
+ */
34
+ declare function processManimScroll(projectDir: string, config: ManimScrollConfig): Promise<void>;
35
+ /**
36
+ * Wrap a Next.js config with ManimScroll build-time processing.
37
+ *
38
+ * @example
39
+ * ```js
40
+ * // next.config.js
41
+ * const { withManimScroll } = require("@mihirsarya/manim-scroll-next");
42
+ *
43
+ * module.exports = withManimScroll({
44
+ * manimScroll: {
45
+ * pythonPath: "python3",
46
+ * quality: "h",
47
+ * },
48
+ * });
49
+ * ```
50
+ */
51
+ export declare function withManimScroll(nextConfig?: NextConfigWithManimScroll): NextConfig;
52
+ export { extractAnimations, type ExtractedAnimation } from "./extractor";
53
+ export { computePropsHash, isCached, getCacheEntry, getAnimationsToRender, writeCacheManifest, readCacheManifest, cleanOrphanedCache, } from "./cache";
54
+ export { renderAnimations, type RenderResult, type RenderOptions } from "./renderer";
55
+ export { processManimScroll };