@barocss/math-editor 0.5.0 → 0.6.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.
Files changed (49) hide show
  1. package/API-SESSION.md +25 -1
  2. package/CHANGELOG.md +48 -0
  3. package/CLIPBOARD.md +54 -0
  4. package/EDITING-SCENARIOS.md +55 -20
  5. package/GETTING-STARTED.md +54 -0
  6. package/IMPLEMENTATION.md +10 -0
  7. package/KEYBOARD.md +41 -0
  8. package/LATEX-GUIDE.md +30 -2
  9. package/README.md +52 -10
  10. package/RELEASING.md +22 -8
  11. package/ROADMAP.md +106 -1
  12. package/STYLING.md +28 -1
  13. package/TEXT-EDITORS.md +157 -0
  14. package/VALIDATION.md +102 -0
  15. package/dist/dom/caret-geometry.js +1 -1
  16. package/dist/dom/help.d.ts +5 -0
  17. package/dist/dom/help.js +104 -0
  18. package/dist/dom/menu-position.js +4 -3
  19. package/dist/dom/readable-layout.d.ts +3 -0
  20. package/dist/dom/readable-layout.js +52 -0
  21. package/dist/dom/toolbar-catalog.d.ts +1 -1
  22. package/dist/dom/toolbar.d.ts +2 -0
  23. package/dist/dom/toolbar.js +8 -1
  24. package/dist/dom.d.ts +11 -1
  25. package/dist/dom.js +138 -15
  26. package/dist/enter-policy.js +1 -1
  27. package/dist/lines.d.ts +2 -0
  28. package/dist/lines.js +15 -0
  29. package/dist/locales/en.js +21 -3
  30. package/dist/locales/en.json +21 -3
  31. package/dist/locales/ko.js +21 -3
  32. package/dist/locales/ko.json +21 -3
  33. package/dist/math-editor-toolbar.js +2 -1
  34. package/dist/math-editor.d.ts +1 -0
  35. package/dist/math-editor.js +135 -39
  36. package/dist/model.d.ts +2 -0
  37. package/dist/model.js +23 -1
  38. package/dist/range.d.ts +10 -2
  39. package/dist/range.js +31 -6
  40. package/dist/selection-shortcuts.d.ts +13 -0
  41. package/dist/selection-shortcuts.js +35 -0
  42. package/dist/session.d.ts +1 -0
  43. package/dist/session.js +1 -1
  44. package/dist/symbols.d.ts +1 -1
  45. package/dist/symbols.js +1 -0
  46. package/dist/vertical-navigation.d.ts +5 -0
  47. package/dist/vertical-navigation.js +33 -0
  48. package/package.json +1 -1
  49. package/src/style.css +227 -57
@@ -8,6 +8,7 @@ export interface MathEditorHandle {
8
8
  /** Replace with supported LaTeX as one undoable edit; failures preserve all editor state. */
9
9
  importLatex(source: string): LatexParseResult;
10
10
  pasteLatex(source: string): LatexInsertionResult;
11
+ showHelp(): void;
11
12
  }
