emoji-picker-react 4.20.4 → 4.20.5

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.
@@ -27899,6 +27899,7 @@ function useKeyboardNavigation() {
27899
27899
  useSearchInputKeyboardEvents();
27900
27900
  useSkinTonePickerKeyboardEvents();
27901
27901
  useCategoryNavigationKeyboardEvents();
27902
+ useReactionsKeyboardEvents();
27902
27903
  useBodyKeyboardEvents();
27903
27904
  }
27904
27905
  function usePickerMainKeyboardEvents() {
@@ -28104,6 +28105,65 @@ function useCategoryNavigationKeyboardEvents() {
28104
28105
  };
28105
28106
  }, [CategoryNavigationRef, BodyRef, onKeyDown]);
28106
28107
  }
28108
+ // Arrow keys move between reaction buttons (and the expand button)
28109
+ // while the reactions bar has focus. The bar lives outside the scroll
28110
+ // body, so the body handler never sees these events.
28111
+ // https://github.com/ealush/emoji-picker-react/issues/411
28112
+ function useReactionsKeyboardEvents() {
28113
+ var ReactionsRef = useReactionsRef();
28114
+ // The reactions bar mounts late when the user collapses the full
28115
+ // picker, so the listener effect must rerun when it opens.
28116
+ var _useReactionsModeStat = useReactionsModeState(),
28117
+ reactionsOpen = _useReactionsModeStat[0];
28118
+ var onKeyDown = useMemo(function () {
28119
+ return function onKeyDown(event) {
28120
+ var key = event.key;
28121
+ var current = ReactionsRef.current;
28122
+ if (!current) {
28123
+ return;
28124
+ }
28125
+ var delta = reactionFocusDelta(key);
28126
+ if (delta !== 0 && focusReactionSibling(current, delta)) {
28127
+ event.preventDefault();
28128
+ }
28129
+ };
28130
+ },
28131
+ // reactionsOpen: the bar mounts late on collapse, and the new
28132
+ // callback identity reinstalls the listener effect below.
28133
+ [ReactionsRef, reactionsOpen]);
28134
+ useEffect(function () {
28135
+ var current = ReactionsRef.current;
28136
+ if (!current) {
28137
+ return;
28138
+ }
28139
+ current.addEventListener('keydown', onKeyDown);
28140
+ return function () {
28141
+ current.removeEventListener('keydown', onKeyDown);
28142
+ };
28143
+ }, [ReactionsRef, onKeyDown]);
28144
+ }
28145
+ function reactionFocusDelta(key) {
28146
+ switch (key) {
28147
+ case KeyboardEvents.ArrowRight:
28148
+ case KeyboardEvents.ArrowDown:
28149
+ return 1;
28150
+ case KeyboardEvents.ArrowLeft:
28151
+ case KeyboardEvents.ArrowUp:
28152
+ return -1;
28153
+ default:
28154
+ return 0;
28155
+ }
28156
+ }
28157
+ function focusReactionSibling(root, delta) {
28158
+ var buttons = Array.from(root.querySelectorAll('button'));
28159
+ var activeIndex = buttons.indexOf(getActiveElement());
28160
+ var sibling = activeIndex === -1 ? null : buttons[activeIndex + delta];
28161
+ if (!sibling) {
28162
+ return false;
28163
+ }
28164
+ focusElement(sibling);
28165
+ return true;
28166
+ }
28107
28167
  function useBodyKeyboardEvents() {
28108
28168
  var BodyRef = useBodyRef();
28109
28169
  var goUpFromBody = useGoUpFromBody();
@@ -28479,9 +28539,20 @@ var styles = /*#__PURE__*/stylesheet.create({
28479
28539
  });
28480
28540
 
28481
28541
  function elementCountInRow(parent, element) {
28542
+ var _parent$dataset;
28482
28543
  if (!parent || !element) {
28483
28544
  return 0;
28484
28545
  }
28546
+ // The virtualized layout positions emojis with a fixed per-row count that
28547
+ // is rendered onto the category content element. That count is the single
28548
+ // source of truth: deriving it again from fractional rect widths can be
28549
+ // off by one (e.g. category padding shrinking the content box), which
28550
+ // makes arrow-up/arrow-down drift sideways by a column.
28551
+ // https://github.com/ealush/emoji-picker-react/issues/502
28552
+ var declared = Number((_parent$dataset = parent.dataset) == null ? void 0 : _parent$dataset.emojisPerRow);
28553
+ if (Number.isInteger(declared) && declared > 0) {
28554
+ return declared;
28555
+ }
28485
28556
  var parentWidth = parent.getBoundingClientRect().width;
28486
28557
  var elementWidth = element.getBoundingClientRect().width;
28487
28558
  return Math.floor(parentWidth / elementWidth);
@@ -29119,6 +29190,7 @@ function ClickableEmoji(_ref) {
29119
29190
  lazyLoad = _ref.lazyLoad,
29120
29191
  getEmojiUrl = _ref.getEmojiUrl,
29121
29192
  className = _ref.className,
29193
+ buttonClassName = _ref.buttonClassName,
29122
29194
  _ref$noBackground = _ref.noBackground,
29123
29195
  noBackground = _ref$noBackground === void 0 ? false : _ref$noBackground,
29124
29196
  style = _ref.style;
@@ -29131,7 +29203,8 @@ function ClickableEmoji(_ref) {
29131
29203
  emojiNames: emojiNames(emoji),
29132
29204
  unified: unified,
29133
29205
  noBackground: noBackground,
29134
- style: style
29206
+ style: style,
29207
+ className: buttonClassName
29135
29208
  }, createElement(ViewOnlyEmoji, {
29136
29209
  unified: unified,
29137
29210
  emoji: emoji,
@@ -29235,6 +29308,7 @@ function Reactions() {
29235
29308
  unified: reaction,
29236
29309
  showVariations: false,
29237
29310
  className: cx(styles$6.emojiButton),
29311
+ buttonClassName: cx(styles$6.reactionButton),
29238
29312
  noBackground: true,
29239
29313
  getEmojiUrl: getEmojiUrl
29240
29314
  }));
@@ -29250,6 +29324,24 @@ var styles$6 = /*#__PURE__*/stylesheet.create({
29250
29324
  alignItems: 'center',
29251
29325
  height: '100%'
29252
29326
  },
29327
+ reactionButton: {
29328
+ // Every picker button sets outline: none, and reaction buttons also
29329
+ // suppress the focus background, so keyboard focus was invisible.
29330
+ // Focus mirrors the hover treatment (scale plus a soft circular
29331
+ // wash, no outline ring): the scale transform on the inner image
29332
+ // alone is clipped by the button's overflow: hidden, and does
29333
+ // nothing on inline native emoji, so the button itself carries the
29334
+ // effect. Scoped to the reactions bar so grid focus treatment is
29335
+ // untouched.
29336
+ // https://github.com/ealush/emoji-picker-react/issues/473
29337
+ ':focus-visible': {
29338
+ transform: 'scale(1.2)',
29339
+ transition: 'background-color 0.2s, transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.5)',
29340
+ backgroundColor: 'var(--epr-hover-bg-color-reduced-opacity)',
29341
+ borderRadius: '50%',
29342
+ outline: 'none'
29343
+ }
29344
+ },
29253
29345
  emojiButton: {
29254
29346
  ':hover': {
29255
29347
  transform: 'scale(1.2)'
@@ -29473,12 +29565,14 @@ function EmojiCategory(_ref) {
29473
29565
  children = _ref.children,
29474
29566
  hidden = _ref.hidden,
29475
29567
  hiddenOnSearch = _ref.hiddenOnSearch,
29476
- height = _ref.height;
29568
+ height = _ref.height,
29569
+ emojisPerRow = _ref.emojisPerRow;
29477
29570
  var category = categoryFromCategoryConfig(categoryConfig);
29478
29571
  var categoryName = categoryNameFromCategoryConfig(categoryConfig);
29479
29572
  return createElement("li", {
29480
29573
  className: cx(styles$7.category, hidden && commonStyles.hidden, hiddenOnSearch && commonInteractionStyles.hiddenOnSearch),
29481
29574
  "data-name": category,
29575
+ role: "rowgroup",
29482
29576
  "aria-label": categoryName
29483
29577
  }, createElement("h2", {
29484
29578
  className: cx(styles$7.label)
@@ -29486,7 +29580,9 @@ function EmojiCategory(_ref) {
29486
29580
  className: cx(styles$7.categoryContent),
29487
29581
  style: {
29488
29582
  height: height
29489
- }
29583
+ },
29584
+ role: "row",
29585
+ "data-emojis-per-row": emojisPerRow
29490
29586
  }, children));
29491
29587
  }
29492
29588
  var styles$7 = /*#__PURE__*/stylesheet.create({
@@ -29588,9 +29684,19 @@ function EmojiList(_ref) {
29588
29684
  var getEmojisByCategory = useGetEmojisByCategory();
29589
29685
  var labelHeight = getLabelHeight(EmojiListRef.current);
29590
29686
  var topOffset = 0;
29687
+ // role="grid" makes screen readers switch out of virtual-cursor mode so
29688
+ // arrow keys reach the picker's keyboard handler. Each category is one
29689
+ // rowgroup: visual rows shift under virtualization, so the category is
29690
+ // the only stable row unit. Emoji buttons intentionally keep their
29691
+ // native button role for activation semantics.
29692
+ // https://github.com/ealush/emoji-picker-react/issues/508
29693
+ // The list markup is kept for a backwards-compatible DOM structure;
29694
+ // the grid role override is intentional (see below).
29695
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
29591
29696
  return createElement("ul", {
29592
29697
  className: cx(styles$8.emojiList),
29593
- ref: EmojiListRef
29698
+ ref: EmojiListRef,
29699
+ role: "grid"
29594
29700
  }, createElement(MeasureEmoji, null), categories.map(function (categoryConfig) {
29595
29701
  var category = categoryFromCategoryConfig(categoryConfig);
29596
29702
  var currentOffset = topOffset;
@@ -29636,6 +29742,7 @@ function RenderCategory(_ref2) {
29636
29742
  return createElement(EmojiCategory, {
29637
29743
  categoryConfig: categoryConfig,
29638
29744
  height: dimensions == null ? void 0 : dimensions.categoryHeight,
29745
+ emojisPerRow: dimensions == null ? void 0 : dimensions.emojisPerRow,
29639
29746
  // Indicates that there are no visible emojis
29640
29747
  // Hence, the category should be hidden
29641
29748
  hidden: !emojis.length && virtualizedCounter === 0
@@ -30002,10 +30109,12 @@ function BtnSkinToneVariation(_ref) {
30002
30109
  onClick = _ref.onClick,
30003
30110
  isActive = _ref.isActive,
30004
30111
  skinToneVariation = _ref.skinToneVariation,
30005
- style = _ref.style;
30112
+ style = _ref.style,
30113
+ tabIndex = _ref.tabIndex;
30006
30114
  return createElement(Button, {
30007
30115
  style: style,
30008
30116
  onClick: onClick,
30117
+ tabIndex: tabIndex,
30009
30118
  className: cx("epr-tone-" + skinToneVariation, styles$c.tone, !isOpen && styles$c.closedTone, isActive && styles$c.active),
30010
30119
  "aria-pressed": isActive,
30011
30120
  "aria-label": "Skin tone " + skinTonesNamed[skinToneVariation]
@@ -30114,6 +30223,12 @@ function SkinTonePicker(_ref) {
30114
30223
  key: skinToneVariation,
30115
30224
  skinToneVariation: skinToneVariation,
30116
30225
  isOpen: isOpen,
30226
+ // Roving tabindex: when the fan is closed the inactive tones
30227
+ // are invisible (opacity: 0, stacked), so they must not be
30228
+ // reachable via Tab. The active tone stays tabbable to open
30229
+ // the fan from the keyboard.
30230
+ // https://github.com/ealush/emoji-picker-react/issues/492
30231
+ tabIndex: isOpen || active ? 0 : -1,
30117
30232
  style: {
30118
30233
  transform: cx(vertical ? "translateY(-" + i * (isOpen ? ITEM_SIZE : 0) + "px)" : "translateX(-" + i * (isOpen ? ITEM_SIZE : 0) + "px)", isOpen && active && 'scale(1.3)')
30119
30234
  },