@mikedown/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,6 +8,10 @@ import { TaskList } from "@tiptap/extension-task-list";
8
8
  // src/taskitem-drag.ts
9
9
  import { TaskItem } from "@tiptap/extension-task-item";
10
10
  import { NodeSelection } from "@tiptap/pm/state";
11
+ function taskCheckboxLabel(node) {
12
+ const text = (node.firstChild?.textContent ?? "").replace(/\s+/g, " ").trim();
13
+ return text || "Task";
14
+ }
11
15
  var DraggableTaskItem = TaskItem.extend({
12
16
  draggable: true,
13
17
  addNodeView() {
@@ -27,6 +31,7 @@ var DraggableTaskItem = TaskItem.extend({
27
31
  const checkbox = document.createElement("input");
28
32
  checkbox.type = "checkbox";
29
33
  checkbox.checked = !!node.attrs.checked;
34
+ checkbox.setAttribute("aria-label", taskCheckboxLabel(node));
30
35
  const bullet = document.createElement("span");
31
36
  label.append(checkbox, bullet);
32
37
  const content = document.createElement("div");
@@ -67,6 +72,10 @@ var DraggableTaskItem = TaskItem.extend({
67
72
  if (checkbox.checked !== checked) {
68
73
  checkbox.checked = checked;
69
74
  }
75
+ const name = taskCheckboxLabel(updated);
76
+ if (checkbox.getAttribute("aria-label") !== name) {
77
+ checkbox.setAttribute("aria-label", name);
78
+ }
70
79
  return true;
71
80
  },
72
81
  // Let the checkbox change event and the handle dragstart run without
@@ -1237,6 +1246,7 @@ function buildDecorations(doc, _view) {
1237
1246
  checkbox.type = "checkbox";
1238
1247
  checkbox.checked = checked;
1239
1248
  checkbox.classList.add("table-checkbox");
1249
+ checkbox.setAttribute("aria-label", "Task done");
1240
1250
  checkbox.addEventListener("mousedown", (e) => {
1241
1251
  e.preventDefault();
1242
1252
  const newText = checked ? "[ ]" : "[x]";
@@ -4415,7 +4425,8 @@ function refreshTableToolbarStates(_editor) {
4415
4425
  }
4416
4426
  function updateTableToolbar(editor) {
4417
4427
  const isInTable = editor.isActive("table");
4418
- if (!isInTable) {
4428
+ const focusedHere = editor.view.hasFocus() || !!document.activeElement?.closest("#mikedown-table-toolbar");
4429
+ if (!isInTable || !focusedHere) {
4419
4430
  hideTableToolbar();
4420
4431
  return;
4421
4432
  }
@@ -9774,9 +9785,17 @@ function buildActionItem(item) {
9774
9785
  el.classList.add("tdd-active");
9775
9786
  }
9776
9787
  }
9788
+ const disabled = item.isDisabled?.() ?? false;
9789
+ if (disabled) {
9790
+ el.classList.add("tdd-disabled");
9791
+ el.setAttribute("aria-disabled", "true");
9792
+ }
9777
9793
  el.addEventListener("mousedown", (e) => {
9778
9794
  e.preventDefault();
9779
9795
  e.stopPropagation();
9796
+ if (disabled) {
9797
+ return;
9798
+ }
9780
9799
  hideToolbarDropdown();
9781
9800
  item.action();
9782
9801
  });
@@ -9908,6 +9927,54 @@ function handleClickOutside(e) {
9908
9927
  }
9909
9928
  }
9910
9929
 
9930
+ // src/toolbar-overflow.ts
9931
+ var OVERFLOWED = "toolbar-overflowed";
9932
+ function isLaidOut(el) {
9933
+ return !el.hidden && !el.classList.contains(OVERFLOWED) && el.style.display !== "none";
9934
+ }
9935
+ function rowFits(toolbar) {
9936
+ const children = Array.from(toolbar.children).filter(isLaidOut);
9937
+ const last = children[children.length - 1];
9938
+ if (!last) {
9939
+ return true;
9940
+ }
9941
+ const padRight = parseFloat(getComputedStyle(toolbar).paddingRight) || 0;
9942
+ return last.getBoundingClientRect().right <= toolbar.getBoundingClientRect().right - padRight + 0.5;
9943
+ }
9944
+ function layoutToolbarOverflow(toolbar) {
9945
+ const more = toolbar.querySelector(":scope > .toolbar-more");
9946
+ if (!more) {
9947
+ return;
9948
+ }
9949
+ const items = Array.from(toolbar.children).filter((el) => el !== more);
9950
+ items.forEach((el) => el.classList.remove(OVERFLOWED));
9951
+ more.hidden = true;
9952
+ if (rowFits(toolbar)) {
9953
+ return;
9954
+ }
9955
+ more.hidden = false;
9956
+ for (let i = items.length - 1; i >= 1; i--) {
9957
+ items[i].classList.add(OVERFLOWED);
9958
+ if (rowFits(toolbar)) {
9959
+ break;
9960
+ }
9961
+ }
9962
+ for (let i = items.length - 1; i >= 1; i--) {
9963
+ const el = items[i];
9964
+ if (!isLaidOut(el)) {
9965
+ continue;
9966
+ }
9967
+ if (!el.classList.contains("toolbar-separator")) {
9968
+ break;
9969
+ }
9970
+ el.classList.add(OVERFLOWED);
9971
+ }
9972
+ const listed = items.some((el) => el.classList.contains(OVERFLOWED) && el.tagName === "BUTTON" && el.style.display !== "none");
9973
+ if (!listed) {
9974
+ more.hidden = true;
9975
+ }
9976
+ }
9977
+
9911
9978
  // src/imageSrcPolicy.ts
9912
9979
  var FULLY_QUALIFIED = /^(https?|data|blob):/i;
9913
9980
  function resolveImageRenderSrc(src, host) {
@@ -10231,7 +10298,7 @@ function installWheelZoom(target, opts) {
10231
10298
  function resolveFeatures(partial) {
10232
10299
  return { ...DEFAULT_FEATURES, ...partial ?? {} };
10233
10300
  }
10234
- function buildEditorShell(container, features) {
10301
+ function buildEditorShell(container, features, landmark = true) {
10235
10302
  const root = document.createElement("div");
10236
10303
  root.className = "mikedown-root";
10237
10304
  root.setAttribute("data-mikedown-root", "");
@@ -10262,8 +10329,10 @@ function buildEditorShell(container, features) {
10262
10329
  }
10263
10330
  const editorContainer = document.createElement("div");
10264
10331
  editorContainer.className = "mikedown-editor-container";
10265
- editorContainer.setAttribute("role", "main");
10266
- editorContainer.setAttribute("aria-label", "Markdown editor");
10332
+ if (landmark) {
10333
+ editorContainer.setAttribute("role", "main");
10334
+ editorContainer.setAttribute("aria-label", "Markdown editor");
10335
+ }
10267
10336
  const sourceContainer = document.createElement("div");
10268
10337
  sourceContainer.className = "mikedown-source-container";
10269
10338
  sourceContainer.style.display = "none";
@@ -10321,7 +10390,7 @@ ${body}`;
10321
10390
  function formatHotkeyChord(chord) {
10322
10391
  return chord.split("+").map((part) => part.length === 1 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1)).join("+");
10323
10392
  }
10324
- var toolbarSvg = (d, sw = 1.8) => `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
10393
+ var toolbarSvg = (d, sw = 1.8) => `<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
10325
10394
  var toolbarIcons = {
10326
10395
  bold: toolbarSvg('<path d="M4 2.5h5a3 3 0 0 1 0 6H4zM4 8.5h6a3 3 0 0 1 0 6H4z" fill="none"/>', 2),
10327
10396
  italic: toolbarSvg('<line x1="10" y1="2" x2="6" y2="14"/><line x1="7" y1="2" x2="12" y2="2"/><line x1="4" y1="14" x2="9" y2="14"/>'),
@@ -10341,7 +10410,7 @@ var toolbarIcons = {
10341
10410
  undo: toolbarSvg('<polyline points="4 7 2 5 4 3"/><path d="M2 5h8a4 4 0 0 1 0 8H7"/>'),
10342
10411
  redo: toolbarSvg('<polyline points="12 7 14 5 12 3"/><path d="M14 5H6a4 4 0 0 0 0 8h3"/>'),
10343
10412
  source: toolbarSvg('<polyline points="5 4 2 8 5 12"/><polyline points="11 4 14 8 11 12"/><line x1="9" y1="3" x2="7" y2="13"/>'),
10344
- gear: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
10413
+ gear: `<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
10345
10414
  diff: toolbarSvg('<path d="M4 3v10"/><path d="M12 3v10"/><path d="M7 5h2" stroke-opacity="0.5"/><path d="M7 8h2"/><path d="M7 11h2" stroke-opacity="0.5"/>'),
10346
10415
  sun: toolbarSvg('<circle cx="8" cy="8" r="3"/><path d="M8 1.5v1.5M8 13v1.5M1.5 8H3M13 8h1.5M3.4 3.4l1.06 1.06M11.54 11.54l1.06 1.06M3.4 12.6l1.06-1.06M11.54 4.46l1.06-1.06"/>'),
10347
10416
  moon: toolbarSvg('<path d="M13.5 8a5.5 5.5 0 0 1-8.38 4.68A5.5 5.5 0 0 1 8 2.5c0 .5.05 1 .16 1.47A4.5 4.5 0 0 0 13.5 8z"/>'),
@@ -10353,6 +10422,7 @@ var toolbarIcons = {
10353
10422
  headingNumbers: toolbarSvg('<text x="8" y="11.5" text-anchor="middle" font-size="8" font-weight="700" fill="currentColor" stroke="none" font-family="sans-serif">1.2</text>', 0),
10354
10423
  zoomIn: toolbarSvg('<circle cx="7" cy="7" r="4.5"/><line x1="10.2" y1="10.2" x2="14" y2="14"/><line x1="5" y1="7" x2="9" y2="7"/><line x1="7" y1="5" x2="7" y2="9"/>'),
10355
10424
  zoomOut: toolbarSvg('<circle cx="7" cy="7" r="4.5"/><line x1="10.2" y1="10.2" x2="14" y2="14"/><line x1="5" y1="7" x2="9" y2="7"/>'),
10425
+ more: toolbarSvg('<circle cx="3.5" cy="8" r="1.2" fill="currentColor" stroke="none"/><circle cx="8" cy="8" r="1.2" fill="currentColor" stroke="none"/><circle cx="12.5" cy="8" r="1.2" fill="currentColor" stroke="none"/>'),
10356
10426
  emoji: toolbarSvg('<circle cx="8" cy="8" r="6"/><circle cx="6" cy="6.5" r="0.6" fill="currentColor" stroke="none"/><circle cx="10" cy="6.5" r="0.6" fill="currentColor" stroke="none"/><path d="M5.5 9.5c.7 1.2 1.7 1.8 2.5 1.8s1.8-.6 2.5-1.8"/>')
10357
10427
  };
10358
10428
  var CALLOUT_KIND_LABEL = {
@@ -10413,7 +10483,18 @@ function createMikeDownEditor(container, options) {
10413
10483
  const host = options.host;
10414
10484
  const hostOwnsShortcuts = options.hostOwnsShortcuts === true;
10415
10485
  const features = resolveFeatures(options.features);
10416
- const shell = buildEditorShell(container, features);
10486
+ const shell = buildEditorShell(container, features, options.landmark !== false);
10487
+ const surfaceAttrs = {};
10488
+ if (options.id) {
10489
+ surfaceAttrs.id = options.id;
10490
+ }
10491
+ if (options.ariaLabelledBy) {
10492
+ surfaceAttrs["aria-labelledby"] = options.ariaLabelledBy;
10493
+ }
10494
+ surfaceAttrs["aria-label"] = options.ariaLabel || (options.ariaLabelledBy ? "" : "Document text");
10495
+ if (!surfaceAttrs["aria-label"]) {
10496
+ delete surfaceAttrs["aria-label"];
10497
+ }
10417
10498
  const { root, editorContainer, sourceContainer } = shell;
10418
10499
  const disposers = [];
10419
10500
  function listen(target, type, fn, opts) {
@@ -11721,6 +11802,9 @@ function createMikeDownEditor(container, options) {
11721
11802
  if (btn) {
11722
11803
  btn.style.display = mode === "off" ? "none" : "";
11723
11804
  }
11805
+ if (shell.toolbar) {
11806
+ layoutToolbarOverflow(shell.toolbar);
11807
+ }
11724
11808
  }
11725
11809
  let sidebarFooterLink = null;
11726
11810
  let outlineSidebar = null;
@@ -11930,13 +12014,21 @@ function createMikeDownEditor(container, options) {
11930
12014
  sep.setAttribute("role", "separator");
11931
12015
  return sep;
11932
12016
  }
12017
+ const overflowOpeners = /* @__PURE__ */ new Map();
12018
+ function wireDropdown(btn, open) {
12019
+ overflowOpeners.set(btn, open);
12020
+ btn.addEventListener("click", () => {
12021
+ if (isToolbarDropdownOpen()) {
12022
+ hideToolbarDropdown();
12023
+ return;
12024
+ }
12025
+ open(btn);
12026
+ });
12027
+ }
11933
12028
  const textFormatBtn = makeDropdownTrigger("textFormat", "Text Format", getBlockLabel());
11934
- textFormatBtn.addEventListener("click", () => {
11935
- if (isToolbarDropdownOpen()) {
11936
- hideToolbarDropdown();
11937
- return;
11938
- }
11939
- showToolbarDropdown(textFormatBtn, [
12029
+ textFormatBtn.setAttribute("aria-label", `${getBlockLabel()} Text Format`);
12030
+ wireDropdown(textFormatBtn, (anchor) => {
12031
+ showToolbarDropdown(anchor, [
11940
12032
  { type: "action", id: "h1", label: "Heading 1", icon: '<span style="font-weight:700;font-size:14px">H1</span>', isActive: () => editor2.isActive("heading", { level: 1 }), action: () => editor2.chain().focus().toggleHeading({ level: 1 }).run() },
11941
12033
  { type: "action", id: "h2", label: "Heading 2", icon: '<span style="font-weight:600;font-size:13px">H2</span>', isActive: () => editor2.isActive("heading", { level: 2 }), action: () => editor2.chain().focus().toggleHeading({ level: 2 }).run() },
11942
12034
  { type: "action", id: "h3", label: "Heading 3", icon: '<span style="font-weight:500;font-size:12px">H3</span>', isActive: () => editor2.isActive("heading", { level: 3 }), action: () => editor2.chain().focus().toggleHeading({ level: 3 }).run() },
@@ -11955,12 +12047,8 @@ function createMikeDownEditor(container, options) {
11955
12047
  ]);
11956
12048
  });
11957
12049
  const listBlockBtn = makeDropdownTrigger("listBlock", "Lists & Blocks", icons.ul);
11958
- listBlockBtn.addEventListener("click", () => {
11959
- if (isToolbarDropdownOpen()) {
11960
- hideToolbarDropdown();
11961
- return;
11962
- }
11963
- showToolbarDropdown(listBlockBtn, [
12050
+ wireDropdown(listBlockBtn, (anchor) => {
12051
+ showToolbarDropdown(anchor, [
11964
12052
  { type: "action", id: "bulletList", label: "Bullet List", icon: icons.ul, isActive: () => editor2.isActive("bulletList"), action: () => editor2.chain().focus().toggleBulletList().run() },
11965
12053
  { type: "action", id: "orderedList", label: "Ordered List", icon: icons.ol, isActive: () => editor2.isActive("orderedList"), action: () => editor2.chain().focus().toggleOrderedList().run() },
11966
12054
  { type: "action", id: "taskList", label: "Task List", icon: icons.task, isActive: () => editor2.isActive("taskList"), action: () => editor2.chain().focus().toggleTaskList().run() },
@@ -11984,14 +12072,10 @@ function createMikeDownEditor(container, options) {
11984
12072
  const linkBtn = makeBtn("link", "Insert Link (Cmd+K)", icons.link);
11985
12073
  linkBtn.addEventListener("click", () => showLinkDialog(editor2));
11986
12074
  const insertBtn = makeDropdownTrigger("insert", "Insert", icons.image);
11987
- insertBtn.addEventListener("click", () => {
11988
- if (isToolbarDropdownOpen()) {
11989
- hideToolbarDropdown();
11990
- return;
11991
- }
11992
- showToolbarDropdown(insertBtn, [
12075
+ wireDropdown(insertBtn, (anchor) => {
12076
+ showToolbarDropdown(anchor, [
11993
12077
  { type: "action", id: "image", label: "Image", icon: icons.image, action: () => showImageInsertDialog(editor2) },
11994
- { type: "action", id: "table", label: "Table", icon: icons.table, isActive: () => editor2.isActive("table"), action: () => showTableGridPicker(editor2, insertBtn) },
12078
+ { type: "action", id: "table", label: "Table", icon: icons.table, isActive: () => editor2.isActive("table"), action: () => showTableGridPicker(editor2, anchor) },
11995
12079
  { type: "action", id: "hr", label: "Horizontal Rule", icon: icons.hr, action: () => editor2.chain().focus().setHorizontalRule().run() }
11996
12080
  ]);
11997
12081
  });
@@ -12002,12 +12086,8 @@ function createMikeDownEditor(container, options) {
12002
12086
  const sourceBtn = makeBtn("sourceToggle", "Toggle Source Mode (Cmd+/)", icons.source);
12003
12087
  sourceBtn.addEventListener("click", () => toggleSourceMode());
12004
12088
  const shareBtn = makeDropdownTrigger("share", "Share", icons.share);
12005
- shareBtn.addEventListener("click", () => {
12006
- if (isToolbarDropdownOpen()) {
12007
- hideToolbarDropdown();
12008
- return;
12009
- }
12010
- showToolbarDropdown(shareBtn, [
12089
+ wireDropdown(shareBtn, (anchor) => {
12090
+ showToolbarDropdown(anchor, [
12011
12091
  ...host.viewInBrowser ? [{ type: "action", id: "viewInBrowser", label: "View in Browser", icon: icons.browser, action: () => {
12012
12092
  host.viewInBrowser?.(getHtml());
12013
12093
  } }] : [],
@@ -12183,12 +12263,65 @@ function createMikeDownEditor(container, options) {
12183
12263
  headingNumbersBtn.style.display = currentHeadingNumbering === "off" ? "none" : "";
12184
12264
  const emojiBtn = makeBtn("emoji", "Insert Emoji (Cmd+;)", icons.emoji);
12185
12265
  emojiBtn.addEventListener("mousedown", (e) => e.preventDefault());
12186
- emojiBtn.addEventListener("click", () => {
12266
+ const openEmoji = (anchor) => {
12187
12267
  if (isEmojiPickerOpen()) {
12188
12268
  hideEmojiPicker();
12189
12269
  return;
12190
12270
  }
12191
- showEmojiPicker(editor2, { anchorRect: emojiBtn.getBoundingClientRect() });
12271
+ showEmojiPicker(editor2, { anchorRect: anchor.getBoundingClientRect() });
12272
+ };
12273
+ overflowOpeners.set(emojiBtn, openEmoji);
12274
+ emojiBtn.addEventListener("click", () => openEmoji(emojiBtn));
12275
+ const moreBtn = makeBtn("more", "More", icons.more);
12276
+ moreBtn.classList.add("toolbar-more");
12277
+ moreBtn.setAttribute("aria-haspopup", "menu");
12278
+ moreBtn.hidden = true;
12279
+ moreBtn.addEventListener("mousedown", (e) => e.preventDefault());
12280
+ moreBtn.addEventListener("click", () => {
12281
+ if (isToolbarDropdownOpen()) {
12282
+ hideToolbarDropdown();
12283
+ return;
12284
+ }
12285
+ const entries = [];
12286
+ for (const el of Array.from(toolbar.children)) {
12287
+ if (!el.classList.contains("toolbar-overflowed")) {
12288
+ continue;
12289
+ }
12290
+ if (el.classList.contains("toolbar-separator")) {
12291
+ if (entries.length && entries[entries.length - 1].type !== "separator") {
12292
+ entries.push({ type: "separator" });
12293
+ }
12294
+ continue;
12295
+ }
12296
+ if (!(el instanceof HTMLButtonElement) || el.style.display === "none") {
12297
+ continue;
12298
+ }
12299
+ const btn = el;
12300
+ const svg2 = btn.querySelector("svg");
12301
+ const opener = overflowOpeners.get(btn);
12302
+ entries.push({
12303
+ type: "action",
12304
+ id: `more-${btn.dataset.action ?? ""}`,
12305
+ label: btn.dataset.menuLabel ?? btn.dataset.tip ?? btn.getAttribute("aria-label") ?? "",
12306
+ // An empty icon slot keeps text-only entries (the size readout) aligned.
12307
+ icon: svg2 ? svg2.outerHTML : '<span aria-hidden="true"></span>',
12308
+ isActive: () => btn.classList.contains("active"),
12309
+ isDisabled: () => btn.disabled,
12310
+ action: () => {
12311
+ if (opener) {
12312
+ opener(moreBtn);
12313
+ } else {
12314
+ btn.click();
12315
+ }
12316
+ }
12317
+ });
12318
+ }
12319
+ while (entries.length && entries[entries.length - 1].type === "separator") {
12320
+ entries.pop();
12321
+ }
12322
+ if (entries.length) {
12323
+ showToolbarDropdown(moreBtn, entries);
12324
+ }
12192
12325
  });
12193
12326
  toolbar.appendChild(textFormatBtn);
12194
12327
  toolbar.appendChild(listBlockBtn);
@@ -12216,11 +12349,27 @@ function createMikeDownEditor(container, options) {
12216
12349
  toolbar.appendChild(makeSeparator());
12217
12350
  toolbar.appendChild(themeBtn);
12218
12351
  toolbar.appendChild(settingsBtn);
12352
+ toolbar.appendChild(moreBtn);
12353
+ if (typeof ResizeObserver !== "undefined") {
12354
+ let frame = 0;
12355
+ const ro = new ResizeObserver(() => {
12356
+ cancelAnimationFrame(frame);
12357
+ frame = requestAnimationFrame(() => layoutToolbarOverflow(toolbar));
12358
+ });
12359
+ ro.observe(toolbar);
12360
+ disposers.push(() => {
12361
+ cancelAnimationFrame(frame);
12362
+ ro.disconnect();
12363
+ });
12364
+ }
12365
+ layoutToolbarOverflow(toolbar);
12219
12366
  toolbar.__condensedUpdate = () => {
12220
12367
  const labelEl = textFormatBtn.querySelector(".condensed-btn-label");
12221
- if (labelEl) {
12222
- labelEl.innerHTML = getBlockLabel();
12368
+ const blockLabel = getBlockLabel();
12369
+ if (labelEl && labelEl.innerHTML !== blockLabel) {
12370
+ labelEl.innerHTML = blockLabel;
12223
12371
  }
12372
+ textFormatBtn.setAttribute("aria-label", `${blockLabel} Text Format`);
12224
12373
  textFormatBtn.classList.toggle("active", isTextFormatActive());
12225
12374
  listBlockBtn.classList.toggle("active", isBlockActive());
12226
12375
  linkBtn.classList.toggle("active", editor2.isActive("link"));
@@ -12239,10 +12388,15 @@ function createMikeDownEditor(container, options) {
12239
12388
  const size = currentFontSize();
12240
12389
  const resetBtn = toolbar.querySelector('button[data-action="zoomReset"]');
12241
12390
  if (resetBtn) {
12391
+ const sizeChanged = resetBtn.textContent !== String(size);
12242
12392
  resetBtn.textContent = String(size);
12393
+ resetBtn.dataset.menuLabel = `Reset Font Size (${size}px)`;
12243
12394
  const tip = `Font size ${size}px (click to reset to ${DEFAULT_FONT_SIZE}px)`;
12244
12395
  resetBtn.dataset.tip = tip;
12245
- resetBtn.setAttribute("aria-label", tip);
12396
+ resetBtn.setAttribute("aria-label", `${size} px font size (click to reset to ${DEFAULT_FONT_SIZE}px)`);
12397
+ if (sizeChanged && resetBtn.isConnected) {
12398
+ layoutToolbarOverflow(toolbar);
12399
+ }
12246
12400
  }
12247
12401
  const outBtn = toolbar.querySelector('button[data-action="zoomOut"]');
12248
12402
  if (outBtn) {
@@ -12624,7 +12778,7 @@ function createMikeDownEditor(container, options) {
12624
12778
  // dispatching a no-op transaction (tr.setMeta('slashPlaceholder', true))
12625
12779
  // rather than touching editor.view.dom directly.
12626
12780
  Placeholder.configure({
12627
- placeholder: () => features.slashCommands && currentSlashCommandsEnabled ? "Type / for commands\u2026" : "Start writing\u2026"
12781
+ placeholder: () => options.placeholder ?? (features.slashCommands && currentSlashCommandsEnabled ? "Type / for commands\u2026" : "Start writing\u2026")
12628
12782
  }),
12629
12783
  // ── Markdown serialiser / input rules (M2b / M2c) ─────────────────────────
12630
12784
  // tiptap-markdown provides:
@@ -12724,7 +12878,7 @@ function createMikeDownEditor(container, options) {
12724
12878
  // ── Keyboard shortcuts ──────────────────────────────────────────────────────
12725
12879
  editorProps: {
12726
12880
  // axe aria-input-field-name: the contenteditable textbox needs an accessible name.
12727
- attributes: { "aria-label": "Document text", "aria-multiline": "true" },
12881
+ attributes: { ...surfaceAttrs, role: "textbox", "aria-multiline": "true" },
12728
12882
  handleKeyDown(view, event) {
12729
12883
  if (features.findReplace && (event.metaKey || event.ctrlKey) && event.key === "f") {
12730
12884
  event.preventDefault();
@@ -12964,6 +13118,7 @@ function createMikeDownEditor(container, options) {
12964
13118
  const { from, to } = editor.state.selection;
12965
13119
  postStats({ selection: from === to ? null : editor.state.doc.textBetween(from, to, "\n") });
12966
13120
  });
13121
+ editor.on("focus", () => updateTableToolbar(editor));
12967
13122
  editor.on("blur", () => {
12968
13123
  setTimeout(() => {
12969
13124
  if (!document.activeElement?.closest("#mikedown-table-toolbar")) {
@@ -14106,6 +14261,7 @@ function createMikeDownEditor(container, options) {
14106
14261
  buildCmTheme(),
14107
14262
  cmReadOnly.of(EditorState3.readOnly.of(readOnly)),
14108
14263
  EditorView2.lineWrapping,
14264
+ EditorView2.contentAttributes.of(options.id || options.ariaLabel || options.ariaLabelledBy ? { ...surfaceAttrs, "aria-multiline": "true" } : {}),
14109
14265
  EditorView2.updateListener.of((update2) => {
14110
14266
  if (update2.docChanged && !cmLoading) {
14111
14267
  const md = update2.state.doc.toString();
@@ -15047,9 +15203,224 @@ var NoopHost = class {
15047
15203
  onChange(_markdown, _meta) {
15048
15204
  }
15049
15205
  };
15206
+
15207
+ // src/binding.ts
15208
+ function selectorAttributes(selector) {
15209
+ const names = /* @__PURE__ */ new Set();
15210
+ if (/\.[\w-]+/.test(selector)) {
15211
+ names.add("class");
15212
+ }
15213
+ if (/#[\w-]+/.test(selector)) {
15214
+ names.add("id");
15215
+ }
15216
+ for (const m of selector.matchAll(/\[\s*([\w:-]+)/g)) {
15217
+ if (m[1]) {
15218
+ names.add(m[1]);
15219
+ }
15220
+ }
15221
+ return names.size ? [...names] : void 0;
15222
+ }
15223
+ function bindMikeDownValue(container, options = {}) {
15224
+ const {
15225
+ initialValue = "",
15226
+ debounceMs = 150,
15227
+ onValue,
15228
+ onTouched,
15229
+ disabled: initialDisabled = false,
15230
+ theme: initialTheme = "auto",
15231
+ darkSelector,
15232
+ outputTransform,
15233
+ host: hostOverrides,
15234
+ ...editorOptions
15235
+ } = options;
15236
+ const out = (md) => outputTransform ? outputTransform(md) : md;
15237
+ let destroyed = false;
15238
+ let timer;
15239
+ let pending = null;
15240
+ let lastValue = out(initialValue);
15241
+ let theme = initialTheme;
15242
+ let readOnly = editorOptions.readOnly === true;
15243
+ let disabled = initialDisabled;
15244
+ const flush = () => {
15245
+ if (timer !== void 0) {
15246
+ clearTimeout(timer);
15247
+ timer = void 0;
15248
+ }
15249
+ if (pending === null) {
15250
+ return;
15251
+ }
15252
+ const value = out(pending);
15253
+ pending = null;
15254
+ if (value === lastValue) {
15255
+ return;
15256
+ }
15257
+ lastValue = value;
15258
+ onValue?.(value);
15259
+ };
15260
+ let labelEl = null;
15261
+ let ariaLabelledBy = editorOptions.ariaLabelledBy;
15262
+ if (editorOptions.id && !ariaLabelledBy) {
15263
+ const root = container.getRootNode();
15264
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(editorOptions.id) : editorOptions.id;
15265
+ labelEl = root.querySelector?.(`label[for="${escaped}"]`) ?? null;
15266
+ if (labelEl) {
15267
+ if (!labelEl.id) {
15268
+ labelEl.id = `${editorOptions.id}-label`;
15269
+ }
15270
+ ariaLabelledBy = labelEl.id;
15271
+ }
15272
+ }
15273
+ const isDarkNow = () => {
15274
+ if (darkSelector) {
15275
+ try {
15276
+ return !!container.closest(darkSelector);
15277
+ } catch {
15278
+ return false;
15279
+ }
15280
+ }
15281
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
15282
+ };
15283
+ const resolve = (t) => t === "auto" ? isDarkNow() ? "dark" : "light" : t;
15284
+ const handle = createMikeDownEditor(container, {
15285
+ ...editorOptions,
15286
+ ariaLabelledBy,
15287
+ landmark: editorOptions.landmark ?? false,
15288
+ readOnly: readOnly || disabled,
15289
+ initialMarkdown: initialValue,
15290
+ theme: resolve(theme),
15291
+ host: {
15292
+ ...hostOverrides,
15293
+ onChange: (md, meta) => {
15294
+ hostOverrides?.onChange?.(md, meta);
15295
+ if (destroyed) {
15296
+ return;
15297
+ }
15298
+ pending = md;
15299
+ if (debounceMs <= 0) {
15300
+ flush();
15301
+ return;
15302
+ }
15303
+ if (timer !== void 0) {
15304
+ clearTimeout(timer);
15305
+ }
15306
+ timer = setTimeout(flush, debounceMs);
15307
+ }
15308
+ }
15309
+ });
15310
+ const unBlur = handle.on("blur", flush);
15311
+ let observer = null;
15312
+ let mql = null;
15313
+ let applied = resolve(theme);
15314
+ const applyTheme = () => {
15315
+ if (destroyed) {
15316
+ return;
15317
+ }
15318
+ const next = resolve(theme);
15319
+ if (next === applied) {
15320
+ return;
15321
+ }
15322
+ applied = next;
15323
+ handle.setTheme(next);
15324
+ };
15325
+ const stopFollowing = () => {
15326
+ observer?.disconnect();
15327
+ observer = null;
15328
+ mql?.removeEventListener?.("change", applyTheme);
15329
+ mql = null;
15330
+ };
15331
+ const startFollowing = () => {
15332
+ stopFollowing();
15333
+ if (theme !== "auto") {
15334
+ return;
15335
+ }
15336
+ if (darkSelector) {
15337
+ if (typeof MutationObserver === "undefined") {
15338
+ return;
15339
+ }
15340
+ const attributeFilter = selectorAttributes(darkSelector);
15341
+ observer = new MutationObserver(applyTheme);
15342
+ observer.observe(container.ownerDocument.documentElement, {
15343
+ attributes: true,
15344
+ subtree: true,
15345
+ ...attributeFilter ? { attributeFilter } : {}
15346
+ });
15347
+ } else if (window.matchMedia) {
15348
+ mql = window.matchMedia("(prefers-color-scheme: dark)");
15349
+ mql.addEventListener?.("change", applyTheme);
15350
+ }
15351
+ };
15352
+ startFollowing();
15353
+ const onFocusOut = (event) => {
15354
+ const next = event.relatedTarget;
15355
+ if (next && container.contains(next)) {
15356
+ return;
15357
+ }
15358
+ if (next instanceof Element && next.closest(".mikedown-portal")) {
15359
+ return;
15360
+ }
15361
+ flush();
15362
+ onTouched?.();
15363
+ };
15364
+ container.addEventListener("focusout", onFocusOut);
15365
+ const onLabelClick = () => handle.focus();
15366
+ labelEl?.addEventListener("click", onLabelClick);
15367
+ return {
15368
+ handle,
15369
+ setValue(markdown) {
15370
+ if (destroyed) {
15371
+ return;
15372
+ }
15373
+ if (timer !== void 0) {
15374
+ clearTimeout(timer);
15375
+ timer = void 0;
15376
+ }
15377
+ pending = null;
15378
+ lastValue = markdown;
15379
+ if (markdown === out(handle.getMarkdown())) {
15380
+ return;
15381
+ }
15382
+ handle.setMarkdown(markdown, { pristine: true });
15383
+ },
15384
+ flush,
15385
+ setTheme(next) {
15386
+ theme = next;
15387
+ if (!destroyed) {
15388
+ applied = resolve(theme);
15389
+ handle.setTheme(applied);
15390
+ }
15391
+ startFollowing();
15392
+ },
15393
+ setReadOnly(next) {
15394
+ readOnly = next;
15395
+ if (!destroyed) {
15396
+ handle.setReadOnly(readOnly || disabled);
15397
+ }
15398
+ },
15399
+ setDisabled(next) {
15400
+ disabled = next;
15401
+ if (!destroyed) {
15402
+ handle.setReadOnly(readOnly || disabled);
15403
+ }
15404
+ },
15405
+ focus: () => handle.focus(),
15406
+ destroy() {
15407
+ if (destroyed) {
15408
+ return;
15409
+ }
15410
+ flush();
15411
+ destroyed = true;
15412
+ stopFollowing();
15413
+ unBlur();
15414
+ container.removeEventListener("focusout", onFocusOut);
15415
+ labelEl?.removeEventListener("click", onLabelClick);
15416
+ handle.destroy();
15417
+ }
15418
+ };
15419
+ }
15050
15420
  export {
15051
15421
  DEFAULT_FEATURES,
15052
15422
  NoopHost,
15423
+ bindMikeDownValue,
15053
15424
  createMikeDownEditor
15054
15425
  };
15055
15426
  //# sourceMappingURL=index.js.map