12
13
  export interface MathEditorProps {
13
14
  preferences?: MathPreferences;
@@ -1,5 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { followReadableMathLayout } from './dom/readable-layout.js';
3
+ import { attachMathHelp, requestMathHelp } from './dom/help.js';
4
+ import { mathEnterAction } from './enter-policy.js';
2
5
  import { mathLayout } from './math-layout.js';
6
+ import { wrapSelectionShortcut } from './selection-shortcuts.js';
3
7
  import { transformRoot } from './root-transform.js';
4
8
  import { structureEditingContext, changeContextFence } from './context-tools.js';
5
9
  import { focusContextAction } from './dom/context-keyboard.js';
@@ -20,18 +24,18 @@ import { MathEditorToolbar } from './math-editor-toolbar.js';
20
24
  import { SymbolBrowser } from './symbol-browser.js';
21
25
  import { insertTemplate } from './templates.js';
22
26
  import { translate, mathLocaleDirection, } from './i18n.js';
23
- import { extendKeyboardRange, collapseKeyboardRange, wrapRange, wrappingKinds, copyRange, deleteRange, fragmentLatex, MATH_CLIPBOARD_TYPE, parseFragment, pasteFragment, resolveRange, } from './range.js';
27
+ import { extendKeyboardRange, isTokenNavigationKey, collapseKeyboardRange, wrapRange, wrappingKinds, copyRange, deleteRange, fragmentLatex, MATH_CLIPBOARD_TYPE, parseFragment, pasteFragment, resolveRange, } from './range.js';
24
28
  import { mathRunSpacing } from './math-spacing.js';
25
29
  import { tokenizeMathText, tokenIndexAt } from './tokens.js';
26
- import { splitLine, joinPreviousLine } from './lines.js';
27
- import { moveVertical } from './vertical-navigation.js';
30
+ import { splitLine, joinPreviousLine, joinNextLine } from './lines.js';
31
+ import { createVerticalNavigation } from './vertical-navigation.js';
28
32
  import { renderedCaretGeometry } from './dom/caret-geometry.js';
29
33
  import { insertIdentityMatrix, insertMatrix, activeGrid, MATRIX_MAX_SIZE, matrixEnvironments, resizeMatrix, setMatrixEnvironment, } from './matrix.js';
30
34
  import { MATH_MATRIX_CLIPBOARD_TYPE, parseMatrixFragment, matrixFragment, matrixFromTSV, matrixRangeBetween, resolveMatrixRange, extendMatrixRange, focusMatrixCell, copyMatrixRange, clearMatrixRange, pasteMatrixFragment, transposeMatrix, } from './matrix-range.js';
31
35
  import { createPortal } from 'react-dom';
32
36
  import { acceptSuggestion, findStateSuggestions, mathStructures } from './suggestions.js';
33
37
  import { useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
34
- import { gridDeletionTarget, removeGrid, row, documentRows, isLiteralText, isGrid, commit, createHistory, initialState, insertStructure, moveCaret, redo, setText, textNodes, toLatex, undo, unwrapPrevious, unwrapEmptySlot, } from './model.js';
38
+ import { gridDeletionTarget, removeGrid, row, documentRows, isLiteralText, isGrid, commit, createHistory, initialState, insertStructure, moveCaret, redo, setText, textNodes, toLatex, undo, unwrapPrevious, unwrapNext, unwrapEmptySlot, } from './model.js';
35
39
  const structures = mathStructures;
36
40
  /** Native text fields own IME and local text selection; the tree owns structural editing. */
37
41
  export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus = false, enterBehavior = 'newline', onCommit, onCancel, showPopovers = true, defaultValue, excludedStructures = [], multiline = true, onChange, onExit, label, locale = 'ko', showLineNumbers = true, toolbar = true, toolbarMaxItems = 8, contextTools = true, toolbarEnd, showTokenLegend = true, }) {
@@ -52,6 +56,15 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
52
56
  const [matrixRange, setMatrixRange] = useState();
53
57
  const [clipboardMessage, setClipboardMessage] = useState('');
54
58
  const surface = useRef(null);
59
+ useLayoutEffect(() => {
60
+ if (surface.current)
61
+ return followReadableMathLayout(surface.current);
62
+ }, []);
63
+ useLayoutEffect(() => {
64
+ const root = surface.current?.closest('.me-editor');
65
+ if (root)
66
+ return attachMathHelp(root, () => locale);
67
+ }, [locale]);
55
68
  const drag = useRef(undefined);
56
69
  const [focused, setFocused] = useState(false);
57
70
  const [selectedGrid, setSelectedGrid] = useState();
@@ -110,9 +123,11 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
110
123
  : [];
111
124
  const selectedIndex = Math.min(candidateIndex, Math.max(0, candidates.length - 1));
112
125
  // All React history updates pass here. Caret movement must not produce host save events.
126
+ const verticalNavigation = useRef(createVerticalNavigation());
113
127
  const publish = (next, focus = false, notify = true) => {
114
128
  const changed = next.present.document !== historyRef.current.present.document;
115
129
  if (changed) {
130
+ verticalNavigation.current.reset();
116
131
  setSelectedGrid(undefined);
117
132
  setRange(undefined);
118
133
  setMatrixRange(undefined);
@@ -153,6 +168,11 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
153
168
  };
154
169
  useImperativeHandle(apiRef, () => ({
155
170
  pasteLatex,
171
+ showHelp() {
172
+ const root = surface.current?.closest('.me-editor');
173
+ if (root)
174
+ requestMathHelp(root);
175
+ },
156
176
  importLatex(source) {
157
177
  const result = parseLatex(source, { multiline, excludedStructures });
158
178
  if (!result.ok)
@@ -240,10 +260,11 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
240
260
  if (option && list) {
241
261
  const itemRect = option.getBoundingClientRect();
242
262
  const listRect = list.getBoundingClientRect();
243
- if (itemRect.top < listRect.top)
244
- list.scrollTop -= listRect.top - itemRect.top;
245
- else if (itemRect.bottom > listRect.bottom)
246
- list.scrollTop += itemRect.bottom - listRect.bottom;
263
+ const scaleY = listRect.height / list.offsetHeight || 1;
264
+ list.scrollTop +=
265
+ (itemRect.top + itemRect.height / 2 - listRect.top) / scaleY -
266
+ list.clientTop -
267
+ list.clientHeight / 2;
247
268
  }
248
269
  }, [selectedIndex, queryKey]);
249
270
  const choose = (index) => {
@@ -464,6 +485,12 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
464
485
  return;
465
486
  const source = event.clipboardData.getData(MATH_CLIPBOARD_TYPE);
466
487
  const fragment = source ? parseFragment(source) : undefined;
488
+ if (source && !fragment) {
489
+ event.preventDefault();
490
+ event.stopPropagation();
491
+ setClipboardMessage('clipboard.invalid');
492
+ return;
493
+ }
467
494
  const text = event.clipboardData.getData('text/plain');
468
495
  const matrixSource = event.clipboardData.getData(MATH_MATRIX_CLIPBOARD_TYPE);
469
496
  const cells = matrixSource ? parseMatrixFragment(matrixSource) : matrixFromTSV(text);
@@ -510,6 +537,10 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
510
537
  .split('\n')
511
538
  .map((value) => row(value)),
512
539
  };
540
+ if (!multiline && payload.rows.length > 1) {
541
+ setClipboardMessage('clipboard.multilineRejected');
542
+ return;
543
+ }
513
544
  const next = pasteFragment(historyRef.current.present, payload, range);
514
545
  if (next === historyRef.current.present) {
515
546
  setClipboardMessage('clipboard.multilineRejected');
@@ -555,6 +586,35 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
555
586
  }
556
587
  return false;
557
588
  };
589
+ const tokenNavigation = (event) => {
590
+ if (compositionStart.current ||
591
+ event.nativeEvent.isComposing ||
592
+ matrixRange ||
593
+ !isTokenNavigationKey(event, event.currentTarget.ownerDocument.defaultView.navigator.platform))
594
+ return false;
595
+ event.preventDefault();
596
+ event.stopPropagation();
597
+ verticalNavigation.current.reset();
598
+ const state = historyRef.current.present;
599
+ const selection = range ??
600
+ (state.caret.start !== state.caret.end
601
+ ? {
602
+ anchor: { id: state.caret.id, offset: state.caret.start },
603
+ focus: { id: state.caret.id, offset: state.caret.end },
604
+ }
605
+ : undefined);
606
+ if (event.shiftKey) {
607
+ setRange(extendKeyboardRange(state, selection, event.key, 'token'));
608
+ setEditing(false);
609
+ surface.current?.focus();
610
+ }
611
+ else {
612
+ const point = collapseKeyboardRange(state.document, selection, event.key) ??
613
+ extendKeyboardRange(state, undefined, event.key, 'token').focus;
614
+ activate({ ...point, affinity: event.key === 'ArrowLeft' ? 'forward' : 'backward' });
615
+ }
616
+ return true;
617
+ };
558
618
  const keyboardSelect = (event) => {
559
619
  if (!event.shiftKey ||
560
620
  event.altKey ||
@@ -568,10 +628,26 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
568
628
  surface.current?.focus();
569
629
  return true;
570
630
  };
631
+ const directSelectionKey = (event) => {
632
+ if (compositionStart.current || event.nativeEvent.isComposing)
633
+ return false;
634
+ const current = historyRef.current.present;
635
+ const next = wrapSelectionShortcut(current, range, event);
636
+ if (!next)
637
+ return false;
638
+ event.preventDefault();
639
+ event.stopPropagation();
640
+ if (next !== current)
641
+ apply(next);
642
+ return true;
643
+ };
571
644
  const selectionKey = (event) => {
572
645
  if (event.target instanceof HTMLInputElement || compositionStart.current)
573
646
  return;
574
- if (matrixSelectionKey(event) || keyboardSelect(event))
647
+ if (matrixSelectionKey(event) ||
648
+ tokenNavigation(event) ||
649
+ keyboardSelect(event) ||
650
+ directSelectionKey(event))
575
651
  return;
576
652
  const current = historyRef.current.present, nodes = textNodes(current.document);
577
653
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
@@ -631,13 +707,13 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
631
707
  };
632
708
  const selectedRange = matrixRange
633
709
  ? undefined
634
- : range ??
710
+ : (range ??
635
711
  (state.caret.start !== state.caret.end
636
712
  ? {
637
713
  anchor: { id: state.caret.id, offset: state.caret.start },
638
714
  focus: { id: state.caret.id, offset: state.caret.end },
639
715
  }
640
- : undefined);
716
+ : undefined));
641
717
  const selectionFragment = selectedRange ? copyRange(state.document, selectedRange) : undefined;
642
718
  const canWrap = selectionFragment?.rows.length === 1 &&
643
719
  !isLiteralText(state.document, selectedRange.anchor.id);
@@ -705,10 +781,9 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
705
781
  if (!list || !option)
706
782
  return;
707
783
  const item = option.getBoundingClientRect(), box = list.getBoundingClientRect();
708
- if (item.top < box.top)
709
- list.scrollTop -= box.top - item.top;
710
- else if (item.bottom > box.bottom)
711
- list.scrollTop += item.bottom - box.bottom;
784
+ const scaleY = box.height / list.offsetHeight || 1;
785
+ list.scrollTop +=
786
+ (item.top + item.height / 2 - box.top) / scaleY - list.clientTop - list.clientHeight / 2;
712
787
  }, [selectionIndex]);
713
788
  const insertTool = (kind) => {
714
789
  if (excludedStructures.includes(kind) || matrixRange)
@@ -859,7 +934,13 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
859
934
  compositionStart.current ||
860
935
  event.keyCode === 229)
861
936
  return;
862
- if (matrixSelectionKey(event) || keyboardSelect(event))
937
+ // A held arrow can reach this handler before React emits onSelect.
938
+ // Boundary navigation must use the input's current native selection.
939
+ select(caretFrom(event.currentTarget));
940
+ if (matrixSelectionKey(event) ||
941
+ tokenNavigation(event) ||
942
+ keyboardSelect(event) ||
943
+ directSelectionKey(event))
863
944
  return;
864
945
  const current = historyRef.current.present;
865
946
  if (!event.shiftKey &&
@@ -917,7 +998,7 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
917
998
  !event.ctrlKey &&
918
999
  !event.metaKey &&
919
1000
  activeGrid(current) &&
920
- ['ArrowUp', 'ArrowLeft'].includes(event.key)) {
1001
+ ['ArrowUp', 'Backspace'].includes(event.key)) {
921
1002
  event.preventDefault();
922
1003
  apply(resizeMatrix(current, event.key === 'ArrowUp' ? 'row' : 'column', 'delete'));
923
1004
  return;
@@ -974,28 +1055,27 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
974
1055
  candidates.length);
975
1056
  return;
976
1057
  }
