quill-table-up 3.4.0 → 3.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.
@@ -2,6 +2,7 @@ import Quill from 'quill';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { TableCellInnerFormat } from '../../formats';
4
4
  import { TableUp } from '../../table-up';
5
+ import { parseCSSRules } from '../../utils';
5
6
  import { createQuillWithTableModule, createTableBodyHTML, createTableCaptionHTML, createTableDeltaOps, createTableHTML, createTaleColHTML, datasetTag, expectDelta, replaceAttrEmptyRow, simulatePasteHTML } from './utils';
6
7
 
7
8
  const Delta = Quill.import('delta');
@@ -246,7 +247,7 @@ describe('clipboard cell structure', () => {
246
247
  });
247
248
 
248
249
  it('clipboard convert multiple merged cell 3', async () => {
249
- const quill = createQuillWithTableModule(`<p><br></p>`);
250
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
250
251
  quill.setContents(
251
252
  quill.clipboard.convert({
252
253
  html: '<table><tbody><tr><td rowspan="3">merge1</td><td colspan="2">merge2</td><td>3</td></tr><tr><td rowspan="2">merge3</td><td>1</td><td>4</td></tr><tr><td>2</td><td>5</td></tr></tbody></table>',
@@ -579,7 +580,7 @@ describe('clipboard cell structure', () => {
579
580
  });
580
581
 
581
582
  it('clipboard convert empty cell should not ignore', async () => {
582
- const quill = createQuillWithTableModule(`<p><br></p>`);
583
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
583
584
  quill.setContents(
584
585
  quill.clipboard.convert({
585
586
  html: `<table><tbody><tr><th>q</th><th>w</th><th>e</th></tr><tr><th></th><td>2</td><td>3</td></tr><tr><th></th><td>4</td><td>5</td></tr></tbody></table>`,
@@ -744,7 +745,7 @@ describe('clipboard cell structure', () => {
744
745
  });
745
746
 
746
747
  it('convert background on table/tbody/tr/td', async () => {
747
- const quill = createQuillWithTableModule(`<p><br></p>`);
748
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
748
749
  quill.setContents(
749
750
  quill.clipboard.convert({
750
751
  html: `
@@ -825,7 +826,7 @@ describe('clipboard cell structure', () => {
825
826
  });
826
827
 
827
828
  it('clipboard convert should generate colgroup at correct position', async () => {
828
- const quill = createQuillWithTableModule(`<p><br></p>`);
829
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
829
830
  quill.setContents(
830
831
  quill.clipboard.convert({
831
832
  html: '<table class="ql-table" data-table-id="0u473v7j5th" cellpadding="0" cellspacing="0"><tbody data-table-id="0u473v7j5th"><tr class="ql-table-row" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj"><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="0025lmzkmn85x" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="0025lmzkmn85x" data-rowspan="1" data-colspan="1" contenteditable="true"><p>1</p></div></td><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="g8v4waukxpe" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="g8v4waukxpe" data-rowspan="1" data-colspan="1" contenteditable="true"><p>2</p></div></td><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="7ubw564uue5" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="7ubw564uue5" data-rowspan="1" data-colspan="1" contenteditable="true"><p>3</p></div></td></tr></tbody><caption class="ql-table-caption" data-table-id="0u473v7j5th" contenteditable="true">title</caption></table>',
@@ -958,7 +959,7 @@ describe('clipboard cell structure', () => {
958
959
  });
959
960
 
960
961
  it('clipboard convert empty multiple tr to `emptyRow`', async () => {
961
- const quill = createQuillWithTableModule('<p><br></p>', { autoMergeCell: false });
962
+ const quill = createQuillWithTableModule('<p><br></p>', { autoMergeCell: false, full: false });
962
963
  quill.setContents(
963
964
  quill.clipboard.convert({
964
965
  html: `<table><tbody><tr><td rowspan="3" colspan="3">1</td></tr><tr></tr><tr></tr><tr><td>2</td><td>3</td><td>4</td></tr></tbody></table>`,
@@ -1117,7 +1118,7 @@ describe('clipboard cell structure', () => {
1117
1118
  });
1118
1119
 
1119
1120
  it('clipboard convert empty tr to `emptyRow` in thead', async () => {
1120
- const quill = createQuillWithTableModule(`<p><br></p>`, { autoMergeCell: false });
1121
+ const quill = createQuillWithTableModule(`<p><br></p>`, { autoMergeCell: false, full: false });
1121
1122
  quill.setContents(
1122
1123
  quill.clipboard.convert({
1123
1124
  html: `<table><thead><tr><td rowspan="2" colspan="2">1</td></tr><tr></tr></thead><tbody><tr><td>1</td><td>2</td></tr><tr><td>1</td><td>2</td></tr></tbody></table>`,
@@ -1190,106 +1191,8 @@ describe('clipboard cell structure', () => {
1190
1191
  );
1191
1192
  });
1192
1193
 
1193
- it('clipboard convert col with span attribute', async () => {
1194
- const quill = createQuillWithTableModule(`<p><br></p>`, { autoMergeCell: false });
1195
- quill.setContents(
1196
- quill.clipboard.convert({
1197
- html: `
1198
- <body link=blue vlink=purple>
1199
-
1200
- <table border=0 cellpadding=0 cellspacing=0 width=252 style='border-collapse:
1201
- collapse;width:188pt'>
1202
- <!--StartFragment-->
1203
- <col width=63 span=4 style='width:47pt'>
1204
- <tr height=19 style='height:14.4pt'>
1205
- <td colspan=2 rowspan=6 height=114 class=xl65 width=126 style='height:86.4pt;
1206
- width:94pt'>1</td>
1207
- <td colspan=2 rowspan=2 class=xl65 width=126 style='width:94pt'>2</td>
1208
- </tr>
1209
- <tr height=19 style='height:14.4pt'>
1210
- </tr>
1211
- <tr height=19 style='height:14.4pt'>
1212
- <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>3</td>
1213
- </tr>
1214
- <tr height=19 style='height:14.4pt'>
1215
- </tr>
1216
- <tr height=19 style='height:14.4pt'>
1217
- <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>4</td>
1218
- </tr>
1219
- <tr height=19 style='height:14.4pt'>
1220
- </tr>
1221
- <!--EndFragment-->
1222
- </table>
1223
-
1224
- </body>
1225
- `,
1226
- }),
1227
- );
1228
- await vi.runAllTimersAsync();
1229
-
1230
- expect(quill.root).toEqualHTML(
1231
- `
1232
- <p><br></p>
1233
- <div>
1234
- <table cellpadding="0" cellspacing="0">
1235
- ${createTaleColHTML(4, { full: false, width: 63 })}
1236
- <tbody>
1237
- <tr>
1238
- <td rowspan="6" colspan="2" data-empty-row="length:1">
1239
- <div data-empty-row="length:1"><p>1</p></div>
1240
- </td>
1241
- <td rowspan="2" colspan="2" data-empty-row="length:1">
1242
- <div data-empty-row="length:1"><p>2</p></div>
1243
- </td>
1244
- </tr>
1245
- <tr></tr>
1246
- <tr>
1247
- <td rowspan="2" colspan="2" data-empty-row="length:1">
1248
- <div data-empty-row="length:1"><p>3</p></div>
1249
- </td>
1250
- </tr>
1251
- <tr></tr>
1252
- <tr>
1253
- <td rowspan="2" colspan="2" data-empty-row="length:1">
1254
- <div data-empty-row="length:1"><p>4</p></div>
1255
- </td>
1256
- </tr>
1257
- <tr></tr>
1258
- </tbody>
1259
- </table>
1260
- </div>
1261
- <p><br></p>
1262
- `,
1263
- {
1264
- ignoreAttrs: ['data-wrap-tag', 'data-tag', 'class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'],
1265
- replaceAttrs: {
1266
- 'data-empty-row': replaceAttrEmptyRow,
1267
- },
1268
- },
1269
- );
1270
- expectDelta(
1271
- new Delta([
1272
- { insert: '\n' },
1273
- { insert: { 'table-up-col': { full: false, width: 63 } } },
1274
- { insert: { 'table-up-col': { full: false, width: 63 } } },
1275
- { insert: { 'table-up-col': { full: false, width: 63 } } },
1276
- { insert: { 'table-up-col': { full: false, width: 63 } } },
1277
- { insert: '1' },
1278
- { attributes: { 'table-up-cell-inner': { rowspan: 6, colspan: 2 } }, insert: '\n' },
1279
- { insert: '2' },
1280
- { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1281
- { insert: '3' },
1282
- { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1283
- { insert: '4' },
1284
- { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1285
- { insert: '\n' },
1286
- ]),
1287
- quill.getContents(),
1288
- );
1289
- });
1290
-
1291
1194
  it('clipboard convert th correctly', async () => {
1292
- const quill = createQuillWithTableModule(`<p><br></p>`);
1195
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1293
1196
  quill.setContents(
1294
1197
  quill.clipboard.convert({
1295
1198
  html: `
@@ -1385,7 +1288,7 @@ describe('clipboard cell structure', () => {
1385
1288
  });
1386
1289
 
1387
1290
  it('convert thead and tfoot correctly', async () => {
1388
- const quill = createQuillWithTableModule(`<p><br></p>`);
1291
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1389
1292
  quill.setContents(
1390
1293
  quill.clipboard.convert({
1391
1294
  html: `
@@ -1509,7 +1412,7 @@ describe('clipboard cell structure', () => {
1509
1412
  });
1510
1413
 
1511
1414
  it('convert thead rowspan to tbody', async () => {
1512
- const quill = createQuillWithTableModule(`<p><br></p>`);
1415
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1513
1416
  quill.setContents(
1514
1417
  quill.clipboard.convert({
1515
1418
  html: `
@@ -1585,6 +1488,442 @@ describe('clipboard cell structure', () => {
1585
1488
  });
1586
1489
  });
1587
1490
 
1491
+ describe('clipboard column', () => {
1492
+ it('clipboard convert col with span attribute', async () => {
1493
+ const quill = createQuillWithTableModule(`<p><br></p>`, { autoMergeCell: false });
1494
+ quill.setContents(
1495
+ quill.clipboard.convert({
1496
+ html: `
1497
+ <body link=blue vlink=purple>
1498
+
1499
+ <table border=0 cellpadding=0 cellspacing=0 width=252 style='border-collapse:
1500
+ collapse;width:188pt'>
1501
+ <!--StartFragment-->
1502
+ <col width=63 span=4 style='width:47pt'>
1503
+ <tr height=19 style='height:14.4pt'>
1504
+ <td colspan=2 rowspan=6 height=114 class=xl65 width=126 style='height:86.4pt;
1505
+ width:94pt'>1</td>
1506
+ <td colspan=2 rowspan=2 class=xl65 width=126 style='width:94pt'>2</td>
1507
+ </tr>
1508
+ <tr height=19 style='height:14.4pt'>
1509
+ </tr>
1510
+ <tr height=19 style='height:14.4pt'>
1511
+ <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>3</td>
1512
+ </tr>
1513
+ <tr height=19 style='height:14.4pt'>
1514
+ </tr>
1515
+ <tr height=19 style='height:14.4pt'>
1516
+ <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>4</td>
1517
+ </tr>
1518
+ <tr height=19 style='height:14.4pt'>
1519
+ </tr>
1520
+ <!--EndFragment-->
1521
+ </table>
1522
+
1523
+ </body>
1524
+ `,
1525
+ }),
1526
+ );
1527
+ await vi.runAllTimersAsync();
1528
+
1529
+ expect(quill.root).toEqualHTML(
1530
+ `
1531
+ <p><br></p>
1532
+ <div>
1533
+ <table cellpadding="0" cellspacing="0">
1534
+ ${createTaleColHTML(4, { full: false, width: 63 })}
1535
+ <tbody>
1536
+ <tr>
1537
+ <td rowspan="6" colspan="2" data-empty-row="length:1">
1538
+ <div data-empty-row="length:1"><p>1</p></div>
1539
+ </td>
1540
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1541
+ <div data-empty-row="length:1"><p>2</p></div>
1542
+ </td>
1543
+ </tr>
1544
+ <tr></tr>
1545
+ <tr>
1546
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1547
+ <div data-empty-row="length:1"><p>3</p></div>
1548
+ </td>
1549
+ </tr>
1550
+ <tr></tr>
1551
+ <tr>
1552
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1553
+ <div data-empty-row="length:1"><p>4</p></div>
1554
+ </td>
1555
+ </tr>
1556
+ <tr></tr>
1557
+ </tbody>
1558
+ </table>
1559
+ </div>
1560
+ <p><br></p>
1561
+ `,
1562
+ {
1563
+ ignoreAttrs: ['data-wrap-tag', 'data-tag', 'class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'],
1564
+ replaceAttrs: {
1565
+ 'data-empty-row': replaceAttrEmptyRow,
1566
+ },
1567
+ },
1568
+ );
1569
+ expectDelta(
1570
+ new Delta([
1571
+ { insert: '\n' },
1572
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1573
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1574
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1575
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1576
+ { insert: '1' },
1577
+ { attributes: { 'table-up-cell-inner': { rowspan: 6, colspan: 2 } }, insert: '\n' },
1578
+ { insert: '2' },
1579
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1580
+ { insert: '3' },
1581
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1582
+ { insert: '4' },
1583
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1584
+ { insert: '\n' },
1585
+ ]),
1586
+ quill.getContents(),
1587
+ );
1588
+ });
1589
+
1590
+ it('convert col width with option `full: false`', async () => {
1591
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1592
+ quill.setContents(
1593
+ quill.clipboard.convert({
1594
+ html: `
1595
+ <table class="ws-table-all" id="customers">
1596
+ <tbody>
1597
+ <tr>
1598
+ <td>content</td>
1599
+ <td>content</td>
1600
+ <td>content</td>
1601
+ </tr>
1602
+ </tbody>
1603
+ </table>
1604
+ `,
1605
+ }),
1606
+ );
1607
+ await vi.runAllTimersAsync();
1608
+
1609
+ expectDelta(
1610
+ new Delta([
1611
+ { insert: '\n' },
1612
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1613
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1614
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1615
+ { insert: 'content' },
1616
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1617
+ { insert: 'content' },
1618
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1619
+ { insert: 'content' },
1620
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1621
+ { insert: '\n' },
1622
+ ]),
1623
+ quill.getContents(),
1624
+ );
1625
+ });
1626
+
1627
+ it('convert col width with option `full: true`', async () => {
1628
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: true });
1629
+ quill.setContents(
1630
+ quill.clipboard.convert({
1631
+ html: `
1632
+ <table class="ws-table-all" id="customers">
1633
+ <tbody>
1634
+ <tr>
1635
+ <td>content</td>
1636
+ <td>content</td>
1637
+ <td>content</td>
1638
+ </tr>
1639
+ </tbody>
1640
+ </table>
1641
+ `,
1642
+ }),
1643
+ );
1644
+ await vi.runAllTimersAsync();
1645
+
1646
+ expectDelta(
1647
+ new Delta([
1648
+ { insert: '\n' },
1649
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1650
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1651
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1652
+ { insert: 'content' },
1653
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1654
+ { insert: 'content' },
1655
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1656
+ { insert: 'content' },
1657
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1658
+ { insert: '\n' },
1659
+ ]),
1660
+ quill.getContents(),
1661
+ );
1662
+ });
1663
+
1664
+ it('convert table without colgroup should ignore td width when option `full: false`', async () => {
1665
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1666
+ quill.setContents(
1667
+ quill.clipboard.convert({
1668
+ html: `
1669
+ <table class="ws-table-all" id="customers">
1670
+ <tbody>
1671
+ <tr>
1672
+ <td width="220">content</td>
1673
+ <td width="140">content</td>
1674
+ <td width="180">content</td>
1675
+ </tr>
1676
+ </tbody>
1677
+ </table>
1678
+ `,
1679
+ }),
1680
+ );
1681
+ await vi.runAllTimersAsync();
1682
+
1683
+ expectDelta(
1684
+ new Delta([
1685
+ { insert: '\n' },
1686
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1687
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1688
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1689
+ { insert: 'content' },
1690
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1691
+ { insert: 'content' },
1692
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1693
+ { insert: 'content' },
1694
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1695
+ { insert: '\n' },
1696
+ ]),
1697
+ quill.getContents(),
1698
+ );
1699
+ });
1700
+
1701
+ it('convert table without colgroup should ignore td width when option `full: true`', async () => {
1702
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: true });
1703
+ quill.setContents(
1704
+ quill.clipboard.convert({
1705
+ html: `
1706
+ <table class="ws-table-all" id="customers">
1707
+ <tbody>
1708
+ <tr>
1709
+ <td width="220">content</td>
1710
+ <td width="140">content</td>
1711
+ <td width="180">content</td>
1712
+ </tr>
1713
+ </tbody>
1714
+ </table>
1715
+ `,
1716
+ }),
1717
+ );
1718
+ await vi.runAllTimersAsync();
1719
+
1720
+ expectDelta(
1721
+ new Delta([
1722
+ { insert: '\n' },
1723
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1724
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1725
+ { insert: { 'table-up-col': { full: true, width: 33.3333 } } },
1726
+ { insert: 'content' },
1727
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1728
+ { insert: 'content' },
1729
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1730
+ { insert: 'content' },
1731
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1732
+ { insert: '\n' },
1733
+ ]),
1734
+ quill.getContents(),
1735
+ );
1736
+ });
1737
+
1738
+ it('convert col percent width should be `full: true`', async () => {
1739
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1740
+ quill.setContents(
1741
+ quill.clipboard.convert({
1742
+ html: `
1743
+ <table class="ws-table-all" id="customers">
1744
+ <colgroup>
1745
+ <col width="25%">
1746
+ <col width="25%">
1747
+ <col width="25%">
1748
+ <col width="25%">
1749
+ </colgroup>
1750
+ <tbody>
1751
+ <tr>
1752
+ <td>content</td>
1753
+ <td>content</td>
1754
+ <td>content</td>
1755
+ <td>content</td>
1756
+ </tr>
1757
+ </tbody>
1758
+ </table>
1759
+ `,
1760
+ }),
1761
+ );
1762
+ await vi.runAllTimersAsync();
1763
+
1764
+ expectDelta(
1765
+ new Delta([
1766
+ { insert: '\n' },
1767
+ { insert: { 'table-up-col': { full: true, width: 25 } } },
1768
+ { insert: { 'table-up-col': { full: true, width: 25 } } },
1769
+ { insert: { 'table-up-col': { full: true, width: 25 } } },
1770
+ { insert: { 'table-up-col': { full: true, width: 25 } } },
1771
+ { insert: 'content' },
1772
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1773
+ { insert: 'content' },
1774
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1775
+ { insert: 'content' },
1776
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1777
+ { insert: 'content' },
1778
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1779
+ { insert: '\n' },
1780
+ ]),
1781
+ quill.getContents(),
1782
+ );
1783
+ });
1784
+
1785
+ it('convert col fixed width should be `full: false`', async () => {
1786
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1787
+ quill.setContents(
1788
+ quill.clipboard.convert({
1789
+ html: `
1790
+ <table class="ws-table-all" id="customers">
1791
+ <colgroup>
1792
+ <col width="120px">
1793
+ <col width="120px">
1794
+ <col width="120px">
1795
+ <col width="120px">
1796
+ </colgroup>
1797
+ <tbody>
1798
+ <tr>
1799
+ <td>content</td>
1800
+ <td>content</td>
1801
+ <td>content</td>
1802
+ <td>content</td>
1803
+ </tr>
1804
+ </tbody>
1805
+ </table>
1806
+ `,
1807
+ }),
1808
+ );
1809
+ await vi.runAllTimersAsync();
1810
+
1811
+ expectDelta(
1812
+ new Delta([
1813
+ { insert: '\n' },
1814
+ { insert: { 'table-up-col': { full: false, width: 120 } } },
1815
+ { insert: { 'table-up-col': { full: false, width: 120 } } },
1816
+ { insert: { 'table-up-col': { full: false, width: 120 } } },
1817
+ { insert: { 'table-up-col': { full: false, width: 120 } } },
1818
+ { insert: 'content' },
1819
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1820
+ { insert: 'content' },
1821
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1822
+ { insert: 'content' },
1823
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1824
+ { insert: 'content' },
1825
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1826
+ { insert: '\n' },
1827
+ ]),
1828
+ quill.getContents(),
1829
+ );
1830
+ });
1831
+
1832
+ it('convert mixed col width should follow option `full: false`', async () => {
1833
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1834
+ quill.setContents(
1835
+ quill.clipboard.convert({
1836
+ html: `
1837
+ <table class="ws-table-all" id="customers" style="width: 500px;">
1838
+ <colgroup>
1839
+ <col width="100px">
1840
+ <col width="20%">
1841
+ <col width="30%">
1842
+ <col width="150px">
1843
+ </colgroup>
1844
+ <tbody>
1845
+ <tr>
1846
+ <td>content</td>
1847
+ <td>content</td>
1848
+ <td>content</td>
1849
+ <td>content</td>
1850
+ </tr>
1851
+ </tbody>
1852
+ </table>
1853
+ `,
1854
+ }),
1855
+ );
1856
+ await vi.runAllTimersAsync();
1857
+
1858
+ expectDelta(
1859
+ new Delta([
1860
+ { insert: '\n' },
1861
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1862
+ { insert: { 'table-up-col': { full: false, width: 100 } } },
1863
+ { insert: { 'table-up-col': { full: false, width: 150 } } },
1864
+ { insert: { 'table-up-col': { full: false, width: 150 } } },
1865
+ { insert: 'content' },
1866
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1867
+ { insert: 'content' },
1868
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1869
+ { insert: 'content' },
1870
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1871
+ { insert: 'content' },
1872
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1873
+ { insert: '\n' },
1874
+ ]),
1875
+ quill.getContents(),
1876
+ );
1877
+ });
1878
+
1879
+ it('convert mixed col width should follow option `full: true`', async () => {
1880
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: true });
1881
+ quill.setContents(
1882
+ quill.clipboard.convert({
1883
+ html: `
1884
+ <table class="ws-table-all" id="customers" style="width: 500px;">
1885
+ <colgroup>
1886
+ <col width="100px">
1887
+ <col width="20%">
1888
+ <col width="30%">
1889
+ <col width="150px">
1890
+ </colgroup>
1891
+ <tbody>
1892
+ <tr>
1893
+ <td>content</td>
1894
+ <td>content</td>
1895
+ <td>content</td>
1896
+ <td>content</td>
1897
+ </tr>
1898
+ </tbody>
1899
+ </table>
1900
+ `,
1901
+ }),
1902
+ );
1903
+ await vi.runAllTimersAsync();
1904
+
1905
+ expectDelta(
1906
+ new Delta([
1907
+ { insert: '\n' },
1908
+ { insert: { 'table-up-col': { full: true, width: 20 } } },
1909
+ { insert: { 'table-up-col': { full: true, width: 20 } } },
1910
+ { insert: { 'table-up-col': { full: true, width: 30 } } },
1911
+ { insert: { 'table-up-col': { full: true, width: 30 } } },
1912
+ { insert: 'content' },
1913
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1914
+ { insert: 'content' },
1915
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1916
+ { insert: 'content' },
1917
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1918
+ { insert: 'content' },
1919
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1920
+ { insert: '\n' },
1921
+ ]),
1922
+ quill.getContents(),
1923
+ );
1924
+ });
1925
+ });
1926
+
1588
1927
  describe('clipboard content format', () => {
1589
1928
  it('should convert html code-block correctly', async () => {
1590
1929
  const quill = createQuillWithTableModule(`<p><br></p>`);
@@ -1921,7 +2260,7 @@ describe('clipboard content format', () => {
1921
2260
  });
1922
2261
 
1923
2262
  it('clipboard convert cell with background on tr', async () => {
1924
- const quill = createQuillWithTableModule(`<p><br></p>`);
2263
+ const quill = createQuillWithTableModule(`<p><br></p>`, { full: false });
1925
2264
  // color convert hex in quill internal
1926
2265
  quill.setContents(
1927
2266
  quill.clipboard.convert({
@@ -2174,3 +2513,230 @@ describe('test TableUp `getHTMLByCell`', () => {
2174
2513
  }
2175
2514
  });
2176
2515
  });
2516
+
2517
+ describe('clipboard style block resolution', () => {
2518
+ it('class-based background-color is preserved on table cell', () => {
2519
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2520
+ const delta = quill.clipboard.convert({
2521
+ html: '<style>.xl65{background-color:#FFC000}</style><table><tr><td class="xl65">text</td></tr></table>',
2522
+ });
2523
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2524
+ expect(cellOp).toBeDefined();
2525
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2526
+ expect(style).toContain('background-color');
2527
+ expect(style).toMatch(/#FFC000|rgb\(255, 192, 0\)/i);
2528
+ });
2529
+
2530
+ it('class-based color is preserved as Quill inline format', () => {
2531
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2532
+ const delta = quill.clipboard.convert({
2533
+ html: '<style>.xl65{color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2534
+ });
2535
+ // text and \n may be merged into a single op when attributes match
2536
+ const textOp = delta.ops.find(op => typeof op.insert === 'string' && (op.insert as string).includes('text'));
2537
+ expect(textOp).toBeDefined();
2538
+ expect(textOp!.attributes?.color).toBeTruthy();
2539
+ });
2540
+
2541
+ it('class-based bold is preserved as Quill inline format', () => {
2542
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2543
+ const delta = quill.clipboard.convert({
2544
+ html: '<style>.xl65{font-weight:bold}</style><table><tr><td class="xl65">text</td></tr></table>',
2545
+ });
2546
+ // text and \n may be merged into a single op when attributes match
2547
+ const textOp = delta.ops.find(op => typeof op.insert === 'string' && (op.insert as string).includes('text'));
2548
+ expect(textOp).toBeDefined();
2549
+ expect(textOp!.attributes?.bold).toBe(true);
2550
+ });
2551
+
2552
+ it('inline style takes priority over class style', () => {
2553
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2554
+ const delta = quill.clipboard.convert({
2555
+ html: '<style>.xl65{background-color:red}</style><table><tr><td class="xl65" style="background-color:blue">text</td></tr></table>',
2556
+ });
2557
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2558
+ expect(cellOp).toBeDefined();
2559
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2560
+ expect(style).toContain('background-color');
2561
+ expect(style).toMatch(/blue/i);
2562
+ expect(style).not.toMatch(/red/i);
2563
+ });
2564
+
2565
+ it('@-rules are skipped without error', () => {
2566
+ const rules = parseCSSRules('@page{margin:1in} .xl65{background:red}');
2567
+ expect(rules.length).toBe(1);
2568
+ expect(rules[0].selector).toBe('.xl65');
2569
+ expect(rules[0].styles.background).toBe('red');
2570
+ });
2571
+
2572
+ it('multiple style blocks are all processed', () => {
2573
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2574
+ const delta = quill.clipboard.convert({
2575
+ html: '<style>.a{background-color:red}</style><style>.b{background-color:blue}</style><table><tr><td class="a">A</td><td class="b">B</td></tr></table>',
2576
+ });
2577
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2578
+ expect(cellOps.length).toBeGreaterThanOrEqual(2);
2579
+ expect((cellOps[0].attributes!['table-up-cell-inner'] as any).style).toContain('background-color');
2580
+ expect((cellOps[1].attributes!['table-up-cell-inner'] as any).style).toContain('background-color');
2581
+ });
2582
+
2583
+ it('simple tag selectors (td) are skipped by default', () => {
2584
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2585
+ const delta = quill.clipboard.convert({
2586
+ html: '<style>td{background-color:red} .xl65{background-color:blue}</style><table><tr><td class="xl65">text</td></tr></table>',
2587
+ });
2588
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2589
+ expect(cellOp).toBeDefined();
2590
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2591
+ // class selector applied, tag selector skipped
2592
+ expect(style).toMatch(/blue/i);
2593
+ });
2594
+
2595
+ it('descendant tag selectors (table td) are skipped by default', () => {
2596
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2597
+ const delta = quill.clipboard.convert({
2598
+ html: '<style>table td{background-color:red} .xl65{background-color:blue}</style><table><tr><td class="xl65">text</td></tr></table>',
2599
+ });
2600
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2601
+ expect(cellOp).toBeDefined();
2602
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2603
+ expect(style).toMatch(/blue/i);
2604
+ expect(style).not.toMatch(/red/i);
2605
+ });
2606
+
2607
+ it('child combinator tag selectors (tr > td) are skipped by default', () => {
2608
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2609
+ const delta = quill.clipboard.convert({
2610
+ html: '<style>tr > td{background-color:red}</style><table><tr><td>text</td></tr></table>',
2611
+ });
2612
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2613
+ expect(cellOp).toBeDefined();
2614
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2615
+ expect(style).not.toContain('background-color');
2616
+ });
2617
+
2618
+ it('deep combinator tag selectors (table > tbody > tr > td) are skipped by default', () => {
2619
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2620
+ const delta = quill.clipboard.convert({
2621
+ html: '<style>table > tbody > tr > td{background-color:red}</style><table><tbody><tr><td>text</td></tr></tbody></table>',
2622
+ });
2623
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2624
+ expect(cellOp).toBeDefined();
2625
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2626
+ expect(style).not.toContain('background-color');
2627
+ });
2628
+
2629
+ it('sibling combinator tag selectors (td + td) are skipped by default', () => {
2630
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2631
+ const delta = quill.clipboard.convert({
2632
+ html: '<style>td + td{background-color:red}</style><table><tr><td>a</td><td>b</td></tr></table>',
2633
+ });
2634
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2635
+ for (const op of cellOps) {
2636
+ const style = (op.attributes!['table-up-cell-inner'] as any).style || '';
2637
+ expect(style).not.toContain('background-color');
2638
+ }
2639
+ });
2640
+
2641
+ it('tag+class selectors (td.xl65) are NOT skipped', () => {
2642
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2643
+ const delta = quill.clipboard.convert({
2644
+ html: '<style>td.xl65{background-color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2645
+ });
2646
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2647
+ expect(cellOp).toBeDefined();
2648
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2649
+ expect(style).toContain('background-color');
2650
+ });
2651
+
2652
+ it('tag+id selectors (td#main) are NOT skipped', () => {
2653
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2654
+ const delta = quill.clipboard.convert({
2655
+ html: '<style>td#main{background-color:red}</style><table><tr><td id="main">text</td></tr></table>',
2656
+ });
2657
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2658
+ expect(cellOp).toBeDefined();
2659
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2660
+ expect(style).toContain('background-color');
2661
+ });
2662
+
2663
+ it('tag+attribute selectors (td[data-x]) are NOT skipped', () => {
2664
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2665
+ const delta = quill.clipboard.convert({
2666
+ html: '<style>td[data-x]{background-color:red}</style><table><tr><td data-x="1">text</td></tr></table>',
2667
+ });
2668
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2669
+ expect(cellOp).toBeDefined();
2670
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2671
+ expect(style).toContain('background-color');
2672
+ });
2673
+
2674
+ it('class descendant tag selectors (.cls td) are NOT skipped', () => {
2675
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2676
+ const delta = quill.clipboard.convert({
2677
+ html: '<style>.wrap td{background-color:#FFC000}</style><table class="wrap"><tr><td>text</td></tr></table>',
2678
+ });
2679
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2680
+ expect(cellOp).toBeDefined();
2681
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2682
+ expect(style).toContain('background-color');
2683
+ expect(style).toMatch(/#FFC000|rgb\(255, 192, 0\)/i);
2684
+ });
2685
+
2686
+ it('pseudo selectors (td:first-child) are NOT skipped', () => {
2687
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2688
+ const delta = quill.clipboard.convert({
2689
+ html: '<style>td:first-child{background-color:red}</style><table><tr><td>a</td><td>b</td></tr></table>',
2690
+ });
2691
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2692
+ // first cell should have background-color, second should not
2693
+ const styles = cellOps.map(op => (op.attributes!['table-up-cell-inner'] as any).style || '');
2694
+ expect(styles.some(s => s.includes('background-color'))).toBe(true);
2695
+ expect(styles.some(s => !s.includes('background-color'))).toBe(true);
2696
+ });
2697
+
2698
+ it('comma-separated mixed selectors skip only the tag-only parts', () => {
2699
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2700
+ const delta = quill.clipboard.convert({
2701
+ html: '<style>td, .xl65{background-color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2702
+ });
2703
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2704
+ expect(cellOp).toBeDefined();
2705
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2706
+ // ".xl65" part applied (tag "td" part skipped, but .xl65 matches the same element)
2707
+ expect(style).toContain('background-color');
2708
+ });
2709
+
2710
+ it('complex tag selectors are applied when pasteDefaultTagStyle is true', () => {
2711
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true, pasteDefaultTagStyle: true });
2712
+ const delta = quill.clipboard.convert({
2713
+ html: '<style>table td{background-color:red}</style><table><tr><td>text</td></tr></table>',
2714
+ });
2715
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2716
+ expect(cellOp).toBeDefined();
2717
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2718
+ expect(style).toContain('background-color');
2719
+ });
2720
+
2721
+ it('pasteStyleSheet=false disables style resolution', () => {
2722
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: false });
2723
+ const delta = quill.clipboard.convert({
2724
+ html: '<style>.xl65{background-color:#FFC000}</style><table><tr><td class="xl65">text</td></tr></table>',
2725
+ });
2726
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2727
+ expect(cellOp).toBeDefined();
2728
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2729
+ expect(style).not.toContain('background-color');
2730
+ });
2731
+
2732
+ it('pasteDefaultTagStyle=true enables tag selector styles', () => {
2733
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true, pasteDefaultTagStyle: true });
2734
+ const delta = quill.clipboard.convert({
2735
+ html: '<style>td{background-color:#FFC000}</style><table><tr><td>text</td></tr></table>',
2736
+ });
2737
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2738
+ expect(cellOp).toBeDefined();
2739
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2740
+ expect(style).toContain('background-color');
2741
+ });
2742
+ });