@liqvid/katex 0.0.1 → 0.0.2

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/dist/index.js CHANGED
@@ -1,4 +1,168 @@
1
- export { KTXBlocking as KTX, KTXBlocking } from "./Blocking";
2
- export { KaTeXReady } from "./loading";
3
- export { KTXNonBlocking } from "./NonBlocking";
4
- export { RenderGroup } from "./RenderGroup";
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var react$1 = require('@liqvid/utils/react');
7
+ var liqvid = require('liqvid');
8
+ var react = require('react');
9
+ var plain = require('./plain');
10
+
11
+ /** Component for KaTeX code */
12
+ const KTX = react.forwardRef(function KTX(props, ref) {
13
+ const { obstruct = "canplay canplaythrough", reparse = false, ...attrs } = props;
14
+ const plain$1 = react.useRef();
15
+ const combined = react$1.combineRefs(plain$1, ref);
16
+ const player = liqvid.usePlayer();
17
+ react.useEffect(() => {
18
+ // obstruction
19
+ if (obstruct.match(/\bcanplay\b/)) {
20
+ player.obstruct("canplay", plain$1.current.ready);
21
+ }
22
+ if (obstruct.match("canplaythrough")) {
23
+ player.obstruct("canplaythrough", plain$1.current.ready);
24
+ }
25
+ // reparsing
26
+ if (reparse) {
27
+ plain$1.current.ready.then(() => player.reparseTree(plain$1.current.domElement));
28
+ }
29
+ }, []);
30
+ return (jsxRuntime.jsx(plain.KTX, { ref: combined, ...attrs }));
31
+ });
32
+
33
+ // option of loading KaTeX asynchronously
34
+ const KaTeXLoad = new Promise((resolve) => {
35
+ const script = document.querySelector("script[src*=\"katex.js\"], script[src*=\"katex.min.js\"]");
36
+ if (!script)
37
+ return;
38
+ if (window.hasOwnProperty("katex")) {
39
+ resolve(katex);
40
+ }
41
+ else {
42
+ script.addEventListener("load", () => resolve(katex));
43
+ }
44
+ });
45
+ // load macros from <head>
46
+ const KaTeXMacros = new Promise((resolve) => {
47
+ const macros = {};
48
+ const scripts = Array.from(document.querySelectorAll("head > script[type='math/tex']"));
49
+ return Promise.all(scripts.map(script => fetch(script.src)
50
+ .then(res => {
51
+ if (res.ok)
52
+ return res.text();
53
+ throw new Error(`${res.status} ${res.statusText}: ${script.src}`);
54
+ })
55
+ .then(tex => {
56
+ Object.assign(macros, parseMacros(tex));
57
+ }))).then(() => resolve(macros));
58
+ });
59
+ /**
60
+ * Ready Promise
61
+ */
62
+ const KaTeXReady = Promise.all([KaTeXLoad, KaTeXMacros]);
63
+ /**
64
+ Parse \newcommand macros in a file.
65
+ Also supports \ktxnewcommand (for use in conjunction with MathJax).
66
+ */
67
+ function parseMacros(file) {
68
+ const macros = {};
69
+ const rgx = /\\(?:ktx)?newcommand\{(.+?)\}(?:\[\d+\])?\{/g;
70
+ let match;
71
+ while (match = rgx.exec(file)) {
72
+ let body = "";
73
+ const macro = match[1];
74
+ let braceCount = 1;
75
+ for (let i = match.index + match[0].length; (braceCount > 0) && (i < file.length); ++i) {
76
+ const char = file[i];
77
+ if (char === "{") {
78
+ braceCount++;
79
+ }
80
+ else if (char === "}") {
81
+ braceCount--;
82
+ if (braceCount === 0)
83
+ break;
84
+ }
85
+ else if (char === "\\") {
86
+ body += file.slice(i, i + 2);
87
+ ++i;
88
+ continue;
89
+ }
90
+ body += char;
91
+ }
92
+ macros[macro] = body;
93
+ }
94
+ return macros;
95
+ }
96
+
97
+ /**
98
+ * Wait for several things to be rendered
99
+ */
100
+ const RenderGroup = react.forwardRef(function RenderGroup(props, ref) {
101
+ const [ready, resolve] = react$1.usePromise();
102
+ // handle
103
+ react.useImperativeHandle(ref, () => ({ ready }));
104
+ const elements = react.useRef([]);
105
+ const promises = react.useRef([]);
106
+ // reparsing
107
+ const player = liqvid.usePlayer();
108
+ react.useEffect(() => {
109
+ // promises
110
+ Promise.all(promises.current).then(() => {
111
+ // reparse
112
+ if (props.reparse) {
113
+ player.reparseTree(leastCommonAncestor(elements.current));
114
+ }
115
+ // ready()
116
+ resolve();
117
+ });
118
+ }, []);
119
+ return react$1.recursiveMap(props.children, node => {
120
+ if (shouldInspect(node)) {
121
+ const originalRef = node.ref;
122
+ return react.cloneElement(node, {
123
+ ref: (ref) => {
124
+ if (!ref)
125
+ return;
126
+ elements.current.push(ref.domElement);
127
+ promises.current.push(ref.ready);
128
+ // pass along original ref
129
+ if (typeof originalRef === "function") {
130
+ originalRef(ref);
131
+ }
132
+ else if (originalRef && typeof originalRef === "object") {
133
+ originalRef.current = ref;
134
+ }
135
+ }
136
+ });
137
+ }
138
+ return node;
139
+ });
140
+ });
141
+ /**
142
+ * Determine whether the node is a <KTX> element
143
+ * @param node Element to check
144
+ */
145
+ function shouldInspect(node) {
146
+ return react.isValidElement(node) && typeof node.type === "object" && (node.type === KTX || node.type === plain.KTX);
147
+ }
148
+ /**
149
+ * Find least common ancestor of an array of elements
150
+ * @param elements Elements
151
+ * @returns Deepest node containing all passed elements
152
+ */
153
+ function leastCommonAncestor(elements) {
154
+ if (elements.length === 0) {
155
+ throw new Error("Must pass at least one element");
156
+ }
157
+ let ancestor = elements[0];
158
+ let failing = elements.slice(1);
159
+ while (failing.length > 0) {
160
+ ancestor = ancestor.parentElement;
161
+ failing = failing.filter(node => !ancestor.contains(node));
162
+ }
163
+ return ancestor;
164
+ }
165
+
166
+ exports.KTX = KTX;
167
+ exports.KaTeXReady = KaTeXReady;
168
+ exports.RenderGroup = RenderGroup;
package/dist/index.mjs ADDED
@@ -0,0 +1,206 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { combineRefs, usePromise, recursiveMap } from '@liqvid/utils/react';
3
+ import { usePlayer } from 'liqvid';
4
+ import { forwardRef, useRef, useEffect, useImperativeHandle, isValidElement, cloneElement } from 'react';
5
+ import { KTX as KTX$2 } from './plain.mjs';
6
+
7
+ /** Component for KaTeX code */
8
+ const KTX$1 = forwardRef(function KTX(props, ref) {
9
+ const { obstruct = "canplay canplaythrough", reparse = false, ...attrs } = props;
10
+ const plain = useRef();
11
+ const combined = combineRefs(plain, ref);
12
+ const player = usePlayer();
13
+ useEffect(() => {
14
+ // obstruction
15
+ if (obstruct.match(/\bcanplay\b/)) {
16
+ player.obstruct("canplay", plain.current.ready);
17
+ }
18
+ if (obstruct.match("canplaythrough")) {
19
+ player.obstruct("canplaythrough", plain.current.ready);
20
+ }
21
+ // reparsing
22
+ if (reparse) {
23
+ plain.current.ready.then(() => player.reparseTree(plain.current.domElement));
24
+ }
25
+ }, []);
26
+ return (jsx(KTX$2, { ref: combined, ...attrs }));
27
+ });
28
+
29
+ // option of loading KaTeX asynchronously
30
+ const KaTeXLoad = new Promise((resolve) => {
31
+ const script = document.querySelector("script[src*=\"katex.js\"], script[src*=\"katex.min.js\"]");
32
+ if (!script)
33
+ return;
34
+ if (window.hasOwnProperty("katex")) {
35
+ resolve(katex);
36
+ }
37
+ else {
38
+ script.addEventListener("load", () => resolve(katex));
39
+ }
40
+ });
41
+ // load macros from <head>
42
+ const KaTeXMacros = new Promise((resolve) => {
43
+ const macros = {};
44
+ const scripts = Array.from(document.querySelectorAll("head > script[type='math/tex']"));
45
+ return Promise.all(scripts.map(script => fetch(script.src)
46
+ .then(res => {
47
+ if (res.ok)
48
+ return res.text();
49
+ throw new Error(`${res.status} ${res.statusText}: ${script.src}`);
50
+ })
51
+ .then(tex => {
52
+ Object.assign(macros, parseMacros(tex));
53
+ }))).then(() => resolve(macros));
54
+ });
55
+ /**
56
+ * Ready Promise
57
+ */
58
+ const KaTeXReady = Promise.all([KaTeXLoad, KaTeXMacros]);
59
+ /**
60
+ Parse \newcommand macros in a file.
61
+ Also supports \ktxnewcommand (for use in conjunction with MathJax).
62
+ */
63
+ function parseMacros(file) {
64
+ const macros = {};
65
+ const rgx = /\\(?:ktx)?newcommand\{(.+?)\}(?:\[\d+\])?\{/g;
66
+ let match;
67
+ while (match = rgx.exec(file)) {
68
+ let body = "";
69
+ const macro = match[1];
70
+ let braceCount = 1;
71
+ for (let i = match.index + match[0].length; (braceCount > 0) && (i < file.length); ++i) {
72
+ const char = file[i];
73
+ if (char === "{") {
74
+ braceCount++;
75
+ }
76
+ else if (char === "}") {
77
+ braceCount--;
78
+ if (braceCount === 0)
79
+ break;
80
+ }
81
+ else if (char === "\\") {
82
+ body += file.slice(i, i + 2);
83
+ ++i;
84
+ continue;
85
+ }
86
+ body += char;
87
+ }
88
+ macros[macro] = body;
89
+ }
90
+ return macros;
91
+ }
92
+
93
+ /** Component for KaTeX code */
94
+ const KTX = forwardRef(function KTX(props, ref) {
95
+ const spanRef = useRef();
96
+ const { children, display = false, ...attrs } = props;
97
+ const [ready, resolve] = usePromise();
98
+ // handle
99
+ useImperativeHandle(ref, () => ({
100
+ domElement: spanRef.current,
101
+ ready
102
+ }));
103
+ useEffect(() => {
104
+ KaTeXReady.then(([katex, macros]) => {
105
+ katex.render(children.toString(), spanRef.current, {
106
+ displayMode: !!display,
107
+ macros,
108
+ strict: "ignore",
109
+ throwOnError: false,
110
+ trust: true
111
+ });
112
+ /* move katex into placeholder element */
113
+ const child = spanRef.current.firstElementChild;
114
+ // copy classes
115
+ for (let i = 0, len = child.classList.length; i < len; ++i) {
116
+ spanRef.current.classList.add(child.classList.item(i));
117
+ }
118
+ // move children
119
+ while (child.childNodes.length > 0) {
120
+ spanRef.current.appendChild(child.firstChild);
121
+ }
122
+ // delete child
123
+ child.remove();
124
+ // resolve promise
125
+ resolve();
126
+ });
127
+ }, [children]);
128
+ // Google Chrome fails without this
129
+ if (display) {
130
+ if (!attrs.style)
131
+ attrs.style = {};
132
+ attrs.style.display = "block";
133
+ }
134
+ return (jsx("span", { ...attrs, ref: spanRef }));
135
+ });
136
+
137
+ /**
138
+ * Wait for several things to be rendered
139
+ */
140
+ const RenderGroup = forwardRef(function RenderGroup(props, ref) {
141
+ const [ready, resolve] = usePromise();
142
+ // handle
143
+ useImperativeHandle(ref, () => ({ ready }));
144
+ const elements = useRef([]);
145
+ const promises = useRef([]);
146
+ // reparsing
147
+ const player = usePlayer();
148
+ useEffect(() => {
149
+ // promises
150
+ Promise.all(promises.current).then(() => {
151
+ // reparse
152
+ if (props.reparse) {
153
+ player.reparseTree(leastCommonAncestor(elements.current));
154
+ }
155
+ // ready()
156
+ resolve();
157
+ });
158
+ }, []);
159
+ return recursiveMap(props.children, node => {
160
+ if (shouldInspect(node)) {
161
+ const originalRef = node.ref;
162
+ return cloneElement(node, {
163
+ ref: (ref) => {
164
+ if (!ref)
165
+ return;
166
+ elements.current.push(ref.domElement);
167
+ promises.current.push(ref.ready);
168
+ // pass along original ref
169
+ if (typeof originalRef === "function") {
170
+ originalRef(ref);
171
+ }
172
+ else if (originalRef && typeof originalRef === "object") {
173
+ originalRef.current = ref;
174
+ }
175
+ }
176
+ });
177
+ }
178
+ return node;
179
+ });
180
+ });
181
+ /**
182
+ * Determine whether the node is a <KTX> element
183
+ * @param node Element to check
184
+ */
185
+ function shouldInspect(node) {
186
+ return isValidElement(node) && typeof node.type === "object" && (node.type === KTX$1 || node.type === KTX);
187
+ }
188
+ /**
189
+ * Find least common ancestor of an array of elements
190
+ * @param elements Elements
191
+ * @returns Deepest node containing all passed elements
192
+ */
193
+ function leastCommonAncestor(elements) {
194
+ if (elements.length === 0) {
195
+ throw new Error("Must pass at least one element");
196
+ }
197
+ let ancestor = elements[0];
198
+ let failing = elements.slice(1);
199
+ while (failing.length > 0) {
200
+ ancestor = ancestor.parentElement;
201
+ failing = failing.filter(node => !ancestor.contains(node));
202
+ }
203
+ return ancestor;
204
+ }
205
+
206
+ export { KTX$1 as KTX, KaTeXReady, RenderGroup };
package/dist/plain.js ADDED
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var react = require('react');
7
+ var react$1 = require('@liqvid/utils/react');
8
+
9
+ // option of loading KaTeX asynchronously
10
+ const KaTeXLoad = new Promise((resolve) => {
11
+ const script = document.querySelector("script[src*=\"katex.js\"], script[src*=\"katex.min.js\"]");
12
+ if (!script)
13
+ return;
14
+ if (window.hasOwnProperty("katex")) {
15
+ resolve(katex);
16
+ }
17
+ else {
18
+ script.addEventListener("load", () => resolve(katex));
19
+ }
20
+ });
21
+ // load macros from <head>
22
+ const KaTeXMacros = new Promise((resolve) => {
23
+ const macros = {};
24
+ const scripts = Array.from(document.querySelectorAll("head > script[type='math/tex']"));
25
+ return Promise.all(scripts.map(script => fetch(script.src)
26
+ .then(res => {
27
+ if (res.ok)
28
+ return res.text();
29
+ throw new Error(`${res.status} ${res.statusText}: ${script.src}`);
30
+ })
31
+ .then(tex => {
32
+ Object.assign(macros, parseMacros(tex));
33
+ }))).then(() => resolve(macros));
34
+ });
35
+ /**
36
+ * Ready Promise
37
+ */
38
+ const KaTeXReady = Promise.all([KaTeXLoad, KaTeXMacros]);
39
+ /**
40
+ Parse \newcommand macros in a file.
41
+ Also supports \ktxnewcommand (for use in conjunction with MathJax).
42
+ */
43
+ function parseMacros(file) {
44
+ const macros = {};
45
+ const rgx = /\\(?:ktx)?newcommand\{(.+?)\}(?:\[\d+\])?\{/g;
46
+ let match;
47
+ while (match = rgx.exec(file)) {
48
+ let body = "";
49
+ const macro = match[1];
50
+ let braceCount = 1;
51
+ for (let i = match.index + match[0].length; (braceCount > 0) && (i < file.length); ++i) {
52
+ const char = file[i];
53
+ if (char === "{") {
54
+ braceCount++;
55
+ }
56
+ else if (char === "}") {
57
+ braceCount--;
58
+ if (braceCount === 0)
59
+ break;
60
+ }
61
+ else if (char === "\\") {
62
+ body += file.slice(i, i + 2);
63
+ ++i;
64
+ continue;
65
+ }
66
+ body += char;
67
+ }
68
+ macros[macro] = body;
69
+ }
70
+ return macros;
71
+ }
72
+
73
+ /** Component for KaTeX code */
74
+ const KTX = react.forwardRef(function KTX(props, ref) {
75
+ const spanRef = react.useRef();
76
+ const { children, display = false, ...attrs } = props;
77
+ const [ready, resolve] = react$1.usePromise();
78
+ // handle
79
+ react.useImperativeHandle(ref, () => ({
80
+ domElement: spanRef.current,
81
+ ready
82
+ }));
83
+ react.useEffect(() => {
84
+ KaTeXReady.then(([katex, macros]) => {
85
+ katex.render(children.toString(), spanRef.current, {
86
+ displayMode: !!display,
87
+ macros,
88
+ strict: "ignore",
89
+ throwOnError: false,
90
+ trust: true
91
+ });
92
+ /* move katex into placeholder element */
93
+ const child = spanRef.current.firstElementChild;
94
+ // copy classes
95
+ for (let i = 0, len = child.classList.length; i < len; ++i) {
96
+ spanRef.current.classList.add(child.classList.item(i));
97
+ }
98
+ // move children
99
+ while (child.childNodes.length > 0) {
100
+ spanRef.current.appendChild(child.firstChild);
101
+ }
102
+ // delete child
103
+ child.remove();
104
+ // resolve promise
105
+ resolve();
106
+ });
107
+ }, [children]);
108
+ // Google Chrome fails without this
109
+ if (display) {
110
+ if (!attrs.style)
111
+ attrs.style = {};
112
+ attrs.style.display = "block";
113
+ }
114
+ return (jsxRuntime.jsx("span", { ...attrs, ref: spanRef }));
115
+ });
116
+
117
+ exports.KTX = KTX;
package/dist/plain.mjs ADDED
@@ -0,0 +1,113 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
3
+ import { usePromise } from '@liqvid/utils/react';
4
+
5
+ // option of loading KaTeX asynchronously
6
+ const KaTeXLoad = new Promise((resolve) => {
7
+ const script = document.querySelector("script[src*=\"katex.js\"], script[src*=\"katex.min.js\"]");
8
+ if (!script)
9
+ return;
10
+ if (window.hasOwnProperty("katex")) {
11
+ resolve(katex);
12
+ }
13
+ else {
14
+ script.addEventListener("load", () => resolve(katex));
15
+ }
16
+ });
17
+ // load macros from <head>
18
+ const KaTeXMacros = new Promise((resolve) => {
19
+ const macros = {};
20
+ const scripts = Array.from(document.querySelectorAll("head > script[type='math/tex']"));
21
+ return Promise.all(scripts.map(script => fetch(script.src)
22
+ .then(res => {
23
+ if (res.ok)
24
+ return res.text();
25
+ throw new Error(`${res.status} ${res.statusText}: ${script.src}`);
26
+ })
27
+ .then(tex => {
28
+ Object.assign(macros, parseMacros(tex));
29
+ }))).then(() => resolve(macros));
30
+ });
31
+ /**
32
+ * Ready Promise
33
+ */
34
+ const KaTeXReady = Promise.all([KaTeXLoad, KaTeXMacros]);
35
+ /**
36
+ Parse \newcommand macros in a file.
37
+ Also supports \ktxnewcommand (for use in conjunction with MathJax).
38
+ */
39
+ function parseMacros(file) {
40
+ const macros = {};
41
+ const rgx = /\\(?:ktx)?newcommand\{(.+?)\}(?:\[\d+\])?\{/g;
42
+ let match;
43
+ while (match = rgx.exec(file)) {
44
+ let body = "";
45
+ const macro = match[1];
46
+ let braceCount = 1;
47
+ for (let i = match.index + match[0].length; (braceCount > 0) && (i < file.length); ++i) {
48
+ const char = file[i];
49
+ if (char === "{") {
50
+ braceCount++;
51
+ }
52
+ else if (char === "}") {
53
+ braceCount--;
54
+ if (braceCount === 0)
55
+ break;
56
+ }
57
+ else if (char === "\\") {
58
+ body += file.slice(i, i + 2);
59
+ ++i;
60
+ continue;
61
+ }
62
+ body += char;
63
+ }
64
+ macros[macro] = body;
65
+ }
66
+ return macros;
67
+ }
68
+
69
+ /** Component for KaTeX code */
70
+ const KTX = forwardRef(function KTX(props, ref) {
71
+ const spanRef = useRef();
72
+ const { children, display = false, ...attrs } = props;
73
+ const [ready, resolve] = usePromise();
74
+ // handle
75
+ useImperativeHandle(ref, () => ({
76
+ domElement: spanRef.current,
77
+ ready
78
+ }));
79
+ useEffect(() => {
80
+ KaTeXReady.then(([katex, macros]) => {
81
+ katex.render(children.toString(), spanRef.current, {
82
+ displayMode: !!display,
83
+ macros,
84
+ strict: "ignore",
85
+ throwOnError: false,
86
+ trust: true
87
+ });
88
+ /* move katex into placeholder element */
89
+ const child = spanRef.current.firstElementChild;
90
+ // copy classes
91
+ for (let i = 0, len = child.classList.length; i < len; ++i) {
92
+ spanRef.current.classList.add(child.classList.item(i));
93
+ }
94
+ // move children
95
+ while (child.childNodes.length > 0) {
96
+ spanRef.current.appendChild(child.firstChild);
97
+ }
98
+ // delete child
99
+ child.remove();
100
+ // resolve promise
101
+ resolve();
102
+ });
103
+ }, [children]);
104
+ // Google Chrome fails without this
105
+ if (display) {
106
+ if (!attrs.style)
107
+ attrs.style = {};
108
+ attrs.style.display = "block";
109
+ }
110
+ return (jsx("span", { ...attrs, ref: spanRef }));
111
+ });
112
+
113
+ export { KTX };
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ /** RenderGroup element API */
3
+ interface Handle {
4
+ /** Promise that resolves once all KTX descendants have finished typesetting */
5
+ ready: Promise<void>;
6
+ }
7
+ interface Props {
8
+ /**
9
+ * Whether to reparse descendants for `during()` and `from()`
10
+ * @default false
11
+ */
12
+ reparse?: boolean;
13
+ }
14
+ /**
15
+ * Wait for several things to be rendered
16
+ */
17
+ export declare const RenderGroup: React.ForwardRefExoticComponent<Props & React.RefAttributes<Handle>>;
18
+ export {};
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ import { Handle, KTX as KTXPlain } from "./plain";
3
+ interface Props extends React.ComponentProps<typeof KTXPlain> {
4
+ /**
5
+ * Player events to obstruct
6
+ * @default "canplay canplaythrough"
7
+ */
8
+ obstruct?: string;
9
+ /**
10
+ * Whether to reparse descendants for `during()` and `from()`
11
+ * @default false
12
+ */
13
+ reparse?: boolean;
14
+ }
15
+ /** Component for KaTeX code */
16
+ export declare const KTX: import("react").ForwardRefExoticComponent<Pick<Props, "hidden" | "color" | "style" | "display" | "translate" | "slot" | "title" | "children" | "key" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "id" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "obstruct" | "reparse"> & import("react").RefAttributes<Handle>>;
17
+ export {};
@@ -1,6 +1,6 @@
1
- export { KTXBlocking as KTX, KTXBlocking } from "./Blocking";
1
+ export { KTX } from "./fancy";
2
2
  export { KaTeXReady } from "./loading";
3
- export { Handle, KTXNonBlocking } from "./NonBlocking";
3
+ export { Handle } from "./plain";
4
4
  export { RenderGroup } from "./RenderGroup";
5
5
  declare global {
6
6
  const katex: typeof katex;
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Ready Promise
3
+ */
1
4
  export declare const KaTeXReady: Promise<[typeof import("katex"), {
2
5
  [key: string]: string;
3
6
  }]>;
@@ -0,0 +1,20 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * KTX element API
4
+ */
5
+ export interface Handle {
6
+ /** The underlying <span> element */
7
+ domElement: HTMLSpanElement;
8
+ /** Promise that resolves once typesetting is finished */
9
+ ready: Promise<void>;
10
+ }
11
+ interface Props extends React.HTMLAttributes<HTMLSpanElement> {
12
+ /**
13
+ * Whether to render in display style
14
+ * @default false
15
+ */
16
+ display?: boolean;
17
+ }
18
+ /** Component for KaTeX code */
19
+ export declare const KTX: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<Handle>>;
20
+ export {};
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "@liqvid/katex",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "KaTeX integration for Liqvid",
5
5
  "files": [
6
6
  "dist/*"
7
7
  ],
8
8
  "exports": {
9
- ".": "./dist/index.js"
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ },
13
+ "./plain": {
14
+ "import": "./dist/plain.mjs",
15
+ "require": "./dist/plain.js"
16
+ }
10
17
  },
11
18
  "typesVersions": {
12
19
  "*": {
13
20
  "*": [
14
- "./dist/*"
21
+ "./dist/types/*"
15
22
  ]
16
23
  }
17
24
  },
@@ -20,6 +27,10 @@
20
27
  "liqvid",
21
28
  "katex"
22
29
  ],
30
+ "scripts": {
31
+ "build": "tsc --build --force",
32
+ "lint": "eslint --ext ts,tsx --fix src"
33
+ },
23
34
  "repository": {
24
35
  "type": "git",
25
36
  "url": "git+https://github.com/liqvidjs/liqvid.git"
@@ -30,9 +41,10 @@
30
41
  "homepage": "https://github.com/liqvidjs/liqvid/tree/main/packages/katex",
31
42
  "license": "MIT",
32
43
  "peerDependencies": {
33
- "@types/react": "^17.0.39",
34
- "liqvid": "2.1.0-beta.3",
35
- "react": "^17.0.2"
44
+ "@types/katex": "^0.11.1",
45
+ "@types/react": ">=17.0.0",
46
+ "liqvid": ">=2.1.0-beta.4",
47
+ "react": ">=17.0.0"
36
48
  },
37
49
  "peerDependenciesMeta": {
38
50
  "liqvid": {
@@ -46,12 +58,12 @@
46
58
  "@yuri/eslint-config": "^1.0.1",
47
59
  "eslint": "^8.10.0",
48
60
  "eslint-plugin-react": "^7.29.3",
49
- "liqvid": "2.1.0-beta.3",
61
+ "liqvid": ">=2.1.0-beta.4",
50
62
  "react": "^17.0.2",
63
+ "rollup": "^2.70.0",
51
64
  "typescript": "^4.6.2"
52
65
  },
53
66
  "dependencies": {
54
- "@types/events": "^3.0.0",
55
- "@types/katex": "^0.11.1"
67
+ "@liqvid/utils": "^1.3.0"
56
68
  }
57
69
  }
@@ -1,2 +0,0 @@
1
- /// <reference types="react" />
2
- export declare const KTXBlocking: import("react").ForwardRefExoticComponent<Pick<Props, string | number | symbol> & import("react").RefAttributes<Handle>>;
package/dist/Blocking.js DELETED
@@ -1,32 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { usePlayer } from "liqvid";
3
- import { forwardRef, useEffect, useMemo, useRef } from "react";
4
- import { KTXNonBlocking } from "./NonBlocking";
5
- export const KTXBlocking = forwardRef(function KTX(props, ref) {
6
- const player = usePlayer();
7
- const innerRef = useRef();
8
- if (typeof ref === "function") {
9
- ref(innerRef.current);
10
- }
11
- else if (ref) {
12
- ref.current = innerRef.current;
13
- }
14
- const resolve = useRef(null);
15
- useMemo(() => {
16
- const promise = new Promise((res) => {
17
- resolve.current = res;
18
- });
19
- player.obstruct("canplay", promise);
20
- player.obstruct("canplaythrough", promise);
21
- }, []);
22
- useEffect(() => {
23
- if (typeof ref === "function") {
24
- ref(innerRef.current);
25
- }
26
- else if (ref) {
27
- ref.current = innerRef.current;
28
- }
29
- innerRef.current.ready.then(() => resolve.current());
30
- }, []);
31
- return (_jsx(KTXNonBlocking, Object.assign({ ref: innerRef }, props)));
32
- });
@@ -1,9 +0,0 @@
1
- /// <reference types="react" />
2
- export interface Handle {
3
- domElement: HTMLSpanElement;
4
- ready: Promise<void>;
5
- }
6
- export declare const KTXNonBlocking: import("react").ForwardRefExoticComponent<{
7
- display?: boolean;
8
- reparse?: boolean;
9
- } & import("react").HTMLAttributes<HTMLSpanElement> & import("react").RefAttributes<Handle>>;
@@ -1,60 +0,0 @@
1
- var __rest = (this && this.__rest) || function (s, e) {
2
- var t = {};
3
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
- t[p] = s[p];
5
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
- t[p[i]] = s[p[i]];
9
- }
10
- return t;
11
- };
12
- import { jsx as _jsx } from "react/jsx-runtime";
13
- import { usePlayer } from "liqvid";
14
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from "react";
15
- import { KaTeXReady } from "./loading";
16
- const implementation = function KTX(props, ref) {
17
- const spanRef = useRef();
18
- const { children, display, reparse } = props, attrs = __rest(props, ["children", "display", "reparse"]);
19
- const resolveRef = useRef();
20
- const player = usePlayer();
21
- const ready = useMemo(() => {
22
- return new Promise((resolve) => {
23
- resolveRef.current = resolve;
24
- });
25
- }, []);
26
- useImperativeHandle(ref, () => ({
27
- domElement: spanRef.current,
28
- ready
29
- }));
30
- useEffect(() => {
31
- KaTeXReady.then(([katex, macros]) => {
32
- katex.render(children.toString(), spanRef.current, {
33
- displayMode: !!display,
34
- macros,
35
- strict: "ignore",
36
- throwOnError: false,
37
- trust: true
38
- });
39
- const child = spanRef.current.firstElementChild;
40
- for (let i = 0, len = child.classList.length; i < len; ++i) {
41
- spanRef.current.classList.add(child.classList.item(i));
42
- }
43
- while (child.childNodes.length > 0) {
44
- spanRef.current.appendChild(child.firstChild);
45
- }
46
- child.remove();
47
- if (reparse) {
48
- player.reparseTree(spanRef.current);
49
- }
50
- resolveRef.current();
51
- });
52
- }, [children]);
53
- if (display) {
54
- if (!attrs.style)
55
- attrs.style = {};
56
- attrs.style.display = "block";
57
- }
58
- return (_jsx("span", Object.assign({}, attrs, { ref: spanRef })));
59
- };
60
- export const KTXNonBlocking = forwardRef(implementation);
@@ -1,10 +0,0 @@
1
- /// <reference types="react" />
2
- interface Handle {
3
- ready: Promise<void>;
4
- }
5
- interface Props {
6
- reparse?: boolean;
7
- }
8
- export declare const RenderGroup: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<Handle>>;
9
- export declare function recursiveMap(children: React.ReactNode, fn: (child: React.ReactNode) => React.ReactNode): React.ReactNode;
10
- export {};
@@ -1,76 +0,0 @@
1
- import { Children, cloneElement, forwardRef, isValidElement, useEffect, useMemo, useRef, useImperativeHandle } from "react";
2
- import { usePlayer } from "liqvid";
3
- import { KTXNonBlocking } from "./NonBlocking";
4
- import { KTXBlocking } from "./Blocking";
5
- export const RenderGroup = forwardRef(function RenderGroup(props, ref) {
6
- const [ready, resolve] = usePromise();
7
- useImperativeHandle(ref, () => ({ ready }));
8
- const elements = useRef([]);
9
- const promises = useRef([]);
10
- const player = usePlayer();
11
- useEffect(() => {
12
- Promise.all(promises.current).then(() => {
13
- if (props.reparse) {
14
- player.reparseTree(leastCommonAncestor(elements.current));
15
- }
16
- resolve();
17
- });
18
- }, []);
19
- return recursiveMap(props.children, node => {
20
- if (shouldInspect(node)) {
21
- const originalRef = node.ref;
22
- return cloneElement(node, {
23
- ref: (ref) => {
24
- if (!ref)
25
- return;
26
- elements.current.push(ref.domElement);
27
- promises.current.push(ref.ready);
28
- if (typeof originalRef === "function") {
29
- originalRef(ref);
30
- }
31
- else if (originalRef && typeof originalRef === "object") {
32
- originalRef.current = ref;
33
- }
34
- }
35
- });
36
- }
37
- return node;
38
- });
39
- });
40
- function shouldInspect(node) {
41
- return isValidElement(node) && typeof node.type === "object" && (node.type === KTXBlocking || node.type === KTXNonBlocking);
42
- }
43
- export function recursiveMap(children, fn) {
44
- return Children.map(children, (child) => {
45
- if (!isValidElement(child)) {
46
- return child;
47
- }
48
- if ("children" in child.props) {
49
- child = cloneElement(child, {
50
- children: recursiveMap(child.props.children, fn)
51
- });
52
- }
53
- return fn(child);
54
- });
55
- }
56
- function usePromise(deps = []) {
57
- const resolve = useRef();
58
- const reject = useRef();
59
- const promise = useMemo(() => new Promise((res, rej) => {
60
- resolve.current = res;
61
- reject.current = rej;
62
- }), deps);
63
- return [promise, resolve.current, reject.current];
64
- }
65
- function leastCommonAncestor(elements) {
66
- if (elements.length === 0) {
67
- throw new Error("Must pass at least one element");
68
- }
69
- let ancestor = elements[0];
70
- let failing = elements.slice(1);
71
- while (failing.length > 0) {
72
- ancestor = ancestor.parentElement;
73
- failing = failing.filter(node => !ancestor.contains(node));
74
- }
75
- return ancestor;
76
- }
package/dist/loading.js DELETED
@@ -1,54 +0,0 @@
1
- const KaTeXLoad = new Promise((resolve) => {
2
- const script = document.querySelector(`script[src*="katex.js"], script[src*="katex.min.js"]`);
3
- if (!script)
4
- return;
5
- if (window.hasOwnProperty("katex")) {
6
- resolve(katex);
7
- }
8
- else {
9
- script.addEventListener("load", () => resolve(katex));
10
- }
11
- });
12
- const KaTeXMacros = new Promise((resolve) => {
13
- const macros = {};
14
- const scripts = Array.from(document.querySelectorAll("head > script[type='math/tex']"));
15
- return Promise.all(scripts.map(script => fetch(script.src)
16
- .then(res => {
17
- if (res.ok)
18
- return res.text();
19
- throw new Error(`${res.status} ${res.statusText}: ${script.src}`);
20
- })
21
- .then(tex => {
22
- Object.assign(macros, parseMacros(tex));
23
- }))).then(() => resolve(macros));
24
- });
25
- export const KaTeXReady = Promise.all([KaTeXLoad, KaTeXMacros]);
26
- function parseMacros(file) {
27
- const macros = {};
28
- const rgx = /\\(?:ktx)?newcommand\{(.+?)\}(?:\[\d+\])?\{/g;
29
- let match;
30
- while (match = rgx.exec(file)) {
31
- let body = "";
32
- const macro = match[1];
33
- let braceCount = 1;
34
- for (let i = match.index + match[0].length; (braceCount > 0) && (i < file.length); ++i) {
35
- const char = file[i];
36
- if (char === "{") {
37
- braceCount++;
38
- }
39
- else if (char === "}") {
40
- braceCount--;
41
- if (braceCount === 0)
42
- break;
43
- }
44
- else if (char === "\\") {
45
- body += file.slice(i, i + 2);
46
- ++i;
47
- continue;
48
- }
49
- body += char;
50
- }
51
- macros[macro] = body;
52
- }
53
- return macros;
54
- }