@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.
@@ -1168,7 +1168,7 @@ function parseReferences(json) {
1168
1168
  }
1169
1169
  return references;
1170
1170
  }
1171
- function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = []) {
1171
+ function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = [], formats = []) {
1172
1172
  const mode = options.mode ?? "english";
1173
1173
  const spans = detectInlineSpans2(value);
1174
1174
  const mathSpans = spans.filter((span) => span.kind === "math");
@@ -1180,7 +1180,7 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1180
1180
  }
1181
1181
  for (const span of spans) {
1182
1182
  if (span.start > index) {
1183
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index, span.start) });
1183
+ pushFormattedTextTokens(tokens, value.slice(index, span.start), index, formats);
1184
1184
  }
1185
1185
  if (span.kind === "cite") {
1186
1186
  tokens.push({
@@ -1236,9 +1236,81 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1236
1236
  index = span.end;
1237
1237
  }
1238
1238
  if (index < value.length || tokens.length === 0) {
1239
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index) });
1239
+ pushFormattedTextTokens(tokens, value.slice(index), index, formats);
1240
+ }
1241
+ return { id: document2Id("field"), tokens: compactTextTokens(tokens) };
1242
+ }
1243
+ function styleFromFormat(format) {
1244
+ const style = {};
1245
+ if (format.bold === true) {
1246
+ style.bold = true;
1247
+ }
1248
+ if (format.italic === true) {
1249
+ style.italic = true;
1250
+ }
1251
+ if (format.underline === true) {
1252
+ style.underline = true;
1253
+ }
1254
+ return style.bold || style.italic || style.underline ? style : null;
1255
+ }
1256
+ function mergeTextStyle(base, added) {
1257
+ return {
1258
+ ...base?.bold ? { bold: true } : {},
1259
+ ...base?.italic ? { italic: true } : {},
1260
+ ...base?.underline ? { underline: true } : {},
1261
+ ...added.bold ? { bold: true } : {},
1262
+ ...added.italic ? { italic: true } : {},
1263
+ ...added.underline ? { underline: true } : {}
1264
+ };
1265
+ }
1266
+ function textStylesEqual(a, b) {
1267
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
1268
+ }
1269
+ function compactTextTokens(tokens) {
1270
+ const compacted = [];
1271
+ for (const token of tokens) {
1272
+ const previous = compacted[compacted.length - 1];
1273
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual(previous.style, token.style)) {
1274
+ previous.text += token.text;
1275
+ } else {
1276
+ compacted.push(token);
1277
+ }
1278
+ }
1279
+ return compacted;
1280
+ }
1281
+ function styleKey(style) {
1282
+ return `${style?.bold ? "b" : ""}${style?.italic ? "i" : ""}${style?.underline ? "u" : ""}`;
1283
+ }
1284
+ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1285
+ if (text.length === 0) {
1286
+ tokens.push({ id: document2Id("text"), kind: "text", text });
1287
+ return;
1288
+ }
1289
+ const styles = Array.from({ length: text.length });
1290
+ for (const format of formats) {
1291
+ const style = styleFromFormat(format);
1292
+ if (!style || !Number.isFinite(format.start) || !Number.isFinite(format.end)) {
1293
+ continue;
1294
+ }
1295
+ const start = Math.max(0, Math.min(text.length, Math.trunc(format.start) - sourceStart));
1296
+ const end = Math.max(0, Math.min(text.length, Math.trunc(format.end) - sourceStart));
1297
+ if (end <= start) {
1298
+ continue;
1299
+ }
1300
+ for (let index = start; index < end; index += 1) {
1301
+ styles[index] = mergeTextStyle(styles[index], style);
1302
+ }
1303
+ }
1304
+ let chunkStart = 0;
1305
+ for (let index = 1; index <= text.length; index += 1) {
1306
+ if (index < text.length && styleKey(styles[index]) === styleKey(styles[chunkStart])) {
1307
+ continue;
1308
+ }
1309
+ const chunk = text.slice(chunkStart, index);
1310
+ const style = styles[chunkStart];
1311
+ tokens.push({ id: document2Id("text"), kind: "text", text: chunk, ...style ? { style } : {} });
1312
+ chunkStart = index;
1240
1313
  }
1241
- return { id: document2Id("field"), tokens };
1242
1314
  }
1243
1315
  function closingForList(command) {
1244
1316
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
@@ -1249,7 +1321,7 @@ function parseTextBlock(json, options, path, diagnostics) {
1249
1321
  id: document2Id("block"),
1250
1322
  kind: "textBlock",
1251
1323
  command: json.command,
1252
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1324
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1253
1325
  ...json.centered === true ? { centered: true } : {}
1254
1326
  };
1255
1327
  }
@@ -1258,7 +1330,7 @@ function parseListItem(json, options, path, diagnostics) {
1258
1330
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1259
1331
  return {
1260
1332
  id: document2Id("item"),
1261
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1333
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1262
1334
  blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1263
1335
  };
1264
1336
  }
@@ -1291,7 +1363,8 @@ function parseTableBlock(json, options, path, diagnostics) {
1291
1363
  const spanCount = detectMathSpans2(value).length;
1292
1364
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1293
1365
  mathObjectIndex += spanCount;
1294
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics);
1366
+ const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1367
+ return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1295
1368
  });
1296
1369
  });
1297
1370
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
@@ -6362,7 +6435,8 @@ ${BUTEX_FONT_FACE_CSS}
6362
6435
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
6363
6436
  }
6364
6437
 
