@drghaliasri/butex 5.4.6 → 5.5.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.
@@ -1133,7 +1133,7 @@ function parseReferences(json) {
1133
1133
  }
1134
1134
  return references;
1135
1135
  }
1136
- function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = []) {
1136
+ function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = [], formats = []) {
1137
1137
  const mode = options.mode ?? "english";
1138
1138
  const spans = detectInlineSpans2(value);
1139
1139
  const mathSpans = spans.filter((span) => span.kind === "math");
@@ -1145,7 +1145,7 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1145
1145
  }
1146
1146
  for (const span of spans) {
1147
1147
  if (span.start > index) {
1148
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index, span.start) });
1148
+ pushFormattedTextTokens(tokens, value.slice(index, span.start), index, formats);
1149
1149
  }
1150
1150
  if (span.kind === "cite") {
1151
1151
  tokens.push({
@@ -1201,9 +1201,81 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1201
1201
  index = span.end;
1202
1202
  }
1203
1203
  if (index < value.length || tokens.length === 0) {
1204
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index) });
1204
+ pushFormattedTextTokens(tokens, value.slice(index), index, formats);
1205
+ }
1206
+ return { id: document2Id("field"), tokens: compactTextTokens(tokens) };
1207
+ }
1208
+ function styleFromFormat(format) {
1209
+ const style = {};
1210
+ if (format.bold === true) {
1211
+ style.bold = true;
1212
+ }
1213
+ if (format.italic === true) {
1214
+ style.italic = true;
1215
+ }
1216
+ if (format.underline === true) {
1217
+ style.underline = true;
1218
+ }
1219
+ return style.bold || style.italic || style.underline ? style : null;
1220
+ }
1221
+ function mergeTextStyle(base, added) {
1222
+ return {
1223
+ ...base?.bold ? { bold: true } : {},
1224
+ ...base?.italic ? { italic: true } : {},
1225
+ ...base?.underline ? { underline: true } : {},
1226
+ ...added.bold ? { bold: true } : {},
1227
+ ...added.italic ? { italic: true } : {},
1228
+ ...added.underline ? { underline: true } : {}
1229
+ };
1230
+ }
1231
+ function textStylesEqual(a, b) {
1232
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
1233
+ }
1234
+ function compactTextTokens(tokens) {
1235
+ const compacted = [];
1236
+ for (const token of tokens) {
1237
+ const previous = compacted[compacted.length - 1];
1238
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual(previous.style, token.style)) {
1239
+ previous.text += token.text;
1240
+ } else {
1241
+ compacted.push(token);
1242
+ }
1243
+ }
1244
+ return compacted;
1245
+ }
1246
+ function styleKey(style) {
1247
+ return `${style?.bold ? "b" : ""}${style?.italic ? "i" : ""}${style?.underline ? "u" : ""}`;
1248
+ }
1249
+ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1250
+ if (text.length === 0) {
1251
+ tokens.push({ id: document2Id("text"), kind: "text", text });
1252
+ return;
1253
+ }
1254
+ const styles = Array.from({ length: text.length });
1255
+ for (const format of formats) {
1256
+ const style = styleFromFormat(format);
1257
+ if (!style || !Number.isFinite(format.start) || !Number.isFinite(format.end)) {
1258
+ continue;
1259
+ }
1260
+ const start = Math.max(0, Math.min(text.length, Math.trunc(format.start) - sourceStart));
1261
+ const end = Math.max(0, Math.min(text.length, Math.trunc(format.end) - sourceStart));
1262
+ if (end <= start) {
1263
+ continue;
1264
+ }
1265
+ for (let index = start; index < end; index += 1) {
1266
+ styles[index] = mergeTextStyle(styles[index], style);
1267
+ }
1268
+ }
1269
+ let chunkStart = 0;
1270
+ for (let index = 1; index <= text.length; index += 1) {
1271
+ if (index < text.length && styleKey(styles[index]) === styleKey(styles[chunkStart])) {
1272
+ continue;
1273
+ }
1274
+ const chunk = text.slice(chunkStart, index);
1275
+ const style = styles[chunkStart];
1276
+ tokens.push({ id: document2Id("text"), kind: "text", text: chunk, ...style ? { style } : {} });
1277
+ chunkStart = index;
1205
1278
  }
1206
- return { id: document2Id("field"), tokens };
1207
1279
  }
1208
1280
  function closingForList(command) {
1209
1281
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
@@ -1214,7 +1286,7 @@ function parseTextBlock(json, options, path, diagnostics) {
1214
1286
  id: document2Id("block"),
1215
1287
  kind: "textBlock",
1216
1288
  command: json.command,
1217
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1289
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1218
1290
  ...json.centered === true ? { centered: true } : {}
1219
1291
  };
1220
1292
  }
@@ -1223,7 +1295,7 @@ function parseListItem(json, options, path, diagnostics) {
1223
1295
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1224
1296
  return {
1225
1297
  id: document2Id("item"),
1226
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1298
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1227
1299
  blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1228
1300
  };
1229
1301
  }
@@ -1256,7 +1328,8 @@ function parseTableBlock(json, options, path, diagnostics) {
1256
1328
  const spanCount = detectMathSpans2(value).length;
1257
1329
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1258
1330
  mathObjectIndex += spanCount;
1259
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics);
1331
+ const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1332
+ return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1260
1333
  });
1261
1334
  });
1262
1335
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
@@ -6327,7 +6400,8 @@ ${BUTEX_FONT_FACE_CSS}
6327
6400
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
6328
6401
  }
6329
6402
 