977
- if (event.key === 'Enter' &&
978
- candidates.length &&
979
- ((!candidates[0].wrapOperand &&
980
- !candidates[0].transformRootId &&
981
- !candidates[0].transformFenceId) ||
982
- operandMenuKey.current === queryKey)) {
983
- event.preventDefault();
984
- choose(selectedIndex);
985
- return;
986
- }
987
1058
  if (event.key === 'Enter') {
988
1059
  event.preventDefault();
989
- if (enterBehavior === 'commit' && !event.shiftKey) {
990
- onCommit?.();
991
- return;
992
- }
993
- if (activeGrid(current)) {
994
- if (activeGrid(current).matrix.type !== 'matrix')
995
- apply(resizeMatrix(current, 'row', 'insert'));
996
- }
997
- else if (multiline)
1060
+ const action = mathEnterAction({
1061
+ mode: multiline ? 'block' : 'inline',
1062
+ behavior: enterBehavior,
1063
+ shiftKey: event.shiftKey,
1064
+ gridKind: activeGrid(current)?.matrix.type,
1065
+ hasSuggestion: candidates.length > 0 &&
1066
+ ((!candidates[0].wrapOperand &&
1067
+ !candidates[0].transformRootId &&
1068
+ !candidates[0].transformFenceId) ||
1069
+ operandMenuKey.current === queryKey),
1070
+ });
1071
+ if (action === 'suggestion')
1072
+ choose(selectedIndex);
1073
+ else if (action === 'grid-row')
1074
+ apply(resizeMatrix(current, 'row', 'insert'));
1075
+ else if (action === 'newline')
998
1076
  apply(splitLine(current));
1077
+ else if (action === 'commit')
1078
+ onCommit?.();
999
1079
  return;
1000
1080
  }
1001
1081
  if (event.key === 'Escape') {
@@ -1013,7 +1093,7 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
1013
1093
  if (!event.shiftKey &&
1014
1094
  current.caret.start === current.caret.end &&
1015
1095
  ['ArrowUp', 'ArrowDown'].includes(event.key)) {
1016
- const next = moveVertical(current, event.key === 'ArrowUp' ? -1 : 1, surface.current ? renderedCaretGeometry(surface.current) : undefined);
1096
+ const next = verticalNavigation.current.move(current, event.key === 'ArrowUp' ? -1 : 1, surface.current ? renderedCaretGeometry(surface.current) : undefined);
1017
1097
  if (next !== current) {
1018
1098
  event.preventDefault();
1019
1099
  publish({ ...historyRef.current, present: next }, true);
@@ -1051,6 +1131,14 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
1051
1131
  apply(setText(current, node.text.slice(0, token.end) + node.text.slice(token.end + count), token.end));
1052
1132
  return;
1053
1133
  }
1134
+ if (event.key === 'Delete' && current.caret.end === node.text.length) {
1135
+ const joined = joinNextLine(current);
1136
+ const next = joined !== current ? joined : unwrapNext(current);
1137
+ if (next !== current) {
1138
+ event.preventDefault();
1139
+ apply(next);
1140
+ }
1141
+ }
1054
1142
  if (event.key === 'Backspace' && current.caret.start === 0) {
1055
1143
  const joined = joinPreviousLine(current);
1056
1144
  const next = joined !== current ? joined : unwrapPrevious(current);
@@ -1063,7 +1151,15 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
1063
1151
  });
1064
1152
  }) }, r.id));
1065
1153
  };
