#!/usr/bin/env node
/**
* Generate SVG examples and README content for AbacusReact
*
* This script creates actual SVG files using react-dom/server and
* generates a balanced README with usage examples.
*/
const fs = require('fs').promises;
const path = require('path');
const React = require('react');
const { renderToStaticMarkup } = require('react-dom/server');
// Setup comprehensive DOM globals for React Spring and dependencies
const { JSDOM } = require('jsdom');
if (typeof global.window === 'undefined') {
const dom = new JSDOM('
', {
url: 'http://localhost',
pretendToBeVisual: true,
resources: 'usable'
});
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.HTMLElement = dom.window.HTMLElement;
global.SVGElement = dom.window.SVGElement;
global.Element = dom.window.Element;
global.requestAnimationFrame = dom.window.requestAnimationFrame || function(cb) { return setTimeout(cb, 16); };
global.cancelAnimationFrame = dom.window.cancelAnimationFrame || function(id) { return clearTimeout(id); };
// Add customElements for number-flow compatibility
global.customElements = {
define: function() {},
get: function() { return undefined; },
whenDefined: function() { return Promise.resolve(); }
};
// Add ResizeObserver mock
global.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
// Mock React Spring to return static components but preserve all SVG elements
const createAnimatedComponent = (tag) => {
// Return a React component that forwards props to the base element
return React.forwardRef((props, ref) => {
return React.createElement(tag, { ...props, ref });
});
};
const mockAnimated = {
div: createAnimatedComponent('div'),
svg: createAnimatedComponent('svg'),
g: createAnimatedComponent('g'),
circle: createAnimatedComponent('circle'),
rect: createAnimatedComponent('rect'),
path: createAnimatedComponent('path'),
text: createAnimatedComponent('text'),
polygon: createAnimatedComponent('polygon'),
line: createAnimatedComponent('line'),
foreignObject: createAnimatedComponent('foreignObject')
};
// Mock @react-spring/web with better stubs
require.cache[require.resolve('@react-spring/web')] = {
exports: {
useSpring: () => [{ x: 0, y: 0 }, { start: () => {}, set: () => {} }],
useSpringValue: () => ({ start: () => {}, get: () => 0, to: () => {} }),
animated: mockAnimated,
config: { default: {}, slow: {}, wobbly: {}, stiff: {} },
to: (springs, fn) => fn ? fn(springs) : springs
}
};
// Mock @use-gesture/react with proper signatures
require.cache[require.resolve('@use-gesture/react')] = {
exports: {
useDrag: () => () => ({}),
useGesture: () => () => ({})
}
};
// Mock @number-flow/react to return the actual value properly
require.cache[require.resolve('@number-flow/react')] = {
exports: {
__esModule: true,
default: ({ children, value, format, style, ...props }) => {
// Use value if provided, otherwise fallback to children
const displayValue = value !== undefined ? value : children;
// Return a simple span element that will work in foreignObject context
return React.createElement('span', {
style: {
fontSize: '12px',
color: '#333',
fontFamily: 'monospace',
fontWeight: 'bold',
...style
}
}, String(displayValue));
}
}
};
}
// Import our component after setting up globals - use source directly
const { AbacusReact } = require('./src/AbacusReact.tsx');
// Key example configurations for different use cases
const examples = [
{
name: 'basic-usage',
title: 'Basic Usage',
description: 'Simple abacus showing a number',
code: ``,
props: {
value: 123,
columns: 3,
showNumbers: true,
scaleFactor: 1.0,
animated: false // Disable animations for static SVG
}
},
{
name: 'interactive',
title: 'Interactive Mode',
description: 'Clickable abacus with animations',
code: ` console.log('New value:', newValue),
onBeadClick: (event) => console.log('Bead clicked:', event)
}}
/>`,
props: {
value: 456,
columns: 3,
interactive: true,
animated: false, // Disable animations for static SVG
showNumbers: true
}
},
{
name: 'custom-styling',
title: 'Custom Styling',
description: 'Personalized colors and highlights',
code: ``,
props: {
value: 789,
columns: 3,
colorScheme: 'place-value',
beadShape: 'circle',
animated: false, // Disable animations for static SVG
customStyles: {
heavenBeads: { fill: '#ff6b35' },
earthBeads: { fill: '#3498db' },
numerals: { color: '#2c3e50', fontWeight: 'bold' }
},
highlightBeads: [
{ columnIndex: 1, beadType: 'heaven' }
]
}
},
{
name: 'tutorial-mode',
title: 'Tutorial System',
description: 'Educational guidance with tooltips',
code: `Click this bead!,
offset: { x: 0, y: -30 }
}]}
callbacks={{
onBeadClick: (event) => {
if (event.columnIndex === 0 && event.beadType === 'earth' && event.position === 1) {
console.log('Correct!');
}
}
}}
/>`,
props: {
value: 42,
columns: 2,
interactive: true,
animated: false, // Disable animations for static SVG
showNumbers: true
}
}
];
/**
* Generate SVG examples using react-dom/server
*/
async function generateSVGExamples() {
if (!AbacusReact) {
console.log('šØ Building package first...');
const { execSync } = require('child_process');
try {
execSync('pnpm run build', { stdio: 'inherit' });
AbacusReact = require('./dist/index.cjs.js').AbacusReact;
} catch (error) {
console.error('ā Failed to build package:', error.message);
throw error;
}
}
console.log('šØ Generating SVG examples...');
// Create examples directory
const examplesDir = path.join(__dirname, 'examples');
try {
await fs.mkdir(examplesDir, { recursive: true });
} catch (error) {
// Directory might already exist
}
const generatedFiles = [];
for (const example of examples) {
try {
console.log(`š Generating ${example.name}.svg...`);
// Create React element with the example props
const element = React.createElement(AbacusReact, example.props);
// Render using react-dom/server to show the actual component
const fullMarkup = renderToStaticMarkup(element);
// Extract just the SVG content from the rendered HTML
const svgMatch = fullMarkup.match(/