@domternal/angular 0.4.1 → 0.5.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/README.md +1 -1
- package/dist/README.md +1 -1
- package/dist/fesm2022/domternal-angular.mjs +107 -6
- 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,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
|
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
|
|
@@ -470,6 +470,28 @@ class DomternalToolbarComponent {
|
|
|
470
470
|
this.controller.navigateLast();
|
|
471
471
|
this.focusCurrentButton();
|
|
472
472
|
break;
|
|
473
|
+
case 'ArrowDown': {
|
|
474
|
+
event.preventDefault();
|
|
475
|
+
if (this.openDropdown()) {
|
|
476
|
+
this.focusDropdownItem(1);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const buttons = this.elRef.nativeElement.querySelectorAll('.dm-toolbar-button');
|
|
480
|
+
const btn = buttons[this.controller?.focusedIndex ?? 0];
|
|
481
|
+
if (btn?.getAttribute('aria-haspopup')) {
|
|
482
|
+
btn.click();
|
|
483
|
+
requestAnimationFrame(() => this.focusDropdownItem(0, true));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
case 'ArrowUp': {
|
|
489
|
+
event.preventDefault();
|
|
490
|
+
if (this.openDropdown()) {
|
|
491
|
+
this.focusDropdownItem(-1);
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
473
495
|
case 'Escape':
|
|
474
496
|
if (this.openDropdown()) {
|
|
475
497
|
event.preventDefault();
|
|
@@ -483,6 +505,24 @@ class DomternalToolbarComponent {
|
|
|
483
505
|
}
|
|
484
506
|
}
|
|
485
507
|
// === Private ===
|
|
508
|
+
focusDropdownItem(direction, first) {
|
|
509
|
+
const panel = this.elRef.nativeElement.querySelector('.dm-toolbar-dropdown-panel');
|
|
510
|
+
if (!panel)
|
|
511
|
+
return;
|
|
512
|
+
const items = Array.from(panel.querySelectorAll('[role="menuitem"]'));
|
|
513
|
+
if (!items.length)
|
|
514
|
+
return;
|
|
515
|
+
if (first) {
|
|
516
|
+
items[0]?.focus();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const current = document.activeElement;
|
|
520
|
+
const idx = items.indexOf(current);
|
|
521
|
+
const next = idx === -1
|
|
522
|
+
? (direction > 0 ? 0 : items.length - 1)
|
|
523
|
+
: (idx + direction + items.length) % items.length;
|
|
524
|
+
items[next]?.focus();
|
|
525
|
+
}
|
|
486
526
|
resolveIconSvg(name) {
|
|
487
527
|
const customIcons = this.icons();
|
|
488
528
|
if (customIcons) {
|
|
@@ -642,6 +682,7 @@ class DomternalToolbarComponent {
|
|
|
642
682
|
class="dm-color-swatch"
|
|
643
683
|
[class.dm-color-swatch--active]="isActive(sub.name)"
|
|
644
684
|
role="menuitem"
|
|
685
|
+
[attr.tabindex]="-1"
|
|
645
686
|
[attr.aria-label]="sub.label"
|
|
646
687
|
[title]="sub.label"
|
|
647
688
|
[style.background-color]="sub.color"
|
|
@@ -653,8 +694,10 @@ class DomternalToolbarComponent {
|
|
|
653
694
|
type="button"
|
|
654
695
|
class="dm-color-palette-reset"
|
|
655
696
|
role="menuitem"
|
|
697
|
+
[attr.tabindex]="-1"
|
|
656
698
|
[attr.aria-label]="sub.label"
|
|
657
699
|
[innerHTML]="getCachedItemContent(sub.icon, sub.label)"
|
|
700
|
+
[attr.tabindex]="-1"
|
|
658
701
|
(mousedown)="$event.preventDefault()"
|
|
659
702
|
(click)="onDropdownItemClick(sub)"
|
|
660
703
|
></button>
|
|
@@ -670,6 +713,7 @@ class DomternalToolbarComponent {
|
|
|
670
713
|
class="dm-toolbar-dropdown-item"
|
|
671
714
|
[class.dm-toolbar-dropdown-item--active]="isActive(sub.name)"
|
|
672
715
|
role="menuitem"
|
|
716
|
+
[attr.tabindex]="-1"
|
|
673
717
|
[attr.aria-label]="sub.label"
|
|
674
718
|
[attr.style]="sub.style ?? null"
|
|
675
719
|
[innerHTML]="getCachedItemContent(sub.icon, sub.label, asDropdown(item).displayMode)"
|
|
@@ -752,6 +796,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
752
796
|
class="dm-color-swatch"
|
|
753
797
|
[class.dm-color-swatch--active]="isActive(sub.name)"
|
|
754
798
|
role="menuitem"
|
|
799
|
+
[attr.tabindex]="-1"
|
|
755
800
|
[attr.aria-label]="sub.label"
|
|
756
801
|
[title]="sub.label"
|
|
757
802
|
[style.background-color]="sub.color"
|
|
@@ -763,8 +808,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
763
808
|
type="button"
|
|
764
809
|
class="dm-color-palette-reset"
|
|
765
810
|
role="menuitem"
|
|
811
|
+
[attr.tabindex]="-1"
|
|
766
812
|
[attr.aria-label]="sub.label"
|
|
767
813
|
[innerHTML]="getCachedItemContent(sub.icon, sub.label)"
|
|
814
|
+
[attr.tabindex]="-1"
|
|
768
815
|
(mousedown)="$event.preventDefault()"
|
|
769
816
|
(click)="onDropdownItemClick(sub)"
|
|
770
817
|
></button>
|
|
@@ -780,6 +827,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
780
827
|
class="dm-toolbar-dropdown-item"
|
|
781
828
|
[class.dm-toolbar-dropdown-item--active]="isActive(sub.name)"
|
|
782
829
|
role="menuitem"
|
|
830
|
+
[attr.tabindex]="-1"
|
|
783
831
|
[attr.aria-label]="sub.label"
|
|
784
832
|
[attr.style]="sub.style ?? null"
|
|
785
833
|
[innerHTML]="getCachedItemContent(sub.icon, sub.label, asDropdown(item).displayMode)"
|
|
@@ -1115,13 +1163,14 @@ class DomternalBubbleMenuComponent {
|
|
|
1115
1163
|
}
|
|
1116
1164
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1117
1165
|
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">
|
|
1166
|
+
<div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
|
|
1119
1167
|
@for (item of resolvedItems(); track item.name) {
|
|
1120
1168
|
@if (item.type === 'separator') {
|
|
1121
|
-
<span class="dm-toolbar-separator"></span>
|
|
1169
|
+
<span class="dm-toolbar-separator" role="separator"></span>
|
|
1122
1170
|
} @else {
|
|
1123
1171
|
<button type="button" class="dm-toolbar-button"
|
|
1124
1172
|
[class.dm-toolbar-button--active]="isItemActive(item)"
|
|
1173
|
+
[attr.aria-pressed]="isItemActive(item)"
|
|
1125
1174
|
[disabled]="isItemDisabled(item)"
|
|
1126
1175
|
[title]="item.label"
|
|
1127
1176
|
[attr.aria-label]="item.label"
|
|
@@ -1137,13 +1186,14 @@ class DomternalBubbleMenuComponent {
|
|
|
1137
1186
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, decorators: [{
|
|
1138
1187
|
type: Component,
|
|
1139
1188
|
args: [{ selector: 'domternal-bubble-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
1140
|
-
<div #menuEl class="dm-bubble-menu">
|
|
1189
|
+
<div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
|
|
1141
1190
|
@for (item of resolvedItems(); track item.name) {
|
|
1142
1191
|
@if (item.type === 'separator') {
|
|
1143
|
-
<span class="dm-toolbar-separator"></span>
|
|
1192
|
+
<span class="dm-toolbar-separator" role="separator"></span>
|
|
1144
1193
|
} @else {
|
|
1145
1194
|
<button type="button" class="dm-toolbar-button"
|
|
1146
1195
|
[class.dm-toolbar-button--active]="isItemActive(item)"
|
|
1196
|
+
[attr.aria-pressed]="isItemActive(item)"
|
|
1147
1197
|
[disabled]="isItemDisabled(item)"
|
|
1148
1198
|
[title]="item.label"
|
|
1149
1199
|
[attr.aria-label]="item.label"
|
|
@@ -1301,6 +1351,45 @@ class DomternalEmojiPickerComponent {
|
|
|
1301
1351
|
}
|
|
1302
1352
|
});
|
|
1303
1353
|
}
|
|
1354
|
+
onGridKeydown(event) {
|
|
1355
|
+
const grid = this.elRef.nativeElement.querySelector('.dm-emoji-picker-grid');
|
|
1356
|
+
if (!grid)
|
|
1357
|
+
return;
|
|
1358
|
+
const swatches = Array.from(grid.querySelectorAll('.dm-emoji-swatch'));
|
|
1359
|
+
if (!swatches.length)
|
|
1360
|
+
return;
|
|
1361
|
+
const current = document.activeElement;
|
|
1362
|
+
const idx = swatches.indexOf(current);
|
|
1363
|
+
if (idx === -1)
|
|
1364
|
+
return;
|
|
1365
|
+
const cols = 8;
|
|
1366
|
+
let next = idx;
|
|
1367
|
+
switch (event.key) {
|
|
1368
|
+
case 'ArrowRight':
|
|
1369
|
+
event.preventDefault();
|
|
1370
|
+
next = Math.min(idx + 1, swatches.length - 1);
|
|
1371
|
+
break;
|
|
1372
|
+
case 'ArrowLeft':
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
next = Math.max(idx - 1, 0);
|
|
1375
|
+
break;
|
|
1376
|
+
case 'ArrowDown':
|
|
1377
|
+
event.preventDefault();
|
|
1378
|
+
next = Math.min(idx + cols, swatches.length - 1);
|
|
1379
|
+
break;
|
|
1380
|
+
case 'ArrowUp':
|
|
1381
|
+
event.preventDefault();
|
|
1382
|
+
next = Math.max(idx - cols, 0);
|
|
1383
|
+
break;
|
|
1384
|
+
case 'Enter':
|
|
1385
|
+
case ' ':
|
|
1386
|
+
event.preventDefault();
|
|
1387
|
+
swatches[idx]?.click();
|
|
1388
|
+
return;
|
|
1389
|
+
default: return;
|
|
1390
|
+
}
|
|
1391
|
+
swatches[next]?.focus();
|
|
1392
|
+
}
|
|
1304
1393
|
onGridScroll() {
|
|
1305
1394
|
const grid = this.elRef.nativeElement.querySelector('.dm-emoji-picker-grid');
|
|
1306
1395
|
if (!grid || this.searchQuery())
|
|
@@ -1423,6 +1512,7 @@ class DomternalEmojiPickerComponent {
|
|
|
1423
1512
|
placeholder="Search emoji..."
|
|
1424
1513
|
[value]="searchQuery()"
|
|
1425
1514
|
(input)="onSearch($event)"
|
|
1515
|
+
aria-label="Search emoji"
|
|
1426
1516
|
(keydown.escape)="close()"
|
|
1427
1517
|
/>
|
|
1428
1518
|
</div>
|
|
@@ -1433,6 +1523,8 @@ class DomternalEmojiPickerComponent {
|
|
|
1433
1523
|
type="button"
|
|
1434
1524
|
class="dm-emoji-picker-tab"
|
|
1435
1525
|
[class.dm-emoji-picker-tab--active]="activeCategory() === cat"
|
|
1526
|
+
role="tab"
|
|
1527
|
+
[attr.aria-selected]="activeCategory() === cat"
|
|
1436
1528
|
[title]="cat"
|
|
1437
1529
|
[attr.aria-label]="cat"
|
|
1438
1530
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1441,12 +1533,13 @@ class DomternalEmojiPickerComponent {
|
|
|
1441
1533
|
}
|
|
1442
1534
|
</div>
|
|
1443
1535
|
|
|
1444
|
-
<div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()">
|
|
1536
|
+
<div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
|
|
1445
1537
|
@if (searchQuery()) {
|
|
1446
1538
|
@for (item of filteredEmojis(); track item.name) {
|
|
1447
1539
|
<button
|
|
1448
1540
|
type="button"
|
|
1449
1541
|
class="dm-emoji-swatch"
|
|
1542
|
+
[attr.tabindex]="-1"
|
|
1450
1543
|
[title]="formatName(item.name)"
|
|
1451
1544
|
[attr.aria-label]="formatName(item.name)"
|
|
1452
1545
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1463,6 +1556,7 @@ class DomternalEmojiPickerComponent {
|
|
|
1463
1556
|
<button
|
|
1464
1557
|
type="button"
|
|
1465
1558
|
class="dm-emoji-swatch"
|
|
1559
|
+
[attr.tabindex]="-1"
|
|
1466
1560
|
[title]="formatName(item.name)"
|
|
1467
1561
|
[attr.aria-label]="formatName(item.name)"
|
|
1468
1562
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1476,6 +1570,7 @@ class DomternalEmojiPickerComponent {
|
|
|
1476
1570
|
<button
|
|
1477
1571
|
type="button"
|
|
1478
1572
|
class="dm-emoji-swatch"
|
|
1573
|
+
[attr.tabindex]="-1"
|
|
1479
1574
|
[title]="formatName(item.name)"
|
|
1480
1575
|
[attr.aria-label]="formatName(item.name)"
|
|
1481
1576
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1506,6 +1601,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1506
1601
|
placeholder="Search emoji..."
|
|
1507
1602
|
[value]="searchQuery()"
|
|
1508
1603
|
(input)="onSearch($event)"
|
|
1604
|
+
aria-label="Search emoji"
|
|
1509
1605
|
(keydown.escape)="close()"
|
|
1510
1606
|
/>
|
|
1511
1607
|
</div>
|
|
@@ -1516,6 +1612,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1516
1612
|
type="button"
|
|
1517
1613
|
class="dm-emoji-picker-tab"
|
|
1518
1614
|
[class.dm-emoji-picker-tab--active]="activeCategory() === cat"
|
|
1615
|
+
role="tab"
|
|
1616
|
+
[attr.aria-selected]="activeCategory() === cat"
|
|
1519
1617
|
[title]="cat"
|
|
1520
1618
|
[attr.aria-label]="cat"
|
|
1521
1619
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1524,12 +1622,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1524
1622
|
}
|
|
1525
1623
|
</div>
|
|
1526
1624
|
|
|
1527
|
-
<div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()">
|
|
1625
|
+
<div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
|
|
1528
1626
|
@if (searchQuery()) {
|
|
1529
1627
|
@for (item of filteredEmojis(); track item.name) {
|
|
1530
1628
|
<button
|
|
1531
1629
|
type="button"
|
|
1532
1630
|
class="dm-emoji-swatch"
|
|
1631
|
+
[attr.tabindex]="-1"
|
|
1533
1632
|
[title]="formatName(item.name)"
|
|
1534
1633
|
[attr.aria-label]="formatName(item.name)"
|
|
1535
1634
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1546,6 +1645,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1546
1645
|
<button
|
|
1547
1646
|
type="button"
|
|
1548
1647
|
class="dm-emoji-swatch"
|
|
1648
|
+
[attr.tabindex]="-1"
|
|
1549
1649
|
[title]="formatName(item.name)"
|
|
1550
1650
|
[attr.aria-label]="formatName(item.name)"
|
|
1551
1651
|
(mousedown)="$event.preventDefault()"
|
|
@@ -1559,6 +1659,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1559
1659
|
<button
|
|
1560
1660
|
type="button"
|
|
1561
1661
|
class="dm-emoji-swatch"
|
|
1662
|
+
[attr.tabindex]="-1"
|
|
1562
1663
|
[title]="formatName(item.name)"
|
|
1563
1664
|
[attr.aria-label]="formatName(item.name)"
|
|
1564
1665
|
(mousedown)="$event.preventDefault()"
|