@burger-editor/client 4.0.0-alpha.63 → 4.0.0-alpha.64

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/dist/client.js CHANGED
@@ -1266,7 +1266,6 @@ function setValue(el, name, datum, attr = 'field', filter, xssSanitize = true) {
1266
1266
  if (filter) {
1267
1267
  datum = filter(datum);
1268
1268
  }
1269
- // console.log({ name, field, fieldName, propName, datum, el: el.innerHTML });
1270
1269
  if (!propName) {
1271
1270
  setContent$1(el, datum, undefined, xssSanitize);
1272
1271
  return;
@@ -1315,6 +1314,53 @@ function setValue(el, name, datum, attr = 'field', filter, xssSanitize = true) {
1315
1314
  }
1316
1315
  el.setAttribute(propName, `${datum}`);
1317
1316
  }
1317
+ /**
1318
+ * Set a boolean attribute on an element via its DOM property.
1319
+ * Empty string is treated as true (HTML boolean attribute convention).
1320
+ * Falls back to setAttribute if the element doesn't match any of the expected types.
1321
+ * @param el
1322
+ * @param datum
1323
+ * @param types
1324
+ * @param prop
1325
+ * @param emptyIsTrue
1326
+ * @returns true if handled
1327
+ */
1328
+ function setBooleanAttr(el, datum, types, prop, emptyIsTrue = true) {
1329
+ if (types.some((T) => el instanceof T)) {
1330
+ // SAFETY: prop is always a hardcoded string literal from setBooleanAttrByName
1331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1332
+ el[prop] = emptyIsTrue ? (datum === '' ? true : !!datum) : !!datum;
1333
+ return true;
1334
+ }
1335
+ return false;
1336
+ }
1337
+ /**
1338
+ * Set an integer attribute on an element via its DOM property.
1339
+ * Removes the attribute if the parsed value is NaN.
1340
+ * Falls back to setAttribute if the element doesn't match any of the expected types.
1341
+ * @param el
1342
+ * @param name
1343
+ * @param datum
1344
+ * @param types
1345
+ * @param prop
1346
+ * @param floor
1347
+ * @returns true if handled
1348
+ */
1349
+ function setIntegerAttr(el, name, datum, types, prop, floor = false) {
1350
+ if (types.some((T) => el instanceof T)) {
1351
+ const val = toInt(datum);
1352
+ if (Number.isNaN(val)) {
1353
+ el.removeAttribute(name);
1354
+ }
1355
+ else {
1356
+ // SAFETY: prop is always a hardcoded string literal from setIntegerAttrByName
1357
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1358
+ el[prop] = floor ? Math.floor(val) : val;
1359
+ }
1360
+ return true;
1361
+ }
1362
+ return false;
1363
+ }
1318
1364
  /**
1319
1365
  *
1320
1366
  * @param el
@@ -1374,6 +1420,25 @@ function set$1(el, prefix, name, datum, xssSanitize = true) {
1374
1420
  }
1375
1421
  return;
1376
1422
  }
1423
+ if (setSpecialAttr(el, name, datum)) {
1424
+ return;
1425
+ }
1426
+ if (setBooleanAttrByName(el, name, datum)) {
1427
+ return;
1428
+ }
1429
+ if (setIntegerAttrByName(el, name, datum)) {
1430
+ return;
1431
+ }
1432
+ el.setAttribute(name, `${datum}`);
1433
+ }
1434
+ /**
1435
+ * Handle attributes with unique logic that doesn't fit boolean/integer patterns.
1436
+ * @param el
1437
+ * @param name
1438
+ * @param datum
1439
+ * @returns true if handled
1440
+ */
1441
+ function setSpecialAttr(el, name, datum) {
1377
1442
  switch (name) {
1378
1443
  case 'contenteditable': {
1379
1444
  switch (datum) {
@@ -1387,7 +1452,7 @@ function set$1(el, prefix, name, datum, xssSanitize = true) {
1387
1452
  el.removeAttribute(name);
1388
1453
  }
1389
1454
  }
1390
- return;
1455
+ return true;
1391
1456
  }
1392
1457
  case 'dir': {
1393
1458
  switch (datum) {
@@ -1396,45 +1461,45 @@ function set$1(el, prefix, name, datum, xssSanitize = true) {
1396
1461
  el.dir = datum;
1397
1462
  break;
1398
1463
  }
1399
- // case 'auto':
1400
1464
  default: {
1401
1465
  el.removeAttribute(name);
1402
1466
  }
1403
1467
  }
1404
- return;
1468
+ return true;
1405
1469
  }
1406
1470
  case 'draggable': {
1407
1471
  if (typeof datum === 'boolean') {
1408
1472
  el.draggable = datum;
1409
- break;
1410
1473
  }
1411
- switch (datum) {
1412
- case 'true': {
1413
- el.draggable = true;
1414
- break;
1415
- }
1416
- case 'false': {
1417
- el.draggable = false;
1418
- break;
1419
- }
1420
- // case 'auto':
1421
- default: {
1422
- el.removeAttribute(name);
1474
+ else {
1475
+ switch (datum) {
1476
+ case 'true': {
1477
+ el.draggable = true;
1478
+ break;
1479
+ }
1480
+ case 'false': {
1481
+ el.draggable = false;
1482
+ break;
1483
+ }
1484
+ default: {
1485
+ el.removeAttribute(name);
1486
+ }
1423
1487
  }
1424
1488
  }
1425
- return;
1489
+ return true;
1426
1490
  }
1427
1491
  case 'hidden': {
1428
1492
  if (datum === 'until-found') {
1429
1493
  el.setAttribute(name, datum);
1430
- return;
1431
1494
  }
1432
- el.hidden = !!datum;
1433
- return;
1495
+ else {
1496
+ el.hidden = !!datum;
1497
+ }
1498
+ return true;
1434
1499
  }
1435
1500
  case 'spellcheck': {
1436
1501
  el.spellcheck = !!datum;
1437
- return;
1502
+ return true;
1438
1503
  }
1439
1504
  case 'tabindex': {
1440
1505
  let i;
@@ -1445,501 +1510,224 @@ function set$1(el, prefix, name, datum, xssSanitize = true) {
1445
1510
  i = Number.parseInt(datum, 10);
1446
1511
  }
1447
1512
  else {
1448
- i = datum;
1513
+ i = datum ?? -1;
1449
1514
  }
1450
1515
  el.tabIndex = Number.isNaN(i) ? -1 : Math.floor(i);
1451
- return;
1452
- }
1453
- case 'async': {
1454
- if (el instanceof HTMLScriptElement) {
1455
- el.async = !!datum;
1456
- }
1457
- else {
1458
- el.setAttribute(name, `${datum}`);
1459
- }
1460
- return;
1516
+ return true;
1461
1517
  }
1462
1518
  case 'autocomplete': {
1463
1519
  if (el instanceof HTMLInputElement || el instanceof HTMLFormElement) {
1464
- // @ts-ignore
1465
- el.autocomplete = datum ? `${datum}` : 'off';
1520
+ el.setAttribute(name, datum ? `${datum}` : 'off');
1466
1521
  }
1467
1522
  else {
1468
1523
  el.setAttribute(name, `${datum}`);
1469
1524
  }
1470
- return;
1525
+ return true;
1471
1526
  }
1472
- case 'autofocus': {
1473
- if (el instanceof HTMLButtonElement ||
1474
- el instanceof HTMLInputElement ||
1475
- el instanceof HTMLSelectElement ||
1476
- el instanceof HTMLTextAreaElement) {
1477
- el.autofocus = datum === '' ? true : !!datum;
1527
+ case 'download': {
1528
+ if (el instanceof HTMLAnchorElement) {
1529
+ if (datum || datum === '') {
1530
+ el.download = `${datum}`;
1531
+ }
1532
+ else {
1533
+ el.removeAttribute(name);
1534
+ }
1478
1535
  }
1479
1536
  else {
1480
1537
  el.setAttribute(name, `${datum}`);
1481
1538
  }
1482
- return;
1539
+ return true;
1483
1540
  }
1484
- case 'autoplay': {
1541
+ case 'preload': {
1485
1542
  if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
1486
- el.autoplay = datum === '' ? true : !!datum;
1543
+ switch (datum) {
1544
+ case '':
1545
+ case 'auto':
1546
+ case 'metadata': {
1547
+ el.preload = datum;
1548
+ break;
1549
+ }
1550
+ default: {
1551
+ el.preload = 'none';
1552
+ }
1553
+ }
1487
1554
  }
1488
1555
  else {
1489
1556
  el.setAttribute(name, `${datum}`);
1490
1557
  }
1491
- return;
1558
+ return true;
1492
1559
  }
1493
- case 'checked': {
1560
+ case 'step': {
1494
1561
  if (el instanceof HTMLInputElement) {
1495
- el.checked = datum === '' ? true : !!datum;
1496
- }
1497
- else {
1498
- el.setAttribute(name, `${datum}`);
1499
- }
1500
- return;
1501
- }
1502
- case 'cols': {
1503
- if (el instanceof HTMLTextAreaElement) {
1504
- const cols = toInt(datum);
1505
- if (Number.isNaN(cols)) {
1506
- el.removeAttribute(name);
1562
+ if (datum === 'any') {
1563
+ el.step = datum;
1507
1564
  }
1508
1565
  else {
1509
- el.cols = cols;
1566
+ const step = toNum(datum);
1567
+ if (Number.isNaN(step)) {
1568
+ el.removeAttribute(name);
1569
+ }
1570
+ else {
1571
+ el.step = step.toString(10);
1572
+ }
1510
1573
  }
1511
1574
  }
1512
1575
  else {
1513
1576
  el.setAttribute(name, `${datum}`);
1514
1577
  }
1515
- return;
1578
+ return true;
1516
1579
  }
1517
- case 'colspan': {
1518
- if (el instanceof HTMLTableCellElement) {
1519
- const colSpan = toInt(datum);
1520
- if (Number.isNaN(colSpan)) {
1521
- el.removeAttribute(name);
1580
+ case 'target': {
1581
+ if (el instanceof HTMLAnchorElement ||
1582
+ el instanceof HTMLAreaElement ||
1583
+ el instanceof HTMLFormElement) {
1584
+ if (datum) {
1585
+ el.target = `${datum}`;
1522
1586
  }
1523
1587
  else {
1524
- el.colSpan = colSpan;
1588
+ el.removeAttribute(name);
1525
1589
  }
1526
1590
  }
1527
1591
  else {
1528
1592
  el.setAttribute(name, `${datum}`);
1529
1593
  }
1530
- return;
1594
+ return true;
1595
+ }
1596
+ default: {
1597
+ return false;
1598
+ }
1599
+ }
1600
+ }
1601
+ /**
1602
+ * Handle boolean HTML attributes via their DOM properties.
1603
+ * @param el
1604
+ * @param name
1605
+ * @param datum
1606
+ * @returns true if handled
1607
+ */
1608
+ function setBooleanAttrByName(el, name, datum) {
1609
+ switch (name) {
1610
+ case 'async': {
1611
+ return setBooleanAttr(el, datum, [HTMLScriptElement], 'async', false);
1612
+ }
1613
+ case 'autofocus': {
1614
+ return setBooleanAttr(el, datum, [HTMLButtonElement, HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement], 'autofocus');
1615
+ }
1616
+ case 'autoplay': {
1617
+ return setBooleanAttr(el, datum, [HTMLAudioElement, HTMLVideoElement], 'autoplay');
1618
+ }
1619
+ case 'checked': {
1620
+ return setBooleanAttr(el, datum, [HTMLInputElement], 'checked');
1531
1621
  }
1532
1622
  case 'controls': {
1533
- if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
1534
- el.controls = datum === '' ? true : !!datum;
1535
- }
1536
- else {
1537
- el.setAttribute(name, `${datum}`);
1538
- }
1539
- return;
1623
+ return setBooleanAttr(el, datum, [HTMLAudioElement, HTMLVideoElement], 'controls');
1540
1624
  }
1541
1625
  case 'default': {
1542
- if (el instanceof HTMLTrackElement) {
1543
- el.default = datum === '' ? true : !!datum;
1544
- }
1545
- else {
1546
- el.setAttribute(name, `${datum}`);
1547
- }
1548
- return;
1626
+ return setBooleanAttr(el, datum, [HTMLTrackElement], 'default');
1549
1627
  }
1550
1628
  case 'defer': {
1551
- if (el instanceof HTMLScriptElement) {
1552
- el.defer = !!datum;
1553
- }
1554
- else {
1555
- el.setAttribute(name, `${datum}`);
1556
- }
1557
- return;
1629
+ return setBooleanAttr(el, datum, [HTMLScriptElement], 'defer', false);
1558
1630
  }
1559
1631
  case 'disabled': {
1560
- if (el instanceof HTMLButtonElement ||
1561
- el instanceof HTMLFieldSetElement ||
1562
- el instanceof HTMLInputElement ||
1563
- el instanceof HTMLOptGroupElement ||
1564
- el instanceof HTMLOptionElement ||
1565
- el instanceof HTMLSelectElement ||
1566
- el instanceof HTMLTextAreaElement) {
1567
- el.disabled = datum === '' ? true : !!datum;
1568
- }
1569
- else {
1570
- el.setAttribute(name, `${datum}`);
1571
- }
1572
- return;
1573
- }
1574
- case 'download': {
1575
- if (el instanceof HTMLAnchorElement) {
1576
- if (datum || datum === '') {
1577
- el.download = `${datum}`;
1578
- }
1579
- else {
1580
- el.removeAttribute(name);
1581
- }
1582
- }
1583
- else {
1584
- el.setAttribute(name, `${datum}`);
1585
- }
1586
- return;
1587
- }
1588
- case 'high': {
1589
- if (el instanceof HTMLMeterElement) {
1590
- const high = toInt(datum);
1591
- if (Number.isNaN(high)) {
1592
- el.removeAttribute(name);
1593
- }
1594
- else {
1595
- el.high = high;
1596
- }
1597
- }
1598
- else {
1599
- el.setAttribute(name, `${datum}`);
1600
- }
1601
- return;
1632
+ return setBooleanAttr(el, datum, [
1633
+ HTMLButtonElement,
1634
+ HTMLFieldSetElement,
1635
+ HTMLInputElement,
1636
+ HTMLOptGroupElement,
1637
+ HTMLOptionElement,
1638
+ HTMLSelectElement,
1639
+ HTMLTextAreaElement,
1640
+ ], 'disabled');
1602
1641
  }
1603
1642
  case 'ismap': {
1604
- if (el instanceof HTMLImageElement) {
1605
- el.isMap = datum === '' ? true : !!datum;
1606
- }
1607
- else {
1608
- el.setAttribute(name, `${datum}`);
1609
- }
1610
- return;
1643
+ return setBooleanAttr(el, datum, [HTMLImageElement], 'isMap');
1611
1644
  }
1612
1645
  case 'loop': {
1613
- if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
1614
- el.loop = datum === '' ? true : !!datum;
1615
- }
1616
- else {
1617
- el.setAttribute(name, `${datum}`);
1618
- }
1619
- return;
1620
- }
1621
- case 'low': {
1622
- if (el instanceof HTMLMeterElement) {
1623
- const low = toInt(datum);
1624
- if (Number.isNaN(low)) {
1625
- el.removeAttribute(name);
1626
- }
1627
- else {
1628
- el.low = low;
1629
- }
1630
- }
1631
- else {
1632
- el.setAttribute(name, `${datum}`);
1633
- }
1634
- return;
1635
- }
1636
- case 'max': {
1637
- if (el instanceof HTMLInputElement ||
1638
- el instanceof HTMLMeterElement ||
1639
- el instanceof HTMLProgressElement) {
1640
- const max = toInt(datum);
1641
- if (Number.isNaN(max)) {
1642
- el.removeAttribute(name);
1643
- }
1644
- else {
1645
- el.max = max;
1646
- }
1647
- }
1648
- else {
1649
- el.setAttribute(name, `${datum}`);
1650
- }
1651
- return;
1652
- }
1653
- case 'maxlength': {
1654
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1655
- const maxLength = toInt(datum);
1656
- if (Number.isNaN(maxLength)) {
1657
- el.removeAttribute(name);
1658
- }
1659
- else {
1660
- el.maxLength = Math.floor(maxLength);
1661
- }
1662
- }
1663
- else {
1664
- el.setAttribute(name, `${datum}`);
1665
- }
1666
- return;
1667
- }
1668
- case 'min': {
1669
- if (el instanceof HTMLInputElement || el instanceof HTMLMeterElement) {
1670
- const min = toInt(datum);
1671
- if (Number.isNaN(min)) {
1672
- el.removeAttribute(name);
1673
- }
1674
- else {
1675
- el.min = min;
1676
- }
1677
- }
1678
- else {
1679
- el.setAttribute(name, `${datum}`);
1680
- }
1681
- return;
1646
+ return setBooleanAttr(el, datum, [HTMLAudioElement, HTMLVideoElement], 'loop');
1682
1647
  }
1683
1648
  case 'multiple': {
1684
- if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
1685
- el.multiple = datum === '' ? true : !!datum;
1686
- }
1687
- else {
1688
- el.setAttribute(name, `${datum}`);
1689
- }
1690
- return;
1649
+ return setBooleanAttr(el, datum, [HTMLInputElement, HTMLSelectElement], 'multiple');
1691
1650
  }
1692
1651
  case 'novalidate': {
1693
- if (el instanceof HTMLFormElement) {
1694
- el.noValidate = datum === '' ? true : !!datum;
1695
- }
1696
- else {
1697
- el.setAttribute(name, `${datum}`);
1698
- }
1699
- return;
1652
+ return setBooleanAttr(el, datum, [HTMLFormElement], 'noValidate');
1700
1653
  }
1701
1654
  case 'open': {
1702
- if (el instanceof HTMLDetailsElement) {
1703
- el.open = datum === '' ? true : !!datum;
1704
- }
1705
- else {
1706
- el.setAttribute(name, `${datum}`);
1707
- }
1708
- break;
1709
- }
1710
- case 'optimum': {
1711
- if (el instanceof HTMLMeterElement) {
1712
- const optimum = toInt(datum);
1713
- if (Number.isNaN(optimum)) {
1714
- el.removeAttribute(name);
1715
- }
1716
- else {
1717
- el.optimum = Math.floor(optimum);
1718
- }
1719
- }
1720
- else {
1721
- el.setAttribute(name, `${datum}`);
1722
- }
1723
- return;
1724
- }
1725
- case 'preload': {
1726
- if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
1727
- switch (datum) {
1728
- case '':
1729
- case 'auto':
1730
- case 'metadata': {
1731
- el.preload = datum;
1732
- break;
1733
- }
1734
- default: {
1735
- el.preload = 'none';
1736
- }
1737
- }
1738
- }
1739
- else {
1740
- el.setAttribute(name, `${datum}`);
1741
- }
1742
- return;
1655
+ return setBooleanAttr(el, datum, [HTMLDetailsElement], 'open');
1743
1656
  }
1744
1657
  case 'readonly': {
1745
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1746
- el.readOnly = datum === '' ? true : !!datum;
1747
- }
1748
- else {
1749
- el.setAttribute(name, `${datum}`);
1750
- }
1751
- return;
1658
+ return setBooleanAttr(el, datum, [HTMLInputElement, HTMLTextAreaElement], 'readOnly');
1752
1659
  }
1753
1660
  case 'required': {
1754
- if (el instanceof HTMLInputElement ||
1755
- el instanceof HTMLSelectElement ||
1756
- el instanceof HTMLTextAreaElement) {
1757
- el.required = datum === '' ? true : !!datum;
1758
- }
1759
- else {
1760
- el.setAttribute(name, `${datum}`);
1761
- }
1762
- return;
1661
+ return setBooleanAttr(el, datum, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement], 'required');
1763
1662
  }
1764
1663
  case 'reversed': {
1765
- if (el instanceof HTMLOListElement) {
1766
- el.reversed = datum === '' ? true : !!datum;
1767
- }
1768
- else {
1769
- el.setAttribute(name, `${datum}`);
1770
- }
1771
- return;
1664
+ return setBooleanAttr(el, datum, [HTMLOListElement], 'reversed');
1665
+ }
1666
+ default: {
1667
+ return false;
1668
+ }
1669
+ }
1670
+ }
1671
+ /**
1672
+ * Handle integer HTML attributes via their DOM properties.
1673
+ * @param el
1674
+ * @param name
1675
+ * @param datum
1676
+ * @returns true if handled
1677
+ */
1678
+ function setIntegerAttrByName(el, name, datum) {
1679
+ switch (name) {
1680
+ case 'cols': {
1681
+ return setIntegerAttr(el, name, datum, [HTMLTextAreaElement], 'cols');
1682
+ }
1683
+ case 'colspan': {
1684
+ return setIntegerAttr(el, name, datum, [HTMLTableCellElement], 'colSpan');
1685
+ }
1686
+ case 'high': {
1687
+ return setIntegerAttr(el, name, datum, [HTMLMeterElement], 'high');
1688
+ }
1689
+ case 'low': {
1690
+ return setIntegerAttr(el, name, datum, [HTMLMeterElement], 'low');
1691
+ }
1692
+ case 'max': {
1693
+ return setIntegerAttr(el, name, datum, [HTMLInputElement, HTMLMeterElement, HTMLProgressElement], 'max');
1694
+ }
1695
+ case 'maxlength': {
1696
+ return setIntegerAttr(el, name, datum, [HTMLInputElement, HTMLTextAreaElement], 'maxLength', true);
1697
+ }
1698
+ case 'min': {
1699
+ return setIntegerAttr(el, name, datum, [HTMLInputElement, HTMLMeterElement], 'min');
1700
+ }
1701
+ case 'optimum': {
1702
+ return setIntegerAttr(el, name, datum, [HTMLMeterElement], 'optimum', true);
1772
1703
  }
1773
1704
  case 'rows': {
1774
- if (el instanceof HTMLTextAreaElement) {
1775
- const rows = toInt(datum);
1776
- if (Number.isNaN(rows)) {
1777
- el.removeAttribute(name);
1778
- }
1779
- else {
1780
- el.rows = Math.floor(rows);
1781
- }
1782
- }
1783
- else {
1784
- el.setAttribute(name, `${datum}`);
1785
- }
1786
- return;
1705
+ return setIntegerAttr(el, name, datum, [HTMLTextAreaElement], 'rows', true);
1787
1706
  }
1788
1707
  case 'rowspan': {
1789
- if (el instanceof HTMLTableCellElement) {
1790
- const rowSpan = toInt(datum);
1791
- if (Number.isNaN(rowSpan)) {
1792
- el.removeAttribute(name);
1793
- }
1794
- else {
1795
- el.rowSpan = Math.floor(rowSpan);
1796
- }
1797
- }
1798
- else {
1799
- el.setAttribute(name, `${datum}`);
1800
- }
1801
- return;
1708
+ return setIntegerAttr(el, name, datum, [HTMLTableCellElement], 'rowSpan', true);
1802
1709
  }
1803
1710
  case 'size': {
1804
- if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
1805
- const size = toInt(datum);
1806
- if (Number.isNaN(size)) {
1807
- el.removeAttribute(name);
1808
- }
1809
- else {
1810
- el.size = size;
1811
- }
1812
- }
1813
- else {
1814
- el.setAttribute(name, `${datum}`);
1815
- }
1816
- return;
1711
+ return setIntegerAttr(el, name, datum, [HTMLInputElement, HTMLSelectElement], 'size');
1817
1712
  }
1818
1713
  case 'span': {
1819
- if (el instanceof HTMLTableColElement) {
1820
- const span = toInt(datum);
1821
- if (Number.isNaN(span)) {
1822
- el.removeAttribute(name);
1823
- }
1824
- else {
1825
- el.span = span;
1826
- }
1827
- }
1828
- else {
1829
- el.setAttribute(name, `${datum}`);
1830
- }
1831
- return;
1714
+ return setIntegerAttr(el, name, datum, [HTMLTableColElement], 'span');
1832
1715
  }
1833
1716
  case 'start': {
1834
- if (el instanceof HTMLOListElement) {
1835
- const start = toInt(datum);
1836
- if (Number.isNaN(start)) {
1837
- el.removeAttribute(name);
1838
- }
1839
- else {
1840
- el.start = start;
1841
- }
1842
- }
1843
- else {
1844
- el.setAttribute(name, `${datum}`);
1845
- }
1846
- return;
1847
- }
1848
- case 'step': {
1849
- if (el instanceof HTMLInputElement) {
1850
- if (datum === 'any') {
1851
- el.step = datum;
1852
- break;
1853
- }
1854
- const step = toNum(datum);
1855
- if (Number.isNaN(step)) {
1856
- el.removeAttribute(name);
1857
- }
1858
- else {
1859
- el.step = step.toString(10);
1860
- }
1861
- }
1862
- else {
1863
- el.setAttribute(name, `${datum}`);
1864
- }
1865
- return;
1866
- }
1867
- case 'target': {
1868
- if (el instanceof HTMLAnchorElement ||
1869
- el instanceof HTMLAreaElement ||
1870
- el instanceof HTMLFormElement) {
1871
- if (datum) {
1872
- el.target = `${datum}`;
1873
- }
1874
- else {
1875
- el.removeAttribute(name);
1876
- }
1877
- }
1878
- else {
1879
- el.setAttribute(name, `${datum}`);
1880
- }
1881
- return;
1717
+ return setIntegerAttr(el, name, datum, [HTMLOListElement], 'start');
1882
1718
  }
1883
- // case 'accesskey':
1884
- // case 'class':
1885
- // case 'contextmenu':
1886
- // case 'dropzone': // breakable support
1887
- // case 'id':
1888
- // case 'itemid':
1889
- // case 'itemprop':
1890
- // case 'itemref':
1891
- // case 'itemscope':
1892
- // case 'itemtype':
1893
- // case 'lang':
1894
- // case 'slot': // breakable support
1895
- // case 'style':
1896
- // case 'title':
1897
- // case 'translate': // breakable support
1898
- // case 'accept':
1899
- // case 'accept-charset':
1900
- // case 'action':
1901
- // case 'align':
1902
- // case 'alt':
1903
- // case 'cite':
1904
- // case 'coords':
1905
- // case 'datetime':
1906
- // case 'enctype':
1907
- // case 'for':
1908
- // case 'headers':
1909
- // case 'href':
1910
- // case 'hreflang':
1911
- // case 'kind':
1912
- // case 'label':
1913
- // case 'list':
1914
- // case 'media':
1915
- // case 'method':
1916
- // case 'name':
1917
- // case 'pattern':
1918
- // case 'placeholder':
1919
- // case 'poster':
1920
- // case 'pubdate':
1921
- // case 'rel':
1922
- // case 'sandbox':
1923
- // case 'src':
1924
- // case 'srcdoc':
1925
- // case 'srclang':
1926
- // case 'summary':
1927
- // case 'type':
1928
- // case 'usemap':
1929
- // case 'value':
1930
- // case 'wrap':
1931
- // and Data-* attributes
1932
1719
  default: {
1933
- el.setAttribute(name, `${datum}`);
1720
+ return false;
1934
1721
  }
1935
1722
  }
1936
1723
  }
1937
1724
  /**
1938
- *
1939
- * @param el
1940
- * @param datum
1941
- * @param asHtml
1942
- * @param xssSanitize
1725
+ * Set content value on a form element or general element.
1726
+ * Handles checkbox/radio checked state, input values, and innerHTML/textContent.
1727
+ * @param el - The target element
1728
+ * @param datum - The value to set
1729
+ * @param asHtml - Whether to set as innerHTML (true) or textContent (false)
1730
+ * @param xssSanitize - Whether to sanitize HTML content for XSS protection
1943
1731
  */
1944
1732
  function setContent$1(el, datum, asHtml = true, xssSanitize = true) {
1945
1733
  if (el instanceof HTMLInputElement ||
@@ -2909,8 +2697,11 @@ class EditorUI {
2909
2697
  show() {
2910
2698
  this.#el.hidden = false;
2911
2699
  }
2700
+ /**
2701
+ * @returns Whether the UI element is currently visible (not hidden)
2702
+ */
2912
2703
  visible() {
2913
- return !!this.#el.hidden;
2704
+ return !this.#el.hidden;
2914
2705
  }
2915
2706
  }
2916
2707
 
@@ -2963,10 +2754,11 @@ class EditorDialog extends EditorUI {
2963
2754
  this.clearTemplate();
2964
2755
  this.#onClosed();
2965
2756
  }
2966
- complete(
2967
- // @ts-ignore
2968
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2969
- formData) {
2757
+ /**
2758
+ * Handle form completion. Subclasses override to process form data before closing.
2759
+ * @param _formData - The submitted form data
2760
+ */
2761
+ complete(_formData) {
2970
2762
  this.close();
2971
2763
  }
2972
2764
  dispatchEvent(event) {
@@ -2981,17 +2773,18 @@ class EditorDialog extends EditorUI {
2981
2773
  ? elements.map((el) => el.shadowRoot?.querySelector(shadowSelector) ?? el)
2982
2774
  : elements;
2983
2775
  }
2984
- onSubmit(
2985
- // @ts-ignore
2986
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2987
- e,
2988
- // @ts-ignore
2989
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2990
- submitter) { }
2991
- open(
2992
- // @ts-ignore
2993
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2994
- ...args) {
2776
+ /**
2777
+ * Handle form submission. Subclasses override to add custom behavior.
2778
+ * Return `false` to cancel submission.
2779
+ * @param _e - The submit event
2780
+ * @param _submitter - The element that triggered submission
2781
+ */
2782
+ onSubmit(_e, _submitter) { }
2783
+ /**
2784
+ * Open the dialog. Subclasses override to set up content before showing.
2785
+ * @param _args - Arguments passed by subclass overrides
2786
+ */
2787
+ open(..._args) {
2995
2788
  const cancel = this.#onOpen();
2996
2789
  if (cancel === true) {
2997
2790
  return;
@@ -3215,21 +3008,38 @@ class ComponentObserver {
3215
3008
  constructor() {
3216
3009
  this.#instanceId = instanceId++;
3217
3010
  }
3011
+ /**
3012
+ * Dispatch a custom event on the window.
3013
+ * @template A - The action type key
3014
+ * @param name - The action name to dispatch
3015
+ * @param payload - The event payload matching the action type
3016
+ */
3218
3017
  notify(name, payload) {
3219
3018
  window.dispatchEvent(new CustomEvent(`bge:_${this.#instanceId}_:${name}`, { detail: payload }));
3220
3019
  }
3020
+ /**
3021
+ * Remove all registered event listeners and clear the internal map.
3022
+ */
3221
3023
  off() {
3222
- for (const [listener, name] of this.#listeners) {
3223
- // @ts-ignore
3224
- window.removeEventListener(`bge:_${this.#instanceId}_:${name}`, listener);
3024
+ for (const [wrapper, name] of this.#listeners) {
3025
+ window.removeEventListener(`bge:_${this.#instanceId}_:${name}`, wrapper);
3225
3026
  }
3226
3027
  this.#listeners.clear();
3227
3028
  }
3029
+ /**
3030
+ * Register an event listener. Each call creates a separate wrapper,
3031
+ * so calling with the same listener multiple times will fire it
3032
+ * multiple times per event.
3033
+ * @template A - The action type key
3034
+ * @param name - The action name to listen for
3035
+ * @param listener - Callback receiving the typed payload
3036
+ */
3228
3037
  on(name, listener) {
3229
- this.#listeners.set(listener, name);
3230
- window.addEventListener(`bge:_${this.#instanceId}_:${name}`, (e) => {
3038
+ const wrapper = (e) => {
3231
3039
  listener(e.detail);
3232
- });
3040
+ };
3041
+ this.#listeners.set(wrapper, name);
3042
+ window.addEventListener(`bge:_${this.#instanceId}_:${name}`, wrapper);
3233
3043
  }
3234
3044
  }
3235
3045
 
@@ -4256,9 +4066,21 @@ class ItemEditorDialog extends EditorDialog {
4256
4066
  $ctrl.max = value.toString();
4257
4067
  }
4258
4068
  }
4069
+ /**
4070
+ * Register a change handler for a named form control.
4071
+ * When `runOnInit` is true, fires immediately with the current value
4072
+ * (oldValue is null) and stores the initial value for subsequent comparisons.
4073
+ * @template N - The item data property name
4074
+ * @template D - The data type for the property
4075
+ * @param name - The control name prefixed with `$`
4076
+ * @param handler - Callback receiving new value and previous value
4077
+ * @param runOnInit - Whether to invoke the handler immediately with the current value
4078
+ */
4259
4079
  onChange(name, handler, runOnInit = true) {
4260
4080
  if (runOnInit) {
4261
- handler(this.get(name), null);
4081
+ const initialValue = this.get(name);
4082
+ this.#values.set(name, initialValue);
4083
+ handler(initialValue, null);
4262
4084
  }
4263
4085
  for (const $ctrl of this.#findAll(name)) {
4264
4086
  $ctrl.addEventListener('change', () => {
@@ -4763,12 +4585,10 @@ class BurgerEditorEngine {
4763
4585
  const mainInitialContent = typeof options.initialContents === 'string'
4764
4586
  ? options.initialContents
4765
4587
  : options.initialContents.main;
4766
- // @ts-ignore force assign to readonly property
4767
4588
  engine.#main =
4768
4589
  //
4769
4590
  await EditableArea.new('main', mainInitialContent, engine, options.blockMenu, options.initialInsertionButton, stylesheets, options.config.classList, options.editableAreaShell);
4770
4591
  const draftInitialContent = typeof options.initialContents === 'string' ? null : options.initialContents.draft;
4771
- // @ts-ignore force assign to readonly property
4772
4592
  engine.#draft =
4773
4593
  draftInitialContent == null
4774
4594
  ? null
@@ -33712,6 +33532,281 @@ function getCurrentEditorState(wysiwygElement) {
33712
33532
  };
33713
33533
  }
33714
33534
 
33535
+ const controlUIStyles = `
33536
+ :host {
33537
+ display: block;
33538
+ }
33539
+
33540
+ textarea,
33541
+ iframe {
33542
+ block-size: 50svh;
33543
+ inline-size: 100%;
33544
+ resize: vertical;
33545
+ overflow-y: auto;
33546
+ background: var(--bge-lightest-color);
33547
+ border: 1px solid var(--bge-border-color);
33548
+ border-radius: var(--border-radius);
33549
+ }
33550
+
33551
+ iframe[data-focus-within="true"],
33552
+ textarea:focus-visible {
33553
+ --bge-border-color: var(--bge-ui-primary-color);
33554
+ --bge-outline-color: var(--bge-ui-primary-color);
33555
+ outline: var(--bge-focus-outline-width) solid var(--bge-outline-color);
33556
+ }
33557
+
33558
+ textarea {
33559
+ font-family: var(--bge-font-family-monospace);
33560
+ }
33561
+
33562
+ [data-bge-mode="wysiwyg"] textarea {
33563
+ display: none;
33564
+ }
33565
+
33566
+ [data-bge-mode="html"] iframe {
33567
+ display: none;
33568
+ }
33569
+
33570
+ [data-text-only-editor] {
33571
+ block-size: 50svh;
33572
+ inline-size: 100%;
33573
+ resize: vertical;
33574
+ overflow-y: auto;
33575
+ background: var(--bge-lightest-color);
33576
+ border: 1px solid var(--bge-border-color);
33577
+ border-radius: var(--border-radius);
33578
+ padding: 1rem;
33579
+ box-sizing: border-box;
33580
+ }
33581
+
33582
+ [data-bge-mode="wysiwyg"] [data-text-only-editor],
33583
+ [data-bge-mode="html"] [data-text-only-editor] {
33584
+ display: none;
33585
+ }
33586
+
33587
+ [data-bge-mode="text-only"] iframe,
33588
+ [data-bge-mode="text-only"] textarea {
33589
+ display: none;
33590
+ }
33591
+
33592
+ [contenteditable="plaintext-only"] {
33593
+ outline: 1px dashed var(--bge-border-color);
33594
+ cursor: text;
33595
+ }
33596
+
33597
+ [contenteditable="plaintext-only"]:hover {
33598
+ outline-color: var(--bge-ui-primary-color);
33599
+ }
33600
+
33601
+ [contenteditable="plaintext-only"]:focus {
33602
+ outline: 2px solid var(--bge-ui-primary-color);
33603
+ outline-offset: 2px;
33604
+ }
33605
+
33606
+ [role="alert"] {
33607
+ margin-block-start: 0.5rem;
33608
+ padding: 0.5rem;
33609
+ background-color: var(--bge-error-color, #fee);
33610
+ border: 1px solid var(--bge-error-border-color, #fcc);
33611
+ border-radius: var(--border-radius);
33612
+ color: var(--bge-error-text-color, #c00);
33613
+ font-size: 0.875rem;
33614
+ }
33615
+ `;
33616
+ const editorContentStyles = (css) => `
33617
+ :where(html, body) {
33618
+ margin: 0;
33619
+ padding: 0;
33620
+
33621
+ :where(&, *) {
33622
+ box-sizing: border-box;
33623
+ }
33624
+ }
33625
+
33626
+ iframe,
33627
+ [contenteditable="true"] {
33628
+ padding: 1rem;
33629
+ block-size: 100%;
33630
+ box-sizing: border-box;
33631
+ }
33632
+
33633
+ [contenteditable="true"]:focus-visible {
33634
+ outline: none;
33635
+ }
33636
+
33637
+ ${css}
33638
+
33639
+ a:any-link {
33640
+ pointer-events: none !important;
33641
+ }
33642
+ `;
33643
+
33644
+ var __typeError$2 = (msg) => {
33645
+ throw TypeError(msg);
33646
+ };
33647
+ var __accessCheck$2 = (obj, member, msg) => member.has(obj) || __typeError$2("Cannot " + msg);
33648
+ var __privateGet$2 = (obj, member, getter) => (__accessCheck$2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
33649
+ var __privateAdd$2 = (obj, member, value) => member.has(obj) ? __typeError$2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
33650
+ var __privateSet$2 = (obj, member, value, setter) => (__accessCheck$2(obj, member, "write to private field"), member.set(obj, value), value);
33651
+ var __privateMethod$1 = (obj, member, method) => (__accessCheck$2(obj, member, "access private method"), method);
33652
+ var _container, _setToTextarea, _preventEnterKey, _syncToTextarea, _TextOnlyModeController_instances, attachEventListeners_fn, identifyEditableElements_fn;
33653
+ class TextOnlyModeController {
33654
+ /**
33655
+ * @param setToTextarea - Callback to sync cleaned HTML back to the textarea
33656
+ */
33657
+ constructor(setToTextarea) {
33658
+ __privateAdd$2(this, _TextOnlyModeController_instances);
33659
+ __privateAdd$2(this, _container, null);
33660
+ __privateAdd$2(this, _setToTextarea);
33661
+ /**
33662
+ * Enter キーを防止するハンドラー
33663
+ * @param event
33664
+ */
33665
+ __privateAdd$2(this, _preventEnterKey, (event) => {
33666
+ if (event.key === "Enter") {
33667
+ event.preventDefault();
33668
+ }
33669
+ });
33670
+ /**
33671
+ * text-onlyコンテナからtextareaへ同期
33672
+ */
33673
+ __privateAdd$2(this, _syncToTextarea, () => {
33674
+ if (!__privateGet$2(this, _container)) {
33675
+ return;
33676
+ }
33677
+ const cleanHTML = this.cleanHTML();
33678
+ __privateGet$2(this, _setToTextarea).call(this, cleanHTML);
33679
+ });
33680
+ __privateSet$2(this, _setToTextarea, setToTextarea);
33681
+ }
33682
+ /**
33683
+ * The text-only editing container element, or null if not yet activated.
33684
+ */
33685
+ get container() {
33686
+ return __privateGet$2(this, _container);
33687
+ }
33688
+ /**
33689
+ * text-onlyモードを有効化
33690
+ * @param shadowRoot
33691
+ * @param currentHTML
33692
+ * @param structureChangeMessage
33693
+ * @param previewStyleContent
33694
+ */
33695
+ activate(shadowRoot, currentHTML, structureChangeMessage, previewStyleContent) {
33696
+ if (!__privateGet$2(this, _container)) {
33697
+ __privateSet$2(this, _container, document.createElement("div"));
33698
+ __privateGet$2(this, _container).dataset.textOnlyEditor = "";
33699
+ const modeContainer = shadowRoot.querySelector("[data-bge-mode]");
33700
+ modeContainer.insertBefore(__privateGet$2(this, _container), structureChangeMessage);
33701
+ }
33702
+ const textOnlyStyle = document.createElement("style");
33703
+ textOnlyStyle.dataset.textOnlyStyle = "";
33704
+ if (previewStyleContent) {
33705
+ textOnlyStyle.textContent = previewStyleContent;
33706
+ }
33707
+ __privateGet$2(this, _container).innerHTML = currentHTML;
33708
+ __privateGet$2(this, _container).prepend(textOnlyStyle);
33709
+ __privateMethod$1(this, _TextOnlyModeController_instances, identifyEditableElements_fn).call(this, __privateGet$2(this, _container));
33710
+ __privateMethod$1(this, _TextOnlyModeController_instances, attachEventListeners_fn).call(this);
33711
+ shadowRoot.querySelector(`[data-bge-mode]`).dataset.bgeMode = "text-only";
33712
+ }
33713
+ /**
33714
+ * contenteditable属性を削除してクリーンなHTMLを返す
33715
+ */
33716
+ cleanHTML() {
33717
+ if (!__privateGet$2(this, _container)) {
33718
+ return "";
33719
+ }
33720
+ const clone = __privateGet$2(this, _container).cloneNode(true);
33721
+ const editableElements = clone.querySelectorAll('[contenteditable="plaintext-only"]');
33722
+ for (const el of editableElements) {
33723
+ el.removeAttribute("contenteditable");
33724
+ }
33725
+ const styleElements = clone.querySelectorAll("[data-text-only-style]");
33726
+ for (const el of styleElements) {
33727
+ el.remove();
33728
+ }
33729
+ return clone.innerHTML;
33730
+ }
33731
+ /**
33732
+ * text-onlyモードを無効化
33733
+ */
33734
+ deactivate() {
33735
+ if (!__privateGet$2(this, _container)) {
33736
+ return;
33737
+ }
33738
+ const editableElements = __privateGet$2(this, _container).querySelectorAll(
33739
+ '[contenteditable="plaintext-only"]'
33740
+ );
33741
+ for (const el of editableElements) {
33742
+ el.removeEventListener("keydown", __privateGet$2(this, _preventEnterKey));
33743
+ el.removeEventListener("input", __privateGet$2(this, _syncToTextarea));
33744
+ }
33745
+ __privateGet$2(this, _container).innerHTML = "";
33746
+ }
33747
+ /**
33748
+ * text-onlyモードの値を取得
33749
+ * @param textareaValue
33750
+ */
33751
+ getValue(textareaValue) {
33752
+ if (!__privateGet$2(this, _container)) {
33753
+ return textareaValue;
33754
+ }
33755
+ return this.cleanHTML();
33756
+ }
33757
+ /**
33758
+ * text-onlyモードに値を設定
33759
+ * @param value
33760
+ * @param shadowRoot
33761
+ * @param structureChangeMessage
33762
+ * @param previewStyleContent
33763
+ */
33764
+ setValue(value, shadowRoot, structureChangeMessage, previewStyleContent) {
33765
+ this.deactivate();
33766
+ __privateGet$2(this, _setToTextarea).call(this, value);
33767
+ this.activate(shadowRoot, value, structureChangeMessage, previewStyleContent);
33768
+ }
33769
+ }
33770
+ _container = new WeakMap();
33771
+ _setToTextarea = new WeakMap();
33772
+ _preventEnterKey = new WeakMap();
33773
+ _syncToTextarea = new WeakMap();
33774
+ _TextOnlyModeController_instances = new WeakSet();
33775
+ attachEventListeners_fn = function() {
33776
+ if (!__privateGet$2(this, _container)) {
33777
+ return;
33778
+ }
33779
+ const editableElements = __privateGet$2(this, _container).querySelectorAll(
33780
+ '[contenteditable="plaintext-only"]'
33781
+ );
33782
+ for (const el of editableElements) {
33783
+ el.addEventListener("keydown", __privateGet$2(this, _preventEnterKey));
33784
+ el.addEventListener("input", __privateGet$2(this, _syncToTextarea));
33785
+ }
33786
+ };
33787
+ identifyEditableElements_fn = function(container) {
33788
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
33789
+ acceptNode: (node) => {
33790
+ const element = node;
33791
+ const hasDirectTextChild = [...element.childNodes].some(
33792
+ (child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()
33793
+ );
33794
+ if (hasDirectTextChild) {
33795
+ return NodeFilter.FILTER_ACCEPT;
33796
+ }
33797
+ return NodeFilter.FILTER_SKIP;
33798
+ }
33799
+ });
33800
+ const editableElements = [];
33801
+ let currentNode;
33802
+ while (currentNode = walker.nextNode()) {
33803
+ editableElements.push(currentNode);
33804
+ }
33805
+ for (const el of editableElements) {
33806
+ el.setAttribute("contenteditable", "plaintext-only");
33807
+ }
33808
+ };
33809
+
33715
33810
  var __defProp$1 = Object.defineProperty;
33716
33811
  var __typeError$1 = (msg) => {
33717
33812
  throw TypeError(msg);
@@ -33723,7 +33818,7 @@ var __privateGet$1 = (obj, member, getter) => (__accessCheck$1(obj, member, "rea
33723
33818
  var __privateAdd$1 = (obj, member, value) => member.has(obj) ? __typeError$1("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
33724
33819
  var __privateSet$1 = (obj, member, value, setter) => (__accessCheck$1(obj, member, "write to private field"), member.set(obj, value), value);
33725
33820
  var __privateMethod = (obj, member, method) => (__accessCheck$1(obj, member, "access private method"), method);
33726
- var _editor, _editorRoot, _hasStructureChange, _isExpectingHTML, _isInitializing, _previewStyle, _structureChangeMessage, _textarea, _textareaDescriptor, _textOnlyContainer, _preventEnterKey, _syncTextOnlyToTextarea, _BgeWysiwygElement_instances, activateTextOnlyMode_fn, attachTextOnlyEventListeners_fn, checkStructureChange_fn, cleanTextOnlyHTML_fn, deactivateTextOnlyMode_fn, getTextOnlyValue_fn, identifyEditableElements_fn, setStructureChange_fn, setTextOnlyValue_fn, setToTextarea_fn, transaction_fn, updateStructureChangeMessage_fn;
33821
+ var _editor, _editorRoot, _hasStructureChange, _isExpectingHTML, _isInitializing, _previewStyle, _structureChangeMessage, _textarea, _textareaDescriptor, _textOnlyMode, _BgeWysiwygElement_instances, checkStructureChange_fn, setStructureChange_fn, setToTextarea_fn, transaction_fn, updateStructureChangeMessage_fn;
33727
33822
  function defineBgeWysiwygElement(options, global = window) {
33728
33823
  if (options?.extensions) {
33729
33824
  BgeWysiwygElement.extensions = options.extensions;
@@ -33752,26 +33847,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33752
33847
  __privateAdd$1(this, _structureChangeMessage, null);
33753
33848
  __privateAdd$1(this, _textarea, null);
33754
33849
  __privateAdd$1(this, _textareaDescriptor, null);
33755
- __privateAdd$1(this, _textOnlyContainer, null);
33756
- /**
33757
- * Enter キーを防止するハンドラー
33758
- * @param event
33759
- */
33760
- __privateAdd$1(this, _preventEnterKey, (event) => {
33761
- if (event.key === "Enter") {
33762
- event.preventDefault();
33763
- }
33764
- });
33765
- /**
33766
- * text-onlyコンテナからtextareaへ同期
33767
- */
33768
- __privateAdd$1(this, _syncTextOnlyToTextarea, () => {
33769
- if (this.mode !== "text-only" || !__privateGet$1(this, _textOnlyContainer)) {
33770
- return;
33771
- }
33772
- const cleanHTML = __privateMethod(this, _BgeWysiwygElement_instances, cleanTextOnlyHTML_fn).call(this, "");
33773
- __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, cleanHTML);
33774
- });
33850
+ __privateAdd$1(this, _textOnlyMode, null);
33775
33851
  this.attachShadow({ mode: "open" });
33776
33852
  if (!this.shadowRoot) {
33777
33853
  throw new Error("Not supported shadow DOM");
@@ -33782,7 +33858,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33782
33858
  throw new ReferenceError("<bge-wysiwyg> is not connected");
33783
33859
  }
33784
33860
  if (this.mode === "text-only") {
33785
- return __privateMethod(this, _BgeWysiwygElement_instances, getTextOnlyValue_fn).call(this);
33861
+ return __privateGet$1(this, _textOnlyMode)?.getValue(__privateGet$1(this, _textarea).value) ?? __privateGet$1(this, _textarea).value;
33786
33862
  }
33787
33863
  if (this.mode === "wysiwyg") {
33788
33864
  if (!__privateGet$1(this, _editor)) {
@@ -33799,7 +33875,12 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33799
33875
  throw new ReferenceError("<bge-wysiwyg> is not connected");
33800
33876
  }
33801
33877
  if (this.mode === "text-only") {
33802
- __privateMethod(this, _BgeWysiwygElement_instances, setTextOnlyValue_fn).call(this, value);
33878
+ __privateGet$1(this, _textOnlyMode)?.setValue(
33879
+ value,
33880
+ this.shadowRoot,
33881
+ __privateGet$1(this, _structureChangeMessage),
33882
+ __privateGet$1(this, _previewStyle)?.textContent ?? null
33883
+ );
33803
33884
  return;
33804
33885
  }
33805
33886
  if (this.mode === "html") {
@@ -33834,14 +33915,20 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33834
33915
  return;
33835
33916
  }
33836
33917
  if (this.mode === "text-only") {
33837
- __privateMethod(this, _BgeWysiwygElement_instances, deactivateTextOnlyMode_fn).call(this);
33918
+ __privateGet$1(this, _textOnlyMode)?.deactivate();
33838
33919
  }
33839
33920
  if (mode === "wysiwyg" && this.mode === "html" && __privateMethod(this, _BgeWysiwygElement_instances, checkStructureChange_fn).call(this)) {
33840
33921
  __privateMethod(this, _BgeWysiwygElement_instances, setStructureChange_fn).call(this, true);
33841
33922
  return;
33842
33923
  }
33843
33924
  if (mode === "text-only") {
33844
- __privateMethod(this, _BgeWysiwygElement_instances, activateTextOnlyMode_fn).call(this);
33925
+ const currentHTML = this.mode === "wysiwyg" ? __privateGet$1(this, _editor).getHTML().replaceAll("<p></p>", "") : __privateGet$1(this, _textarea).value;
33926
+ __privateGet$1(this, _textOnlyMode)?.activate(
33927
+ this.shadowRoot,
33928
+ currentHTML,
33929
+ __privateGet$1(this, _structureChangeMessage),
33930
+ __privateGet$1(this, _previewStyle)?.textContent ?? null
33931
+ );
33845
33932
  return;
33846
33933
  }
33847
33934
  this.shadowRoot.querySelector(`[data-bge-mode]`)?.setAttribute(
@@ -33886,87 +33973,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33886
33973
  const textarea = this.shadowRoot.querySelector("textarea");
33887
33974
  const controlUIStyle = document.createElement("style");
33888
33975
  this.shadowRoot.append(controlUIStyle);
33889
- controlUIStyle.textContent = `
33890
- :host {
33891
- display: block;
33892
- }
33893
-
33894
- textarea,
33895
- iframe {
33896
- block-size: 50svh;
33897
- inline-size: 100%;
33898
- resize: vertical;
33899
- overflow-y: auto;
33900
- background: var(--bge-lightest-color);
33901
- border: 1px solid var(--bge-border-color);
33902
- border-radius: var(--border-radius);
33903
- }
33904
-
33905
- iframe[data-focus-within="true"],
33906
- textarea:focus-visible {
33907
- --bge-border-color: var(--bge-ui-primary-color);
33908
- --bge-outline-color: var(--bge-ui-primary-color);
33909
- outline: var(--bge-focus-outline-width) solid var(--bge-outline-color);
33910
- }
33911
-
33912
- textarea {
33913
- font-family: var(--bge-font-family-monospace);
33914
- }
33915
-
33916
- [data-bge-mode="wysiwyg"] textarea {
33917
- display: none;
33918
- }
33919
-
33920
- [data-bge-mode="html"] iframe {
33921
- display: none;
33922
- }
33923
-
33924
- [data-text-only-editor] {
33925
- block-size: 50svh;
33926
- inline-size: 100%;
33927
- resize: vertical;
33928
- overflow-y: auto;
33929
- background: var(--bge-lightest-color);
33930
- border: 1px solid var(--bge-border-color);
33931
- border-radius: var(--border-radius);
33932
- padding: 1rem;
33933
- box-sizing: border-box;
33934
- }
33935
-
33936
- [data-bge-mode="wysiwyg"] [data-text-only-editor],
33937
- [data-bge-mode="html"] [data-text-only-editor] {
33938
- display: none;
33939
- }
33940
-
33941
- [data-bge-mode="text-only"] iframe,
33942
- [data-bge-mode="text-only"] textarea {
33943
- display: none;
33944
- }
33945
-
33946
- [contenteditable="plaintext-only"] {
33947
- outline: 1px dashed var(--bge-border-color);
33948
- cursor: text;
33949
- }
33950
-
33951
- [contenteditable="plaintext-only"]:hover {
33952
- outline-color: var(--bge-ui-primary-color);
33953
- }
33954
-
33955
- [contenteditable="plaintext-only"]:focus {
33956
- outline: 2px solid var(--bge-ui-primary-color);
33957
- outline-offset: 2px;
33958
- }
33959
-
33960
- [role="alert"] {
33961
- margin-block-start: 0.5rem;
33962
- padding: 0.5rem;
33963
- background-color: var(--bge-error-color, #fee);
33964
- border: 1px solid var(--bge-error-border-color, #fcc);
33965
- border-radius: var(--border-radius);
33966
- color: var(--bge-error-text-color, #c00);
33967
- font-size: 0.875rem;
33968
- }
33969
- `;
33976
+ controlUIStyle.textContent = controlUIStyles;
33970
33977
  preview.contentDocument.body.addEventListener("focusin", () => {
33971
33978
  preview.dataset.focusWithin = "true";
33972
33979
  });
@@ -34027,6 +34034,9 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
34027
34034
  HTMLTextAreaElement.prototype,
34028
34035
  "value"
34029
34036
  ));
34037
+ __privateSet$1(this, _textOnlyMode, new TextOnlyModeController((html) => {
34038
+ __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, html);
34039
+ }));
34030
34040
  Object.defineProperty(__privateGet$1(this, _textarea), "value", {
34031
34041
  get: () => {
34032
34042
  return __privateGet$1(this, _textareaDescriptor)?.get?.call(__privateGet$1(this, _textarea)) ?? "";
@@ -34087,33 +34097,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
34087
34097
  if (!__privateGet$1(this, _previewStyle)) {
34088
34098
  throw new ReferenceError("<bge-wysiwyg> is not connected");
34089
34099
  }
34090
- __privateGet$1(this, _previewStyle).textContent = `
34091
- :where(html, body) {
34092
- margin: 0;
34093
- padding: 0;
34094
-
34095
- :where(&, *) {
34096
- box-sizing: border-box;
34097
- }
34098
- }
34099
-
34100
- iframe,
34101
- [contenteditable="true"] {
34102
- padding: 1rem;
34103
- block-size: 100%;
34104
- box-sizing: border-box;
34105
- }
34106
-
34107
- [contenteditable="true"]:focus-visible {
34108
- outline: none;
34109
- }
34110
-
34111
- ${css}
34112
-
34113
- a:any-link {
34114
- pointer-events: none !important;
34115
- }
34116
- `;
34100
+ __privateGet$1(this, _previewStyle).textContent = editorContentStyles(css);
34117
34101
  }
34118
34102
  syncWysiwygToTextarea() {
34119
34103
  if (__privateGet$1(this, _isExpectingHTML)) {
@@ -34184,47 +34168,8 @@ _previewStyle = new WeakMap();
34184
34168
  _structureChangeMessage = new WeakMap();
34185
34169
  _textarea = new WeakMap();
34186
34170
  _textareaDescriptor = new WeakMap();
34187
- _textOnlyContainer = new WeakMap();
34188
- _preventEnterKey = new WeakMap();
34189
- _syncTextOnlyToTextarea = new WeakMap();
34171
+ _textOnlyMode = new WeakMap();
34190
34172
  _BgeWysiwygElement_instances = new WeakSet();
34191
- /**
34192
- * text-onlyモードを有効化
34193
- */
34194
- activateTextOnlyMode_fn = function() {
34195
- const currentHTML = this.mode === "wysiwyg" ? __privateGet$1(this, _editor).getHTML().replaceAll("<p></p>", "") : __privateGet$1(this, _textarea).value;
34196
- if (!__privateGet$1(this, _textOnlyContainer)) {
34197
- __privateSet$1(this, _textOnlyContainer, document.createElement("div"));
34198
- __privateGet$1(this, _textOnlyContainer).dataset.textOnlyEditor = "";
34199
- const modeContainer = this.shadowRoot.querySelector("[data-bge-mode]");
34200
- modeContainer.insertBefore(__privateGet$1(this, _textOnlyContainer), __privateGet$1(this, _structureChangeMessage));
34201
- }
34202
- const textOnlyStyle = document.createElement("style");
34203
- textOnlyStyle.dataset.textOnlyStyle = "";
34204
- if (__privateGet$1(this, _previewStyle)) {
34205
- textOnlyStyle.textContent = __privateGet$1(this, _previewStyle).textContent;
34206
- }
34207
- __privateGet$1(this, _textOnlyContainer).innerHTML = currentHTML;
34208
- __privateGet$1(this, _textOnlyContainer).prepend(textOnlyStyle);
34209
- __privateMethod(this, _BgeWysiwygElement_instances, identifyEditableElements_fn).call(this, __privateGet$1(this, _textOnlyContainer));
34210
- __privateMethod(this, _BgeWysiwygElement_instances, attachTextOnlyEventListeners_fn).call(this);
34211
- this.shadowRoot.querySelector(`[data-bge-mode]`).dataset.bgeMode = "text-only";
34212
- };
34213
- /**
34214
- * text-onlyモード用のイベントリスナーを設定
34215
- */
34216
- attachTextOnlyEventListeners_fn = function() {
34217
- if (!__privateGet$1(this, _textOnlyContainer)) {
34218
- return;
34219
- }
34220
- const editableElements = __privateGet$1(this, _textOnlyContainer).querySelectorAll(
34221
- '[contenteditable="plaintext-only"]'
34222
- );
34223
- for (const el of editableElements) {
34224
- el.addEventListener("keydown", __privateGet$1(this, _preventEnterKey));
34225
- el.addEventListener("input", __privateGet$1(this, _syncTextOnlyToTextarea));
34226
- }
34227
- };
34228
34173
  checkStructureChange_fn = function() {
34229
34174
  if (!__privateGet$1(this, _editor) || !__privateGet$1(this, _textarea) || this.mode !== "html") {
34230
34175
  return false;
@@ -34233,76 +34178,6 @@ checkStructureChange_fn = function() {
34233
34178
  const isStructureSame = normalizeHtmlStructure(__privateGet$1(this, _textarea).value, expectedHTML);
34234
34179
  return !isStructureSame;
34235
34180
  };
34236
- /**
34237
- * contenteditable属性を削除してクリーンなHTMLを返す
34238
- * @param html
34239
- */
34240
- cleanTextOnlyHTML_fn = function(html) {
34241
- if (!__privateGet$1(this, _textOnlyContainer)) {
34242
- return html;
34243
- }
34244
- const clone = __privateGet$1(this, _textOnlyContainer).cloneNode(true);
34245
- const editableElements = clone.querySelectorAll('[contenteditable="plaintext-only"]');
34246
- for (const el of editableElements) {
34247
- el.removeAttribute("contenteditable");
34248
- }
34249
- const styleElements = clone.querySelectorAll("[data-text-only-style]");
34250
- for (const el of styleElements) {
34251
- el.remove();
34252
- }
34253
- return clone.innerHTML;
34254
- };
34255
- /**
34256
- * text-onlyモードを無効化
34257
- */
34258
- deactivateTextOnlyMode_fn = function() {
34259
- if (!__privateGet$1(this, _textOnlyContainer)) {
34260
- return;
34261
- }
34262
- const editableElements = __privateGet$1(this, _textOnlyContainer).querySelectorAll(
34263
- '[contenteditable="plaintext-only"]'
34264
- );
34265
- for (const el of editableElements) {
34266
- el.removeEventListener("keydown", __privateGet$1(this, _preventEnterKey));
34267
- el.removeEventListener("input", __privateGet$1(this, _syncTextOnlyToTextarea));
34268
- }
34269
- __privateGet$1(this, _textOnlyContainer).innerHTML = "";
34270
- };
34271
- /**
34272
- * text-onlyモードの値を取得
34273
- */
34274
- getTextOnlyValue_fn = function() {
34275
- if (!__privateGet$1(this, _textOnlyContainer)) {
34276
- return __privateGet$1(this, _textarea)?.value ?? "";
34277
- }
34278
- return __privateMethod(this, _BgeWysiwygElement_instances, cleanTextOnlyHTML_fn).call(this, "");
34279
- };
34280
- /**
34281
- * 編集可能要素を特定してcontenteditable="plaintext-only"を付与
34282
- * @param container
34283
- */
34284
- identifyEditableElements_fn = function(container) {
34285
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
34286
- acceptNode: (node) => {
34287
- const element = node;
34288
- const hasDirectTextChild = [...element.childNodes].some(
34289
- (child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()
34290
- );
34291
- if (hasDirectTextChild) {
34292
- return NodeFilter.FILTER_ACCEPT;
34293
- }
34294
- return NodeFilter.FILTER_SKIP;
34295
- }
34296
- });
34297
- const editableElements = [];
34298
- let currentNode;
34299
- while (currentNode = walker.nextNode()) {
34300
- editableElements.push(currentNode);
34301
- }
34302
- for (const el of editableElements) {
34303
- el.setAttribute("contenteditable", "plaintext-only");
34304
- }
34305
- };
34306
34181
  setStructureChange_fn = function(hasChange) {
34307
34182
  if (__privateGet$1(this, _hasStructureChange) === hasChange) {
34308
34183
  return;
@@ -34317,22 +34192,12 @@ setStructureChange_fn = function(hasChange) {
34317
34192
  })
34318
34193
  );
34319
34194
  };
34320
- /**
34321
- * text-onlyモードに値を設定
34322
- * @param value
34323
- */
34324
- setTextOnlyValue_fn = function(value) {
34325
- __privateMethod(this, _BgeWysiwygElement_instances, deactivateTextOnlyMode_fn).call(this);
34326
- __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, value);
34327
- __privateMethod(this, _BgeWysiwygElement_instances, activateTextOnlyMode_fn).call(this);
34328
- };
34329
34195
  setToTextarea_fn = function(html) {
34330
34196
  if (!__privateGet$1(this, _textarea) || !__privateGet$1(this, _textareaDescriptor)) {
34331
34197
  throw new ReferenceError("<bge-wysiwyg> is not connected");
34332
34198
  }
34333
34199
  __privateGet$1(this, _textareaDescriptor).set?.call(__privateGet$1(this, _textarea), html);
34334
34200
  };
34335
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
34336
34201
  transaction_fn = function(_editor2) {
34337
34202
  const data = {
34338
34203
  state: getCurrentEditorState(this)
@@ -45105,7 +44970,7 @@ function parseConfig(config) {
45105
44970
  }
45106
44971
  }
45107
44972
 
45108
- const version = "4.0.0-alpha.62";
44973
+ const version = "4.0.0-alpha.63";
45109
44974
  function attachDraftSwitcher(engine) {
45110
44975
  if (engine.hasDraft()) {
45111
44976
  const container = document.createElement("div");