@domternal/angular 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/README.md CHANGED
@@ -24,7 +24,7 @@ See <u>[Packages & Bundle Size](https://domternal.dev/v1/packages)</u> for a ful
24
24
  - **Tree-shakeable** - import only what you use, your bundler strips the rest
25
25
  - **~38 KB gzipped** (own code), <u>[~108 KB total](https://domternal.dev/v1/packages)</u> with ProseMirror
26
26
  - **TypeScript first** - 100% typed, zero `any`
27
- - **7,500+ tests** - 3,936 unit tests and 3,652 E2E tests across 76 Playwright specs
27
+ - **6,400+ tests** - 2,677 unit tests and 3,793 E2E tests across 80 Playwright specs
28
28
  - **Light and dark theme** - 70+ CSS custom properties for full visual control
29
29
  - **Inline styles export** - `getHTML({ styled: true })` produces inline CSS ready for email clients, CMS, and Google Docs
30
30
  - **SSR helpers** - `generateHTML`, `generateJSON`, `generateText` for server-side rendering
package/dist/README.md CHANGED
@@ -24,7 +24,7 @@ See <u>[Packages & Bundle Size](https://domternal.dev/v1/packages)</u> for a ful
24
24
  - **Tree-shakeable** - import only what you use, your bundler strips the rest
25
25
  - **~38 KB gzipped** (own code), <u>[~108 KB total](https://domternal.dev/v1/packages)</u> with ProseMirror
26
26
  - **TypeScript first** - 100% typed, zero `any`
27
- - **7,500+ tests** - 3,936 unit tests and 3,652 E2E tests across 76 Playwright specs
27
+ - **6,400+ tests** - 2,677 unit tests and 3,767 E2E tests across 78 Playwright specs
28
28
  - **Light and dark theme** - 70+ CSS custom properties for full visual control
29
29
  - **Inline styles export** - `getHTML({ styled: true })` produces inline CSS ready for email clients, CMS, and Google Docs
30
30
  - **SSR helpers** - `generateHTML`, `generateJSON`, `generateText` for server-side rendering
@@ -184,7 +184,7 @@ class DomternalEditorComponent {
184
184
  });
185
185
  }
186
186
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.5", type: DomternalEditorComponent, isStandalone: true, selector: "domternal-editor", inputs: { extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editorCreated: "editorCreated", contentUpdated: "contentUpdated", selectionChanged: "selectionChanged", focusChanged: "focusChanged", blurChanged: "blurChanged", editorDestroyed: "editorDestroyed" }, host: { classAttribute: "dm-editor" }, providers: [
187
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.5", type: DomternalEditorComponent, isStandalone: true, selector: "domternal-editor", inputs: { extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editorCreated: "editorCreated", contentUpdated: "contentUpdated", selectionChanged: "selectionChanged", focusChanged: "focusChanged", blurChanged: "blurChanged", editorDestroyed: "editorDestroyed" }, host: { attributes: { "data-dm-editor-ui": "" }, classAttribute: "dm-editor" }, providers: [
188
188
  {
189
189
  provide: NG_VALUE_ACCESSOR,
190
190
  useExisting: forwardRef(() => DomternalEditorComponent),
@@ -194,7 +194,7 @@ class DomternalEditorComponent {
194
194
  }
195
195
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEditorComponent, decorators: [{
196
196
  type: Component,
197
- args: [{ selector: 'domternal-editor', template: '<div #editorRef></div>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'dm-editor' }, providers: [
197
+ args: [{ selector: 'domternal-editor', template: '<div #editorRef></div>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'dm-editor', 'data-dm-editor-ui': '' }, providers: [
198
198
  {
199
199
  provide: NG_VALUE_ACCESSOR,
200
200
  useExisting: forwardRef(() => DomternalEditorComponent),
@@ -401,6 +401,14 @@ class DomternalToolbarComponent {
401
401
  return;
402
402
  }
403
403
  this.controller?.executeCommand(item);
404
+ // If the button was activated via keyboard (Enter/Space on a focused
405
+ // toolbar button), refocus the editor so the browser renders the
406
+ // ::selection highlight for the still-active range.
407
+ // Keyboard-triggered click events have detail === 0.
408
+ // Always refocus editor after executing a command via toolbar button.
409
+ // Mouse clicks already keep focus via mousedown.preventDefault();
410
+ // keyboard activations (Enter/Space) need explicit refocus.
411
+ requestAnimationFrame(() => this.editor().view.focus());
404
412
  }
405
413
  onDropdownToggle(dropdown) {
406
414
  this.cleanupFloating?.();
@@ -439,6 +447,8 @@ class DomternalToolbarComponent {
439
447
  else {
440
448
  this.controller?.executeCommand(item);
441
449
  }
450
+ // Refocus editor so ::selection highlight stays visible
451
+ requestAnimationFrame(() => this.editor().view.focus());
442
452
  }
443
453
  onButtonFocus(name) {
444
454
  const index = this.controller?.getFlatIndex(name) ?? -1;
@@ -470,6 +480,27 @@ class DomternalToolbarComponent {
470
480
  this.controller.navigateLast();
471
481
  this.focusCurrentButton();
472
482
  break;
483
+ case 'ArrowDown': {
484
+ event.preventDefault();
485
+ if (this.openDropdown()) {
486
+ this.focusDropdownItem(1);
487
+ }
488
+ else {
489
+ const btn = document.activeElement;
490
+ if (btn?.getAttribute('aria-haspopup') && btn.closest('.dm-toolbar')) {
491
+ btn.click();
492
+ requestAnimationFrame(() => this.focusDropdownItem(0, true));
493
+ }
494
+ }
495
+ break;
496
+ }
497
+ case 'ArrowUp': {
498
+ event.preventDefault();
499
+ if (this.openDropdown()) {
500
+ this.focusDropdownItem(-1);
501
+ }
502
+ break;
503
+ }
473
504
  case 'Escape':
474
505
  if (this.openDropdown()) {
475
506
  event.preventDefault();
@@ -483,6 +514,24 @@ class DomternalToolbarComponent {
483
514
  }
484
515
  }
485
516
  // === Private ===
517
+ focusDropdownItem(direction, first) {
518
+ const panel = this.elRef.nativeElement.querySelector('.dm-toolbar-dropdown-panel');
519
+ if (!panel)
520
+ return;
521
+ const items = Array.from(panel.querySelectorAll('[role="menuitem"]'));
522
+ if (!items.length)
523
+ return;
524
+ if (first) {
525
+ items[0]?.focus();
526
+ return;
527
+ }
528
+ const current = document.activeElement;
529
+ const idx = items.indexOf(current);
530
+ const next = idx === -1
531
+ ? (direction > 0 ? 0 : items.length - 1)
532
+ : (idx + direction + items.length) % items.length;
533
+ items[next]?.focus();
534
+ }
486
535
  resolveIconSvg(name) {
487
536
  const customIcons = this.icons();
488
537
  if (customIcons) {
@@ -589,7 +638,7 @@ class DomternalToolbarComponent {
589
638
  buttons[idx]?.focus();
590
639
  }
591
640
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
592
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalToolbarComponent, isStandalone: true, selector: "domternal-toolbar", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-label": "\"Editor formatting\"" }, classAttribute: "dm-toolbar" }, ngImport: i0, template: `
641
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalToolbarComponent, isStandalone: true, selector: "domternal-toolbar", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar", "data-dm-editor-ui": "" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-label": "\"Editor formatting\"" }, classAttribute: "dm-toolbar" }, ngImport: i0, template: `
593
642
  @for (group of groups(); track group.name; let gi = $index) {
594
643
  @if (gi > 0) {
595
644
  <div class="dm-toolbar-separator" role="separator"></div>
@@ -642,6 +691,7 @@ class DomternalToolbarComponent {
642
691
  class="dm-color-swatch"
643
692
  [class.dm-color-swatch--active]="isActive(sub.name)"
644
693
  role="menuitem"
694
+ [attr.tabindex]="-1"
645
695
  [attr.aria-label]="sub.label"
646
696
  [title]="sub.label"
647
697
  [style.background-color]="sub.color"
@@ -653,8 +703,10 @@ class DomternalToolbarComponent {
653
703
  type="button"
654
704
  class="dm-color-palette-reset"
655
705
  role="menuitem"
706
+ [attr.tabindex]="-1"
656
707
  [attr.aria-label]="sub.label"
657
708
  [innerHTML]="getCachedItemContent(sub.icon, sub.label)"
709
+ [attr.tabindex]="-1"
658
710
  (mousedown)="$event.preventDefault()"
659
711
  (click)="onDropdownItemClick(sub)"
660
712
  ></button>
@@ -670,6 +722,7 @@ class DomternalToolbarComponent {
670
722
  class="dm-toolbar-dropdown-item"
671
723
  [class.dm-toolbar-dropdown-item--active]="isActive(sub.name)"
672
724
  role="menuitem"
725
+ [attr.tabindex]="-1"
673
726
  [attr.aria-label]="sub.label"
674
727
  [attr.style]="sub.style ?? null"
675
728
  [innerHTML]="getCachedItemContent(sub.icon, sub.label, asDropdown(item).displayMode)"
@@ -696,6 +749,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
696
749
  host: {
697
750
  'class': 'dm-toolbar',
698
751
  'role': 'toolbar',
752
+ 'data-dm-editor-ui': '',
699
753
  '[attr.aria-label]': '"Editor formatting"',
700
754
  '(keydown)': 'onKeydown($event)',
701
755
  },
@@ -752,6 +806,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
752
806
  class="dm-color-swatch"
753
807
  [class.dm-color-swatch--active]="isActive(sub.name)"
754
808
  role="menuitem"
809
+ [attr.tabindex]="-1"
755
810
  [attr.aria-label]="sub.label"
756
811
  [title]="sub.label"
757
812
  [style.background-color]="sub.color"
@@ -763,8 +818,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
763
818
  type="button"
764
819
  class="dm-color-palette-reset"
765
820
  role="menuitem"
821
+ [attr.tabindex]="-1"
766
822
  [attr.aria-label]="sub.label"
767
823
  [innerHTML]="getCachedItemContent(sub.icon, sub.label)"
824
+ [attr.tabindex]="-1"
768
825
  (mousedown)="$event.preventDefault()"
769
826
  (click)="onDropdownItemClick(sub)"
770
827
  ></button>
@@ -780,6 +837,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
780
837
  class="dm-toolbar-dropdown-item"
781
838
  [class.dm-toolbar-dropdown-item--active]="isActive(sub.name)"
782
839
  role="menuitem"
840
+ [attr.tabindex]="-1"
783
841
  [attr.aria-label]="sub.label"
784
842
  [attr.style]="sub.style ?? null"
785
843
  [innerHTML]="getCachedItemContent(sub.icon, sub.label, asDropdown(item).displayMode)"
@@ -1115,13 +1173,14 @@ class DomternalBubbleMenuComponent {
1115
1173
  }
1116
1174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1117
1175
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalBubbleMenuComponent, isStandalone: true, selector: "domternal-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, shouldShow: { classPropertyName: "shouldShow", publicName: "shouldShow", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, updateDelay: { classPropertyName: "updateDelay", publicName: "updateDelay", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contexts: { classPropertyName: "contexts", publicName: "contexts", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["menuEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1118
- <div #menuEl class="dm-bubble-menu">
1176
+ <div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
1119
1177
  @for (item of resolvedItems(); track item.name) {
1120
1178
  @if (item.type === 'separator') {
1121
- <span class="dm-toolbar-separator"></span>
1179
+ <span class="dm-toolbar-separator" role="separator"></span>
1122
1180
  } @else {
1123
1181
  <button type="button" class="dm-toolbar-button"
1124
1182
  [class.dm-toolbar-button--active]="isItemActive(item)"
1183
+ [attr.aria-pressed]="isItemActive(item)"
1125
1184
  [disabled]="isItemDisabled(item)"
1126
1185
  [title]="item.label"
1127
1186
  [attr.aria-label]="item.label"
@@ -1137,13 +1196,14 @@ class DomternalBubbleMenuComponent {
1137
1196
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, decorators: [{
1138
1197
  type: Component,
1139
1198
  args: [{ selector: 'domternal-bubble-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
1140
- <div #menuEl class="dm-bubble-menu">
1199
+ <div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
1141
1200
  @for (item of resolvedItems(); track item.name) {
1142
1201
  @if (item.type === 'separator') {
1143
- <span class="dm-toolbar-separator"></span>
1202
+ <span class="dm-toolbar-separator" role="separator"></span>
1144
1203
  } @else {
1145
1204
  <button type="button" class="dm-toolbar-button"
1146
1205
  [class.dm-toolbar-button--active]="isItemActive(item)"
1206
+ [attr.aria-pressed]="isItemActive(item)"
1147
1207
  [disabled]="isItemDisabled(item)"
1148
1208
  [title]="item.label"
1149
1209
  [attr.aria-label]="item.label"
@@ -1297,10 +1357,62 @@ class DomternalEmojiPickerComponent {
1297
1357
  const label = grid.querySelector(`[data-category="${cat}"]`);
1298
1358
  if (label) {
1299
1359
  // Use manual scrollTop instead of scrollIntoView to avoid scrolling the page
1300
- grid.scrollTo({ top: label.offsetTop - grid.offsetTop, behavior: 'smooth' });
1360
+ grid.scrollTo({ top: label.offsetTop - grid.offsetTop });
1361
+ // Focus first emoji swatch after scroll completes
1362
+ setTimeout(() => {
1363
+ const firstSwatch = label.nextElementSibling;
1364
+ if (firstSwatch instanceof HTMLElement && firstSwatch.classList.contains('dm-emoji-swatch')) {
1365
+ firstSwatch.focus();
1366
+ }
1367
+ }, 50);
1301
1368
  }
1302
1369
  });
1303
1370
  }
1371
+ onGridKeydown(event) {
1372
+ const grid = this.elRef.nativeElement.querySelector('.dm-emoji-picker-grid');
1373
+ if (!grid)
1374
+ return;
1375
+ const swatches = Array.from(grid.querySelectorAll('.dm-emoji-swatch'));
1376
+ if (!swatches.length)
1377
+ return;
1378
+ const current = document.activeElement;
1379
+ let idx = swatches.indexOf(current);
1380
+ if (idx === -1) {
1381
+ // Focus is on grid container, not a swatch — enter the grid
1382
+ if (['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'].includes(event.key)) {
1383
+ event.preventDefault();
1384
+ swatches[0]?.focus();
1385
+ }
1386
+ return;
1387
+ }
1388
+ const cols = 8;
1389
+ let next = idx;
1390
+ switch (event.key) {
1391
+ case 'ArrowRight':
1392
+ event.preventDefault();
1393
+ next = Math.min(idx + 1, swatches.length - 1);
1394
+ break;
1395
+ case 'ArrowLeft':
1396
+ event.preventDefault();
1397
+ next = Math.max(idx - 1, 0);
1398
+ break;
1399
+ case 'ArrowDown':
1400
+ event.preventDefault();
1401
+ next = Math.min(idx + cols, swatches.length - 1);
1402
+ break;
1403
+ case 'ArrowUp':
1404
+ event.preventDefault();
1405
+ next = Math.max(idx - cols, 0);
1406
+ break;
1407
+ case 'Enter':
1408
+ case ' ':
1409
+ event.preventDefault();
1410
+ swatches[idx]?.click();
1411
+ return;
1412
+ default: return;
1413
+ }
1414
+ swatches[next]?.focus();
1415
+ }
1304
1416
  onGridScroll() {
1305
1417
  const grid = this.elRef.nativeElement.querySelector('.dm-emoji-picker-grid');
1306
1418
  if (!grid || this.searchQuery())
@@ -1423,6 +1535,7 @@ class DomternalEmojiPickerComponent {
1423
1535
  placeholder="Search emoji..."
1424
1536
  [value]="searchQuery()"
1425
1537
  (input)="onSearch($event)"
1538
+ aria-label="Search emoji"
1426
1539
  (keydown.escape)="close()"
1427
1540
  />
1428
1541
  </div>
@@ -1433,6 +1546,8 @@ class DomternalEmojiPickerComponent {
1433
1546
  type="button"
1434
1547
  class="dm-emoji-picker-tab"
1435
1548
  [class.dm-emoji-picker-tab--active]="activeCategory() === cat"
1549
+ role="tab"
1550
+ [attr.aria-selected]="activeCategory() === cat"
1436
1551
  [title]="cat"
1437
1552
  [attr.aria-label]="cat"
1438
1553
  (mousedown)="$event.preventDefault()"
@@ -1441,12 +1556,13 @@ class DomternalEmojiPickerComponent {
1441
1556
  }
1442
1557
  </div>
1443
1558
 
1444
- <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()">
1559
+ <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
1445
1560
  @if (searchQuery()) {
1446
1561
  @for (item of filteredEmojis(); track item.name) {
1447
1562
  <button
1448
1563
  type="button"
1449
1564
  class="dm-emoji-swatch"
1565
+ [attr.tabindex]="-1"
1450
1566
  [title]="formatName(item.name)"
1451
1567
  [attr.aria-label]="formatName(item.name)"
1452
1568
  (mousedown)="$event.preventDefault()"
@@ -1463,6 +1579,7 @@ class DomternalEmojiPickerComponent {
1463
1579
  <button
1464
1580
  type="button"
1465
1581
  class="dm-emoji-swatch"
1582
+ [attr.tabindex]="-1"
1466
1583
  [title]="formatName(item.name)"
1467
1584
  [attr.aria-label]="formatName(item.name)"
1468
1585
  (mousedown)="$event.preventDefault()"
@@ -1476,6 +1593,7 @@ class DomternalEmojiPickerComponent {
1476
1593
  <button
1477
1594
  type="button"
1478
1595
  class="dm-emoji-swatch"
1596
+ [attr.tabindex]="-1"
1479
1597
  [title]="formatName(item.name)"
1480
1598
  [attr.aria-label]="formatName(item.name)"
1481
1599
  (mousedown)="$event.preventDefault()"
@@ -1506,6 +1624,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1506
1624
  placeholder="Search emoji..."
1507
1625
  [value]="searchQuery()"
1508
1626
  (input)="onSearch($event)"
1627
+ aria-label="Search emoji"
1509
1628
  (keydown.escape)="close()"
1510
1629
  />
1511
1630
  </div>
@@ -1516,6 +1635,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1516
1635
  type="button"
1517
1636
  class="dm-emoji-picker-tab"
1518
1637
  [class.dm-emoji-picker-tab--active]="activeCategory() === cat"
1638
+ role="tab"
1639
+ [attr.aria-selected]="activeCategory() === cat"
1519
1640
  [title]="cat"
1520
1641
  [attr.aria-label]="cat"
1521
1642
  (mousedown)="$event.preventDefault()"
@@ -1524,12 +1645,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1524
1645
  }
1525
1646
  </div>
1526
1647
 
1527
- <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()">
1648
+ <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
1528
1649
  @if (searchQuery()) {
1529
1650
  @for (item of filteredEmojis(); track item.name) {
1530
1651
  <button
1531
1652
  type="button"
1532
1653
  class="dm-emoji-swatch"
1654
+ [attr.tabindex]="-1"
1533
1655
  [title]="formatName(item.name)"
1534
1656
  [attr.aria-label]="formatName(item.name)"
1535
1657
  (mousedown)="$event.preventDefault()"
@@ -1546,6 +1668,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1546
1668
  <button
1547
1669
  type="button"
1548
1670
  class="dm-emoji-swatch"
1671
+ [attr.tabindex]="-1"
1549
1672
  [title]="formatName(item.name)"
1550
1673
  [attr.aria-label]="formatName(item.name)"
1551
1674
  (mousedown)="$event.preventDefault()"
@@ -1559,6 +1682,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1559
1682
  <button
1560
1683
  type="button"
1561
1684
  class="dm-emoji-swatch"
1685
+ [attr.tabindex]="-1"
1562
1686
  [title]="formatName(item.name)"
1563
1687
  [attr.aria-label]="formatName(item.name)"
1564
1688
  (mousedown)="$event.preventDefault()"