@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 +1 -1
- package/dist/README.md +1 -1
- package/dist/fesm2022/domternal-angular.mjs +134 -10
- package/dist/fesm2022/domternal-angular.mjs.map +1 -1
- package/dist/types/domternal-angular.d.ts +2 -0
- package/package.json +3 -3
- package/dist/src/lib/bubble-menu.component.d.ts +0 -58
- package/dist/src/lib/bubble-menu.component.d.ts.map +0 -1
- package/dist/src/lib/editor.component.d.ts +0 -54
- package/dist/src/lib/editor.component.d.ts.map +0 -1
- package/dist/src/lib/emoji-picker.component.d.ts +0 -42
- package/dist/src/lib/emoji-picker.component.d.ts.map +0 -1
- package/dist/src/lib/floating-menu.component.d.ts +0 -16
- package/dist/src/lib/floating-menu.component.d.ts.map +0 -1
- package/dist/src/lib/toolbar.component.d.ts +0 -61
- package/dist/src/lib/toolbar.component.d.ts.map +0 -1
- package/dist/src/public-api.d.ts +0 -9
- package/dist/src/public-api.d.ts.map +0 -1
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
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
|
-
- **
|
|
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
|
-
- **
|
|
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
|
|
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()"
|