@domternal/react 0.4.1 → 0.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.
package/dist/index.js CHANGED
@@ -465,6 +465,20 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
465
465
  const buttons = toolbarRef.current?.querySelectorAll(".dm-toolbar-button");
466
466
  buttons?.[idx]?.focus();
467
467
  }, []);
468
+ const focusDropdownItem = useCallback((direction, first) => {
469
+ const panel = toolbarRef.current?.querySelector(".dm-toolbar-dropdown-panel");
470
+ if (!panel) return;
471
+ const items = Array.from(panel.querySelectorAll('[role="menuitem"]'));
472
+ if (!items.length) return;
473
+ if (first) {
474
+ items[0]?.focus();
475
+ return;
476
+ }
477
+ const current = document.activeElement;
478
+ const idx = items.indexOf(current);
479
+ const next = idx === -1 ? direction > 0 ? 0 : items.length - 1 : (idx + direction + items.length) % items.length;
480
+ items[next]?.focus();
481
+ }, []);
468
482
  const onKeyDown = useCallback((event) => {
469
483
  const controller = controllerRef.current;
470
484
  if (!controller) return;
@@ -479,6 +493,26 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
479
493
  controller.navigatePrev();
480
494
  focusCurrentButton();
481
495
  break;
496
+ case "ArrowDown": {
497
+ event.preventDefault();
498
+ if (controller.openDropdown) {
499
+ focusDropdownItem(1);
500
+ } else {
501
+ const btn = document.activeElement;
502
+ if (btn?.getAttribute("aria-haspopup") && btn.closest(".dm-toolbar")) {
503
+ btn.click();
504
+ requestAnimationFrame(() => focusDropdownItem(0, true));
505
+ }
506
+ }
507
+ break;
508
+ }
509
+ case "ArrowUp": {
510
+ event.preventDefault();
511
+ if (controller.openDropdown) {
512
+ focusDropdownItem(-1);
513
+ }
514
+ break;
515
+ }
482
516
  case "Home":
483
517
  event.preventDefault();
484
518
  controller.navigateFirst();
@@ -497,7 +531,7 @@ function useKeyboardNav(controllerRef, toolbarRef, closeDropdown) {
497
531
  }
498
532
  break;
499
533
  }
500
- }, [closeDropdown, focusCurrentButton]);
534
+ }, [closeDropdown, focusCurrentButton, focusDropdownItem]);
501
535
  return { onKeyDown, focusCurrentButton };
502
536
  }
503
537
 