6365
- .surface:focus {
6438
+ .surface:focus,
6439
+ .surface.surface--typing-focus {
6366
6440
  border-color: var(--butex-focus-border, rgba(13, 148, 136, 0.45));
6367
6441
  box-shadow: 0 0 0 3px var(--butex-focus-shadow, rgba(13, 148, 136, 0.12));
6368
6442
  }
@@ -6564,7 +6638,7 @@ function injectBuTeXEditorStyles(doc) {
6564
6638
 
6565
6639
  // src/editor/runtime.ts
6566
6640
  function createEditorRuntime(options) {
6567
- const { surfaceEl } = options;
6641
+ const { surfaceEl, inputEl } = options;
6568
6642
  const buttonElements = options.buttonElements ?? {};
6569
6643
  const outsidePointerIgnoreSelectors = options.outsidePointerIgnoreSelectors ?? [];
6570
6644
  let session = options.initialSession ?? createStarterSession();
@@ -6578,6 +6652,23 @@ function createEditorRuntime(options) {
6578
6652
  let dragSelecting = false;
6579
6653
  let dragChainId = null;
6580
6654
  let dragChanged = false;
6655
+ let composingInput = false;
6656
+ let pendingCompositionText = null;
6657
+ function resetInputElement() {
6658
+ if (!inputEl) {
6659
+ return;
6660
+ }
6661
+ inputEl.value = "";
6662
+ }
6663
+ function focusInput() {
6664
+ if (!inputEl) {
6665
+ surfaceEl.focus();
6666
+ return;
6667
+ }
6668
+ inputEl.focus({ preventScroll: true });
6669
+ const end = inputEl.value.length;
6670
+ inputEl.setSelectionRange(end, end);
6671
+ }
6581
6672
  function notifySessionChange() {
6582
6673
  options.onSessionChange?.(session);
6583
6674
  }
@@ -6842,7 +6933,7 @@ function createEditorRuntime(options) {
6842
6933
  event.preventDefault();
6843
6934
  event.stopPropagation();
6844
6935
  startDragSelection(chainId, index);
6845
- surfaceEl.focus();
6936
+ focusInput();
6846
6937
  });
6847
6938
  slot.addEventListener("click", (event) => {
6848
6939
  if (dragChanged) {
@@ -6852,7 +6943,7 @@ function createEditorRuntime(options) {
6852
6943
  return;
6853
6944
  }
6854
6945
  applyTransient((prev) => setCaret(prev, chainId, index));
6855
- surfaceEl.focus();
6946
+ focusInput();
6856
6947
  });
6857
6948
  if (session.caret.chainId === chainId && session.caret.index === index) {
6858
6949
  const caret = document.createElement("span");
@@ -6884,7 +6975,7 @@ function createEditorRuntime(options) {
6884
6975
  event.preventDefault();
6885
6976
  event.stopPropagation();
6886
6977
  startDragSelection(chainId, indexInChain);
6887
- surfaceEl.focus();
6978
+ focusInput();
6888
6979
  });
6889
6980
  nodeEl.addEventListener("click", (event) => {
6890
6981
  if (dragChanged) {
@@ -6895,7 +6986,7 @@ function createEditorRuntime(options) {
6895
6986
  }
6896
6987
  event.stopPropagation();
6897
6988
  applyTransient((prev) => selectNode(prev, node.id));
6898
- surfaceEl.focus();
6989
+ focusInput();
6899
6990
  });
6900
6991
  const body = document.createElement("span");
6901
6992
  body.className = "node-body";
@@ -6930,7 +7021,7 @@ function createEditorRuntime(options) {
6930
7021
  const activeTree = session[activeTreeKey(session.activeSide)];
6931
7022
  surfaceEl.appendChild(buildChain(activeTree));
6932
7023
  if (initialKeyboardFocusPending) {
6933
- requestAnimationFrame(() => surfaceEl.focus());
7024
+ requestAnimationFrame(focusInput);
6934
7025
  initialKeyboardFocusPending = false;
6935
7026
  }
6936
7027
  notifySessionChange();
@@ -6968,7 +7059,7 @@ function createEditorRuntime(options) {
6968
7059
  }
6969
7060
  session = restored;
6970
7061
  render();
6971
- surfaceEl.focus();
7062
+ focusInput();
6972
7063
  }
6973
7064
  function performRedo() {
6974
7065
  const restored = restoreRedo(undoRedo, session);
@@ -6977,9 +7068,12 @@ function createEditorRuntime(options) {
6977
7068
  }
6978
7069
  session = restored;
6979
7070
  render();
6980
- surfaceEl.focus();
7071
+ focusInput();
6981
7072
  }
6982
7073
  function onKeyDown(event) {
7074
+ if (event.isComposing || event.key === "Process" || event.keyCode === 229) {
7075
+ return;
7076
+ }
6983
7077
  const shortcutMod = event.ctrlKey || event.metaKey;
6984
7078
  const keyLower = typeof event.key === "string" ? event.key.toLowerCase() : "";
6985
7079
  if (shortcutMod && !event.altKey && keyLower === "z") {
@@ -7078,6 +7172,70 @@ function createEditorRuntime(options) {
7078
7172
  applyStructural((prev) => insertChar(prev, char));
7079
7173
  }
7080
7174
  }
7175
+ function onBeforeInput(event) {
7176
+ if (composingInput || event.isComposing) {
7177
+ return;
7178
+ }
7179
+ if (event.inputType === "deleteContentBackward") {
7180
+ event.preventDefault();
7181
+ applyStructural((prev) => deleteBackward(prev));
7182
+ resetInputElement();
7183
+ return;
7184
+ }
7185
+ if (event.inputType === "deleteContentForward") {
7186
+ event.preventDefault();
7187
+ applyStructural((prev) => deleteForward(prev));
7188
+ resetInputElement();
7189
+ }
7190
+ }
7191
+ function onInput(event) {
7192
+ if (!inputEl || composingInput) {
7193
+ return;
7194
+ }
7195
+ const inputEvent = event;
7196
+ const text = inputEvent.data ?? inputEl.value;
7197
+ resetInputElement();
7198
+ if (pendingCompositionText !== null && (inputEvent.inputType === "insertFromComposition" || text === pendingCompositionText)) {
7199
+ pendingCompositionText = null;
7200
+ return;
7201
+ }
7202
+ pendingCompositionText = null;
7203
+ if (inputEvent.inputType === "deleteContentBackward") {
7204
+ applyStructural((prev) => deleteBackward(prev));
7205
+ return;
7206
+ }
7207
+ if (inputEvent.inputType === "deleteContentForward") {
7208
+ applyStructural((prev) => deleteForward(prev));
7209
+ return;
7210
+ }
7211
+ if (text) {
7212
+ applyStructural((prev) => pasteText(prev, text));
7213
+ }
7214
+ }
7215
+ function onCompositionStart() {
7216
+ composingInput = true;
7217
+ }
7218
+ function onCompositionEnd(event) {
7219
+ if (!inputEl) {
7220
+ return;
7221
+ }
7222
+ composingInput = false;
7223
+ const text = event.data || inputEl.value;
7224
+ resetInputElement();
7225
+ if (text) {
7226
+ applyStructural((prev) => pasteText(prev, text));
7227
+ }
7228
+ pendingCompositionText = text || null;
7229
+ window.setTimeout(() => {
7230
+ pendingCompositionText = null;
7231
+ }, 0);
7232
+ }
7233
+ function onInputFocus() {
7234
+ surfaceEl.classList.add("surface--typing-focus");
7235
+ }
7236
+ function onInputBlur() {
7237
+ surfaceEl.classList.remove("surface--typing-focus");
7238
+ }
7081
7239
  function isShortcutOnlyEvent(event) {
7082
7240
  if (!(event.ctrlKey || event.metaKey) || event.altKey) {
7083
7241
  return false;
@@ -7127,7 +7285,7 @@ function createEditorRuntime(options) {
7127
7285
  }
7128
7286
  event.preventDefault();
7129
7287
  startDragSelection(resolved.chainId, resolved.slotIndex);
7130
- surfaceEl.focus();
7288
+ focusInput();
7131
7289
  }
7132
7290
  function onSurfaceClick(event) {
7133
7291
  if (dragChanged) {
@@ -7144,7 +7302,7 @@ function createEditorRuntime(options) {
7144
7302
  } else {
7145
7303
  clearSelectionAndNodeHighlights();
7146
7304
  }
7147
- surfaceEl.focus();
7305
+ focusInput();
7148
7306
  }
7149
7307
  function onDocumentPointerDown(event) {
7150
7308
  const target = event.target;
@@ -7160,6 +7318,13 @@ function createEditorRuntime(options) {
7160
7318
  clearSelectionAndNodeHighlights();
7161
7319
  }
7162
7320
  surfaceEl.addEventListener("keydown", onKeyDown);
7321
+ inputEl?.addEventListener("keydown", onKeyDown);
7322
+ inputEl?.addEventListener("beforeinput", onBeforeInput);
7323
+ inputEl?.addEventListener("input", onInput);
7324
+ inputEl?.addEventListener("compositionstart", onCompositionStart);
7325
+ inputEl?.addEventListener("compositionend", onCompositionEnd);
7326
+ inputEl?.addEventListener("focus", onInputFocus);
7327
+ inputEl?.addEventListener("blur", onInputBlur);
7163
7328
  document.addEventListener("keydown", onDocumentKeyDown);
7164
7329
  document.addEventListener("mouseup", endDragSelection);
7165
7330
  document.addEventListener("mouseleave", endDragSelection);
@@ -7171,6 +7336,13 @@ function createEditorRuntime(options) {
7171
7336
  render,
7172
7337
  destroy: () => {
7173
7338
  surfaceEl.removeEventListener("keydown", onKeyDown);
7339
+ inputEl?.removeEventListener("keydown", onKeyDown);
7340
+ inputEl?.removeEventListener("beforeinput", onBeforeInput);
7341
+ inputEl?.removeEventListener("input", onInput);
7342
+ inputEl?.removeEventListener("compositionstart", onCompositionStart);
7343
+ inputEl?.removeEventListener("compositionend", onCompositionEnd);
7344
+ inputEl?.removeEventListener("focus", onInputFocus);
7345
+ inputEl?.removeEventListener("blur", onInputBlur);
7174
7346
  document.removeEventListener("keydown", onDocumentKeyDown);
7175
7347
  document.removeEventListener("mouseup", endDragSelection);
7176
7348
  document.removeEventListener("mouseleave", endDragSelection);
@@ -7191,6 +7363,7 @@ function createEditorRuntime(options) {
7191
7363
  uiLocale = locale;
7192
7364
  render();
7193
7365
  },
7366
+ focusInput,
7194
7367
  performUndo,
7195
7368
  performRedo,
7196
7369
  performCopy,
@@ -7198,27 +7371,27 @@ function createEditorRuntime(options) {
7198
7371
  performPaste,
7199
7372
  toggleSide: () => {
7200
7373
  applyTransient((prev) => switchSide(prev));
7201
- surfaceEl.focus();
7374
+ focusInput();
7202
7375
  },
7203
7376
  toggleSplitLeafTyping: () => {
7204
7377
  applyStructural((prev) => setSplitLeafTyping(prev, !prev.splitLeafTyping));
7205
- surfaceEl.focus();
7378
+ focusInput();
7206
7379
  },
7207
7380
  toggleArabicReverseNumberTyping: () => {
7208
7381
  applyStructural((prev) => setArabicReverseNumberTyping(prev, !prev.arabicReverseNumberTyping));
7209
- surfaceEl.focus();
7382
+ focusInput();
7210
7383
  },
7211
7384
  toggleTakweenCharacterFont: () => {
7212
7385
  applyTransient((prev) => toggleTakweenCharacterFont(prev));
7213
- surfaceEl.focus();
7386
+ focusInput();
7214
7387
  },
7215
7388
  setCurrentCharacterFont: (fontId) => {
7216
7389
  applyTransient((prev) => setCurrentCharacterFont(prev, fontId));
7217
- surfaceEl.focus();
7390
+ focusInput();
7218
7391
  },
7219
7392
  setCurrentDigitForm: (digitForm) => {
7220
7393
  applyStructural((prev) => setCurrentDigitForm(prev, digitForm));
7221
- surfaceEl.focus();
7394
+ focusInput();
7222
7395
  },
7223
7396
  insertDelimiterByKind: (kind) => {
7224
7397
  if (kind === "paren") {
@@ -7228,79 +7401,79 @@ function createEditorRuntime(options) {
7228
7401
  } else {
7229
7402
  applyStructural((prev) => insertDelimiterPair(prev, "\\left\\{", "\\right\\}"));
7230
7403
  }
7231
- surfaceEl.focus();
7404
+ focusInput();
7232
7405
  },
7233
7406
  insertFraction: () => {
7234
7407
  applyStructural((prev) => insertFraction(prev));
7235
- surfaceEl.focus();
7408
+ focusInput();
7236
7409
  },
7237
7410
  insertFirstDerivative: () => {
7238
7411
  applyStructural((prev) => insertFirstDerivative(prev));
7239
- surfaceEl.focus();
7412
+ focusInput();
7240
7413
  },
7241
7414
  insertSecondDerivative: () => {
7242
7415
  applyStructural((prev) => insertSecondDerivative(prev));
7243
- surfaceEl.focus();
7416
+ focusInput();
7244
7417
  },
7245
7418
  insertMatrixEnv: (style, rows, columns) => {
7246
7419
  applyStructural((prev) => insertMatrixEnv(prev, style, rows, columns));
7247
- surfaceEl.focus();
7420
+ focusInput();
7248
7421
  },
7249
7422
  insertArrayEnv: (rows, columns) => {
7250
7423
  applyStructural((prev) => insertArrayEnv(prev, rows, columns));
7251
- surfaceEl.focus();
7424
+ focusInput();
7252
7425
  },
7253
7426
  insertAlignedEnv: (rows, columns) => {
7254
7427
  applyStructural((prev) => insertAlignedEnv(prev, rows, columns));
7255
- surfaceEl.focus();
7428
+ focusInput();
7256
7429
  },
7257
7430
  insertSqrt: () => {
7258
7431
  applyStructural((prev) => insertSqrt(prev));
7259
- surfaceEl.focus();
7432
+ focusInput();
7260
7433
  },
7261
7434
  insertOverset: () => {
7262
7435
  applyStructural((prev) => insertOverset(prev));
7263
- surfaceEl.focus();
7436
+ focusInput();
7264
7437
  },
7265
7438
  insertUnderset: () => {
7266
7439
  applyStructural((prev) => insertUnderset(prev));
7267
- surfaceEl.focus();
7440
+ focusInput();
7268
7441
  },
7269
7442
  insertLimitsSeriesTemplate: (commandId) => {
7270
7443
  applyStructural((prev) => insertLimitsSeriesTemplate(prev, commandId));
7271
- surfaceEl.focus();
7444
+ focusInput();
7272
7445
  },
7273
7446
  insertAccentPair: () => {
7274
7447
  applyStructural((prev) => insertAccentPair(prev));
7275
- surfaceEl.focus();
7448
+ focusInput();
7276
7449
  },
7277
7450
  insertAccent: (accentId) => {
7278
7451
  applyStructural((prev) => insertAccent(prev, accentId));
7279
- surfaceEl.focus();
7452
+ focusInput();
7280
7453
  },
7281
7454
  insertAtomicCommand: (commandId) => {
7282
7455
  applyStructural((prev) => insertAtomicCommand(prev, commandId));
7283
- surfaceEl.focus();
7456
+ focusInput();
7284
7457
  },
7285
7458
  insertAtomicOperatorCommand: (commandId) => {
7286
7459
  applyStructural((prev) => insertAtomicOperatorCommand(prev, commandId));
7287
- surfaceEl.focus();
7460
+ focusInput();
7288
7461
  },
7289
7462
  addSup: () => {
7290
7463
  applyStructural((prev) => addSup(prev));
7291
- surfaceEl.focus();
7464
+ focusInput();
7292
7465
  },
7293
7466
  addSub: () => {
7294
7467
  applyStructural((prev) => addSub(prev));
7295
- surfaceEl.focus();
7468
+ focusInput();
7296
7469
  },
7297
7470
  removeSup: () => {
7298
7471
  applyStructural((prev) => removeSup(prev));
7299
- surfaceEl.focus();
7472
+ focusInput();
7300
7473
  },
7301
7474
  removeSub: () => {
7302
7475
  applyStructural((prev) => removeSub(prev));
7303
- surfaceEl.focus();
7476
+ focusInput();
7304
7477
  },
7305
7478
  setMatrixEnvStyle: (style) => {
7306
7479
  applyStructural((prev) => setMatrixEnvStyle(prev, style));
@@ -7322,7 +7495,7 @@ function createEditorRuntime(options) {
7322
7495
  },
7323
7496
  deleteStructure: () => {
7324
7497
  applyStructural((prev) => deleteStructure(prev));
7325
- surfaceEl.focus();
7498
+ focusInput();
7326
7499
  }
7327
7500
  };
7328
7501
  render();
@@ -8116,6 +8289,79 @@ function normalizeFieldTokens(tokens) {
8116
8289
  }
8117
8290
  return tokens;
8118
8291
  }
8292
+ function textStylesEqual2(a, b) {
8293
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
8294
+ }
8295
+ function compactAdjacentTextTokens(tokens) {
8296
+ const compacted = [];
8297
+ for (const token of tokens) {
8298
+ const previous = compacted[compacted.length - 1];
8299
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual2(previous.style, token.style)) {
8300
+ previous.text += token.text;
8301
+ } else {
8302
+ compacted.push(token);
8303
+ }
8304
+ }
8305
+ return normalizeFieldTokens(compacted);
8306
+ }
8307
+ function withoutEmptyStyle(style) {
8308
+ const next = {};
8309
+ if (style.bold) {
8310
+ next.bold = true;
8311
+ }
8312
+ if (style.italic) {
8313
+ next.italic = true;
8314
+ }
8315
+ if (style.underline) {
8316
+ next.underline = true;
8317
+ }
8318
+ return next.bold || next.italic || next.underline ? next : void 0;
8319
+ }
8320
+ function textTokenPart(text, style, id = document2Id("text")) {
8321
+ return { id, kind: "text", text, ...style ? { style } : {} };
8322
+ }
8323
+ function toggleTextTokenStyle(document2, fieldId, tokenId, selectionStart, selectionEnd, styleName) {
8324
+ if (selectionEnd <= selectionStart) {
8325
+ return document2;
8326
+ }
8327
+ const next = cloneDocument(document2);
8328
+ visitFields(next.blocks, (field) => {
8329
+ if (field.id !== fieldId) {
8330
+ return false;
8331
+ }
8332
+ const tokenIndex = field.tokens.findIndex((entry) => entry.id === tokenId && entry.kind === "text");
8333
+ if (tokenIndex < 0) {
8334
+ return true;
8335
+ }
8336
+ const token = field.tokens[tokenIndex];
8337
+ const start = Math.max(0, Math.min(token.text.length, selectionStart));
8338
+ const end = Math.max(0, Math.min(token.text.length, selectionEnd));
8339
+ if (end <= start) {
8340
+ return true;
8341
+ }
8342
+ const selectedStyle = token.style ?? {};
8343
+ const enabled = selectedStyle[styleName] === true;
8344
+ const nextStyle = withoutEmptyStyle({ ...selectedStyle, [styleName]: enabled ? void 0 : true });
8345
+ const parts = [];
8346
+ const before = token.text.slice(0, start);
8347
+ const selected = token.text.slice(start, end);
8348
+ const after = token.text.slice(end);
8349
+ if (before.length > 0) {
8350
+ parts.push(textTokenPart(before, token.style, token.id));
8351
+ }
8352
+ parts.push(textTokenPart(selected, nextStyle, before.length > 0 ? document2Id("text") : token.id));
8353
+ if (after.length > 0) {
8354
+ parts.push(textTokenPart(after, token.style));
8355
+ }
8356
+ field.tokens = compactAdjacentTextTokens([
8357
+ ...field.tokens.slice(0, tokenIndex),
8358
+ ...parts,
8359
+ ...field.tokens.slice(tokenIndex + 1)
8360
+ ]);
8361
+ return true;
8362
+ });
8363
+ return next;
8364
+ }
8119
8365
  function stitchTextAroundRemovedToken(tokens, index) {
8120
8366
  const before = tokens[index - 1];
8121
8367
  const after = tokens[index + 1];
@@ -8143,8 +8389,8 @@ function removeMathTokenById(document2, tokenId) {
8143
8389
  function splitTextTokenAt(token, offset) {
8144
8390
  const safeOffset = Math.max(0, Math.min(offset, token.text.length));
8145
8391
  return [
8146
- { id: token.id, kind: "text", text: token.text.slice(0, safeOffset) },
8147
- { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset) }
8392
+ { id: token.id, kind: "text", text: token.text.slice(0, safeOffset), ...token.style ? { style: token.style } : {} },
8393
+ { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset), ...token.style ? { style: token.style } : {} }
8148
8394
  ];
8149
8395
  }
8150
8396
  function insertMathTokenAtCaret(document2, fieldId, textTokenId, caretOffset, session, opening = "$", closing = "$", side = "arabic") {
@@ -8647,7 +8893,7 @@ function arabicXeLatexPreamblePkg(twocolumn = false) {
8647
8893
  \usepackage{bidi}
8648
8894
  \usepackage{multirow}
8649
8895
  \usepackage{booktabs}
8650
- \usepackage{graphicx} % For \butexreflect
8896
+ \usepackage{graphicx} % For \reflectbox
8651
8897
  \usepackage{xcolor}
8652
8898
  \usepackage{tikz}
8653
8899
  \usepackage{tcolorbox} % For tcolorbox environment
@@ -8716,10 +8962,8 @@ function arabicXeLatexPreambleCmd() {
8716
8962
  \newcommand{\horzbar}{\rule[.5ex]{2.5ex}{0.5pt}}
8717
8963
 
8718
8964
 
8719
- \newcommand{\butexreflect}[1]{\tikz[baseline=(n.base)]{\node[inner sep=0pt,outer sep=0pt,xscale=-1](n){#1};}}
8720
-
8721
- \newcommand{\arabsqrt}[2]{\butexreflect{\(\sqrt[\butexreflect{\(#1\)}]{\butexreflect{\(#2\)}}\)}}
8722
- \newcommand{\arabvec}[1]{\butexreflect{$\vec{\butexreflect{$#1$}}$}}
8965
+ \newcommand{\arabsqrt}[2]{\reflectbox{\(\sqrt[\reflectbox{\(#1\)}]{\reflectbox{\(#2\)}}\)}}
8966
+ \newcommand{\arabvec}[1]{\reflectbox{$\vec{\reflectbox{$#1$}}$}}
8723
8967
 
8724
8968
  \newcommand{\arabexp}[1]{{}^{#1}\!\raisebox{-4.5pt}{\text{\diwani{ه}}}}
8725
8969
  \newcommand{\arablog}[2]{\left(\text{#2}\right)\!\prescript{}{\text{#1}}{\text{\diwani{لو}}}}
@@ -8767,9 +9011,9 @@ function arabicXeLatexPreambleCmd() {
8767
9011
  \newcommand{\araboddde}[2][-5pt]{\stackrel{\raisebox{#1}{$\vcenter{\hbox{$\cdot\!\cdot\!\cdot$}}$}}{\text{#2}}}
8768
9012
  \newcommand{\arabpde}[1]{\prescript{}{#1\!\!}{\nabla}} % arabic partial differential equation (PDE)
8769
9013
 
8770
- \newcommand{\arabprime}[0]{\butexreflect{$\prime$}\!} % arabic prime notation
8771
- \newcommand{\arabpprime}[0]{\butexreflect{$\prime$}\butexreflect{$\prime$}\!} % arabic prime notation
8772
- \newcommand{\arabppprime}[0]{\butexreflect{$\prime$}\butexreflect{$\prime$}\butexreflect{$\prime$}\!} % arabic prime notation
9014
+ \newcommand{\arabprime}[0]{\reflectbox{$\prime$}\!} % arabic prime notation
9015
+ \newcommand{\arabpprime}[0]{\reflectbox{$\prime$}\reflectbox{$\prime$}\!} % arabic prime notation
9016
+ \newcommand{\arabppprime}[0]{\reflectbox{$\prime$}\reflectbox{$\prime$}\reflectbox{$\prime$}\!} % arabic prime notation
8773
9017
 
8774
9018
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
8775
9019
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -8789,7 +9033,7 @@ function arabicXeLatexPreambleCmd() {
8789
9033
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
8790
9034
 
8791
9035
 
8792
- \newcommand{\arsqrt}[2][]{\butexreflect{\(\sqrt[\butexreflect{\(#1\)}]{\butexreflect{\(#2\)}}\)}}
9036
+ \newcommand{\arsqrt}[2][]{\reflectbox{\(\sqrt[\reflectbox{\(#1\)}]{\reflectbox{\(#2\)}}\)}}
8793
9037
  \newcommand{\arexp}[0]{\!\raisebox{-4.5pt}{\text{\diwani{ه}}}}
8794
9038
  \newcommand{\arlog}[0]{\!\!\text{\diwani{لو}}}
8795
9039
  \newcommand{\arln}[0]{\!\!\prescript{}{\text{\diwani{ه}}}{\text{\diwani{لو}}}}
@@ -8852,9 +9096,22 @@ function mathBodyLatex(token) {
8852
9096
  const rendered = renderMathNodeLatex(token.math);
8853
9097
  return stripDisplayMathDelimiters(rendered);
8854
9098
  }
9099
+ function styledTextLatex(text, style) {
9100
+ let output = text;
9101
+ if (style?.bold) {
9102
+ output = `\\textbf{${output}}`;
9103
+ }
9104
+ if (style?.italic) {
9105
+ output = `\\textit{${output}}`;
9106
+ }
9107
+ if (style?.underline) {
9108
+ output = `\\underline{${output}}`;
9109
+ }
9110
+ return output;
9111
+ }
8855
9112
  function tokenLatex(token) {
8856
9113
  if (token.kind === "text") {
8857
- return token.text;
9114
+ return styledTextLatex(token.text, token.style);
8858
9115
  }
8859
9116
  if (token.kind === "cite") {
8860
9117
  return citeTokenLatex(token.keys);
@@ -8878,7 +9135,9 @@ function inlineFieldLatex(field) {
8878
9135
  return field.tokens.map((token) => tokenLatex(token)).join("");
8879
9136
  }
8880
9137
  function textBlockLatex(block) {
8881
- const body = `${block.command}{${inlineFieldLatex(block.field)}}`;
9138
+ const content = inlineFieldLatex(block.field);
9139
+ const body = block.command === "\\paragraph" ? `\\par
9140
+ ${content}` : `${block.command}{${content}}`;
8882
9141
  if (block.command === "\\paragraph" && block.centered) {
8883
9142
  return `\\begin{center}
8884
9143
  ${body}
@@ -9130,7 +9389,7 @@ function mathTex(token, equationSide) {
9130
9389
  function previewInlines(field, islands, output, equationSide, document2, previewOptions) {
9131
9390
  return field.tokens.map((token) => {
9132
9391
  if (token.kind === "text") {
9133
- return { kind: "text", text: token.text };
9392
+ return { kind: "text", text: token.text, ...token.style ? { style: token.style } : {} };
9134
9393
  }
9135
9394
  if (token.kind === "cite") {
9136
9395
  return {
@@ -9306,6 +9565,10 @@ var DOCUMENT2_MESSAGES = {
9306
9565
  redo: "\u0625\u0639\u0627\u062F\u0629",
9307
9566
  structure: "\u0647\u064A\u0643\u0644",
9308
9567
  content: "\u0645\u062D\u062A\u0648\u0649",
9568
+ textFormatting: "\u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0646\u0635",
9569
+ bold: "\u063A\u0627\u0645\u0642",
9570
+ italic: "\u0645\u0627\u0626\u0644",
9571
+ underline: "\u062A\u062D\u062A\u0647 \u062E\u0637",
9309
9572
  equations: "\u0645\u0639\u0627\u062F\u0644\u0627\u062A",
9310
9573
  citations: "\u0627\u0642\u062A\u0628\u0627\u0633\u0627\u062A \u0648\u0645\u0631\u0627\u062C\u0639",
9311
9574
  lists: "\u0642\u0648\u0627\u0626\u0645",
@@ -9445,6 +9708,10 @@ var DOCUMENT2_MESSAGES = {
9445
9708
  redo: "Redo",
9446
9709
  structure: "Structure",
9447
9710
  content: "Content",
9711
+ textFormatting: "Text formatting",
9712
+ bold: "Bold",
9713
+ italic: "Italic",
9714
+ underline: "Underline",
9448
9715
  equations: "Equations",
9449
9716
  citations: "Citations and references",
9450
9717
  lists: "Lists",
@@ -10483,7 +10750,9 @@ function rememberCaret(element, blockId, fieldId, tokenId, onFieldFocus) {
10483
10750
  if (!blockId || !onFieldFocus) {
10484
10751
  return;
10485
10752
  }
10486
- onFieldFocus(blockId, fieldId, tokenId, element.selectionStart ?? element.value.length);
10753
+ const selectionStart = element.selectionStart ?? element.value.length;
10754
+ const selectionEnd = element.selectionEnd ?? selectionStart;
10755
+ onFieldFocus(blockId, fieldId, tokenId, selectionEnd, selectionStart, selectionEnd);
10487
10756
  }
10488
10757
  function fitTextareaHeight(element) {
10489
10758
  if (!element) {
@@ -10617,6 +10886,9 @@ function InlineField({
10617
10886
  {
10618
10887
  className: "butex-document2-widget__inline-text",
10619
10888
  value: token.text,
10889
+ "data-bold": token.style?.bold === true ? "true" : void 0,
10890
+ "data-italic": token.style?.italic === true ? "true" : void 0,
10891
+ "data-underline": token.style?.underline === true ? "true" : void 0,
10620
10892
  dir: documentDirection,
10621
10893
  "aria-label": messages.text,
10622
10894
  rows: 1,
@@ -11413,8 +11685,11 @@ function DocumentInsertToolbar({
11413
11685
  selectionCount = 0,
11414
11686
  canMoveSelectionUp = false,
11415
11687
  canMoveSelectionDown = false,
11688
+ canFormatText = false,
11689
+ activeTextStyles = {},
11416
11690
  onUndo,
11417
11691
  onRedo,
11692
+ onToggleTextStyle,
11418
11693
  onMoveSelectionUp,
11419
11694
  onMoveSelectionDown,
11420
11695
  onDeleteSelection,
@@ -11438,6 +11713,56 @@ function DocumentInsertToolbar({
11438
11713
  }) {
11439
11714
  const messages = document2Messages(uiLocale);
11440
11715
  const [digitMenuOpen, setDigitMenuOpen] = (0, import_react7.useState)(false);
11716
+ const [referencesMenuOpen, setReferencesMenuOpen] = (0, import_react7.useState)(false);
11717
+ const referencesMenuRef = (0, import_react7.useRef)(null);
11718
+ const referencesTriggerRef = (0, import_react7.useRef)(null);
11719
+ (0, import_react7.useEffect)(() => {
11720
+ if (!referencesMenuOpen) {
11721
+ return;
11722
+ }
11723
+ referencesMenuRef.current?.querySelector('[role="menuitem"]')?.focus();
11724
+ const closeOnOutsideClick = (event) => {
11725
+ if (!referencesMenuRef.current?.contains(event.target)) {
11726
+ setReferencesMenuOpen(false);
11727
+ }
11728
+ };
11729
+ const closeOnEscape = (event) => {
11730
+ if (event.key === "Escape") {
11731
+ setReferencesMenuOpen(false);
11732
+ referencesTriggerRef.current?.focus();
11733
+ }
11734
+ };
11735
+ document.addEventListener("mousedown", closeOnOutsideClick);
11736
+ document.addEventListener("keydown", closeOnEscape);
11737
+ return () => {
11738
+ document.removeEventListener("mousedown", closeOnOutsideClick);
11739
+ document.removeEventListener("keydown", closeOnEscape);
11740
+ };
11741
+ }, [referencesMenuOpen]);
11742
+ function runReferenceAction(action) {
11743
+ setReferencesMenuOpen(false);
11744
+ action();
11745
+ }
11746
+ function moveReferenceMenuFocus(event) {
11747
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
11748
+ return;
11749
+ }
11750
+ const items = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
11751
+ if (items.length === 0) {
11752
+ return;
11753
+ }
11754
+ event.preventDefault();
11755
+ const currentIndex = items.indexOf(document.activeElement);
11756
+ if (event.key === "Home") {
11757
+ items[0]?.focus();
11758
+ } else if (event.key === "End") {
11759
+ items[items.length - 1]?.focus();
11760
+ } else {
11761
+ const change = event.key === "ArrowDown" ? 1 : -1;
11762
+ const nextIndex = (currentIndex + change + items.length) % items.length;
11763
+ items[nextIndex]?.focus();
11764
+ }
11765
+ }
11441
11766
  function digitTitle(id) {
11442
11767
  if (id === "arabicIndic") {
11443
11768
  return messages.arabicIndicDigits;
@@ -11495,6 +11820,50 @@ function DocumentInsertToolbar({
11495
11820
  messages.selectedBlockCount
11496
11821
  ] }) : null
11497
11822
  ] }),
11823
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.textFormatting, children: [
11824
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11825
+ "button",
11826
+ {
11827
+ type: "button",
11828
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11829
+ title: messages.bold,
11830
+ "aria-label": messages.bold,
11831
+ "aria-pressed": activeTextStyles.bold === true,
11832
+ disabled: !canFormatText,
11833
+ onMouseDown: (event) => event.preventDefault(),
11834
+ onClick: () => onToggleTextStyle?.("bold"),
11835
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--bold", "aria-hidden": "true", children: "\u0646\u0635" })
11836
+ }
11837
+ ),
11838
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11839
+ "button",
11840
+ {
11841
+ type: "button",
11842
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11843
+ title: messages.italic,
11844
+ "aria-label": messages.italic,
11845
+ "aria-pressed": activeTextStyles.italic === true,
11846
+ disabled: !canFormatText,
11847
+ onMouseDown: (event) => event.preventDefault(),
11848
+ onClick: () => onToggleTextStyle?.("italic"),
11849
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--italic", "aria-hidden": "true", children: "\u0646\u0635" })
11850
+ }
11851
+ ),
11852
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11853
+ "button",
11854
+ {
11855
+ type: "button",
11856
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11857
+ title: messages.underline,
11858
+ "aria-label": messages.underline,
11859
+ "aria-pressed": activeTextStyles.underline === true,
11860
+ disabled: !canFormatText,
11861
+ onMouseDown: (event) => event.preventDefault(),
11862
+ onClick: () => onToggleTextStyle?.("underline"),
11863
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--underline", "aria-hidden": "true", children: "\u0646\u0635" })
11864
+ }
11865
+ )
11866
+ ] }),
11498
11867
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.structure, children: [
11499
11868
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11500
11869
  "button",
@@ -11546,35 +11915,57 @@ function DocumentInsertToolbar({
11546
11915
  )) }) : null
11547
11916
  ] })
11548
11917
  ] }),
11549
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: [
11550
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertCitation, "aria-label": messages.insertCitation, onClick: onInsertCitation, children: "[]" }),
11551
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertInternalRef, "aria-label": messages.insertInternalRef, onClick: onInsertInternalRef, children: "\xA7" }),
11552
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertBibliography, "aria-label": messages.insertBibliography, onClick: onInsertBibliography, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: [
11553
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11554
- "path",
11555
- {
11556
- fill: "currentColor",
11557
- 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"
11558
- }
11559
- ),
11560
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11561
- "path",
11562
- {
11563
- fill: "currentColor",
11564
- d: "M3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4z"
11565
- }
11566
- ),
11567
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11568
- "path",
11569
- {
11570
- fill: "currentColor",
11571
- 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"
11572
- }
11573
- )
11574
- ] }) }) }),
11575
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageReferences, "aria-label": messages.manageReferences, onClick: onManageReferences, children: "\u2630" }),
11576
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageLabels, "aria-label": messages.manageLabels, onClick: onManageLabels, children: "\u2317" })
11577
- ] }),
11918
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__references-menu", ref: referencesMenuRef, children: [
11919
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11920
+ "button",
11921
+ {
11922
+ ref: referencesTriggerRef,
11923
+ type: "button",
11924
+ className: "butex-document2-widget__icon-btn butex-document2-widget__references-trigger",
11925
+ title: messages.citations,
11926
+ "aria-label": messages.citations,
11927
+ "aria-haspopup": "menu",
11928
+ "aria-expanded": referencesMenuOpen,
11929
+ onClick: () => setReferencesMenuOpen((open) => !open),
11930
+ children: [
11931
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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" }) }) }),
11932
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.citations }),
11933
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__menu-chevron", "aria-hidden": "true", children: "\u2304" })
11934
+ ]
11935
+ }
11936
+ ),
11937
+ referencesMenuOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11938
+ "div",
11939
+ {
11940
+ className: "butex-document2-widget__references-menu-options",
11941
+ role: "menu",
11942
+ "aria-label": messages.citations,
11943
+ onKeyDown: moveReferenceMenuFocus,
11944
+ children: [
11945
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertCitation, onClick: () => runReferenceAction(onInsertCitation), children: [
11946
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "[]" }),
11947
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertCitation })
11948
+ ] }),
11949
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertInternalRef, onClick: () => runReferenceAction(onInsertInternalRef), children: [
11950
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\xA7" }),
11951
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertInternalRef })
11952
+ ] }),
11953
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertBibliography, onClick: () => runReferenceAction(onInsertBibliography), children: [
11954
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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" }) }) }),
11955
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertBibliography })
11956
+ ] }),
11957
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.manageReferences, onClick: () => runReferenceAction(onManageReferences), children: [
11958
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\u2630" }),
11959
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.manageReferences })
11960
+ ] }),
11961
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.manageLabels, onClick: () => runReferenceAction(onManageLabels), children: [
11962
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\u2317" }),
11963
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.manageLabels })
11964
+ ] })
11965
+ ]
11966
+ }
11967
+ ) : null
11968
+ ] }) }),
11578
11969
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.lists, children: [
11579
11970
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.bulletedList, "aria-label": messages.bulletedList, onClick: onAddList, children: "\u2022\u2261" }),
11580
11971
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.numberedList, "aria-label": messages.numberedList, onClick: onAddEnumerate, children: "1." })
@@ -11589,7 +11980,17 @@ var import_jsx_runtime11 = require("react/jsx-runtime");
11589
11980
  function PreviewInlines({ inlines, output }) {
11590
11981
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: inlines.map((inline, index) => {
11591
11982
  if (inline.kind === "text") {
11592
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: inline.text }, index);
11983
+ let content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: inline.text });
11984
+ if (inline.style?.bold) {
11985
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: content });
11986
+ }
11987
+ if (inline.style?.italic) {
11988
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("em", { children: content });
11989
+ }
11990
+ if (inline.style?.underline) {
11991
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "butex-document2-widget__preview-underline", children: content });
11992
+ }
11993
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: content }, index);
11593
11994
  }
11594
11995
  if (inline.kind === "cite") {
11595
11996
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "butex-document2-widget__preview-cite", children: inline.label }, inline.id);
@@ -11815,6 +12216,22 @@ var WIDGET_CHROME_CSS = `
11815
12216
  padding: 10px 12px;
11816
12217
  }
11817
12218
 
12219
+ .butex-widget .butex-editor-input-bridge {
12220
+ border: 0;
12221
+ font-size: 16px;
12222
+ height: 1px;
12223
+ margin: 0;
12224
+ max-height: 1px;
12225
+ min-height: 1px;
12226
+ opacity: 0;
12227
+ overflow: hidden;
12228
+ padding: 0;
12229
+ pointer-events: none;
12230
+ position: absolute;
12231
+ resize: none;
12232
+ width: 1px;
12233
+ }
12234
+
11818
12235
  .butex-widget .toolbar {
11819
12236
  display: flex;
11820
12237
  flex-wrap: wrap;
@@ -12816,6 +13233,7 @@ var ButexEditor = (0, import_react8.forwardRef)(
12816
13233
  const messages = equationEditorMessages(uiLocale);
12817
13234
  const wrapperRef = (0, import_react8.useRef)(null);
12818
13235
  const surfaceRef = (0, import_react8.useRef)(null);
13236
+ const inputRef = (0, import_react8.useRef)(null);
12819
13237
  const mathOutputRef = (0, import_react8.useRef)(null);
12820
13238
  const renderErrorRef = (0, import_react8.useRef)(null);
12821
13239
  const latexLinesRef = (0, import_react8.useRef)(null);
@@ -12927,6 +13345,7 @@ ${arabic || currentMessages.empty}`;
12927
13345
  }
12928
13346
  const runtime = createEditorRuntime({
12929
13347
  surfaceEl,
13348
+ inputEl: inputRef.current,
12930
13349
  initialSession,
12931
13350
  defaultSide,
12932
13351
  uiLocale,
@@ -12968,7 +13387,7 @@ ${arabic || currentMessages.empty}`;
12968
13387
  } else {
12969
13388
  runtime.render();
12970
13389
  }
12971
- surfaceEl.focus();
13390
+ runtime.focusInput();
12972
13391
  return () => {
12973
13392
  runtime.destroy();
12974
13393
  runtimeRef.current = null;
@@ -12982,7 +13401,7 @@ ${arabic || currentMessages.empty}`;
12982
13401
  }
12983
13402
  }, [uiLocale]);
12984
13403
  function focusSurface() {
12985
- surfaceRef.current?.focus();
13404
+ runtimeRef.current?.focusInput();
12986
13405
  }
12987
13406
  function insertSelectedEnvironment(rows, columns) {
12988
13407
  if (envPaletteMode === "array") {
@@ -14060,6 +14479,20 @@ ${arabic || currentMessages.empty}`;
14060
14479
  "aria-label": messages.equationEditor
14061
14480
  }
14062
14481
  ),
14482
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
14483
+ "textarea",
14484
+ {
14485
+ ref: inputRef,
14486
+ className: "butex-editor-input-bridge",
14487
+ "aria-label": messages.equationEditor,
14488
+ autoCapitalize: "none",
14489
+ autoComplete: "off",
14490
+ autoCorrect: "off",
14491
+ inputMode: "text",
14492
+ spellCheck: false,
14493
+ tabIndex: -1
14494
+ }
14495
+ ),
14063
14496
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { ref: renderErrorRef, className: "error", hidden: true })
14064
14497
  ] }),
14065
14498
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { className: "panel panel-preview", "aria-label": messages.renderPreview, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { ref: mathOutputRef, className: "preview-box" }) }),
@@ -14785,7 +15218,7 @@ function RefPickerPopover({
14785
15218
 
14786
15219
  // src/react-document2/editorFocus.ts
14787
15220
  function createEmptyDocument2EditorFocus() {
14788
- return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0 };
15221
+ return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 };
14789
15222
  }
14790
15223
  function findBlockIdForField(blocks, fieldId) {
14791
15224
  for (const block of blocks) {
@@ -14993,6 +15426,89 @@ var DOCUMENT2_WIDGET_CSS = `
14993
15426
  display: block;
14994
15427
  }
14995
15428
 
15429
+ .butex-document2-widget__format-btn {
15430
+ font-family: "Amiri", "Noto Serif Arabic", Georgia, serif;
15431
+ min-width: 36px;
15432
+ }
15433
+
15434
+ .butex-document2-widget__format-icon {
15435
+ direction: rtl;
15436
+ display: inline-block;
15437
+ font-size: 0.84rem;
15438
+ font-weight: 500;
15439
+ }
15440
+
15441
+ .butex-document2-widget__format-icon--bold {
15442
+ font-weight: 900;
15443
+ }
15444
+
15445
+ .butex-document2-widget__format-icon--italic {
15446
+ font-style: italic;
15447
+ transform: skewX(-7deg);
15448
+ }
15449
+
15450
+ .butex-document2-widget__format-icon--underline {
15451
+ text-decoration: underline;
15452
+ text-underline-offset: 0.12em;
15453
+ }
15454
+
15455
+ .butex-document2-widget__format-underline,
15456
+ .butex-document2-widget__preview-underline {
15457
+ text-decoration: underline;
15458
+ text-underline-offset: 0.12em;
15459
+ }
15460
+
15461
+ .butex-document2-widget__references-menu {
15462
+ position: relative;
15463
+ }
15464
+
15465
+ .butex-document2-widget__references-trigger {
15466
+ gap: 5px;
15467
+ white-space: nowrap;
15468
+ }
15469
+
15470
+ .butex-document2-widget__menu-chevron {
15471
+ color: var(--butex-document2-muted);
15472
+ font-size: 0.8rem;
15473
+ }
15474
+
15475
+ .butex-document2-widget__references-menu-options {
15476
+ background: var(--butex-document2-panel);
15477
+ border: 1px solid var(--butex-document2-border);
15478
+ border-radius: 8px;
15479
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--butex-document2-fg) 16%, transparent);
15480
+ display: grid;
15481
+ gap: 2px;
15482
+ inset-inline-start: 0;
15483
+ margin-top: 4px;
15484
+ min-width: max-content;
15485
+ padding: 4px;
15486
+ position: absolute;
15487
+ top: 100%;
15488
+ z-index: 30;
15489
+ }
15490
+
15491
+ .butex-document2-widget__references-menu-options button {
15492
+ align-items: center;
15493
+ background: transparent;
15494
+ border: 0;
15495
+ border-radius: 6px;
15496
+ color: var(--butex-document2-fg);
15497
+ cursor: pointer;
15498
+ display: grid;
15499
+ font: inherit;
15500
+ gap: 8px;
15501
+ grid-template-columns: 18px 1fr;
15502
+ padding: 7px 9px;
15503
+ text-align: start;
15504
+ }
15505
+
15506
+ .butex-document2-widget__references-menu-options button:hover,
15507
+ .butex-document2-widget__references-menu-options button:focus-visible {
15508
+ background: var(--butex-document2-accent-bg);
15509
+ outline: none;
15510
+ }
15511
+
14996
15512
  .butex-document2-widget__float-meta {
14997
15513
  display: grid;
14998
15514
  gap: 8px;
@@ -15436,6 +15952,19 @@ var DOCUMENT2_WIDGET_CSS = `
15436
15952
  border-color: color-mix(in srgb, var(--butex-document2-text-token-border) 55%, var(--butex-document2-accent));
15437
15953
  }
15438
15954
 
15955
+ .butex-document2-widget__inline-text[data-bold="true"] {
15956
+ font-weight: 700;
15957
+ }
15958
+
15959
+ .butex-document2-widget__inline-text[data-italic="true"] {
15960
+ font-style: italic;
15961
+ }
15962
+
15963
+ .butex-document2-widget__inline-text[data-underline="true"] {
15964
+ text-decoration: underline;
15965
+ text-underline-offset: 0.12em;
15966
+ }
15967
+
15439
15968
  .butex-document2-widget__inline-field input {
15440
15969
  min-width: 12ch;
15441
15970
  }
@@ -16453,6 +16982,50 @@ function topLevelBlockIdForField(document2, fieldId) {
16453
16982
  function topLevelBlockIdForToken(document2, tokenId) {
16454
16983
  return document2.blocks.find((block) => blockHasToken(block, tokenId))?.id ?? null;
16455
16984
  }
16985
+ function findFormatTextToken(document2, focus) {
16986
+ if (!focus.fieldId || !focus.textTokenId || focus.selectionEnd <= focus.selectionStart) {
16987
+ return null;
16988
+ }
16989
+ function textTokenFromField(field) {
16990
+ if (field.id !== focus.fieldId) {
16991
+ return null;
16992
+ }
16993
+ const token = field.tokens.find((entry) => entry.id === focus.textTokenId && entry.kind === "text");
16994
+ return token ?? null;
16995
+ }
16996
+ function visit(blocks) {
16997
+ for (const block of blocks) {
16998
+ if (block.kind === "textBlock") {
16999
+ const token = block.command === "\\paragraph" ? textTokenFromField(block.field) : null;
17000
+ if (token) {
17001
+ return token;
17002
+ }
17003
+ } else if (block.kind === "list") {
17004
+ for (const item of block.items) {
17005
+ const itemToken = textTokenFromField(item.field);
17006
+ if (itemToken) {
17007
+ return itemToken;
17008
+ }
17009
+ const nestedToken = visit(item.blocks);
17010
+ if (nestedToken) {
17011
+ return nestedToken;
17012
+ }
17013
+ }
17014
+ } else if (block.kind === "table") {
17015
+ for (const row of block.rows) {
17016
+ for (const cell of row) {
17017
+ const token = textTokenFromField(cell);
17018
+ if (token) {
17019
+ return token;
17020
+ }
17021
+ }
17022
+ }
17023
+ }
17024
+ }
17025
+ return null;
17026
+ }
17027
+ return visit(document2.blocks);
17028
+ }
16456
17029
  function formatDocument2Diagnostic(d, messages) {
16457
17030
  if (d.message === "math_objects order mismatch") {
16458
17031
  return `${messages.importOrderMismatch} ${messages.path}: ${d.path}`;
@@ -16571,6 +17144,9 @@ function ButexDocumentEditor2({
16571
17144
  const selectionCount = blockSelection ? blockSelection.to - blockSelection.from + 1 : 0;
16572
17145
  const canMoveSelectionUp = Boolean(blockSelection && blockSelection.from > 0);
16573
17146
  const canMoveSelectionDown = Boolean(blockSelection && blockSelection.to < documentNode.blocks.length - 1);
17147
+ const formatTextToken = (0, import_react13.useMemo)(() => findFormatTextToken(documentNode, editorFocus), [documentNode, editorFocus]);
17148
+ const canFormatText = formatTextToken !== null;
17149
+ const activeTextStyles = formatTextToken?.style ?? {};
16574
17150
  function clearBlockSelection() {
16575
17151
  setBlockSelectionState(emptyBlockSelection());
16576
17152
  }
@@ -16771,15 +17347,15 @@ function ButexDocumentEditor2({
16771
17347
  root.addEventListener("keydown", onKeyDown);
16772
17348
  return () => root.removeEventListener("keydown", onKeyDown);
16773
17349
  }, [editorOpen]);
16774
- function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset) {
17350
+ function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd) {
16775
17351
  openBlock(blockId);
16776
17352
  clearBlockSelection();
16777
- setEditorFocus({ blockId, fieldId, textTokenId, caretOffset });
17353
+ setEditorFocus({ blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd });
16778
17354
  }
16779
17355
  function rememberMathFocus(blockId, fieldId) {
16780
17356
  openBlock(blockId);
16781
17357
  clearBlockSelection();
16782
- setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0 });
17358
+ setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 });
16783
17359
  }
16784
17360
  function rememberBlockFocus(blockId) {
16785
17361
  openBlock(blockId);
@@ -17042,6 +17618,22 @@ function ButexDocumentEditor2({
17042
17618
  function deleteRefToken(tokenId) {
17043
17619
  applyDocument(removeRefTokenById(documentRef.current, tokenId), "immediate");
17044
17620
  }
17621
+ function toggleInlineTextStyle(styleName) {
17622
+ if (!canFormatText || !editorFocus.fieldId || !editorFocus.textTokenId) {
17623
+ return;
17624
+ }
17625
+ applyDocument(
17626
+ toggleTextTokenStyle(
17627
+ documentRef.current,
17628
+ editorFocus.fieldId,
17629
+ editorFocus.textTokenId,
17630
+ editorFocus.selectionStart,
17631
+ editorFocus.selectionEnd,
17632
+ styleName
17633
+ ),
17634
+ "immediate"
17635
+ );
17636
+ }
17045
17637
  const showEditorPanel = !previewOnly && editorOpen;
17046
17638
  const showPreviewPanel = previewOnly || previewOpen;
17047
17639
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
@@ -17064,8 +17656,11 @@ function ButexDocumentEditor2({
17064
17656
  selectionCount,
17065
17657
  canMoveSelectionUp,
17066
17658
  canMoveSelectionDown,
17659
+ canFormatText,
17660
+ activeTextStyles,
17067
17661
  onUndo: undoDocument,
17068
17662
  onRedo: redoDocument,
17663
+ onToggleTextStyle: toggleInlineTextStyle,
17069
17664
  onMoveSelectionUp: () => moveSelectedBlocks(-1),
17070
17665
  onMoveSelectionDown: () => moveSelectedBlocks(1),
17071
17666
  onDeleteSelection: deleteSelectedBlocks,