6330
- .surface:focus {
6403
+ .surface:focus,
6404
+ .surface.surface--typing-focus {
6331
6405
  border-color: var(--butex-focus-border, rgba(13, 148, 136, 0.45));
6332
6406
  box-shadow: 0 0 0 3px var(--butex-focus-shadow, rgba(13, 148, 136, 0.12));
6333
6407
  }
@@ -6529,7 +6603,7 @@ function injectBuTeXEditorStyles(doc) {
6529
6603
 
6530
6604
  // src/editor/runtime.ts
6531
6605
  function createEditorRuntime(options) {
6532
- const { surfaceEl } = options;
6606
+ const { surfaceEl, inputEl } = options;
6533
6607
  const buttonElements = options.buttonElements ?? {};
6534
6608
  const outsidePointerIgnoreSelectors = options.outsidePointerIgnoreSelectors ?? [];
6535
6609
  let session = options.initialSession ?? createStarterSession();
@@ -6543,6 +6617,23 @@ function createEditorRuntime(options) {
6543
6617
  let dragSelecting = false;
6544
6618
  let dragChainId = null;
6545
6619
  let dragChanged = false;
6620
+ let composingInput = false;
6621
+ let pendingCompositionText = null;
6622
+ function resetInputElement() {
6623
+ if (!inputEl) {
6624
+ return;
6625
+ }
6626
+ inputEl.value = "";
6627
+ }
6628
+ function focusInput() {
6629
+ if (!inputEl) {
6630
+ surfaceEl.focus();
6631
+ return;
6632
+ }
6633
+ inputEl.focus({ preventScroll: true });
6634
+ const end = inputEl.value.length;
6635
+ inputEl.setSelectionRange(end, end);
6636
+ }
6546
6637
  function notifySessionChange() {
6547
6638
  options.onSessionChange?.(session);
6548
6639
  }
@@ -6807,7 +6898,7 @@ function createEditorRuntime(options) {
6807
6898
  event.preventDefault();
6808
6899
  event.stopPropagation();
6809
6900
  startDragSelection(chainId, index);
6810
- surfaceEl.focus();
6901
+ focusInput();
6811
6902
  });
6812
6903
  slot.addEventListener("click", (event) => {
6813
6904
  if (dragChanged) {
@@ -6817,7 +6908,7 @@ function createEditorRuntime(options) {
6817
6908
  return;
6818
6909
  }
6819
6910
  applyTransient((prev) => setCaret(prev, chainId, index));
6820
- surfaceEl.focus();
6911
+ focusInput();
6821
6912
  });
6822
6913
  if (session.caret.chainId === chainId && session.caret.index === index) {
6823
6914
  const caret = document.createElement("span");
@@ -6849,7 +6940,7 @@ function createEditorRuntime(options) {
6849
6940
  event.preventDefault();
6850
6941
  event.stopPropagation();
6851
6942
  startDragSelection(chainId, indexInChain);
6852
- surfaceEl.focus();
6943
+ focusInput();
6853
6944
  });
6854
6945
  nodeEl.addEventListener("click", (event) => {
6855
6946
  if (dragChanged) {
@@ -6860,7 +6951,7 @@ function createEditorRuntime(options) {
6860
6951
  }
6861
6952
  event.stopPropagation();
6862
6953
  applyTransient((prev) => selectNode(prev, node.id));
6863
- surfaceEl.focus();
6954
+ focusInput();
6864
6955
  });
6865
6956
  const body = document.createElement("span");
6866
6957
  body.className = "node-body";
@@ -6895,7 +6986,7 @@ function createEditorRuntime(options) {
6895
6986
  const activeTree = session[activeTreeKey(session.activeSide)];
6896
6987
  surfaceEl.appendChild(buildChain(activeTree));
6897
6988
  if (initialKeyboardFocusPending) {
6898
- requestAnimationFrame(() => surfaceEl.focus());
6989
+ requestAnimationFrame(focusInput);
6899
6990
  initialKeyboardFocusPending = false;
6900
6991
  }
6901
6992
  notifySessionChange();
@@ -6933,7 +7024,7 @@ function createEditorRuntime(options) {
6933
7024
  }
6934
7025
  session = restored;
6935
7026
  render();
6936
- surfaceEl.focus();
7027
+ focusInput();
6937
7028
  }
6938
7029
  function performRedo() {
6939
7030
  const restored = restoreRedo(undoRedo, session);
@@ -6942,9 +7033,12 @@ function createEditorRuntime(options) {
6942
7033
  }
6943
7034
  session = restored;
6944
7035
  render();
6945
- surfaceEl.focus();
7036
+ focusInput();
6946
7037
  }
6947
7038
  function onKeyDown(event) {
7039
+ if (event.isComposing || event.key === "Process" || event.keyCode === 229) {
7040
+ return;
7041
+ }
6948
7042
  const shortcutMod = event.ctrlKey || event.metaKey;
6949
7043
  const keyLower = typeof event.key === "string" ? event.key.toLowerCase() : "";
6950
7044
  if (shortcutMod && !event.altKey && keyLower === "z") {
@@ -7043,6 +7137,70 @@ function createEditorRuntime(options) {
7043
7137
  applyStructural((prev) => insertChar(prev, char));
7044
7138
  }
7045
7139
  }
7140
+ function onBeforeInput(event) {
7141
+ if (composingInput || event.isComposing) {
7142
+ return;
7143
+ }
7144
+ if (event.inputType === "deleteContentBackward") {
7145
+ event.preventDefault();
7146
+ applyStructural((prev) => deleteBackward(prev));
7147
+ resetInputElement();
7148
+ return;
7149
+ }
7150
+ if (event.inputType === "deleteContentForward") {
7151
+ event.preventDefault();
7152
+ applyStructural((prev) => deleteForward(prev));
7153
+ resetInputElement();
7154
+ }
7155
+ }
7156
+ function onInput(event) {
7157
+ if (!inputEl || composingInput) {
7158
+ return;
7159
+ }
7160
+ const inputEvent = event;
7161
+ const text = inputEvent.data ?? inputEl.value;
7162
+ resetInputElement();
7163
+ if (pendingCompositionText !== null && (inputEvent.inputType === "insertFromComposition" || text === pendingCompositionText)) {
7164
+ pendingCompositionText = null;
7165
+ return;
7166
+ }
7167
+ pendingCompositionText = null;
7168
+ if (inputEvent.inputType === "deleteContentBackward") {
7169
+ applyStructural((prev) => deleteBackward(prev));
7170
+ return;
7171
+ }
7172
+ if (inputEvent.inputType === "deleteContentForward") {
7173
+ applyStructural((prev) => deleteForward(prev));
7174
+ return;
7175
+ }
7176
+ if (text) {
7177
+ applyStructural((prev) => pasteText(prev, text));
7178
+ }
7179
+ }
7180
+ function onCompositionStart() {
7181
+ composingInput = true;
7182
+ }
7183
+ function onCompositionEnd(event) {
7184
+ if (!inputEl) {
7185
+ return;
7186
+ }
7187
+ composingInput = false;
7188
+ const text = event.data || inputEl.value;
7189
+ resetInputElement();
7190
+ if (text) {
7191
+ applyStructural((prev) => pasteText(prev, text));
7192
+ }
7193
+ pendingCompositionText = text || null;
7194
+ window.setTimeout(() => {
7195
+ pendingCompositionText = null;
7196
+ }, 0);
7197
+ }
7198
+ function onInputFocus() {
7199
+ surfaceEl.classList.add("surface--typing-focus");
7200
+ }
7201
+ function onInputBlur() {
7202
+ surfaceEl.classList.remove("surface--typing-focus");
7203
+ }
7046
7204
  function isShortcutOnlyEvent(event) {
7047
7205
  if (!(event.ctrlKey || event.metaKey) || event.altKey) {
7048
7206
  return false;
@@ -7092,7 +7250,7 @@ function createEditorRuntime(options) {
7092
7250
  }
7093
7251
  event.preventDefault();
7094
7252
  startDragSelection(resolved.chainId, resolved.slotIndex);
7095
- surfaceEl.focus();
7253
+ focusInput();
7096
7254
  }
7097
7255
  function onSurfaceClick(event) {
7098
7256
  if (dragChanged) {
@@ -7109,7 +7267,7 @@ function createEditorRuntime(options) {
7109
7267
  } else {
7110
7268
  clearSelectionAndNodeHighlights();
7111
7269
  }
7112
- surfaceEl.focus();
7270
+ focusInput();
7113
7271
  }
7114
7272
  function onDocumentPointerDown(event) {
7115
7273
  const target = event.target;
@@ -7125,6 +7283,13 @@ function createEditorRuntime(options) {
7125
7283
  clearSelectionAndNodeHighlights();
7126
7284
  }
7127
7285
  surfaceEl.addEventListener("keydown", onKeyDown);
7286
+ inputEl?.addEventListener("keydown", onKeyDown);
7287
+ inputEl?.addEventListener("beforeinput", onBeforeInput);
7288
+ inputEl?.addEventListener("input", onInput);
7289
+ inputEl?.addEventListener("compositionstart", onCompositionStart);
7290
+ inputEl?.addEventListener("compositionend", onCompositionEnd);
7291
+ inputEl?.addEventListener("focus", onInputFocus);
7292
+ inputEl?.addEventListener("blur", onInputBlur);
7128
7293
  document.addEventListener("keydown", onDocumentKeyDown);
7129
7294
  document.addEventListener("mouseup", endDragSelection);
7130
7295
  document.addEventListener("mouseleave", endDragSelection);
@@ -7136,6 +7301,13 @@ function createEditorRuntime(options) {
7136
7301
  render,
7137
7302
  destroy: () => {
7138
7303
  surfaceEl.removeEventListener("keydown", onKeyDown);
7304
+ inputEl?.removeEventListener("keydown", onKeyDown);
7305
+ inputEl?.removeEventListener("beforeinput", onBeforeInput);
7306
+ inputEl?.removeEventListener("input", onInput);
7307
+ inputEl?.removeEventListener("compositionstart", onCompositionStart);
7308
+ inputEl?.removeEventListener("compositionend", onCompositionEnd);
7309
+ inputEl?.removeEventListener("focus", onInputFocus);
7310
+ inputEl?.removeEventListener("blur", onInputBlur);
7139
7311
  document.removeEventListener("keydown", onDocumentKeyDown);
7140
7312
  document.removeEventListener("mouseup", endDragSelection);
7141
7313
  document.removeEventListener("mouseleave", endDragSelection);
@@ -7156,6 +7328,7 @@ function createEditorRuntime(options) {
7156
7328
  uiLocale = locale;
7157
7329
  render();
7158
7330
  },
7331
+ focusInput,
7159
7332
  performUndo,
7160
7333
  performRedo,
7161
7334
  performCopy,
@@ -7163,27 +7336,27 @@ function createEditorRuntime(options) {
7163
7336
  performPaste,
7164
7337
  toggleSide: () => {
7165
7338
  applyTransient((prev) => switchSide(prev));
7166
- surfaceEl.focus();
7339
+ focusInput();
7167
7340
  },
7168
7341
  toggleSplitLeafTyping: () => {
7169
7342
  applyStructural((prev) => setSplitLeafTyping(prev, !prev.splitLeafTyping));
7170
- surfaceEl.focus();
7343
+ focusInput();
7171
7344
  },
7172
7345
  toggleArabicReverseNumberTyping: () => {
7173
7346
  applyStructural((prev) => setArabicReverseNumberTyping(prev, !prev.arabicReverseNumberTyping));
7174
- surfaceEl.focus();
7347
+ focusInput();
7175
7348
  },
7176
7349
  toggleTakweenCharacterFont: () => {
7177
7350
  applyTransient((prev) => toggleTakweenCharacterFont(prev));
7178
- surfaceEl.focus();
7351
+ focusInput();
7179
7352
  },
7180
7353
  setCurrentCharacterFont: (fontId) => {
7181
7354
  applyTransient((prev) => setCurrentCharacterFont(prev, fontId));
7182
- surfaceEl.focus();
7355
+ focusInput();
7183
7356
  },
7184
7357
  setCurrentDigitForm: (digitForm) => {
7185
7358
  applyStructural((prev) => setCurrentDigitForm(prev, digitForm));
7186
- surfaceEl.focus();
7359
+ focusInput();
7187
7360
  },
7188
7361
  insertDelimiterByKind: (kind) => {
7189
7362
  if (kind === "paren") {
@@ -7193,79 +7366,79 @@ function createEditorRuntime(options) {
7193
7366
  } else {
7194
7367
  applyStructural((prev) => insertDelimiterPair(prev, "\\left\\{", "\\right\\}"));
7195
7368
  }
7196
- surfaceEl.focus();
7369
+ focusInput();
7197
7370
  },
7198
7371
  insertFraction: () => {
7199
7372
  applyStructural((prev) => insertFraction(prev));
7200
- surfaceEl.focus();
7373
+ focusInput();
7201
7374
  },
7202
7375
  insertFirstDerivative: () => {
7203
7376
  applyStructural((prev) => insertFirstDerivative(prev));
7204
- surfaceEl.focus();
7377
+ focusInput();
7205
7378
  },
7206
7379
  insertSecondDerivative: () => {
7207
7380
  applyStructural((prev) => insertSecondDerivative(prev));
7208
- surfaceEl.focus();
7381
+ focusInput();
7209
7382
  },
7210
7383
  insertMatrixEnv: (style, rows, columns) => {
7211
7384
  applyStructural((prev) => insertMatrixEnv(prev, style, rows, columns));
7212
- surfaceEl.focus();
7385
+ focusInput();
7213
7386
  },
7214
7387
  insertArrayEnv: (rows, columns) => {
7215
7388
  applyStructural((prev) => insertArrayEnv(prev, rows, columns));
7216
- surfaceEl.focus();
7389
+ focusInput();
7217
7390
  },
7218
7391
  insertAlignedEnv: (rows, columns) => {
7219
7392
  applyStructural((prev) => insertAlignedEnv(prev, rows, columns));
7220
- surfaceEl.focus();
7393
+ focusInput();
7221
7394
  },
7222
7395
  insertSqrt: () => {
7223
7396
  applyStructural((prev) => insertSqrt(prev));
7224
- surfaceEl.focus();
7397
+ focusInput();
7225
7398
  },
7226
7399
  insertOverset: () => {
7227
7400
  applyStructural((prev) => insertOverset(prev));
7228
- surfaceEl.focus();
7401
+ focusInput();
7229
7402
  },
7230
7403
  insertUnderset: () => {
7231
7404
  applyStructural((prev) => insertUnderset(prev));
7232
- surfaceEl.focus();
7405
+ focusInput();
7233
7406
  },
7234
7407
  insertLimitsSeriesTemplate: (commandId) => {
7235
7408
  applyStructural((prev) => insertLimitsSeriesTemplate(prev, commandId));
7236
- surfaceEl.focus();
7409
+ focusInput();
7237
7410
  },
7238
7411
  insertAccentPair: () => {
7239
7412
  applyStructural((prev) => insertAccentPair(prev));
7240
- surfaceEl.focus();
7413
+ focusInput();
7241
7414
  },
7242
7415
  insertAccent: (accentId) => {
7243
7416
  applyStructural((prev) => insertAccent(prev, accentId));
7244
- surfaceEl.focus();
7417
+ focusInput();
7245
7418
  },
7246
7419
  insertAtomicCommand: (commandId) => {
7247
7420
  applyStructural((prev) => insertAtomicCommand(prev, commandId));
7248
- surfaceEl.focus();
7421
+ focusInput();
7249
7422
  },
7250
7423
  insertAtomicOperatorCommand: (commandId) => {
7251
7424
  applyStructural((prev) => insertAtomicOperatorCommand(prev, commandId));
7252
- surfaceEl.focus();
7425
+ focusInput();
7253
7426
  },
7254
7427
  addSup: () => {
7255
7428
  applyStructural((prev) => addSup(prev));
7256
- surfaceEl.focus();
7429
+ focusInput();
7257
7430
  },
7258
7431
  addSub: () => {
7259
7432
  applyStructural((prev) => addSub(prev));
7260
- surfaceEl.focus();
7433
+ focusInput();
7261
7434
  },
7262
7435
  removeSup: () => {
7263
7436
  applyStructural((prev) => removeSup(prev));
7264
- surfaceEl.focus();
7437
+ focusInput();
7265
7438
  },
7266
7439
  removeSub: () => {
7267
7440
  applyStructural((prev) => removeSub(prev));
7268
- surfaceEl.focus();
7441
+ focusInput();
7269
7442
  },
7270
7443
  setMatrixEnvStyle: (style) => {
7271
7444
  applyStructural((prev) => setMatrixEnvStyle(prev, style));
@@ -7287,7 +7460,7 @@ function createEditorRuntime(options) {
7287
7460
  },
7288
7461
  deleteStructure: () => {
7289
7462
  applyStructural((prev) => deleteStructure(prev));
7290
- surfaceEl.focus();
7463
+ focusInput();
7291
7464
  }
7292
7465
  };
7293
7466
  render();
@@ -8081,6 +8254,79 @@ function normalizeFieldTokens(tokens) {
8081
8254
  }
8082
8255
  return tokens;
8083
8256
  }
8257
+ function textStylesEqual2(a, b) {
8258
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
8259
+ }
8260
+ function compactAdjacentTextTokens(tokens) {
8261
+ const compacted = [];
8262
+ for (const token of tokens) {
8263
+ const previous = compacted[compacted.length - 1];
8264
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual2(previous.style, token.style)) {
8265
+ previous.text += token.text;
8266
+ } else {
8267
+ compacted.push(token);
8268
+ }
8269
+ }
8270
+ return normalizeFieldTokens(compacted);
8271
+ }
8272
+ function withoutEmptyStyle(style) {
8273
+ const next = {};
8274
+ if (style.bold) {
8275
+ next.bold = true;
8276
+ }
8277
+ if (style.italic) {
8278
+ next.italic = true;
8279
+ }
8280
+ if (style.underline) {
8281
+ next.underline = true;
8282
+ }
8283
+ return next.bold || next.italic || next.underline ? next : void 0;
8284
+ }
8285
+ function textTokenPart(text, style, id = document2Id("text")) {
8286
+ return { id, kind: "text", text, ...style ? { style } : {} };
8287
+ }
8288
+ function toggleTextTokenStyle(document2, fieldId, tokenId, selectionStart, selectionEnd, styleName) {
8289
+ if (selectionEnd <= selectionStart) {
8290
+ return document2;
8291
+ }
8292
+ const next = cloneDocument(document2);
8293
+ visitFields(next.blocks, (field) => {
8294
+ if (field.id !== fieldId) {
8295
+ return false;
8296
+ }
8297
+ const tokenIndex = field.tokens.findIndex((entry) => entry.id === tokenId && entry.kind === "text");
8298
+ if (tokenIndex < 0) {
8299
+ return true;
8300
+ }
8301
+ const token = field.tokens[tokenIndex];
8302
+ const start = Math.max(0, Math.min(token.text.length, selectionStart));
8303
+ const end = Math.max(0, Math.min(token.text.length, selectionEnd));
8304
+ if (end <= start) {
8305
+ return true;
8306
+ }
8307
+ const selectedStyle = token.style ?? {};
8308
+ const enabled = selectedStyle[styleName] === true;
8309
+ const nextStyle = withoutEmptyStyle({ ...selectedStyle, [styleName]: enabled ? void 0 : true });
8310
+ const parts = [];
8311
+ const before = token.text.slice(0, start);
8312
+ const selected = token.text.slice(start, end);
8313
+ const after = token.text.slice(end);
8314
+ if (before.length > 0) {
8315
+ parts.push(textTokenPart(before, token.style, token.id));
8316
+ }
8317
+ parts.push(textTokenPart(selected, nextStyle, before.length > 0 ? document2Id("text") : token.id));
8318
+ if (after.length > 0) {
8319
+ parts.push(textTokenPart(after, token.style));
8320
+ }
8321
+ field.tokens = compactAdjacentTextTokens([
8322
+ ...field.tokens.slice(0, tokenIndex),
8323
+ ...parts,
8324
+ ...field.tokens.slice(tokenIndex + 1)
8325
+ ]);
8326
+ return true;
8327
+ });
8328
+ return next;
8329
+ }
8084
8330
  function stitchTextAroundRemovedToken(tokens, index) {
8085
8331
  const before = tokens[index - 1];
8086
8332
  const after = tokens[index + 1];
@@ -8108,8 +8354,8 @@ function removeMathTokenById(document2, tokenId) {
8108
8354
  function splitTextTokenAt(token, offset) {
8109
8355
  const safeOffset = Math.max(0, Math.min(offset, token.text.length));
8110
8356
  return [
8111
- { id: token.id, kind: "text", text: token.text.slice(0, safeOffset) },
8112
- { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset) }
8357
+ { id: token.id, kind: "text", text: token.text.slice(0, safeOffset), ...token.style ? { style: token.style } : {} },
8358
+ { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset), ...token.style ? { style: token.style } : {} }
8113
8359
  ];
8114
8360
  }
8115
8361
  function insertMathTokenAtCaret(document2, fieldId, textTokenId, caretOffset, session, opening = "$", closing = "$", side = "arabic") {
@@ -8612,7 +8858,7 @@ function arabicXeLatexPreamblePkg(twocolumn = false) {
8612
8858
  \usepackage{bidi}
8613
8859
  \usepackage{multirow}
8614
8860
  \usepackage{booktabs}
8615
- \usepackage{graphicx} % For \butexreflect
8861
+ \usepackage{graphicx} % For \reflectbox
8616
8862
  \usepackage{xcolor}
8617
8863
  \usepackage{tikz}
8618
8864
  \usepackage{tcolorbox} % For tcolorbox environment
@@ -8681,10 +8927,8 @@ function arabicXeLatexPreambleCmd() {
8681
8927
  \newcommand{\horzbar}{\rule[.5ex]{2.5ex}{0.5pt}}
8682
8928
 
8683
8929
 
8684
- \newcommand{\butexreflect}[1]{\tikz[baseline=(n.base)]{\node[inner sep=0pt,outer sep=0pt,xscale=-1](n){#1};}}
8685
-
8686
- \newcommand{\arabsqrt}[2]{\butexreflect{\(\sqrt[\butexreflect{\(#1\)}]{\butexreflect{\(#2\)}}\)}}
8687
- \newcommand{\arabvec}[1]{\butexreflect{$\vec{\butexreflect{$#1$}}$}}
8930
+ \newcommand{\arabsqrt}[2]{\reflectbox{\(\sqrt[\reflectbox{\(#1\)}]{\reflectbox{\(#2\)}}\)}}
8931
+ \newcommand{\arabvec}[1]{\reflectbox{$\vec{\reflectbox{$#1$}}$}}
8688
8932
 
8689
8933
  \newcommand{\arabexp}[1]{{}^{#1}\!\raisebox{-4.5pt}{\text{\diwani{ه}}}}
8690
8934
  \newcommand{\arablog}[2]{\left(\text{#2}\right)\!\prescript{}{\text{#1}}{\text{\diwani{لو}}}}
@@ -8732,9 +8976,9 @@ function arabicXeLatexPreambleCmd() {
8732
8976
  \newcommand{\araboddde}[2][-5pt]{\stackrel{\raisebox{#1}{$\vcenter{\hbox{$\cdot\!\cdot\!\cdot$}}$}}{\text{#2}}}
8733
8977
  \newcommand{\arabpde}[1]{\prescript{}{#1\!\!}{\nabla}} % arabic partial differential equation (PDE)
8734
8978
 
8735
- \newcommand{\arabprime}[0]{\butexreflect{$\prime$}\!} % arabic prime notation
8736
- \newcommand{\arabpprime}[0]{\butexreflect{$\prime$}\butexreflect{$\prime$}\!} % arabic prime notation
8737
- \newcommand{\arabppprime}[0]{\butexreflect{$\prime$}\butexreflect{$\prime$}\butexreflect{$\prime$}\!} % arabic prime notation
8979
+ \newcommand{\arabprime}[0]{\reflectbox{$\prime$}\!} % arabic prime notation
8980
+ \newcommand{\arabpprime}[0]{\reflectbox{$\prime$}\reflectbox{$\prime$}\!} % arabic prime notation
8981
+ \newcommand{\arabppprime}[0]{\reflectbox{$\prime$}\reflectbox{$\prime$}\reflectbox{$\prime$}\!} % arabic prime notation
8738
8982
 
8739
8983
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
8740
8984
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -8754,7 +8998,7 @@ function arabicXeLatexPreambleCmd() {
8754
8998
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
8755
8999
 
8756
9000
 
8757
- \newcommand{\arsqrt}[2][]{\butexreflect{\(\sqrt[\butexreflect{\(#1\)}]{\butexreflect{\(#2\)}}\)}}
9001
+ \newcommand{\arsqrt}[2][]{\reflectbox{\(\sqrt[\reflectbox{\(#1\)}]{\reflectbox{\(#2\)}}\)}}
8758
9002
  \newcommand{\arexp}[0]{\!\raisebox{-4.5pt}{\text{\diwani{ه}}}}
8759
9003
  \newcommand{\arlog}[0]{\!\!\text{\diwani{لو}}}
8760
9004
  \newcommand{\arln}[0]{\!\!\prescript{}{\text{\diwani{ه}}}{\text{\diwani{لو}}}}
@@ -8817,9 +9061,22 @@ function mathBodyLatex(token) {
8817
9061
  const rendered = renderMathNodeLatex(token.math);
8818
9062
  return stripDisplayMathDelimiters(rendered);
8819
9063
  }
9064
+ function styledTextLatex(text, style) {
9065
+ let output = text;
9066
+ if (style?.bold) {
9067
+ output = `\\textbf{${output}}`;
9068
+ }
9069
+ if (style?.italic) {
9070
+ output = `\\textit{${output}}`;
9071
+ }
9072
+ if (style?.underline) {
9073
+ output = `\\underline{${output}}`;
9074
+ }
9075
+ return output;
9076
+ }
8820
9077
  function tokenLatex(token) {
8821
9078
  if (token.kind === "text") {
8822
- return token.text;
9079
+ return styledTextLatex(token.text, token.style);
8823
9080
  }
8824
9081
  if (token.kind === "cite") {
8825
9082
  return citeTokenLatex(token.keys);
@@ -8843,7 +9100,9 @@ function inlineFieldLatex(field) {
8843
9100
  return field.tokens.map((token) => tokenLatex(token)).join("");
8844
9101
  }
8845
9102
  function textBlockLatex(block) {
8846
- const body = `${block.command}{${inlineFieldLatex(block.field)}}`;
9103
+ const content = inlineFieldLatex(block.field);
9104
+ const body = block.command === "\\paragraph" ? `\\par
9105
+ ${content}` : `${block.command}{${content}}`;
8847
9106
  if (block.command === "\\paragraph" && block.centered) {
8848
9107
  return `\\begin{center}
8849
9108
  ${body}
@@ -9095,7 +9354,7 @@ function mathTex(token, equationSide) {
9095
9354
  function previewInlines(field, islands, output, equationSide, document2, previewOptions) {
9096
9355
  return field.tokens.map((token) => {
9097
9356
  if (token.kind === "text") {
9098
- return { kind: "text", text: token.text };
9357
+ return { kind: "text", text: token.text, ...token.style ? { style: token.style } : {} };
9099
9358
  }
9100
9359
  if (token.kind === "cite") {
9101
9360
  return {
@@ -9271,6 +9530,10 @@ var DOCUMENT2_MESSAGES = {
9271
9530
  redo: "\u0625\u0639\u0627\u062F\u0629",
9272
9531
  structure: "\u0647\u064A\u0643\u0644",
9273
9532
  content: "\u0645\u062D\u062A\u0648\u0649",
9533
+ textFormatting: "\u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0646\u0635",
9534
+ bold: "\u063A\u0627\u0645\u0642",
9535
+ italic: "\u0645\u0627\u0626\u0644",
9536
+ underline: "\u062A\u062D\u062A\u0647 \u062E\u0637",
9274
9537
  equations: "\u0645\u0639\u0627\u062F\u0644\u0627\u062A",
9275
9538
  citations: "\u0627\u0642\u062A\u0628\u0627\u0633\u0627\u062A \u0648\u0645\u0631\u0627\u062C\u0639",
9276
9539
  lists: "\u0642\u0648\u0627\u0626\u0645",
@@ -9410,6 +9673,10 @@ var DOCUMENT2_MESSAGES = {
9410
9673
  redo: "Redo",
9411
9674
  structure: "Structure",
9412
9675
  content: "Content",
9676
+ textFormatting: "Text formatting",
9677
+ bold: "Bold",
9678
+ italic: "Italic",
9679
+ underline: "Underline",
9413
9680
  equations: "Equations",
9414
9681
  citations: "Citations and references",
9415
9682
  lists: "Lists",
@@ -9753,7 +10020,7 @@ function focusArticleMetaPanel(panel) {
9753
10020
  }
9754
10021
 
9755
10022
  // src/react-document2/ButexDocumentEditor2.tsx
9756
- import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo2, useRef as useRef7, useState as useState12 } from "react";
10023
+ import { useCallback as useCallback2, useEffect as useEffect9, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
9757
10024
 
9758
10025
  // src/react-document2/BlockEditor.tsx
9759
10026
  import { useState as useState3 } from "react";
@@ -10448,7 +10715,9 @@ function rememberCaret(element, blockId, fieldId, tokenId, onFieldFocus) {
10448
10715
  if (!blockId || !onFieldFocus) {
10449
10716
  return;
10450
10717
  }
10451
- onFieldFocus(blockId, fieldId, tokenId, element.selectionStart ?? element.value.length);
10718
+ const selectionStart = element.selectionStart ?? element.value.length;
10719
+ const selectionEnd = element.selectionEnd ?? selectionStart;
10720
+ onFieldFocus(blockId, fieldId, tokenId, selectionEnd, selectionStart, selectionEnd);
10452
10721
  }
10453
10722
  function fitTextareaHeight(element) {
10454
10723
  if (!element) {
@@ -10582,6 +10851,9 @@ function InlineField({
10582
10851
  {
10583
10852
  className: "butex-document2-widget__inline-text",
10584
10853
  value: token.text,
10854
+ "data-bold": token.style?.bold === true ? "true" : void 0,
10855
+ "data-italic": token.style?.italic === true ? "true" : void 0,
10856
+ "data-underline": token.style?.underline === true ? "true" : void 0,
10585
10857
  dir: documentDirection,
10586
10858
  "aria-label": messages.text,
10587
10859
  rows: 1,
@@ -11300,7 +11572,7 @@ function CitePickerPopover({
11300
11572
  }
11301
11573
 
11302
11574
  // src/react-document2/DocumentInsertToolbar.tsx
11303
- import { useState as useState6 } from "react";
11575
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
11304
11576
 
11305
11577
  // src/react-document2/TableInsertPopover.tsx
11306
11578
  import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
@@ -11378,8 +11650,11 @@ function DocumentInsertToolbar({
11378
11650
  selectionCount = 0,
11379
11651
  canMoveSelectionUp = false,
11380
11652
  canMoveSelectionDown = false,
11653
+ canFormatText = false,
11654
+ activeTextStyles = {},
11381
11655
  onUndo,
11382
11656
  onRedo,
11657
+ onToggleTextStyle,
11383
11658
  onMoveSelectionUp,
11384
11659
  onMoveSelectionDown,
11385
11660
  onDeleteSelection,
@@ -11403,6 +11678,56 @@ function DocumentInsertToolbar({
11403
11678
  }) {
11404
11679
  const messages = document2Messages(uiLocale);
11405
11680
  const [digitMenuOpen, setDigitMenuOpen] = useState6(false);
11681
+ const [referencesMenuOpen, setReferencesMenuOpen] = useState6(false);
11682
+ const referencesMenuRef = useRef5(null);
11683
+ const referencesTriggerRef = useRef5(null);
11684
+ useEffect5(() => {
11685
+ if (!referencesMenuOpen) {
11686
+ return;
11687
+ }
11688
+ referencesMenuRef.current?.querySelector('[role="menuitem"]')?.focus();
11689
+ const closeOnOutsideClick = (event) => {
11690
+ if (!referencesMenuRef.current?.contains(event.target)) {
11691
+ setReferencesMenuOpen(false);
11692
+ }
11693
+ };
11694
+ const closeOnEscape = (event) => {
11695
+ if (event.key === "Escape") {
11696
+ setReferencesMenuOpen(false);
11697
+ referencesTriggerRef.current?.focus();
11698
+ }
11699
+ };
11700
+ document.addEventListener("mousedown", closeOnOutsideClick);
11701
+ document.addEventListener("keydown", closeOnEscape);
11702
+ return () => {
11703
+ document.removeEventListener("mousedown", closeOnOutsideClick);
11704
+ document.removeEventListener("keydown", closeOnEscape);
11705
+ };
11706
+ }, [referencesMenuOpen]);
11707
+ function runReferenceAction(action) {
11708
+ setReferencesMenuOpen(false);
11709
+ action();
11710
+ }
11711
+ function moveReferenceMenuFocus(event) {
11712
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
11713
+ return;
11714
+ }
11715
+ const items = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
11716
+ if (items.length === 0) {
11717
+ return;
11718
+ }
11719
+ event.preventDefault();
11720
+ const currentIndex = items.indexOf(document.activeElement);
11721
+ if (event.key === "Home") {
11722
+ items[0]?.focus();
11723
+ } else if (event.key === "End") {
11724
+ items[items.length - 1]?.focus();
11725
+ } else {
11726
+ const change = event.key === "ArrowDown" ? 1 : -1;
11727
+ const nextIndex = (currentIndex + change + items.length) % items.length;
11728
+ items[nextIndex]?.focus();
11729
+ }
11730
+ }
11406
11731
  function digitTitle(id) {
11407
11732
  if (id === "arabicIndic") {
11408
11733
  return messages.arabicIndicDigits;
@@ -11460,6 +11785,50 @@ function DocumentInsertToolbar({
11460
11785
  messages.selectedBlockCount
11461
11786
  ] }) : null
11462
11787
  ] }),
11788
+ /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.textFormatting, children: [
11789
+ /* @__PURE__ */ jsx10(
11790
+ "button",
11791
+ {
11792
+ type: "button",
11793
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11794
+ title: messages.bold,
11795
+ "aria-label": messages.bold,
11796
+ "aria-pressed": activeTextStyles.bold === true,
11797
+ disabled: !canFormatText,
11798
+ onMouseDown: (event) => event.preventDefault(),
11799
+ onClick: () => onToggleTextStyle?.("bold"),
11800
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--bold", "aria-hidden": "true", children: "\u0646\u0635" })
11801
+ }
11802
+ ),
11803
+ /* @__PURE__ */ jsx10(
11804
+ "button",
11805
+ {
11806
+ type: "button",
11807
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11808
+ title: messages.italic,
11809
+ "aria-label": messages.italic,
11810
+ "aria-pressed": activeTextStyles.italic === true,
11811
+ disabled: !canFormatText,
11812
+ onMouseDown: (event) => event.preventDefault(),
11813
+ onClick: () => onToggleTextStyle?.("italic"),
11814
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--italic", "aria-hidden": "true", children: "\u0646\u0635" })
11815
+ }
11816
+ ),
11817
+ /* @__PURE__ */ jsx10(
11818
+ "button",
11819
+ {
11820
+ type: "button",
11821
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11822
+ title: messages.underline,
11823
+ "aria-label": messages.underline,
11824
+ "aria-pressed": activeTextStyles.underline === true,
11825
+ disabled: !canFormatText,
11826
+ onMouseDown: (event) => event.preventDefault(),
11827
+ onClick: () => onToggleTextStyle?.("underline"),
11828
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--underline", "aria-hidden": "true", children: "\u0646\u0635" })
11829
+ }
11830
+ )
11831
+ ] }),
11463
11832
  /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.structure, children: [
11464
11833
  /* @__PURE__ */ jsx10(
11465
11834
  "button",
@@ -11511,35 +11880,57 @@ function DocumentInsertToolbar({
11511
11880
  )) }) : null
11512
11881
  ] })
11513
11882
  ] }),
11514
- /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: [
11515
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertCitation, "aria-label": messages.insertCitation, onClick: onInsertCitation, children: "[]" }),
11516
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertInternalRef, "aria-label": messages.insertInternalRef, onClick: onInsertInternalRef, children: "\xA7" }),
11517
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertBibliography, "aria-label": messages.insertBibliography, onClick: onInsertBibliography, children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsxs8("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: [
11518
- /* @__PURE__ */ jsx10(
11519
- "path",
11520
- {
11521
- fill: "currentColor",
11522
- d: "M2.2 2.4h7.2c.9 0 1.6.7 1.6 1.6v9.2c0 .5-.6.8-1 .5l-2.2-1.6H2.2c-.9 0-1.6-.7-1.6-1.6V4c0-.9.7-1.6 1.6-1.6zm0 1.2c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4h6.1l1.5 1.1V4c0-.2-.2-.4-.4-.4H2.2z"
11523
- }
11524
- ),
11525
- /* @__PURE__ */ jsx10(
11526
- "path",
11527
- {
11528
- fill: "currentColor",
11529
- d: "M3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4z"
11530
- }
11531
- ),
11532
- /* @__PURE__ */ jsx10(
11533
- "path",
11534
- {
11535
- fill: "currentColor",
11536
- d: "M11.4 4.1h2.2c.8 0 1.4.6 1.4 1.4v7.1c0 .4-.5.7-.8.4l-1.7-1.3h-1.1V4.1zm1.2 1.2v5.4l.7.5V5.5c0-.1-.1-.2-.2-.2h-.5z"
11537
- }
11538
- )
11539
- ] }) }) }),
11540
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageReferences, "aria-label": messages.manageReferences, onClick: onManageReferences, children: "\u2630" }),
11541
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageLabels, "aria-label": messages.manageLabels, onClick: onManageLabels, children: "\u2317" })
11542
- ] }),
11883
+ /* @__PURE__ */ jsx10("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__references-menu", ref: referencesMenuRef, children: [
11884
+ /* @__PURE__ */ jsxs8(
11885
+ "button",
11886
+ {
11887
+ ref: referencesTriggerRef,
11888
+ type: "button",
11889
+ className: "butex-document2-widget__icon-btn butex-document2-widget__references-trigger",
11890
+ title: messages.citations,
11891
+ "aria-label": messages.citations,
11892
+ "aria-haspopup": "menu",
11893
+ "aria-expanded": referencesMenuOpen,
11894
+ onClick: () => setReferencesMenuOpen((open) => !open),
11895
+ children: [
11896
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ jsx10("path", { fill: "currentColor", d: "M2 2.2h4.2c.7 0 1.3.3 1.8.8.5-.5 1.1-.8 1.8-.8H14v10.6H9.8c-.6 0-1.1.3-1.4.8h-.8c-.3-.5-.8-.8-1.4-.8H2V2.2zm1.2 1.2v8.2h3c.4 0 .8.1 1.2.3V4.6c-.2-.7-.6-1.2-1.2-1.2h-3zm9.6 0h-3c-.6 0-1 .5-1.2 1.2v7.3c.4-.2.8-.3 1.2-.3h3V3.4z" }) }) }),
11897
+ /* @__PURE__ */ jsx10("span", { children: messages.citations }),
11898
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__menu-chevron", "aria-hidden": "true", children: "\u2304" })
11899
+ ]
11900
+ }
11901
+ ),
11902
+ referencesMenuOpen ? /* @__PURE__ */ jsxs8(
11903
+ "div",
11904
+ {
11905
+ className: "butex-document2-widget__references-menu-options",
11906
+ role: "menu",
11907
+ "aria-label": messages.citations,
11908
+ onKeyDown: moveReferenceMenuFocus,
11909
+ children: [
11910
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertCitation, onClick: () => runReferenceAction(onInsertCitation), children: [
11911
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "[]" }),
11912
+ /* @__PURE__ */ jsx10("span", { children: messages.insertCitation })
11913
+ ] }),
11914
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertInternalRef, onClick: () => runReferenceAction(onInsertInternalRef), children: [
11915
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\xA7" }),
11916
+ /* @__PURE__ */ jsx10("span", { children: messages.insertInternalRef })
11917
+ ] }),
11918
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertBibliography, onClick: () => runReferenceAction(onInsertBibliography), children: [
11919
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ jsx10("path", { fill: "currentColor", d: "M2.2 2.4h7.2c.9 0 1.6.7 1.6 1.6v9.2c0 .5-.6.8-1 .5l-2.2-1.6H2.2c-.9 0-1.6-.7-1.6-1.6V4c0-.9.7-1.6 1.6-1.6zm0 1.2c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4h6.1l1.5 1.1V4c0-.2-.2-.4-.4-.4H2.2zM3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4zM11.4 4.1h2.2c.8 0 1.4.6 1.4 1.4v7.1c0 .4-.5.7-.8.4l-1.7-1.3h-1.1V4.1zm1.2 1.2v5.4l.7.5V5.5c0-.1-.1-.2-.2-.2h-.5z" }) }) }),
11920
+ /* @__PURE__ */ jsx10("span", { children: messages.insertBibliography })
11921
+ ] }),
11922
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.manageReferences, onClick: () => runReferenceAction(onManageReferences), children: [
11923
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\u2630" }),
11924
+ /* @__PURE__ */ jsx10("span", { children: messages.manageReferences })
11925
+ ] }),
11926
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.manageLabels, onClick: () => runReferenceAction(onManageLabels), children: [
11927
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\u2317" }),
11928
+ /* @__PURE__ */ jsx10("span", { children: messages.manageLabels })
11929
+ ] })
11930
+ ]
11931
+ }
11932
+ ) : null
11933
+ ] }) }),
11543
11934
  /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.lists, children: [
11544
11935
  /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.bulletedList, "aria-label": messages.bulletedList, onClick: onAddList, children: "\u2022\u2261" }),
11545
11936
  /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.numberedList, "aria-label": messages.numberedList, onClick: onAddEnumerate, children: "1." })
@@ -11554,7 +11945,17 @@ import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
11554
11945
  function PreviewInlines({ inlines, output }) {
11555
11946
  return /* @__PURE__ */ jsx11(Fragment, { children: inlines.map((inline, index) => {
11556
11947
  if (inline.kind === "text") {
11557
- return /* @__PURE__ */ jsx11("span", { children: inline.text }, index);
11948
+ let content = /* @__PURE__ */ jsx11("span", { children: inline.text });
11949
+ if (inline.style?.bold) {
11950
+ content = /* @__PURE__ */ jsx11("strong", { children: content });
11951
+ }
11952
+ if (inline.style?.italic) {
11953
+ content = /* @__PURE__ */ jsx11("em", { children: content });
11954
+ }
11955
+ if (inline.style?.underline) {
11956
+ content = /* @__PURE__ */ jsx11("span", { className: "butex-document2-widget__preview-underline", children: content });
11957
+ }
11958
+ return /* @__PURE__ */ jsx11("span", { children: content }, index);
11558
11959
  }
11559
11960
  if (inline.kind === "cite") {
11560
11961
  return /* @__PURE__ */ jsx11("span", { className: "butex-document2-widget__preview-cite", children: inline.label }, inline.id);
@@ -11703,15 +12104,15 @@ function DocumentPreview({
11703
12104
  }
11704
12105
 
11705
12106
  // src/react-document2/EquationDrawer.tsx
11706
- import { useEffect as useEffect6, useRef as useRef6, useState as useState8 } from "react";
12107
+ import { useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
11707
12108
 
11708
12109
  // src/react/ButexEditor.tsx
11709
12110
  import {
11710
12111
  forwardRef,
11711
12112
  useCallback,
11712
- useEffect as useEffect5,
12113
+ useEffect as useEffect6,
11713
12114
  useImperativeHandle,
11714
- useRef as useRef5,
12115
+ useRef as useRef6,
11715
12116
  useState as useState7
11716
12117
  } from "react";
11717
12118
 
@@ -11787,6 +12188,22 @@ var WIDGET_CHROME_CSS = `
11787
12188
  padding: 10px 12px;
11788
12189
  }
11789
12190
 
12191
+ .butex-widget .butex-editor-input-bridge {
12192
+ border: 0;
12193
+ font-size: 16px;
12194
+ height: 1px;
12195
+ margin: 0;
12196
+ max-height: 1px;
12197
+ min-height: 1px;
12198
+ opacity: 0;
12199
+ overflow: hidden;
12200
+ padding: 0;
12201
+ pointer-events: none;
12202
+ position: absolute;
12203
+ resize: none;
12204
+ width: 1px;
12205
+ }
12206
+
11790
12207
  .butex-widget .toolbar {
11791
12208
  display: flex;
11792
12209
  flex-wrap: wrap;
@@ -12786,28 +13203,29 @@ ${latex || messages.empty}`;
12786
13203
  var ButexEditor = forwardRef(
12787
13204
  function ButexEditor2({ className, debug = false, defaultSide, uiLocale = "ar", initialSession, onSessionChange }, ref) {
12788
13205
  const messages = equationEditorMessages(uiLocale);
12789
- const wrapperRef = useRef5(null);
12790
- const surfaceRef = useRef5(null);
12791
- const mathOutputRef = useRef5(null);
12792
- const renderErrorRef = useRef5(null);
12793
- const latexLinesRef = useRef5(null);
12794
- const passivePreviewRef = useRef5(null);
12795
- const debugLogRef = useRef5(null);
12796
- const undoBtnRef = useRef5(null);
12797
- const redoBtnRef = useRef5(null);
12798
- const copyBtnRef = useRef5(null);
12799
- const cutBtnRef = useRef5(null);
12800
- const splitTypingBtnRef = useRef5(null);
12801
- const characterFontBtnRef = useRef5(null);
12802
- const digitFormBtnRef = useRef5(null);
12803
- const runtimeRef = useRef5(null);
12804
- const debugRef = useRef5(debug);
13206
+ const wrapperRef = useRef6(null);
13207
+ const surfaceRef = useRef6(null);
13208
+ const inputRef = useRef6(null);
13209
+ const mathOutputRef = useRef6(null);
13210
+ const renderErrorRef = useRef6(null);
13211
+ const latexLinesRef = useRef6(null);
13212
+ const passivePreviewRef = useRef6(null);
13213
+ const debugLogRef = useRef6(null);
13214
+ const undoBtnRef = useRef6(null);
13215
+ const redoBtnRef = useRef6(null);
13216
+ const copyBtnRef = useRef6(null);
13217
+ const cutBtnRef = useRef6(null);
13218
+ const splitTypingBtnRef = useRef6(null);
13219
+ const characterFontBtnRef = useRef6(null);
13220
+ const digitFormBtnRef = useRef6(null);
13221
+ const runtimeRef = useRef6(null);
13222
+ const debugRef = useRef6(debug);
12805
13223
  debugRef.current = debug;
12806
- const uiLocaleRef = useRef5(uiLocale);
13224
+ const uiLocaleRef = useRef6(uiLocale);
12807
13225
  uiLocaleRef.current = uiLocale;
12808
- const onSessionChangeRef = useRef5(onSessionChange);
13226
+ const onSessionChangeRef = useRef6(onSessionChange);
12809
13227
  onSessionChangeRef.current = onSessionChange;
12810
- const debugBodyHiddenRef = useRef5(false);
13228
+ const debugBodyHiddenRef = useRef6(false);
12811
13229
  const [debugBodyHidden, setDebugBodyHidden] = useState7(false);
12812
13230
  debugBodyHiddenRef.current = debugBodyHidden;
12813
13231
  const [digitMenuOpen, setDigitMenuOpen] = useState7(false);
@@ -12888,9 +13306,9 @@ ${arabic || currentMessages.empty}`;
12888
13306
  }
12889
13307
  }
12890
13308
  }, []);
12891
- const updatePreviewRef = useRef5(updatePreview);
13309
+ const updatePreviewRef = useRef6(updatePreview);
12892
13310
  updatePreviewRef.current = updatePreview;
12893
- useEffect5(() => {
13311
+ useEffect6(() => {
12894
13312
  injectWidgetChromeCss();
12895
13313
  injectBuTeXEditorStyles(typeof document !== "undefined" ? document : void 0);
12896
13314
  const surfaceEl = surfaceRef.current;
@@ -12899,6 +13317,7 @@ ${arabic || currentMessages.empty}`;
12899
13317
  }
12900
13318
  const runtime = createEditorRuntime({
12901
13319
  surfaceEl,
13320
+ inputEl: inputRef.current,
12902
13321
  initialSession,
12903
13322
  defaultSide,
12904
13323
  uiLocale,
@@ -12940,13 +13359,13 @@ ${arabic || currentMessages.empty}`;
12940
13359
  } else {
12941
13360
  runtime.render();
12942
13361
  }
12943
- surfaceEl.focus();
13362
+ runtime.focusInput();
12944
13363
  return () => {
12945
13364
  runtime.destroy();
12946
13365
  runtimeRef.current = null;
12947
13366
  };
12948
13367
  }, []);
12949
- useEffect5(() => {
13368
+ useEffect6(() => {
12950
13369
  runtimeRef.current?.setUiLocale(uiLocale);
12951
13370
  const session = runtimeRef.current?.getSession();
12952
13371
  if (session) {
@@ -12954,7 +13373,7 @@ ${arabic || currentMessages.empty}`;
12954
13373
  }
12955
13374
  }, [uiLocale]);
12956
13375
  function focusSurface() {
12957
- surfaceRef.current?.focus();
13376
+ runtimeRef.current?.focusInput();
12958
13377
  }
12959
13378
  function insertSelectedEnvironment(rows, columns) {
12960
13379
  if (envPaletteMode === "array") {
@@ -14032,6 +14451,20 @@ ${arabic || currentMessages.empty}`;
14032
14451
  "aria-label": messages.equationEditor
14033
14452
  }
14034
14453
  ),
14454
+ /* @__PURE__ */ jsx12(
14455
+ "textarea",
14456
+ {
14457
+ ref: inputRef,
14458
+ className: "butex-editor-input-bridge",
14459
+ "aria-label": messages.equationEditor,
14460
+ autoCapitalize: "none",
14461
+ autoComplete: "off",
14462
+ autoCorrect: "off",
14463
+ inputMode: "text",
14464
+ spellCheck: false,
14465
+ tabIndex: -1
14466
+ }
14467
+ ),
14035
14468
  /* @__PURE__ */ jsx12("div", { ref: renderErrorRef, className: "error", hidden: true })
14036
14469
  ] }),
14037
14470
  /* @__PURE__ */ jsx12("section", { className: "panel panel-preview", "aria-label": messages.renderPreview, children: /* @__PURE__ */ jsx12("div", { ref: mathOutputRef, className: "preview-box" }) }),
@@ -14122,7 +14555,7 @@ function EquationDrawer({
14122
14555
  onDelete
14123
14556
  }) {
14124
14557
  const messages = document2Messages(uiLocale);
14125
- const latestSession = useRef6(session);
14558
+ const latestSession = useRef7(session);
14126
14559
  const [labelOverride, setLabelOverride] = useState8(null);
14127
14560
  const displayedLabel = labelOverride ?? label;
14128
14561
  const labelError = labelEnabled && mathMode === "display" ? document2KeyError(displayedLabel, {
@@ -14130,10 +14563,10 @@ function EquationDrawer({
14130
14563
  labels,
14131
14564
  excludeOwnerId: ownerId || void 0
14132
14565
  }) : null;
14133
- useEffect6(() => {
14566
+ useEffect7(() => {
14134
14567
  setLabelOverride(null);
14135
14568
  }, [ownerId, label]);
14136
- useEffect6(() => {
14569
+ useEffect7(() => {
14137
14570
  const onKeyDown = (event) => {
14138
14571
  if (event.key === "Escape") {
14139
14572
  onClose();
@@ -14675,7 +15108,7 @@ function ReferencesPanel({
14675
15108
  }
14676
15109
 
14677
15110
  // src/react-document2/RefPickerPopover.tsx
14678
- import { useEffect as useEffect7, useState as useState11 } from "react";
15111
+ import { useEffect as useEffect8, useState as useState11 } from "react";
14679
15112
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
14680
15113
  function RefPickerPopover({
14681
15114
  open,
@@ -14692,7 +15125,7 @@ function RefPickerPopover({
14692
15125
  const documentDirection = uiLocale === "ar" ? "rtl" : "ltr";
14693
15126
  const [selected, setSelected] = useState11(initialKeys);
14694
15127
  const [refCommand, setRefCommand] = useState11(initialRefCommand);
14695
- useEffect7(() => {
15128
+ useEffect8(() => {
14696
15129
  if (open) {
14697
15130
  setSelected(initialKeys);
14698
15131
  setRefCommand(initialRefCommand);
@@ -14757,7 +15190,7 @@ function RefPickerPopover({
14757
15190
 
14758
15191
  // src/react-document2/editorFocus.ts
14759
15192
  function createEmptyDocument2EditorFocus() {
14760
- return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0 };
15193
+ return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 };
14761
15194
  }
14762
15195
  function findBlockIdForField(blocks, fieldId) {
14763
15196
  for (const block of blocks) {
@@ -14965,6 +15398,89 @@ var DOCUMENT2_WIDGET_CSS = `
14965
15398
  display: block;
14966
15399
  }
14967
15400
 
15401
+ .butex-document2-widget__format-btn {
15402
+ font-family: "Amiri", "Noto Serif Arabic", Georgia, serif;
15403
+ min-width: 36px;
15404
+ }
15405
+
15406
+ .butex-document2-widget__format-icon {
15407
+ direction: rtl;
15408
+ display: inline-block;
15409
+ font-size: 0.84rem;
15410
+ font-weight: 500;
15411
+ }
15412
+
15413
+ .butex-document2-widget__format-icon--bold {
15414
+ font-weight: 900;
15415
+ }
15416
+
15417
+ .butex-document2-widget__format-icon--italic {
15418
+ font-style: italic;
15419
+ transform: skewX(-7deg);
15420
+ }
15421
+
15422
+ .butex-document2-widget__format-icon--underline {
15423
+ text-decoration: underline;
15424
+ text-underline-offset: 0.12em;
15425
+ }
15426
+
15427
+ .butex-document2-widget__format-underline,
15428
+ .butex-document2-widget__preview-underline {
15429
+ text-decoration: underline;
15430
+ text-underline-offset: 0.12em;
15431
+ }
15432
+
15433
+ .butex-document2-widget__references-menu {
15434
+ position: relative;
15435
+ }
15436
+
15437
+ .butex-document2-widget__references-trigger {
15438
+ gap: 5px;
15439
+ white-space: nowrap;
15440
+ }
15441
+
15442
+ .butex-document2-widget__menu-chevron {
15443
+ color: var(--butex-document2-muted);
15444
+ font-size: 0.8rem;
15445
+ }
15446
+
15447
+ .butex-document2-widget__references-menu-options {
15448
+ background: var(--butex-document2-panel);
15449
+ border: 1px solid var(--butex-document2-border);
15450
+ border-radius: 8px;
15451
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--butex-document2-fg) 16%, transparent);
15452
+ display: grid;
15453
+ gap: 2px;
15454
+ inset-inline-start: 0;
15455
+ margin-top: 4px;
15456
+ min-width: max-content;
15457
+ padding: 4px;
15458
+ position: absolute;
15459
+ top: 100%;
15460
+ z-index: 30;
15461
+ }
15462
+
15463
+ .butex-document2-widget__references-menu-options button {
15464
+ align-items: center;
15465
+ background: transparent;
15466
+ border: 0;
15467
+ border-radius: 6px;
15468
+ color: var(--butex-document2-fg);
15469
+ cursor: pointer;
15470
+ display: grid;
15471
+ font: inherit;
15472
+ gap: 8px;
15473
+ grid-template-columns: 18px 1fr;
15474
+ padding: 7px 9px;
15475
+ text-align: start;
15476
+ }
15477
+
15478
+ .butex-document2-widget__references-menu-options button:hover,
15479
+ .butex-document2-widget__references-menu-options button:focus-visible {
15480
+ background: var(--butex-document2-accent-bg);
15481
+ outline: none;
15482
+ }
15483
+
14968
15484
  .butex-document2-widget__float-meta {
14969
15485
  display: grid;
14970
15486
  gap: 8px;
@@ -15408,6 +15924,19 @@ var DOCUMENT2_WIDGET_CSS = `
15408
15924
  border-color: color-mix(in srgb, var(--butex-document2-text-token-border) 55%, var(--butex-document2-accent));
15409
15925
  }
15410
15926
 
15927
+ .butex-document2-widget__inline-text[data-bold="true"] {
15928
+ font-weight: 700;
15929
+ }
15930
+
15931
+ .butex-document2-widget__inline-text[data-italic="true"] {
15932
+ font-style: italic;
15933
+ }
15934
+
15935
+ .butex-document2-widget__inline-text[data-underline="true"] {
15936
+ text-decoration: underline;
15937
+ text-underline-offset: 0.12em;
15938
+ }
15939
+
15411
15940
  .butex-document2-widget__inline-field input {
15412
15941
  min-width: 12ch;
15413
15942
  }
@@ -16425,6 +16954,50 @@ function topLevelBlockIdForField(document2, fieldId) {
16425
16954
  function topLevelBlockIdForToken(document2, tokenId) {
16426
16955
  return document2.blocks.find((block) => blockHasToken(block, tokenId))?.id ?? null;
16427
16956
  }
16957
+ function findFormatTextToken(document2, focus) {
16958
+ if (!focus.fieldId || !focus.textTokenId || focus.selectionEnd <= focus.selectionStart) {
16959
+ return null;
16960
+ }
16961
+ function textTokenFromField(field) {
16962
+ if (field.id !== focus.fieldId) {
16963
+ return null;
16964
+ }
16965
+ const token = field.tokens.find((entry) => entry.id === focus.textTokenId && entry.kind === "text");
16966
+ return token ?? null;
16967
+ }
16968
+ function visit(blocks) {
16969
+ for (const block of blocks) {
16970
+ if (block.kind === "textBlock") {
16971
+ const token = block.command === "\\paragraph" ? textTokenFromField(block.field) : null;
16972
+ if (token) {
16973
+ return token;
16974
+ }
16975
+ } else if (block.kind === "list") {
16976
+ for (const item of block.items) {
16977
+ const itemToken = textTokenFromField(item.field);
16978
+ if (itemToken) {
16979
+ return itemToken;
16980
+ }
16981
+ const nestedToken = visit(item.blocks);
16982
+ if (nestedToken) {
16983
+ return nestedToken;
16984
+ }
16985
+ }
16986
+ } else if (block.kind === "table") {
16987
+ for (const row of block.rows) {
16988
+ for (const cell of row) {
16989
+ const token = textTokenFromField(cell);
16990
+ if (token) {
16991
+ return token;
16992
+ }
16993
+ }
16994
+ }
16995
+ }
16996
+ }
16997
+ return null;
16998
+ }
16999
+ return visit(document2.blocks);
17000
+ }
16428
17001
  function formatDocument2Diagnostic(d, messages) {
16429
17002
  if (d.message === "math_objects order mismatch") {
16430
17003
  return `${messages.importOrderMismatch} ${messages.path}: ${d.path}`;
@@ -16510,13 +17083,13 @@ function ButexDocumentEditor2({
16510
17083
  const [blockSelectionState, setBlockSelectionState] = useState12(emptyBlockSelection);
16511
17084
  const [historyTick, setHistoryTick] = useState12(0);
16512
17085
  const digitForm = digitFormProp ?? digitFormState ?? (documentDirection === "rtl" ? "arabicIndic" : "western");
16513
- const historyRef = useRef7(createDocument2History());
16514
- const documentRef = useRef7(documentNode);
16515
- const textSnapshotArmedRef = useRef7(false);
16516
- const textDebounceRef = useRef7(null);
16517
- const widgetRef = useRef7(null);
16518
- const articleMetaPanelRef = useRef7(null);
16519
- const pendingFocusRef = useRef7(null);
17086
+ const historyRef = useRef8(createDocument2History());
17087
+ const documentRef = useRef8(documentNode);
17088
+ const textSnapshotArmedRef = useRef8(false);
17089
+ const textDebounceRef = useRef8(null);
17090
+ const widgetRef = useRef8(null);
17091
+ const articleMetaPanelRef = useRef8(null);
17092
+ const pendingFocusRef = useRef8(null);
16520
17093
  documentRef.current = documentNode;
16521
17094
  const latex = document2Latex(documentNode);
16522
17095
  const documentLabels = useMemo2(() => collectDocument2Labels(documentNode), [documentNode]);
@@ -16543,6 +17116,9 @@ function ButexDocumentEditor2({
16543
17116
  const selectionCount = blockSelection ? blockSelection.to - blockSelection.from + 1 : 0;
16544
17117
  const canMoveSelectionUp = Boolean(blockSelection && blockSelection.from > 0);
16545
17118
  const canMoveSelectionDown = Boolean(blockSelection && blockSelection.to < documentNode.blocks.length - 1);
17119
+ const formatTextToken = useMemo2(() => findFormatTextToken(documentNode, editorFocus), [documentNode, editorFocus]);
17120
+ const canFormatText = formatTextToken !== null;
17121
+ const activeTextStyles = formatTextToken?.style ?? {};
16546
17122
  function clearBlockSelection() {
16547
17123
  setBlockSelectionState(emptyBlockSelection());
16548
17124
  }
@@ -16580,15 +17156,15 @@ function ButexDocumentEditor2({
16580
17156
  applyDocument(removeDocument2BlockRange(documentRef.current, selection.from, selection.to), "immediate");
16581
17157
  clearBlockSelection();
16582
17158
  }
16583
- useEffect8(() => {
17159
+ useEffect9(() => {
16584
17160
  injectBuTeXDocument2Styles();
16585
17161
  }, []);
16586
- useEffect8(() => {
17162
+ useEffect9(() => {
16587
17163
  if (!editableEquations || previewOnly) {
16588
17164
  setSelectedMath(null);
16589
17165
  }
16590
17166
  }, [editableEquations, previewOnly]);
16591
- useEffect8(() => {
17167
+ useEffect9(() => {
16592
17168
  const next = resolveInitialDocument2(initialDocument, uiLocale, documentMeta);
16593
17169
  setDocumentNode(next.document);
16594
17170
  setError(next.error);
@@ -16597,13 +17173,13 @@ function ButexDocumentEditor2({
16597
17173
  historyRef.current = createDocument2History();
16598
17174
  setHistoryTick((tick) => tick + 1);
16599
17175
  }, [initialDocument, documentMeta, uiLocale]);
16600
- useEffect8(() => {
17176
+ useEffect9(() => {
16601
17177
  onDocumentChange?.(documentNode);
16602
17178
  }, [documentNode, onDocumentChange]);
16603
- useEffect8(() => {
17179
+ useEffect9(() => {
16604
17180
  onLatexChange?.(latex);
16605
17181
  }, [latex, onLatexChange]);
16606
- useEffect8(() => {
17182
+ useEffect9(() => {
16607
17183
  const pending = pendingFocusRef.current;
16608
17184
  const root = widgetRef.current;
16609
17185
  if (!pending || !root) {
@@ -16719,7 +17295,7 @@ function ButexDocumentEditor2({
16719
17295
  function openAllBlocks() {
16720
17296
  setCollapsedBlockIds(/* @__PURE__ */ new Set());
16721
17297
  }
16722
- useEffect8(() => {
17298
+ useEffect9(() => {
16723
17299
  const root = widgetRef.current;
16724
17300
  if (!root) {
16725
17301
  return;
@@ -16743,15 +17319,15 @@ function ButexDocumentEditor2({
16743
17319
  root.addEventListener("keydown", onKeyDown);
16744
17320
  return () => root.removeEventListener("keydown", onKeyDown);
16745
17321
  }, [editorOpen]);
16746
- function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset) {
17322
+ function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd) {
16747
17323
  openBlock(blockId);
16748
17324
  clearBlockSelection();
16749
- setEditorFocus({ blockId, fieldId, textTokenId, caretOffset });
17325
+ setEditorFocus({ blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd });
16750
17326
  }
16751
17327
  function rememberMathFocus(blockId, fieldId) {
16752
17328
  openBlock(blockId);
16753
17329
  clearBlockSelection();
16754
- setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0 });
17330
+ setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 });
16755
17331
  }
16756
17332
  function rememberBlockFocus(blockId) {
16757
17333
  openBlock(blockId);
@@ -17014,6 +17590,22 @@ function ButexDocumentEditor2({
17014
17590
  function deleteRefToken(tokenId) {
17015
17591
  applyDocument(removeRefTokenById(documentRef.current, tokenId), "immediate");
17016
17592
  }
17593
+ function toggleInlineTextStyle(styleName) {
17594
+ if (!canFormatText || !editorFocus.fieldId || !editorFocus.textTokenId) {
17595
+ return;
17596
+ }
17597
+ applyDocument(
17598
+ toggleTextTokenStyle(
17599
+ documentRef.current,
17600
+ editorFocus.fieldId,
17601
+ editorFocus.textTokenId,
17602
+ editorFocus.selectionStart,
17603
+ editorFocus.selectionEnd,
17604
+ styleName
17605
+ ),
17606
+ "immediate"
17607
+ );
17608
+ }
17017
17609
  const showEditorPanel = !previewOnly && editorOpen;
17018
17610
  const showPreviewPanel = previewOnly || previewOpen;
17019
17611
  return /* @__PURE__ */ jsx17(
@@ -17036,8 +17628,11 @@ function ButexDocumentEditor2({
17036
17628
  selectionCount,
17037
17629
  canMoveSelectionUp,
17038
17630
  canMoveSelectionDown,
17631
+ canFormatText,
17632
+ activeTextStyles,
17039
17633
  onUndo: undoDocument,
17040
17634
  onRedo: redoDocument,
17635
+ onToggleTextStyle: toggleInlineTextStyle,
17041
17636
  onMoveSelectionUp: () => moveSelectedBlocks(-1),
17042
17637
  onMoveSelectionDown: () => moveSelectedBlocks(1),
17043
17638
  onDeleteSelection: deleteSelectedBlocks,