1066
- return (_jsxs("div", { className: "me-editor", "data-math-editor-owner": suggestionId, onKeyDownCapture: (event) => {
1154
+ return (_jsxs("div", { className: "me-editor", "data-math-editor-owner": suggestionId, onPointerDownCapture: () => verticalNavigation.current.reset(), onKeyDownCapture: (event) => {
1155
+ if (event.target.closest('.me-help'))
1156
+ return;
1157
+ if (!['ArrowUp', 'ArrowDown', 'Escape'].includes(event.key) ||
1158
+ event.shiftKey ||
1159
+ event.altKey ||
1160
+ event.ctrlKey ||
1161
+ event.metaKey)
1162
+ verticalNavigation.current.reset();
1067
1163
  if (event.key === 'F6' &&
1068
1164
  structureContext &&
1069
1165
  !composing &&
@@ -1179,7 +1275,7 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
1179
1275
  : undefined, "aria-keyshortcuts": action === 'delete'
1180
1276
  ? axis === 'row'
1181
1277
  ? 'Alt+Shift+ArrowUp'
1182
- : 'Alt+Shift+ArrowLeft'
1278
+ : 'Alt+Shift+Backspace'
1183
1279
  : undefined, disabled: composing ||
1184
1280
  !!matrixRange ||
1185
1281
  !!range ||
package/dist/model.d.ts CHANGED
@@ -70,6 +70,8 @@ export declare function moveCaret(state: MathState, direction: -1 | 1): MathStat
70
70
  export declare function unwrapEmptySlot(state: MathState): MathState;
71
71
  /** At the right edge of a structure, unwrap it instead of silently deleting its contents. */
72
72
  export declare function unwrapPrevious(state: MathState): MathState;
73
+ /** Delete before a structure unwraps it and keeps the caret before its retained contents. */
74
+ export declare function unwrapNext(state: MathState): MathState;
73
75
  export declare function toLatex(document: MathDocument): string;
74
76
  export interface MathHistory {
75
77
  past: MathState[];
package/dist/model.js CHANGED
@@ -81,7 +81,7 @@ const escapeLiteral = (text) => text.replace(/[\\{}_%$#&^~]/g, (char) => ({
81
81
  '\\': '\\textbackslash{}',
82
82
  '^': '\\textasciicircum{}',
83
83
  '~': '\\textasciitilde{}',
84
- }[char] ?? `\\${char}`));
84
+ })[char] ?? `\\${char}`);
85
85
  /** Wrap the selection, or the preceding simple operand; never swallow a preceding + or =. */
86
86
  export function insertStructure(state, kind, consumeOperand = false, mathStyle) {
87
87
  if (isLiteralText(state.document, state.caret.id))
@@ -339,6 +339,28 @@ export function unwrapPrevious(state) {
339
339
  const offset = target.text.length - current.text.length;
340
340
  return { document, caret: { id: target.id, start: offset, end: offset } };
341
341
  }
342
+ /** Delete before a structure unwraps it and keeps the caret before its retained contents. */
343
+ export function unwrapNext(state) {
344
+ if (state.caret.start !== state.caret.end)
345
+ return state;
346
+ const at = documentRows(state.document)
347
+ .map((line) => locate(line, state.caret.id))
348
+ .find(Boolean);
349
+ if (!at)
350
+ return state;
351
+ const current = at.row.children[at.index];
352
+ const structure = at.row.children[at.index + 1];
353
+ const after = at.row.children[at.index + 2];
354
+ if (current.type !== 'text' ||
355
+ state.caret.end !== current.text.length ||
356
+ !structure ||
357
+ structure.type === 'text' ||
358
+ isGrid(structure) ||
359
+ after?.type !== 'text')
360
+ return state;
361
+ const next = unwrapPrevious({ ...state, caret: { id: after.id, start: 0, end: 0 } });
362
+ return next.document === state.document ? state : { ...next, caret: { ...state.caret } };
363
+ }
342
364
  const symbols = Object.fromEntries(mathSymbols
343
365
  .filter(([value, , , , latex]) => value !== latex)
344
366
  .map(([value, , , , latex]) => [value, `${latex} `]));
package/dist/range.d.ts CHANGED
@@ -28,6 +28,14 @@ export declare function parseFragment(source: string): MathFragment | undefined;
28
28
  export declare const wrappingKinds: readonly ["bold", "calligraphic", "blackboard", "fraction", "binomial", "root", "vec", "hat", "tilde", "bar", "dot", "ddot", "widehat", "widetilde", "overline", "indexedRoot", "norm", "braces", "angle", "openClosed", "closedOpen", "overbrace", "underbrace", "overset", "underset", "superscript", "subscript", "parentheses", "brackets", "absolute"];
29
29
  export type WrappingKind = (typeof wrappingKinds)[number];
30
30
  /** Wrap one balanced row. Cross-slot ranges expand to their common structure. */
31
- export declare function wrapRange(state: MathState, range: MathRange, kind: WrappingKind): MathState;
31
+ export declare function wrapRange(state: MathState, range: MathRange, kind: WrappingKind, fractionSlot?: 'numerator' | 'denominator'): MathState;
32
32
  /** Keyboard ranges share drag-selection normalization. Cross a structure as one atom. */
33
- export declare function extendKeyboardRange(state: MathState, range: MathRange | undefined, key: string): MathRange;
33
+ export declare function extendKeyboardRange(state: MathState, range: MathRange | undefined, key: string, unit?: 'character' | 'token'): MathRange;
34
+ /** Match host-platform word movement without taking AltGr or OS shortcuts. */
35
+ export declare function isTokenNavigationKey(event: {
36
+ key: string;
37
+ altKey: boolean;
38
+ ctrlKey: boolean;
39
+ metaKey: boolean;
40
+ isComposing?: boolean;
41
+ }, platform: string): boolean;
package/dist/range.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { tokenizeMathText } from './tokens.js';
1
2
  import { fenceCharacters } from './fences.js';
2
3
  import { documentRows, isLiteralText, row, textNode, textNodes, } from './model.js';
3
4
  const size = (node) => (node.type === 'text' ? node.text.length : 1);
@@ -353,7 +354,7 @@ export const wrappingKinds = [
353
354
  'absolute',
354
355
  ];
355
356
  /** Wrap one balanced row. Cross-slot ranges expand to their common structure. */
356
- export function wrapRange(state, range, kind) {
357
+ export function wrapRange(state, range, kind, fractionSlot = 'numerator') {
357
358
  if (isLiteralText(state.document, range.anchor.id))
358
359
  return state;
359
360
  const fragment = copyRange(state.document, range);
@@ -367,7 +368,8 @@ export function wrapRange(state, range, kind) {
367
368
  : ['parentheses', 'brackets', 'absolute', 'root', 'matrix'].includes(meaningful[0].type));
368
369
  if (kind === 'superscript' && !grouped)
369
370
  content = normalize([{ type: 'parentheses', id: row().id, slots: [content] }]);
370
- const slots = ['indexedRoot', 'overset', 'underset', 'overbrace', 'underbrace'].includes(kind)
371
+ const denominator = kind === 'fraction' && fractionSlot === 'denominator';
372
+ const slots = denominator || ['indexedRoot', 'overset', 'underset', 'overbrace', 'underbrace'].includes(kind)
371
373
  ? [row(), content]
372
374
  : ['fraction', 'binomial', 'superscript', 'subscript'].includes(kind)
373
375
  ? [content, row()]
@@ -390,11 +392,14 @@ export function wrapRange(state, range, kind) {
390
392
  const wrapper = location?.row.children[location.index - 1];
391
393
  if (!wrapper || wrapper.type === 'text')
392
394
  return next;
393
- const target = wrapper.slots[['indexedRoot', 'overset', 'underset', 'overbrace', 'underbrace'].includes(kind) ? 0 : 1].children[0];
395
+ const target = wrapper.slots[denominator ||
396
+ ['indexedRoot', 'overset', 'underset', 'overbrace', 'underbrace'].includes(kind)
397
+ ? 0
398
+ : 1].children[0];
394
399
  return { ...next, caret: { id: target.id, start: 0, end: 0 } };
395
400
  }
396
401
  /** Keyboard ranges share drag-selection normalization. Cross a structure as one atom. */
397
- export function extendKeyboardRange(state, range, key) {
402
+ export function extendKeyboardRange(state, range, key, unit = 'character') {
398
403
  const anchor = range?.anchor ?? { id: state.caret.id, offset: state.caret.start };
399
404
  const point = range?.focus ?? { id: state.caret.id, offset: state.caret.end };
400
405
  const at = address(state.document, point);
@@ -414,11 +419,20 @@ export function extendKeyboardRange(state, range, key) {
414
419
  const node = local.row.children[local.index];
415
420
  if (node.type !== 'text')
416
421
  return { anchor, focus: point };
422
+ if (unit === 'token') {
423
+ const tokens = tokenizeMathText(node.text).filter((token) => token.kind !== 'space');
424
+ const token = direction < 0
425
+ ? tokens.reverse().find((token) => token.start < point.offset)
426
+ : tokens.find((token) => token.end > point.offset);
427
+ if (token)
428
+ return { anchor, focus: { id: point.id, offset: direction < 0 ? token.start : token.end } };
429
+ // Skip remaining whitespace before crossing a structure or leaving this slot.
430
+ }
417
431
  const chars = direction < 0
418
432
  ? Array.from(node.text.slice(0, point.offset))
419
433
  : Array.from(node.text.slice(point.offset));
420
434
  const char = direction < 0 ? chars.at(-1) : chars[0];
421
- if (char)
435
+ if (char && unit === 'character')
422
436
  return { anchor, focus: { id: point.id, offset: point.offset + direction * char.length } };
423
437
  // A sibling structure is skipped intact; leaving a nested slot selects its enclosing structure.
424
438
  for (let depth = at.path.length - 1; depth >= 0; depth--) {
@@ -434,8 +448,19 @@ export function extendKeyboardRange(state, range, key) {
434
448
  }
435
449
  const line = documentRows(state.document)[at.line + direction];
436
450
  if (!line)
437
- return { anchor, focus: point };
451
+ return {
452
+ anchor,
453
+ focus: unit === 'token' ? { id: point.id, offset: direction < 0 ? 0 : node.text.length } : point,
454
+ };
438
455
  const nodes = textNodes({ version: 1, root: line });
439
456
  const target = direction < 0 ? nodes.at(-1) : nodes[0];
440
457
  return { anchor, focus: { id: target.id, offset: direction < 0 ? target.text.length : 0 } };
441
458
  }
459
+ /** Match host-platform word movement without taking AltGr or OS shortcuts. */
460
+ export function isTokenNavigationKey(event, platform) {
461
+ if (event.isComposing || event.metaKey || !['ArrowLeft', 'ArrowRight'].includes(event.key))
462
+ return false;
463
+ return /Mac|iPhone|iPad|iPod/.test(platform)
464
+ ? event.altKey && !event.ctrlKey
465
+ : event.ctrlKey && !event.altKey;
466
+ }
@@ -0,0 +1,13 @@
1
+ import type { MathState } from './model.js';
2
+ import { type MathRange } from './range.js';
3
+ /** A typed opening symbol wraps a selection before ordinary replacement runs.
4
+ * Shift is allowed because several symbols require it on common keyboards.
5
+ * Returning the original state consumes unsupported multi-line wrapping safely.
6
+ */
7
+ export declare function wrapSelectionShortcut(state: MathState, range: MathRange | undefined, event: {
8
+ key: string;
9
+ ctrlKey?: boolean;
10
+ metaKey?: boolean;
11
+ altKey?: boolean;
12
+ isComposing?: boolean;
13
+ }): MathState | undefined;
@@ -0,0 +1,35 @@
1
+ import { isLiteralText } from './model.js';
2
+ import { wrapRange } from './range.js';
3
+ const wrappers = {
4
+ '(': 'parentheses',
5
+ '[': 'brackets',
6
+ '{': 'braces',
7
+ '|': 'absolute',
8
+ '/': 'fraction',
9
+ '^': 'superscript',
10
+ _: 'subscript',
11
+ };
12
+ /** A typed opening symbol wraps a selection before ordinary replacement runs.
13
+ * Shift is allowed because several symbols require it on common keyboards.
14
+ * Returning the original state consumes unsupported multi-line wrapping safely.
15
+ */
16
+ export function wrapSelectionShortcut(state, range, event) {
17
+ if (event.ctrlKey || event.metaKey || event.altKey || event.isComposing)
18
+ return;
19
+ const kind = wrappers[event.key];
20
+ if (!kind)
21
+ return;
22
+ const selection = range ??
23
+ (state.caret.start !== state.caret.end
24
+ ? {
25
+ anchor: { id: state.caret.id, offset: state.caret.start },
26
+ focus: { id: state.caret.id, offset: state.caret.end },
27
+ }
28
+ : undefined);
29
+ if (!selection ||
30
+ (selection.anchor.id === selection.focus.id &&
31
+ selection.anchor.offset === selection.focus.offset) ||
32
+ isLiteralText(state.document, selection.anchor.id))
33
+ return;
34
+ return wrapRange(state, selection, kind);
35
+ }
package/dist/session.d.ts CHANGED
@@ -24,6 +24,7 @@ export type MathCommand = {
24
24
  } | {
25
25
  type: 'structure';
26
26
  kind: StructureKind;
27
+ fractionSlot?: 'numerator' | 'denominator';
27
28
  } | {
28
29
  type: 'template';
29
30
  id: string;
package/dist/session.js CHANGED
@@ -215,7 +215,7 @@ export function createMathSession(options = {}) {
215
215
  return mode === 'inline' ? false : apply(splitLine(state));
216
216
  if (range)
217
217
  return (wrappingKinds.includes(command.kind) &&
218
- apply(wrapRange(state, range, command.kind)));
218
+ apply(wrapRange(state, range, command.kind, command.fractionSlot)));
219
219
  return apply(insertStructure(state, command.kind, ['fraction', 'binomial', 'superscript', 'subscript'].includes(command.kind)));
220
220
  },
221
221
  destroy() {
package/dist/symbols.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One catalog powers discovery and LaTeX export. */
2
- export declare const mathSymbols: readonly [readonly ["↑", "symbol.upArrow", "uparrow arrow 화살표", "- ->", "\\uparrow"], readonly ["↕", "symbol.upDownArrow", "updownarrow arrow 화살표", "- ->", "\\updownarrow"], readonly ["⇐", "symbol.doubleLeftArrow", "Leftarrow arrow 화살표", "- ->", "\\Leftarrow"], readonly ["⇑", "symbol.doubleUpArrow", "Uparrow arrow 화살표", "- ->", "\\Uparrow"], readonly ["⇓", "symbol.doubleDownArrow", "Downarrow arrow 화살표", "- ->", "\\Downarrow"], readonly ["⇕", "symbol.doubleUpDownArrow", "Updownarrow arrow 화살표", "- ->", "\\Updownarrow"], readonly ["↗", "symbol.northEastArrow", "nearrow arrow 화살표", "- ->", "\\nearrow"], readonly ["↘", "symbol.southEastArrow", "searrow arrow 화살표", "- ->", "\\searrow"], readonly ["↙", "symbol.southWestArrow", "swarrow arrow 화살표", "- ->", "\\swarrow"], readonly ["↖", "symbol.northWestArrow", "nwarrow arrow 화살표", "- ->", "\\nwarrow"], readonly ["⟶", "symbol.longRightArrow", "longrightarrow arrow 화살표", "- ->", "\\longrightarrow"], readonly ["⟵", "symbol.longLeftArrow", "longleftarrow arrow 화살표", "- ->", "\\longleftarrow"], readonly ["⟷", "symbol.longLeftRightArrow", "longleftrightarrow arrow 화살표", "-", "\\longleftrightarrow"], readonly ["⟹", "symbol.longDoubleRightArrow", "Longrightarrow arrow 화살표", "- ->", "\\Longrightarrow"], readonly ["⟸", "symbol.longDoubleLeftArrow", "Longleftarrow arrow 화살표", "- ->", "\\Longleftarrow"], readonly ["⟺", "symbol.longDoubleLeftRightArrow", "Longleftrightarrow arrow 화살표", "", "\\Longleftrightarrow"], readonly ["↪", "symbol.hookRightArrow", "hookrightarrow arrow 화살표", "- ->", "\\hookrightarrow"], readonly ["↩", "symbol.hookLeftArrow", "hookleftarrow arrow 화살표", "- ->", "\\hookleftarrow"], readonly ["⇀", "symbol.rightHarpoonUp", "rightharpoonup arrow 화살표", "- ->", "\\rightharpoonup"], readonly ["↼", "symbol.leftHarpoonUp", "leftharpoonup arrow 화살표", "- ->", "\\leftharpoonup"], readonly ["⇁", "symbol.rightHarpoonDown", "rightharpoondown arrow 화살표", "- ->", "\\rightharpoondown"], readonly ["↽", "symbol.leftHarpoonDown", "leftharpoondown arrow 화살표", "- ->", "\\leftharpoondown"], readonly ["⇌", "symbol.rightLeftHarpoons", "rightleftharpoons arrow 화살표", "- ->", "\\rightleftharpoons"], readonly ["⇋", "symbol.leftRightHarpoons", "leftrightharpoons arrow 화살표", "- ->", "\\leftrightharpoons"], readonly ["↓", "symbol.downArrow", "downarrow down 아래화살표 아래쪽화살표", "- ->", "\\downarrow"], readonly ["⩾", "symbol.greaterOrEqualSlanted", "geqslant", "", "\\geqslant"], readonly ["⩽", "symbol.lessOrEqualSlanted", "leqslant", "", "\\leqslant"], readonly ["∋", "symbol.contains", "ni contains", "", "\\ni"], readonly ["⊃", "symbol.superset", "supset superset", "", "\\supset"], readonly ["⊇", "symbol.supersetOrEqual", "supseteq", "", "\\supseteq"], readonly ["⊈", "symbol.notSubsetOrEqual", "nsubseteq", "", "\\nsubseteq"], readonly ["∖", "symbol.setDifference", "setminus difference", "", "\\setminus"], readonly ["ℕ", "symbol.naturals", "naturals natural", "", "\\mathbb{N}"], readonly ["ℤ", "symbol.integers", "integers integer", "", "\\mathbb{Z}"], readonly ["ℚ", "symbol.rationals", "rationals rational", "", "\\mathbb{Q}"], readonly ["ℝ", "symbol.reals", "reals real", "", "\\mathbb{R}"], readonly ["ℂ", "symbol.complexes", "complexes complex", "", "\\mathbb{C}"], readonly ["⊥", "symbol.perpendicular", "perp perpendicular", "", "\\perp"], readonly ["∥", "symbol.parallel", "parallel", "", "\\parallel"], readonly ["≅", "symbol.congruent", "cong congruent", "", "\\cong"], readonly ["∼", "symbol.similar", "sim similar", "", "\\sim"], readonly ["↦", "symbol.mapsTo", "mapsto maps arrow 화살표", "|-> - ->", "\\mapsto"], readonly ["…", "symbol.ellipsis", "ldots ellipsis", "", "\\ldots"], readonly ["⋯", "symbol.centeredDots", "cdots", "", "\\cdots"], readonly ["⋮", "symbol.verticalDots", "vdots", "", "\\vdots"], readonly ["⋱", "symbol.diagonalDots", "ddots", "", "\\ddots"], readonly ["°", "symbol.degree", "degree degrees 각도", "", "{}^{\\circ}"], readonly ["∠", "symbol.angle", "angle", "", "\\angle"], readonly ["+", "symbol.plus", "plus sum", "+", "+"], readonly ["−", "symbol.minus", "minus", "-", "-"], readonly ["±", "symbol.plusMinus", "plusminus sum", "+- +", "\\pm"], readonly ["∓", "symbol.minusPlus", "minusplus", "-+ -", "\\mp"], readonly ["×", "symbol.times", "times multiply", "*", "\\times"], readonly ["·", "symbol.dot", "dot multiply", "*", "\\cdot"], readonly ["÷", "symbol.divide", "divide division", "/", "\\div"], readonly ["=", "symbol.equal", "equal", "=", "="], readonly ["≠", "symbol.notEqual", "neq unequal", "!= /= =", "\\ne"], readonly ["≈", "symbol.approximatelyEqual", "approx", "~ ~~ =", "\\approx"], readonly ["≡", "symbol.equivalent", "equiv", "=== =", "\\equiv"], readonly ["<", "symbol.lessThan", "less", "<", "<"], readonly [">", "symbol.greaterThan", "greater", ">", ">"], readonly ["≤", "symbol.lessOrEqual", "leq less", "<= <- <", "\\le"], readonly ["≥", "symbol.greaterOrEqual", "geq greater", ">= >- >", "\\ge"], readonly ["∝", "symbol.proportional", "proportional", "~", "\\propto"], readonly ["→", "symbol.rightArrow", "rightarrow arrow", "-> -", "\\to"], readonly ["←", "symbol.leftArrow", "leftarrow arrow", "<- < - ->", "\\leftarrow"], readonly ["↔", "symbol.leftRightArrow", "leftrightarrow arrow", "<-> - ->", "\\leftrightarrow"], readonly ["⇒", "symbol.implies", "implies arrow 화살표", "=> = - ->", "\\Rightarrow"], readonly ["⇔", "symbol.iff", "iff arrow 화살표", "<=> - ->", "\\Leftrightarrow"], readonly ["∈", "symbol.elementOf", "in element", "E", "\\in"], readonly ["∉", "symbol.notElementOf", "notin element", "E/", "\\notin"], readonly ["∀", "symbol.forAll", "forall", "A", "\\forall"], readonly ["∃", "symbol.exists", "exists", "E", "\\exists"], readonly ["∪", "symbol.union", "union", "U", "\\cup"], readonly ["∩", "symbol.intersection", "intersection", "", "\\cap"], readonly ["⊂", "symbol.subset", "subset", "<", "\\subset"], readonly ["⊆", "symbol.subsetOrEqual", "subseteq", "<=", "\\subseteq"], readonly ["∅", "symbol.emptySet", "emptyset", "", "\\emptyset"], readonly ["∞", "symbol.infinity", "infinity infty", "", "\\infty"], readonly ["∂", "symbol.partialDerivative", "partial derivative", "", "\\partial"], readonly ["∇", "symbol.nabla", "nabla gradient", "", "\\nabla"], readonly ["∧", "symbol.logicalAnd", "and wedge", "^", "\\wedge"], readonly ["∨", "symbol.logicalOr", "or vee", "", "\\vee"], readonly ["¬", "symbol.logicalNot", "not neg", "!", "\\neg"], readonly ["α", "symbol.alpha", "alpha", "", "\\alpha"], readonly ["β", "symbol.beta", "beta", "", "\\beta"], readonly ["γ", "symbol.gamma", "gamma", "", "\\gamma"], readonly ["δ", "symbol.delta", "delta", "", "\\delta"], readonly ["ε", "symbol.epsilon", "epsilon", "", "\\epsilon"], readonly ["ζ", "symbol.zeta", "zeta", "", "\\zeta"], readonly ["η", "symbol.eta", "eta", "", "\\eta"], readonly ["θ", "symbol.theta", "theta", "", "\\theta"], readonly ["ι", "symbol.iota", "iota", "", "\\iota"], readonly ["κ", "symbol.kappa", "kappa", "", "\\kappa"], readonly ["λ", "symbol.lambda", "lambda", "", "\\lambda"], readonly ["μ", "symbol.mu", "mu", "", "\\mu"], readonly ["ν", "symbol.nu", "nu", "", "\\nu"], readonly ["ξ", "symbol.xi", "xi", "", "\\xi"], readonly ["π", "symbol.pi", "pi", "", "\\pi"], readonly ["ρ", "symbol.rho", "rho", "", "\\rho"], readonly ["σ", "symbol.sigma", "sigma", "", "\\sigma"], readonly ["τ", "symbol.tau", "tau", "", "\\tau"], readonly ["υ", "symbol.upsilon", "upsilon", "", "\\upsilon"], readonly ["φ", "symbol.phi", "phi", "", "\\phi"], readonly ["χ", "symbol.chi", "chi", "", "\\chi"], readonly ["ψ", "symbol.psi", "psi", "", "\\psi"], readonly ["ω", "symbol.omega", "omega", "", "\\omega"], readonly ["Γ", "symbol.upperGamma", "Gamma", "", "\\Gamma"], readonly ["Δ", "symbol.upperDelta", "Delta", "", "\\Delta"], readonly ["Θ", "symbol.upperTheta", "Theta", "", "\\Theta"], readonly ["Λ", "symbol.upperLambda", "Lambda", "", "\\Lambda"], readonly ["Ξ", "symbol.upperXi", "Xi", "", "\\Xi"], readonly ["Π", "symbol.upperPi", "Pi", "", "\\Pi"], readonly ["Σ", "symbol.upperSigma", "Sigma", "", "\\Sigma"], readonly ["Φ", "symbol.upperPhi", "Phi", "", "\\Phi"], readonly ["Ψ", "symbol.upperPsi", "Psi", "", "\\Psi"], readonly ["Ω", "symbol.upperOmega", "Omega", "", "\\Omega"]];
2
+ export declare const mathSymbols: readonly [readonly ["↑", "symbol.upArrow", "uparrow arrow 화살표", "- ->", "\\uparrow"], readonly ["↕", "symbol.upDownArrow", "updownarrow arrow 화살표", "- ->", "\\updownarrow"], readonly ["⇐", "symbol.doubleLeftArrow", "Leftarrow arrow 화살표", "- ->", "\\Leftarrow"], readonly ["⇑", "symbol.doubleUpArrow", "Uparrow arrow 화살표", "- ->", "\\Uparrow"], readonly ["⇓", "symbol.doubleDownArrow", "Downarrow arrow 화살표", "- ->", "\\Downarrow"], readonly ["⇕", "symbol.doubleUpDownArrow", "Updownarrow arrow 화살표", "- ->", "\\Updownarrow"], readonly ["↗", "symbol.northEastArrow", "nearrow arrow 화살표", "- ->", "\\nearrow"], readonly ["↘", "symbol.southEastArrow", "searrow arrow 화살표", "- ->", "\\searrow"], readonly ["↙", "symbol.southWestArrow", "swarrow arrow 화살표", "- ->", "\\swarrow"], readonly ["↖", "symbol.northWestArrow", "nwarrow arrow 화살표", "- ->", "\\nwarrow"], readonly ["⟶", "symbol.longRightArrow", "longrightarrow arrow 화살표", "- ->", "\\longrightarrow"], readonly ["⟵", "symbol.longLeftArrow", "longleftarrow arrow 화살표", "- ->", "\\longleftarrow"], readonly ["⟷", "symbol.longLeftRightArrow", "longleftrightarrow arrow 화살표", "-", "\\longleftrightarrow"], readonly ["⟹", "symbol.longDoubleRightArrow", "Longrightarrow arrow 화살표", "- ->", "\\Longrightarrow"], readonly ["⟸", "symbol.longDoubleLeftArrow", "Longleftarrow arrow 화살표", "- ->", "\\Longleftarrow"], readonly ["⟺", "symbol.longDoubleLeftRightArrow", "Longleftrightarrow arrow 화살표", "", "\\Longleftrightarrow"], readonly ["↪", "symbol.hookRightArrow", "hookrightarrow arrow 화살표", "- ->", "\\hookrightarrow"], readonly ["↩", "symbol.hookLeftArrow", "hookleftarrow arrow 화살표", "- ->", "\\hookleftarrow"], readonly ["⇀", "symbol.rightHarpoonUp", "rightharpoonup arrow 화살표", "- ->", "\\rightharpoonup"], readonly ["↼", "symbol.leftHarpoonUp", "leftharpoonup arrow 화살표", "- ->", "\\leftharpoonup"], readonly ["⇁", "symbol.rightHarpoonDown", "rightharpoondown arrow 화살표", "- ->", "\\rightharpoondown"], readonly ["↽", "symbol.leftHarpoonDown", "leftharpoondown arrow 화살표", "- ->", "\\leftharpoondown"], readonly ["⇌", "symbol.rightLeftHarpoons", "rightleftharpoons arrow 화살표", "- ->", "\\rightleftharpoons"], readonly ["⇋", "symbol.leftRightHarpoons", "leftrightharpoons arrow 화살표", "- ->", "\\leftrightharpoons"], readonly ["↓", "symbol.downArrow", "downarrow down 아래화살표 아래쪽화살표", "- ->", "\\downarrow"], readonly ["⩾", "symbol.greaterOrEqualSlanted", "geqslant", "", "\\geqslant"], readonly ["⩽", "symbol.lessOrEqualSlanted", "leqslant", "", "\\leqslant"], readonly ["∋", "symbol.contains", "ni contains", "", "\\ni"], readonly ["⊃", "symbol.superset", "supset superset", "", "\\supset"], readonly ["⊇", "symbol.supersetOrEqual", "supseteq", "", "\\supseteq"], readonly ["⊈", "symbol.notSubsetOrEqual", "nsubseteq", "", "\\nsubseteq"], readonly ["∖", "symbol.setDifference", "setminus difference", "", "\\setminus"], readonly ["ℕ", "symbol.naturals", "naturals natural", "", "\\mathbb{N}"], readonly ["ℤ", "symbol.integers", "integers integer", "", "\\mathbb{Z}"], readonly ["ℚ", "symbol.rationals", "rationals rational", "", "\\mathbb{Q}"], readonly ["ℝ", "symbol.reals", "reals real", "", "\\mathbb{R}"], readonly ["ℂ", "symbol.complexes", "complexes complex", "", "\\mathbb{C}"], readonly ["⊥", "symbol.perpendicular", "perp perpendicular", "", "\\perp"], readonly ["∥", "symbol.parallel", "parallel", "", "\\parallel"], readonly ["≅", "symbol.congruent", "cong congruent", "", "\\cong"], readonly ["∼", "symbol.similar", "sim similar", "", "\\sim"], readonly ["↦", "symbol.mapsTo", "mapsto maps arrow 화살표", "|-> - ->", "\\mapsto"], readonly ["…", "symbol.ellipsis", "ldots ellipsis", "", "\\ldots"], readonly ["⋯", "symbol.centeredDots", "cdots", "", "\\cdots"], readonly ["⋮", "symbol.verticalDots", "vdots", "", "\\vdots"], readonly ["⋱", "symbol.diagonalDots", "ddots", "", "\\ddots"], readonly ["′", "symbol.prime", "prime 프라임 미분", "", "\\prime"], readonly ["°", "symbol.degree", "degree degrees 각도", "", "{}^{\\circ}"], readonly ["∠", "symbol.angle", "angle", "", "\\angle"], readonly ["+", "symbol.plus", "plus sum", "+", "+"], readonly ["−", "symbol.minus", "minus", "-", "-"], readonly ["±", "symbol.plusMinus", "plusminus sum", "+- +", "\\pm"], readonly ["∓", "symbol.minusPlus", "minusplus", "-+ -", "\\mp"], readonly ["×", "symbol.times", "times multiply", "*", "\\times"], readonly ["·", "symbol.dot", "dot multiply", "*", "\\cdot"], readonly ["÷", "symbol.divide", "divide division", "/", "\\div"], readonly ["=", "symbol.equal", "equal", "=", "="], readonly ["≠", "symbol.notEqual", "neq unequal", "!= /= =", "\\ne"], readonly ["≈", "symbol.approximatelyEqual", "approx", "~ ~~ =", "\\approx"], readonly ["≡", "symbol.equivalent", "equiv", "=== =", "\\equiv"], readonly ["<", "symbol.lessThan", "less", "<", "<"], readonly [">", "symbol.greaterThan", "greater", ">", ">"], readonly ["≤", "symbol.lessOrEqual", "leq less", "<= <- <", "\\le"], readonly ["≥", "symbol.greaterOrEqual", "geq greater", ">= >- >", "\\ge"], readonly ["∝", "symbol.proportional", "proportional", "~", "\\propto"], readonly ["→", "symbol.rightArrow", "rightarrow arrow", "-> -", "\\to"], readonly ["←", "symbol.leftArrow", "leftarrow arrow", "<- < - ->", "\\leftarrow"], readonly ["↔", "symbol.leftRightArrow", "leftrightarrow arrow", "<-> - ->", "\\leftrightarrow"], readonly ["⇒", "symbol.implies", "implies arrow 화살표", "=> = - ->", "\\Rightarrow"], readonly ["⇔", "symbol.iff", "iff arrow 화살표", "<=> - ->", "\\Leftrightarrow"], readonly ["∈", "symbol.elementOf", "in element", "E", "\\in"], readonly ["∉", "symbol.notElementOf", "notin element", "E/", "\\notin"], readonly ["∀", "symbol.forAll", "forall", "A", "\\forall"], readonly ["∃", "symbol.exists", "exists", "E", "\\exists"], readonly ["∪", "symbol.union", "union", "U", "\\cup"], readonly ["∩", "symbol.intersection", "intersection", "", "\\cap"], readonly ["⊂", "symbol.subset", "subset", "<", "\\subset"], readonly ["⊆", "symbol.subsetOrEqual", "subseteq", "<=", "\\subseteq"], readonly ["∅", "symbol.emptySet", "emptyset", "", "\\emptyset"], readonly ["∞", "symbol.infinity", "infinity infty", "", "\\infty"], readonly ["∂", "symbol.partialDerivative", "partial derivative", "", "\\partial"], readonly ["∇", "symbol.nabla", "nabla gradient", "", "\\nabla"], readonly ["∧", "symbol.logicalAnd", "and wedge", "^", "\\wedge"], readonly ["∨", "symbol.logicalOr", "or vee", "", "\\vee"], readonly ["¬", "symbol.logicalNot", "not neg", "!", "\\neg"], readonly ["α", "symbol.alpha", "alpha", "", "\\alpha"], readonly ["β", "symbol.beta", "beta", "", "\\beta"], readonly ["γ", "symbol.gamma", "gamma", "", "\\gamma"], readonly ["δ", "symbol.delta", "delta", "", "\\delta"], readonly ["ε", "symbol.epsilon", "epsilon", "", "\\epsilon"], readonly ["ζ", "symbol.zeta", "zeta", "", "\\zeta"], readonly ["η", "symbol.eta", "eta", "", "\\eta"], readonly ["θ", "symbol.theta", "theta", "", "\\theta"], readonly ["ι", "symbol.iota", "iota", "", "\\iota"], readonly ["κ", "symbol.kappa", "kappa", "", "\\kappa"], readonly ["λ", "symbol.lambda", "lambda", "", "\\lambda"], readonly ["μ", "symbol.mu", "mu", "", "\\mu"], readonly ["ν", "symbol.nu", "nu", "", "\\nu"], readonly ["ξ", "symbol.xi", "xi", "", "\\xi"], readonly ["π", "symbol.pi", "pi", "", "\\pi"], readonly ["ρ", "symbol.rho", "rho", "", "\\rho"], readonly ["σ", "symbol.sigma", "sigma", "", "\\sigma"], readonly ["τ", "symbol.tau", "tau", "", "\\tau"], readonly ["υ", "symbol.upsilon", "upsilon", "", "\\upsilon"], readonly ["φ", "symbol.phi", "phi", "", "\\phi"], readonly ["χ", "symbol.chi", "chi", "", "\\chi"], readonly ["ψ", "symbol.psi", "psi", "", "\\psi"], readonly ["ω", "symbol.omega", "omega", "", "\\omega"], readonly ["Γ", "symbol.upperGamma", "Gamma", "", "\\Gamma"], readonly ["Δ", "symbol.upperDelta", "Delta", "", "\\Delta"], readonly ["Θ", "symbol.upperTheta", "Theta", "", "\\Theta"], readonly ["Λ", "symbol.upperLambda", "Lambda", "", "\\Lambda"], readonly ["Ξ", "symbol.upperXi", "Xi", "", "\\Xi"], readonly ["Π", "symbol.upperPi", "Pi", "", "\\Pi"], readonly ["Σ", "symbol.upperSigma", "Sigma", "", "\\Sigma"], readonly ["Φ", "symbol.upperPhi", "Phi", "", "\\Phi"], readonly ["Ψ", "symbol.upperPsi", "Psi", "", "\\Psi"], readonly ["Ω", "symbol.upperOmega", "Omega", "", "\\Omega"]];
package/dist/symbols.js CHANGED
@@ -70,6 +70,7 @@ export const mathSymbols = [
70
70
  ['⋯', 'symbol.centeredDots', 'cdots', '', '\\cdots'],
71
71
  ['⋮', 'symbol.verticalDots', 'vdots', '', '\\vdots'],
72
72
  ['⋱', 'symbol.diagonalDots', 'ddots', '', '\\ddots'],
73
+ ['′', 'symbol.prime', 'prime 프라임 미분', '', '\\prime'],
73
74
  ['°', 'symbol.degree', 'degree degrees 각도', '', '{}^{\\circ}'],
74
75
  ['∠', 'symbol.angle', 'angle', '', '\\angle'],
75
76
  ['+', 'symbol.plus', 'plus sum', '+', '+'],