@@ -573,6 +607,7 @@ function ToolbarDropdownPanel({
573
607
  type: "button",
574
608
  className: `dm-color-swatch${isActive(sub.name) ? " dm-color-swatch--active" : ""}`,
575
609
  role: "menuitem",
610
+ tabIndex: -1,
576
611
  "aria-label": sub.label,
577
612
  title: sub.label,
578
613
  style: { backgroundColor: sub.color },
@@ -586,6 +621,7 @@ function ToolbarDropdownPanel({
586
621
  type: "button",
587
622
  className: "dm-color-palette-reset",
588
623
  role: "menuitem",
624
+ tabIndex: -1,
589
625
  "aria-label": sub.label,
590
626
  dangerouslySetInnerHTML: { __html: getCachedItemContent(sub.icon, sub.label) },
591
627
  onMouseDown: (e) => e.preventDefault(),
@@ -609,6 +645,7 @@ function ToolbarDropdownPanel({
609
645
  type: "button",
610
646
  className: `dm-toolbar-dropdown-item${isActive(sub.name) ? " dm-toolbar-dropdown-item--active" : ""}`,
611
647
  role: "menuitem",
648
+ tabIndex: -1,
612
649
  "aria-label": sub.label,
613
650
  ref: (el) => {
614
651
  if (el && sub.style) el.setAttribute("style", sub.style);
@@ -700,6 +737,7 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
700
737
  return;
701
738
  }
702
739
  controllerRef.current?.executeCommand(item);
740
+ requestAnimationFrame(() => editor.view.focus());
703
741
  }, [editor, closeDropdown]);
704
742
  const onDropdownItemClick = useCallback((item, event) => {
705
743
  if (!editor) return;
@@ -714,6 +752,7 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
714
752
  } else {
715
753
  controllerRef.current?.executeCommand(item);
716
754
  }
755
+ requestAnimationFrame(() => editor.view.focus());
717
756
  }, [editor, closeDropdown]);
718
757
  const onButtonFocus = useCallback((name) => {
719
758
  const index = controllerRef.current?.getFlatIndex(name) ?? -1;
@@ -729,6 +768,7 @@ function DomternalToolbar({ editor: editorProp, icons, layout }) {
729
768
  className: "dm-toolbar",
730
769
  role: "toolbar",
731
770
  "aria-label": "Editor formatting",
771
+ "data-dm-editor-ui": "",
732
772
  onKeyDown,
733
773
  children: groups.map((group, gi) => /* @__PURE__ */ jsxs(Fragment, { children: [
734
774
  gi > 0 && /* @__PURE__ */ jsx("div", { className: "dm-toolbar-separator", role: "separator" }),
@@ -1269,7 +1309,13 @@ function useEmojiPicker(editor, emojis) {
1269
1309
  if (!grid) return;
1270
1310
  const label = grid.querySelector(`[data-category="${cat}"]`);
1271
1311
  if (label) {
1272
- grid.scrollTo({ top: label.offsetTop - grid.offsetTop, behavior: "smooth" });
1312
+ grid.scrollTo({ top: label.offsetTop - grid.offsetTop });
1313
+ setTimeout(() => {
1314
+ const firstSwatch = label.nextElementSibling;
1315
+ if (firstSwatch instanceof HTMLElement && firstSwatch.classList.contains("dm-emoji-swatch")) {
1316
+ firstSwatch.focus();
1317
+ }
1318
+ }, 50);
1273
1319
  }
1274
1320
  });
1275
1321
  }, []);
@@ -1354,6 +1400,48 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1354
1400
  close,
1355
1401
  categories
1356
1402
  } = useEmojiPicker(editor, emojis);
1403
+ const onGridKeyDown = useCallback((event) => {
1404
+ const grid = event.currentTarget;
1405
+ const swatches = Array.from(grid.querySelectorAll(".dm-emoji-swatch"));
1406
+ if (!swatches.length) return;
1407
+ const current = document.activeElement;
1408
+ let idx = swatches.indexOf(current);
1409
+ if (idx === -1) {
1410
+ if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
1411
+ event.preventDefault();
1412
+ swatches[0]?.focus();
1413
+ }
1414
+ return;
1415
+ }
1416
+ const cols = 8;
1417
+ let next = idx;
1418
+ switch (event.key) {
1419
+ case "ArrowRight":
1420
+ event.preventDefault();
1421
+ next = Math.min(idx + 1, swatches.length - 1);
1422
+ break;
1423
+ case "ArrowLeft":
1424
+ event.preventDefault();
1425
+ next = Math.max(idx - 1, 0);
1426
+ break;
1427
+ case "ArrowDown":
1428
+ event.preventDefault();
1429
+ next = Math.min(idx + cols, swatches.length - 1);
1430
+ break;
1431
+ case "ArrowUp":
1432
+ event.preventDefault();
1433
+ next = Math.max(idx - cols, 0);
1434
+ break;
1435
+ case "Enter":
1436
+ case " ":
1437
+ event.preventDefault();
1438
+ swatches[idx]?.click();
1439
+ return;
1440
+ default:
1441
+ return;
1442
+ }
1443
+ swatches[next]?.focus();
1444
+ }, []);
1357
1445
  if (!isOpen) return /* @__PURE__ */ jsx("div", { ref: pickerRef, className: "dm-emoji-picker-host" });
1358
1446
  return /* @__PURE__ */ jsx("div", { ref: pickerRef, className: "dm-emoji-picker-host", children: /* @__PURE__ */ jsxs("div", { className: "dm-emoji-picker", children: [
1359
1447
  /* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-search", children: /* @__PURE__ */ jsx(
@@ -1361,6 +1449,7 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1361
1449
  {
1362
1450
  type: "text",
1363
1451
  placeholder: "Search emoji...",
1452
+ "aria-label": "Search emoji",
1364
1453
  value: searchQuery,
1365
1454
  onChange: onSearch,
1366
1455
  onKeyDown: (e) => {
@@ -1373,6 +1462,8 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1373
1462
  {
1374
1463
  type: "button",
1375
1464
  className: `dm-emoji-picker-tab${activeCategory === cat ? " dm-emoji-picker-tab--active" : ""}`,
1465
+ role: "tab",
1466
+ "aria-selected": activeCategory === cat,
1376
1467
  title: cat,
1377
1468
  "aria-label": cat,
1378
1469
  onMouseDown: (e) => e.preventDefault(),
@@ -1381,11 +1472,12 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1381
1472
  },
1382
1473
  cat
1383
1474
  )) }),
1384
- /* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-grid", onScroll: onGridScroll, children: searchQuery ? /* @__PURE__ */ jsx(Fragment$1, { children: filteredEmojis.length > 0 ? filteredEmojis.map((item) => /* @__PURE__ */ jsx(
1475
+ /* @__PURE__ */ jsx("div", { className: "dm-emoji-picker-grid", onScroll: onGridScroll, onKeyDown: onGridKeyDown, children: searchQuery ? /* @__PURE__ */ jsx(Fragment$1, { children: filteredEmojis.length > 0 ? filteredEmojis.map((item) => /* @__PURE__ */ jsx(
1385
1476
  "button",
1386
1477
  {
1387
1478
  type: "button",
1388
1479
  className: "dm-emoji-swatch",
1480
+ tabIndex: -1,
1389
1481
  title: formatName(item.name),
1390
1482
  "aria-label": formatName(item.name),
1391
1483
  onMouseDown: (e) => e.preventDefault(),
@@ -1401,6 +1493,7 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1401
1493
  {
1402
1494
  type: "button",
1403
1495
  className: "dm-emoji-swatch",
1496
+ tabIndex: -1,
1404
1497
  title: formatName(item.name),
1405
1498
  "aria-label": formatName(item.name),
1406
1499
  onMouseDown: (e) => e.preventDefault(),
@@ -1417,6 +1510,7 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1417
1510
  {
1418
1511
  type: "button",
1419
1512
  className: "dm-emoji-swatch",
1513
+ tabIndex: -1,
1420
1514
  title: formatName(item.name),
1421
1515
  "aria-label": formatName(item.name),
1422
1516
  onMouseDown: (e) => e.preventDefault(),
@@ -1445,7 +1539,7 @@ function DomternalContent({ className }) {
1445
1539
  }
1446
1540
  }, [editor]);
1447
1541
  const classes = className ? `dm-editor ${className}` : "dm-editor";
1448
- return /* @__PURE__ */ jsx("div", { className: classes, children: /* @__PURE__ */ jsx("div", { ref: containerRef }) });
1542
+ return /* @__PURE__ */ jsx("div", { className: classes, "data-dm-editor-ui": "", children: /* @__PURE__ */ jsx("div", { ref: containerRef }) });
1449
1543
  }
1450
1544
  function DomternalLoading({ children }) {
1451
1545
  const { editor } = useCurrentEditor();
@@ -1534,7 +1628,7 @@ var DomternalEditor = forwardRef(
1534
1628
  const classes = className ? `dm-editor ${className}` : "dm-editor";
1535
1629
  return /* @__PURE__ */ jsxs(EditorProvider, { editor, children: [
1536
1630
  children,
1537
- /* @__PURE__ */ jsx("div", { className: classes, children: /* @__PURE__ */ jsx("div", { ref: editorRef }) })
1631
+ /* @__PURE__ */ jsx("div", { className: classes, "data-dm-editor-ui": "", children: /* @__PURE__ */ jsx("div", { ref: editorRef }) })
1538
1632
  ] });
1539
1633
  }
1540
1634
  );