title | slug | date | hide_table_of_contents |
---|---|---|---|
Mathfield Demo |
/mathfield/demo/ |
Last Modified |
true |
import { useEffect } from 'react'; import { convertLatexToMarkup } from 'mathlive';
export function MathfieldDemo({children}) { const INDENT = ' ';
/**
- Convert a basic type or an object into a HTML string */ function asString( depth, value, options = {} ){ options.quote ??= '"'; options.ancestors ??= [];
//
// BOOLEAN
//
if (typeof value === 'boolean') {
return {
text: `<span class="boolean">${escapeHTML(String(value))}</span>`,
itemCount: 1,
lineCount: 1,
};
}
//
// NUMBER
//
if (typeof value === 'number') {
return {
text: `<span class="number">${escapeHTML(String(value))}</span>`,
itemCount: 1,
lineCount: 1,
};
}
//
// STRING
//
if (typeof value === 'string') {
if (options.quote.length === 0) {
return {
text: escapeHTML(value),
itemCount: 1,
lineCount: value.split(/\r\n|\r|\n/).length,
};
}
return {
text: `<span class="string">${escapeHTML(
options.quote + value + options.quote
)}</span>`,
itemCount: 1,
lineCount: value.split(/\r\n|\r|\n/).length,
};
}
//
// FUNCTION
//
if (typeof value === 'function') {
let functionValue = '';
if ('toString' in value) functionValue = escapeHTML(value.toString());
else functionValue = escapeHTML(String(value));
return {
text: `<span class="function">ƒ ${functionValue}</span>`,
itemCount: 1,
lineCount: functionValue.split(/\r\n|\r|\n/).length,
};
}
//
// NULL/UNDEFINED
//
if (value === null || value === undefined) {
return {
text: `<span class="null">${escapeHTML(String(value))}</span>`,
itemCount: 1,
lineCount: 1,
};
}
// Avoid infinite recursions (e.g. `window.window`)
if (depth > 20) {
return {
text: '<span class="sep">(...)</span>',
itemCount: 1,
lineCount: 1,
};
}
//
// ARRAY
//
if (Array.isArray(value)) {
if (options.ancestors.includes(value))
return {
text: '<span class="sep">[...]</span>',
itemCount: 1,
lineCount: 1,
};
const result = [];
// To account for sparse array, we can't use map() (it skips over empty slots)
for (let i = 0; i < value.length; i++) {
if (Object.keys(value).includes(Number(i).toString())) {
result.push(
asString(depth + 1, value[i], {
ancestors: [...options.ancestors, value],
})
);
} else {
result.push({
text: '<span class="empty">empty</span>',
itemCount: 1,
lineCount: 1,
});
}
}
const itemCount = result.reduce((acc, val) => acc + val.itemCount, 0);
const lineCount = result.reduce(
(acc, val) => Math.max(acc, val.lineCount),
0
);
if (itemCount > 5 || lineCount > 1) {
return {
text:
"<span class='sep'>[</span>\n" +
INDENT.repeat(depth + 1) +
result
.map((x, i) => '<span class="index">' + i + '</span>' + x.text)
.join("<span class='sep'>, </span>\n" + INDENT.repeat(depth + 1)) +
'\n' +
INDENT.repeat(depth) +
"<span class='sep'>]</span>",
itemCount,
lineCount: 2 + result.reduce((acc, val) => acc + val.lineCount, 0),
};
}
return {
text:
"<span class='sep'>[</span>" +
result.map((x) => x.text).join("<span class='sep'>, </span>") +
"<span class='sep'>]</span>",
itemCount: Math.max(1, itemCount),
lineCount: 1,
};
}
//
// HTMLElement
//
if (value instanceof Element) {
if (options.ancestors.includes(value))
return {
text: '<span class="object">Element...</span>',
itemCount: 1,
lineCount: 1,
};
let result = `<${value.localName}`;
let lineCount = 1;
Array.from(value.attributes).forEach((x) => {
result +=
' ' + x.localName + '="' + value.getAttribute(x.localName) + '"';
});
result += '>';
if (value.innerHTML) {
let content = value.innerHTML.split('\n');
if (content.length > 4) {
content = [...content.slice(0, 5), '(...)\n'];
}
result += content.join('\n');
lineCount += content.length;
}
result += `</${value.localName}>`;
return {
text: `<span class="object">${escapeHTML(result)}</span>`,
itemCount: 1,
lineCount: lineCount,
};
}
//
// OBJECT
//
if (typeof value === 'object') {
if (options.ancestors.includes(value))
return {
text: '<span class="sep">{...}</span>',
itemCount: 1,
lineCount: 1,
};
if (value instanceof Map) {
const kv = Object.fromEntries(value);
const result = asString(depth, kv, {
ancestors: [...options.ancestors, value],
});
return { ...result, text: '<span class=object>Map</span>' + result.text };
}
if (value instanceof Set) {
const elts = Array.from(value);
const result = asString(depth, elts, {
ancestors: [...options.ancestors, value],
});
return { ...result, text: '<span class=object>Set</span>' + result.text };
}
if ('toString' in value) {
const s = value.toString();
if (s !== '[object Object]')
return {
text: escapeHTML(s),
itemCount: 1,
lineCount: 1,
};
}
let props = Object.keys(value);
Object.getOwnPropertyNames(value).forEach((prop) => {
if (!props.includes(prop)) {
props.push(prop);
}
});
props = props.filter((x) => !x.startsWith('_'));
if (props.length === 0 && typeof props.toString === 'function') {
const result = value.toString();
if (result === '[object Object]')
return {
text: '<span class="sep">{}</span>',
itemCount: 1,
lineCount: 1,
};
return {
text: result,
itemCount: 1,
lineCount: result.split(/\r\n|\r|\n/).length,
};
}
const propStrings = props.sort().map((key) => {
if (typeof value[key] === 'object' && value[key] !== null) {
let result = asString(depth + 1, value[key], {
ancestors: [...options.ancestors, value],
});
if (result.itemCount > 500) {
result = {
text: "<span class='sep'>(...)</span>",
itemCount: 1,
lineCount: 1,
};
}
return {
text: `<span class="property">${key}</span><span class='sep'>: </span>${result.text}`,
itemCount: result.itemCount,
lineCount: result.lineCount,
};
}
if (typeof value[key] === 'function') {
return {
text: `<span class="property">${key}</span><span class='sep'>: </span><span class='function'>ƒ (...)</span>`,
itemCount: 1,
lineCount: 1,
};
}
const result = asString(depth + 1, value[key], {
ancestors: [...options.ancestors, value],
});
return {
text: `<span class="property">${key}</span><span class='sep'>: </span>${result.text}`,
itemCount: result.itemCount,
lineCount: result.lineCount,
};
});
const itemCount = propStrings.reduce((acc, val) => acc + val.itemCount, 0);
const lineCount = propStrings.reduce((acc, val) => acc + val.lineCount, 0);
if (itemCount < 5) {
return {
text:
"<span class='sep'>{</span>" +
propStrings
.map((x) => x.text)
.join("</span><span class='sep'>, </span>") +
"<span class='sep'>}</span>",
itemCount,
lineCount,
};
}
return {
text:
"<span class='sep'>{</span>\n" +
INDENT.repeat(depth + 1) +
propStrings
.map((x) => x.text)
.join(
"</span><span class='sep'>,</span>\n" + INDENT.repeat(depth + 1)
) +
'\n' +
INDENT.repeat(depth) +
"<span class='sep'>}</span>",
itemCount: itemCount,
lineCount: lineCount + 2,
};
}
return { text: String(value), itemCount: 1, lineCount: 1 };
}
function escapeHTML(s) { return s .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }
const onMathfieldUpdate = (mf) => { // The ref callback is called with null when the element unmounts if (mf === null) return; const latexField = document.getElementById('latex'); if (!latexField) return; latexField.value = mf.value; const expr = mf.expression; // Output MathJSON representation of the expression document.getElementById('math-json').innerHTML = asString(0, expr.json).text;
let result = '';
MathfieldElement.computeEngine.precision = 7;
if (expr.head !== 'Equal') {
const exprN = expr.N();
if (!exprN.isSame(expr)) result = exprN.latex;
}
if (!result) {
const exprSimplify = expr.simplify();
if (!exprSimplify.isSame(expr)) result = exprSimplify.latex;
}
if (expr.head !== 'Equal') {
if (!result) {
const exprEval = expr.evaluate();
if (!exprEval.isSame(expr)) result = exprEval.latex;
}
}
if (result) {
document.getElementById('result').innerHTML = convertLatexToMarkup('= ' + result);
document.getElementById('result-section').classList.add('is-visible');
} else document.getElementById('result-section').classList.remove('is-visible');
};
const onInput = (evt) => onMathfieldUpdate(evt.target);
useEffect(() => { const latexField = document.getElementById('latex'); latexField.value = children; }, [])
// There's a bug somewhere (React?) where return <math-field/>
doesn't work
// (the JSX code produced is invalid). Calling the _jsx function directly
// works around the issue.
return _jsx('math-field', {
onInput,
ref: (node) => onMathfieldUpdate(node),
value: children
});
}
{x=\\frac{-b\\pm \\sqrt{b^2-4ac}}{2a}
}
export default function ({children}) { useEffect(() => { const platform = navigator['userAgentData']?.platform ?? navigator.platform; const isApple = /^mac/i.test(platform) || /iphone|ipod|ipad/i.test(navigator.userAgent);
// The body class gets cleared when the page is reloaded, so we need to
// set it again after a short delay.
if (isApple)
setTimeout(() => document.body.classList.add('glyphs'), 16);
// Restore the body class when the page is reloaded
return () => document.body.classList.remove('glyphs');
}, []); return <>{children}</>; }