@burger-editor/client 4.0.0-alpha.62 → 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
 
@@ -3387,11 +3197,17 @@ function getCustomProperties(scope, containerType) {
3387
3197
  }
3388
3198
  }
3389
3199
  }
3390
- // Merge nested defaults as fallback: use nested default only when
3391
- // no direct (non-nested) default exists for a category.
3392
- for (const [category, property] of nestedDefaultValues.entries()) {
3393
- if (!defaultValues.has(category)) {
3394
- defaultValues.set(category, property);
3200
+ // Merge nested defaults: use nested default when no direct default exists,
3201
+ // or when the nested default has higher cascade layer priority.
3202
+ for (const [category, nestedProperty] of nestedDefaultValues.entries()) {
3203
+ const directProperty = defaultValues.get(category);
3204
+ if (directProperty) {
3205
+ // Compare by layer priority; on equal priority, direct selector wins
3206
+ const winner = compareCustomPropertyByLayerPriority(nestedProperty, directProperty);
3207
+ defaultValues.set(category, winner);
3208
+ }
3209
+ else {
3210
+ defaultValues.set(category, nestedProperty);
3395
3211
  }
3396
3212
  }
3397
3213
  for (const [category, property] of defaultValues.entries()) {
@@ -4250,9 +4066,21 @@ class ItemEditorDialog extends EditorDialog {
4250
4066
  $ctrl.max = value.toString();
4251
4067
  }
4252
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
+ */
4253
4079
  onChange(name, handler, runOnInit = true) {
4254
4080
  if (runOnInit) {
4255
- handler(this.get(name), null);
4081
+ const initialValue = this.get(name);
4082
+ this.#values.set(name, initialValue);
4083
+ handler(initialValue, null);
4256
4084
  }
4257
4085
  for (const $ctrl of this.#findAll(name)) {
4258
4086
  $ctrl.addEventListener('change', () => {
@@ -4757,12 +4585,10 @@ class BurgerEditorEngine {
4757
4585
  const mainInitialContent = typeof options.initialContents === 'string'
4758
4586
  ? options.initialContents
4759
4587
  : options.initialContents.main;
4760
- // @ts-ignore force assign to readonly property
4761
4588
  engine.#main =
4762
4589
  //
4763
4590
  await EditableArea.new('main', mainInitialContent, engine, options.blockMenu, options.initialInsertionButton, stylesheets, options.config.classList, options.editableAreaShell);
4764
4591
  const draftInitialContent = typeof options.initialContents === 'string' ? null : options.initialContents.draft;
4765
- // @ts-ignore force assign to readonly property
4766
4592
  engine.#draft =
4767
4593
  draftInitialContent == null
4768
4594
  ? null
@@ -33706,6 +33532,281 @@ function getCurrentEditorState(wysiwygElement) {
33706
33532
  };
33707
33533
  }
33708
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
+
33709
33810
  var __defProp$1 = Object.defineProperty;
33710
33811
  var __typeError$1 = (msg) => {
33711
33812
  throw TypeError(msg);
@@ -33717,7 +33818,7 @@ var __privateGet$1 = (obj, member, getter) => (__accessCheck$1(obj, member, "rea
33717
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);
33718
33819
  var __privateSet$1 = (obj, member, value, setter) => (__accessCheck$1(obj, member, "write to private field"), member.set(obj, value), value);
33719
33820
  var __privateMethod = (obj, member, method) => (__accessCheck$1(obj, member, "access private method"), method);
33720
- 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;
33721
33822
  function defineBgeWysiwygElement(options, global = window) {
33722
33823
  if (options?.extensions) {
33723
33824
  BgeWysiwygElement.extensions = options.extensions;
@@ -33746,26 +33847,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33746
33847
  __privateAdd$1(this, _structureChangeMessage, null);
33747
33848
  __privateAdd$1(this, _textarea, null);
33748
33849
  __privateAdd$1(this, _textareaDescriptor, null);
33749
- __privateAdd$1(this, _textOnlyContainer, null);
33750
- /**
33751
- * Enter キーを防止するハンドラー
33752
- * @param event
33753
- */
33754
- __privateAdd$1(this, _preventEnterKey, (event) => {
33755
- if (event.key === "Enter") {
33756
- event.preventDefault();
33757
- }
33758
- });
33759
- /**
33760
- * text-onlyコンテナからtextareaへ同期
33761
- */
33762
- __privateAdd$1(this, _syncTextOnlyToTextarea, () => {
33763
- if (this.mode !== "text-only" || !__privateGet$1(this, _textOnlyContainer)) {
33764
- return;
33765
- }
33766
- const cleanHTML = __privateMethod(this, _BgeWysiwygElement_instances, cleanTextOnlyHTML_fn).call(this, "");
33767
- __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, cleanHTML);
33768
- });
33850
+ __privateAdd$1(this, _textOnlyMode, null);
33769
33851
  this.attachShadow({ mode: "open" });
33770
33852
  if (!this.shadowRoot) {
33771
33853
  throw new Error("Not supported shadow DOM");
@@ -33776,7 +33858,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33776
33858
  throw new ReferenceError("<bge-wysiwyg> is not connected");
33777
33859
  }
33778
33860
  if (this.mode === "text-only") {
33779
- 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;
33780
33862
  }
33781
33863
  if (this.mode === "wysiwyg") {
33782
33864
  if (!__privateGet$1(this, _editor)) {
@@ -33793,7 +33875,12 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33793
33875
  throw new ReferenceError("<bge-wysiwyg> is not connected");
33794
33876
  }
33795
33877
  if (this.mode === "text-only") {
33796
- __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
+ );
33797
33884
  return;
33798
33885
  }
33799
33886
  if (this.mode === "html") {
@@ -33828,14 +33915,20 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33828
33915
  return;
33829
33916
  }
33830
33917
  if (this.mode === "text-only") {
33831
- __privateMethod(this, _BgeWysiwygElement_instances, deactivateTextOnlyMode_fn).call(this);
33918
+ __privateGet$1(this, _textOnlyMode)?.deactivate();
33832
33919
  }
33833
33920
  if (mode === "wysiwyg" && this.mode === "html" && __privateMethod(this, _BgeWysiwygElement_instances, checkStructureChange_fn).call(this)) {
33834
33921
  __privateMethod(this, _BgeWysiwygElement_instances, setStructureChange_fn).call(this, true);
33835
33922
  return;
33836
33923
  }
33837
33924
  if (mode === "text-only") {
33838
- __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
+ );
33839
33932
  return;
33840
33933
  }
33841
33934
  this.shadowRoot.querySelector(`[data-bge-mode]`)?.setAttribute(
@@ -33880,87 +33973,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
33880
33973
  const textarea = this.shadowRoot.querySelector("textarea");
33881
33974
  const controlUIStyle = document.createElement("style");
33882
33975
  this.shadowRoot.append(controlUIStyle);
33883
- controlUIStyle.textContent = `
33884
- :host {
33885
- display: block;
33886
- }
33887
-
33888
- textarea,
33889
- iframe {
33890
- block-size: 50svh;
33891
- inline-size: 100%;
33892
- resize: vertical;
33893
- overflow-y: auto;
33894
- background: var(--bge-lightest-color);
33895
- border: 1px solid var(--bge-border-color);
33896
- border-radius: var(--border-radius);
33897
- }
33898
-
33899
- iframe[data-focus-within="true"],
33900
- textarea:focus-visible {
33901
- --bge-border-color: var(--bge-ui-primary-color);
33902
- --bge-outline-color: var(--bge-ui-primary-color);
33903
- outline: var(--bge-focus-outline-width) solid var(--bge-outline-color);
33904
- }
33905
-
33906
- textarea {
33907
- font-family: var(--bge-font-family-monospace);
33908
- }
33909
-
33910
- [data-bge-mode="wysiwyg"] textarea {
33911
- display: none;
33912
- }
33913
-
33914
- [data-bge-mode="html"] iframe {
33915
- display: none;
33916
- }
33917
-
33918
- [data-text-only-editor] {
33919
- block-size: 50svh;
33920
- inline-size: 100%;
33921
- resize: vertical;
33922
- overflow-y: auto;
33923
- background: var(--bge-lightest-color);
33924
- border: 1px solid var(--bge-border-color);
33925
- border-radius: var(--border-radius);
33926
- padding: 1rem;
33927
- box-sizing: border-box;
33928
- }
33929
-
33930
- [data-bge-mode="wysiwyg"] [data-text-only-editor],
33931
- [data-bge-mode="html"] [data-text-only-editor] {
33932
- display: none;
33933
- }
33934
-
33935
- [data-bge-mode="text-only"] iframe,
33936
- [data-bge-mode="text-only"] textarea {
33937
- display: none;
33938
- }
33939
-
33940
- [contenteditable="plaintext-only"] {
33941
- outline: 1px dashed var(--bge-border-color);
33942
- cursor: text;
33943
- }
33944
-
33945
- [contenteditable="plaintext-only"]:hover {
33946
- outline-color: var(--bge-ui-primary-color);
33947
- }
33948
-
33949
- [contenteditable="plaintext-only"]:focus {
33950
- outline: 2px solid var(--bge-ui-primary-color);
33951
- outline-offset: 2px;
33952
- }
33953
-
33954
- [role="alert"] {
33955
- margin-block-start: 0.5rem;
33956
- padding: 0.5rem;
33957
- background-color: var(--bge-error-color, #fee);
33958
- border: 1px solid var(--bge-error-border-color, #fcc);
33959
- border-radius: var(--border-radius);
33960
- color: var(--bge-error-text-color, #c00);
33961
- font-size: 0.875rem;
33962
- }
33963
- `;
33976
+ controlUIStyle.textContent = controlUIStyles;
33964
33977
  preview.contentDocument.body.addEventListener("focusin", () => {
33965
33978
  preview.dataset.focusWithin = "true";
33966
33979
  });
@@ -34021,6 +34034,9 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
34021
34034
  HTMLTextAreaElement.prototype,
34022
34035
  "value"
34023
34036
  ));
34037
+ __privateSet$1(this, _textOnlyMode, new TextOnlyModeController((html) => {
34038
+ __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, html);
34039
+ }));
34024
34040
  Object.defineProperty(__privateGet$1(this, _textarea), "value", {
34025
34041
  get: () => {
34026
34042
  return __privateGet$1(this, _textareaDescriptor)?.get?.call(__privateGet$1(this, _textarea)) ?? "";
@@ -34081,33 +34097,7 @@ const _BgeWysiwygElement = class _BgeWysiwygElement extends HTMLElement {
34081
34097
  if (!__privateGet$1(this, _previewStyle)) {
34082
34098
  throw new ReferenceError("<bge-wysiwyg> is not connected");
34083
34099
  }
34084
- __privateGet$1(this, _previewStyle).textContent = `
34085
- :where(html, body) {
34086
- margin: 0;
34087
- padding: 0;
34088
-
34089
- :where(&, *) {
34090
- box-sizing: border-box;
34091
- }
34092
- }
34093
-
34094
- iframe,
34095
- [contenteditable="true"] {
34096
- padding: 1rem;
34097
- block-size: 100%;
34098
- box-sizing: border-box;
34099
- }
34100
-
34101
- [contenteditable="true"]:focus-visible {
34102
- outline: none;
34103
- }
34104
-
34105
- ${css}
34106
-
34107
- a:any-link {
34108
- pointer-events: none !important;
34109
- }
34110
- `;
34100
+ __privateGet$1(this, _previewStyle).textContent = editorContentStyles(css);
34111
34101
  }
34112
34102
  syncWysiwygToTextarea() {
34113
34103
  if (__privateGet$1(this, _isExpectingHTML)) {
@@ -34178,47 +34168,8 @@ _previewStyle = new WeakMap();
34178
34168
  _structureChangeMessage = new WeakMap();
34179
34169
  _textarea = new WeakMap();
34180
34170
  _textareaDescriptor = new WeakMap();
34181
- _textOnlyContainer = new WeakMap();
34182
- _preventEnterKey = new WeakMap();
34183
- _syncTextOnlyToTextarea = new WeakMap();
34171
+ _textOnlyMode = new WeakMap();
34184
34172
  _BgeWysiwygElement_instances = new WeakSet();
34185
- /**
34186
- * text-onlyモードを有効化
34187
- */
34188
- activateTextOnlyMode_fn = function() {
34189
- const currentHTML = this.mode === "wysiwyg" ? __privateGet$1(this, _editor).getHTML().replaceAll("<p></p>", "") : __privateGet$1(this, _textarea).value;
34190
- if (!__privateGet$1(this, _textOnlyContainer)) {
34191
- __privateSet$1(this, _textOnlyContainer, document.createElement("div"));
34192
- __privateGet$1(this, _textOnlyContainer).dataset.textOnlyEditor = "";
34193
- const modeContainer = this.shadowRoot.querySelector("[data-bge-mode]");
34194
- modeContainer.insertBefore(__privateGet$1(this, _textOnlyContainer), __privateGet$1(this, _structureChangeMessage));
34195
- }
34196
- const textOnlyStyle = document.createElement("style");
34197
- textOnlyStyle.dataset.textOnlyStyle = "";
34198
- if (__privateGet$1(this, _previewStyle)) {
34199
- textOnlyStyle.textContent = __privateGet$1(this, _previewStyle).textContent;
34200
- }
34201
- __privateGet$1(this, _textOnlyContainer).innerHTML = currentHTML;
34202
- __privateGet$1(this, _textOnlyContainer).prepend(textOnlyStyle);
34203
- __privateMethod(this, _BgeWysiwygElement_instances, identifyEditableElements_fn).call(this, __privateGet$1(this, _textOnlyContainer));
34204
- __privateMethod(this, _BgeWysiwygElement_instances, attachTextOnlyEventListeners_fn).call(this);
34205
- this.shadowRoot.querySelector(`[data-bge-mode]`).dataset.bgeMode = "text-only";
34206
- };
34207
- /**
34208
- * text-onlyモード用のイベントリスナーを設定
34209
- */
34210
- attachTextOnlyEventListeners_fn = function() {
34211
- if (!__privateGet$1(this, _textOnlyContainer)) {
34212
- return;
34213
- }
34214
- const editableElements = __privateGet$1(this, _textOnlyContainer).querySelectorAll(
34215
- '[contenteditable="plaintext-only"]'
34216
- );
34217
- for (const el of editableElements) {
34218
- el.addEventListener("keydown", __privateGet$1(this, _preventEnterKey));
34219
- el.addEventListener("input", __privateGet$1(this, _syncTextOnlyToTextarea));
34220
- }
34221
- };
34222
34173
  checkStructureChange_fn = function() {
34223
34174
  if (!__privateGet$1(this, _editor) || !__privateGet$1(this, _textarea) || this.mode !== "html") {
34224
34175
  return false;
@@ -34227,76 +34178,6 @@ checkStructureChange_fn = function() {
34227
34178
  const isStructureSame = normalizeHtmlStructure(__privateGet$1(this, _textarea).value, expectedHTML);
34228
34179
  return !isStructureSame;
34229
34180
  };
34230
- /**
34231
- * contenteditable属性を削除してクリーンなHTMLを返す
34232
- * @param html
34233
- */
34234
- cleanTextOnlyHTML_fn = function(html) {
34235
- if (!__privateGet$1(this, _textOnlyContainer)) {
34236
- return html;
34237
- }
34238
- const clone = __privateGet$1(this, _textOnlyContainer).cloneNode(true);
34239
- const editableElements = clone.querySelectorAll('[contenteditable="plaintext-only"]');
34240
- for (const el of editableElements) {
34241
- el.removeAttribute("contenteditable");
34242
- }
34243
- const styleElements = clone.querySelectorAll("[data-text-only-style]");
34244
- for (const el of styleElements) {
34245
- el.remove();
34246
- }
34247
- return clone.innerHTML;
34248
- };
34249
- /**
34250
- * text-onlyモードを無効化
34251
- */
34252
- deactivateTextOnlyMode_fn = function() {
34253
- if (!__privateGet$1(this, _textOnlyContainer)) {
34254
- return;
34255
- }
34256
- const editableElements = __privateGet$1(this, _textOnlyContainer).querySelectorAll(
34257
- '[contenteditable="plaintext-only"]'
34258
- );
34259
- for (const el of editableElements) {
34260
- el.removeEventListener("keydown", __privateGet$1(this, _preventEnterKey));
34261
- el.removeEventListener("input", __privateGet$1(this, _syncTextOnlyToTextarea));
34262
- }
34263
- __privateGet$1(this, _textOnlyContainer).innerHTML = "";
34264
- };
34265
- /**
34266
- * text-onlyモードの値を取得
34267
- */
34268
- getTextOnlyValue_fn = function() {
34269
- if (!__privateGet$1(this, _textOnlyContainer)) {
34270
- return __privateGet$1(this, _textarea)?.value ?? "";
34271
- }
34272
- return __privateMethod(this, _BgeWysiwygElement_instances, cleanTextOnlyHTML_fn).call(this, "");
34273
- };
34274
- /**
34275
- * 編集可能要素を特定してcontenteditable="plaintext-only"を付与
34276
- * @param container
34277
- */
34278
- identifyEditableElements_fn = function(container) {
34279
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
34280
- acceptNode: (node) => {
34281
- const element = node;
34282
- const hasDirectTextChild = [...element.childNodes].some(
34283
- (child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()
34284
- );
34285
- if (hasDirectTextChild) {
34286
- return NodeFilter.FILTER_ACCEPT;
34287
- }
34288
- return NodeFilter.FILTER_SKIP;
34289
- }
34290
- });
34291
- const editableElements = [];
34292
- let currentNode;
34293
- while (currentNode = walker.nextNode()) {
34294
- editableElements.push(currentNode);
34295
- }
34296
- for (const el of editableElements) {
34297
- el.setAttribute("contenteditable", "plaintext-only");
34298
- }
34299
- };
34300
34181
  setStructureChange_fn = function(hasChange) {
34301
34182
  if (__privateGet$1(this, _hasStructureChange) === hasChange) {
34302
34183
  return;
@@ -34311,22 +34192,12 @@ setStructureChange_fn = function(hasChange) {
34311
34192
  })
34312
34193
  );
34313
34194
  };
34314
- /**
34315
- * text-onlyモードに値を設定
34316
- * @param value
34317
- */
34318
- setTextOnlyValue_fn = function(value) {
34319
- __privateMethod(this, _BgeWysiwygElement_instances, deactivateTextOnlyMode_fn).call(this);
34320
- __privateMethod(this, _BgeWysiwygElement_instances, setToTextarea_fn).call(this, value);
34321
- __privateMethod(this, _BgeWysiwygElement_instances, activateTextOnlyMode_fn).call(this);
34322
- };
34323
34195
  setToTextarea_fn = function(html) {
34324
34196
  if (!__privateGet$1(this, _textarea) || !__privateGet$1(this, _textareaDescriptor)) {
34325
34197
  throw new ReferenceError("<bge-wysiwyg> is not connected");
34326
34198
  }
34327
34199
  __privateGet$1(this, _textareaDescriptor).set?.call(__privateGet$1(this, _textarea), html);
34328
34200
  };
34329
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
34330
34201
  transaction_fn = function(_editor2) {
34331
34202
  const data = {
34332
34203
  state: getCurrentEditorState(this)
@@ -45099,7 +44970,7 @@ function parseConfig(config) {
45099
44970
  }
45100
44971
  }
45101
44972
 
45102
- const version = "4.0.0-alpha.61";
44973
+ const version = "4.0.0-alpha.63";
45103
44974
  function attachDraftSwitcher(engine) {
45104
44975
  if (engine.hasDraft()) {
45105
44976
  const container = document.createElement("div");