suneditor 3.2.5 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/suneditor-contents.min.css +1 -1
  2. package/dist/suneditor.min.css +3 -3
  3. package/dist/suneditor.min.js +1 -1
  4. package/package.json +4 -4
  5. package/src/assets/design/color.css +5 -2
  6. package/src/assets/design/size.css +6 -2
  7. package/src/assets/icons/defaultIcons.js +195 -162
  8. package/src/assets/suneditor.css +99 -99
  9. package/src/core/config/optionProvider.js +5 -4
  10. package/src/core/event/actions/index.js +8 -4
  11. package/src/core/event/effects/keydown.registry.js +59 -10
  12. package/src/core/event/ports.js +2 -0
  13. package/src/core/event/rules/keydown.rule.backspace.js +52 -1
  14. package/src/core/event/rules/keydown.rule.delete.js +51 -0
  15. package/src/core/logic/dom/selection.js +4 -2
  16. package/src/core/logic/panel/finder.js +3 -0
  17. package/src/core/logic/panel/menu.js +38 -1
  18. package/src/core/logic/panel/toolbar.js +1 -0
  19. package/src/core/logic/shell/_commandExecutor.js +2 -1
  20. package/src/core/logic/shell/ui.js +41 -5
  21. package/src/core/schema/frameContext.js +1 -1
  22. package/src/core/schema/options.js +33 -3
  23. package/src/core/section/constructor.js +5 -1
  24. package/src/modules/contract/Controller.js +14 -0
  25. package/src/modules/ui/CommandMenu.js +8 -11
  26. package/src/modules/ui/SelectMenu.js +6 -0
  27. package/src/plugins/dropdown/table/services/table.cell.js +4 -7
  28. package/types/assets/icons/defaultIcons.d.ts +1 -0
  29. package/types/core/event/actions/index.d.ts +1 -0
  30. package/types/core/event/effects/keydown.registry.d.ts +4 -0
  31. package/types/core/event/ports.d.ts +3 -0
  32. package/types/core/logic/panel/menu.d.ts +8 -0
  33. package/types/core/logic/shell/ui.d.ts +17 -0
  34. package/types/core/schema/frameContext.d.ts +6 -2
  35. package/types/core/schema/options.d.ts +70 -4
@@ -263,9 +263,9 @@
263
263
  border-color: var(--se-input-btn-border-color);
264
264
  border-radius: var(--se-border-radius);
265
265
  transition:
266
- border-color 0.3s ease,
267
- background-color 0.3s ease,
268
- color 0.3s ease;
266
+ border-color 0.13s ease,
267
+ background-color 0.13s ease,
268
+ color 0.13s ease;
269
269
  }
270
270
 
271
271
  .sun-editor .se-form-group button.se-btn {
@@ -281,11 +281,8 @@
281
281
 
282
282
  .sun-editor .se-btn:not(.on):not(.active):enabled:active,
283
283
  .sun-editor .se-btn:not(.on):not(.active):enabled.__se__active {
284
- background-color: var(--se-hover-light2-color);
285
- border-color: var(--se-hover-dark2-color) !important;
286
- outline: 1px solid var(--se-hover-dark3-color) !important;
287
- box-shadow: 0 0 0 0.3rem var(--se-hover-light2-color);
288
- transition: box-shadow 0.1s ease-in-out;
284
+ background-color: var(--se-hover-light3-color);
285
+ transition: background-color 0.08s ease;
289
286
  }
290
287
 
291
288
  /* after, before button : hover */
@@ -338,8 +335,8 @@
338
335
 
339
336
  /* se-btn.on */
340
337
  .sun-editor .se-btn:enabled.on {
341
- background-color: var(--se-active-light2-color);
342
- color: var(--se-active-dark3-color);
338
+ background-color: var(--se-active-light4-color);
339
+ color: var(--se-active-dark5-color);
343
340
  border-color: var(--se-active-light5-color);
344
341
  }
345
342
 
@@ -352,13 +349,9 @@
352
349
 
353
350
  .sun-editor .se-btn:enabled.on:active,
354
351
  .sun-editor .se-btn:enabled.on.__se__active {
355
- background-color: var(--se-active-light3-color);
356
- color: var(--se-active-color);
357
- border-color: var(--se-active-light3-color) !important;
358
- outline: 1px solid var(--se-active-color) !important;
359
- color: 1px solid var(--se-active-color);
360
- box-shadow: 0 0 0 0.3rem var(--se-active-light3-color);
361
- transition: box-shadow 0.1s ease-in-out;
352
+ background-color: var(--se-active-light5-color);
353
+ color: var(--se-active-hover-color);
354
+ transition: background-color 0.08s ease;
362
355
  }
363
356
 
364
357
  /* se-btn.active */
@@ -377,12 +370,9 @@
377
370
 
378
371
  .sun-editor .se-btn:enabled.active:active,
379
372
  .sun-editor .se-btn:enabled.active.__se__active {
380
- background-color: var(--se-active-light5-color);
373
+ background-color: var(--se-active-light6-color);
381
374
  color: var(--se-active-hover-color);
382
- border-color: var(--se-active-dark5-color) !important;
383
- outline: 1px solid var(--se-active-dark5-color) !important;
384
- box-shadow: 0 0 0 0.3rem var(--se-active-light5-color);
385
- transition: box-shadow 0.1s ease-in-out;
375
+ transition: background-color 0.08s ease;
386
376
  z-index: 1;
387
377
  }
388
378
 
@@ -408,28 +398,29 @@
408
398
 
409
399
  /** --- primary button */
410
400
  .sun-editor .se-btn-primary {
411
- background-color: var(--se-active-light2-color);
401
+ background-color: var(--se-active-dark5-color);
412
402
  border: 1px solid var(--se-active-dark5-color);
403
+ color: #fff;
404
+ font-weight: 500;
413
405
  border-radius: var(--se-border-radius);
414
406
  outline: 0 none;
415
407
  transition:
416
- border-color 0.3s ease,
417
- background-color 0.3s ease,
418
- color 0.3s ease;
408
+ border-color 0.13s ease,
409
+ background-color 0.13s ease,
410
+ color 0.13s ease;
419
411
  }
420
412
 
421
413
  .sun-editor .se-btn-primary:hover,
422
414
  .sun-editor .se-btn-primary:focus {
423
- background-color: var(--se-active-light5-color);
415
+ background-color: var(--se-active-hover-color);
416
+ border-color: var(--se-active-hover-color);
417
+ color: #fff;
424
418
  }
425
419
 
426
420
  .sun-editor .se-btn-primary:active,
427
421
  .sun-editor .se-btn-primary.__se__active {
428
- background-color: var(--se-active-light2-color);
429
- border-color: var(--se-active-light2-color) !important;
430
- outline: 1px solid var(--se-active-dark5-color) !important;
431
- box-shadow: 0 0 0 0.3rem var(--se-active-light2-color);
432
- transition: box-shadow 0.1s ease-in-out;
422
+ background-color: var(--se-active-hover-color);
423
+ transition: background-color 0.08s ease;
433
424
  }
434
425
 
435
426
  /* --- se-btn.success */
@@ -446,10 +437,7 @@
446
437
  .sun-editor .se-btn.se-btn-success:not(.on):not(.active):enabled:active,
447
438
  .sun-editor .se-btn.se-btn-success:not(.on):not(.active):enabled.__se__active {
448
439
  background-color: var(--se-success-light-color);
449
- border-color: var(--se-success-light-color) !important;
450
- outline: 1px solid var(--se-success-light5-color) !important;
451
- box-shadow: 0 0 0 0.3rem var(--se-success-light-color);
452
- transition: box-shadow 0.1s ease-in-out;
440
+ transition: background-color 0.08s ease;
453
441
  }
454
442
 
455
443
  /* se-btn.success.on */
@@ -470,11 +458,7 @@
470
458
  .sun-editor .se-btn.se-btn-success:enabled.on.__se__active {
471
459
  background-color: var(--se-success-light2-color);
472
460
  color: var(--se-success-color);
473
- border-color: var(--se-success-light2-color) !important;
474
- outline: 1px solid var(--se-success-dark-color) !important;
475
- color: 1px solid var(--se-success-color);
476
- box-shadow: 0 0 0 0.3rem var(--se-success-light2-color);
477
- transition: box-shadow 0.1s ease-in-out;
461
+ transition: background-color 0.08s ease;
478
462
  }
479
463
 
480
464
  /* se-btn.success.active */
@@ -495,10 +479,7 @@
495
479
  .sun-editor .se-btn.se-btn-success:enabled.active.__se__active {
496
480
  background-color: var(--se-success-light3-color);
497
481
  color: var(--se-success-dark3-color);
498
- border-color: var(--se-success-light3-color) !important;
499
- outline: 1px solid var(--se-success-dark2-color) !important;
500
- box-shadow: 0 0 0 0.3rem var(--se-success-light3-color);
501
- transition: box-shadow 0.1s ease-in-out;
482
+ transition: background-color 0.08s ease;
502
483
  }
503
484
 
504
485
  /* --- se-btn.danger */
@@ -516,10 +497,7 @@
516
497
  .sun-editor .se-btn.se-btn-danger:not(.on):not(.active):enabled:active,
517
498
  .sun-editor .se-btn.se-btn-danger:not(.on):not(.active):enabled.__se__active {
518
499
  background-color: var(--se-error-light5-color);
519
- border-color: var(--se-error-light5-color) !important;
520
- outline: 1px solid var(--se-error-dark-color) !important;
521
- box-shadow: 0 0 0 0.3rem var(--se-error-light5-color);
522
- transition: box-shadow 0.1s ease-in-out;
500
+ transition: background-color 0.08s ease;
523
501
  }
524
502
 
525
503
  /* se-btn.danger.on */
@@ -540,10 +518,7 @@
540
518
  .sun-editor .se-btn.se-btn-danger:enabled.on.__se__active {
541
519
  background-color: var(--se-error-light-color);
542
520
  color: var(--se-error-color);
543
- border-color: var(--se-error-light-color) !important;
544
- outline: 1px solid var(--se-error-dark3-color) !important;
545
- box-shadow: 0 0 0 0.3rem var(--se-error-light4-color);
546
- transition: box-shadow 0.1s ease-in-out;
521
+ transition: background-color 0.08s ease;
547
522
  }
548
523
 
549
524
  /* se-btn.danger.active */
@@ -564,10 +539,7 @@
564
539
  .sun-editor .se-btn.se-btn-danger:enabled.active.__se__active {
565
540
  background-color: var(--se-error-light-color);
566
541
  color: var(--se-error-color);
567
- border-color: var(--se-error-light-color) !important;
568
- outline: 1px solid var(--se-error-dark3-color) !important;
569
- box-shadow: 0 0 0 0.3rem var(--se-error-light-color);
570
- transition: box-shadow 0.1s ease-in-out;
542
+ transition: background-color 0.08s ease;
571
543
  }
572
544
 
573
545
  /** --- Input */
@@ -655,8 +627,8 @@
655
627
 
656
628
  .sun-editor button > svg,
657
629
  .sun-editor .se-svg {
658
- width: 20px;
659
- height: 20px;
630
+ width: var(--se-icon-size);
631
+ height: var(--se-icon-size);
660
632
  margin: auto;
661
633
  fill: currentColor;
662
634
  display: block;
@@ -667,9 +639,22 @@
667
639
  width: 15px;
668
640
  height: 15px;
669
641
  }
642
+ /* outline (stroke-based) icons — Lucide/Tabler set */
643
+ .sun-editor svg.se-io {
644
+ fill: none;
645
+ stroke: currentColor;
646
+ stroke-width: 2;
647
+ stroke-linecap: round;
648
+ stroke-linejoin: round;
649
+ }
650
+
651
+ /* Opt-in crispness for straight-stroke outline icons */
652
+ .sun-editor svg.se-io-sharp {
653
+ transform: translateZ(0);
654
+ }
670
655
  .sun-editor .se-text-prefix-icon {
671
- width: 20px;
672
- height: 20px;
656
+ width: var(--se-icon-size);
657
+ height: var(--se-icon-size);
673
658
  float: left;
674
659
  }
675
660
  .sun-editor .se-text-prefix-icon svg {
@@ -691,31 +676,40 @@
691
676
  }
692
677
 
693
678
  /* side button */
694
- .sun-editor button.se-side-btn {
679
+ .sun-editor .se-side-btn {
695
680
  border-radius: 0;
696
681
  margin-left: 0;
697
682
  margin-right: 0;
698
683
  }
699
- .sun-editor button.se-side-btn-a {
684
+ .sun-editor .se-side-btn-a {
700
685
  float: left;
701
686
  border-radius: var(--se-border-radius) 0 0 var(--se-border-radius);
702
687
  margin-right: 0;
703
688
  }
704
- .sun-editor button.se-side-btn-b {
689
+ .sun-editor .se-side-btn-b {
705
690
  float: left;
706
691
  border-radius: 0 var(--se-border-radius) var(--se-border-radius) 0;
707
692
  margin-left: 0;
708
693
  }
709
- .sun-editor button.se-side-btn-before {
694
+ .sun-editor .se-side-btn-before {
710
695
  float: left;
711
696
  margin-right: 0;
712
697
  border-radius: var(--se-border-radius) 0 0 var(--se-border-radius);
713
698
  }
714
- .sun-editor button.se-side-btn-after {
699
+ .sun-editor .se-side-btn-after {
715
700
  margin-left: 0;
716
701
  border-radius: 0 var(--se-border-radius) var(--se-border-radius) 0;
717
702
  }
718
703
 
704
+ .sun-editor .se-menu-list li:has(> button.se-side-btn-before) .se-btn-input input {
705
+ border-top-left-radius: 0;
706
+ border-bottom-left-radius: 0;
707
+ }
708
+ .sun-editor .se-menu-list li:has(> button.se-side-btn-after) .se-btn-input input {
709
+ border-top-right-radius: 0;
710
+ border-bottom-right-radius: 0;
711
+ }
712
+
719
713
  /* list button icons */
720
714
  .sun-editor button.se-btn-list > * li::before {
721
715
  content: '';
@@ -928,8 +922,8 @@
928
922
  position: absolute;
929
923
  z-index: 2147483646;
930
924
  width: auto;
931
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
932
- -webkit-box-shadow: 0 3px 9px var(--se-shadow-layer-color);
925
+ box-shadow: var(--se-shadow-layer);
926
+ -webkit-box-shadow: var(--se-shadow-layer);
933
927
  }
934
928
 
935
929
  /* JS fallback: initial state — override CSS sticky */
@@ -1141,8 +1135,8 @@
1141
1135
  .sun-editor .se-block-menu-icon {
1142
1136
  display: inline-flex;
1143
1137
  align-items: center;
1144
- width: 16px;
1145
- height: 16px;
1138
+ width: var(--se-icon-size-command-menu);
1139
+ height: var(--se-icon-size-command-menu);
1146
1140
  margin-right: 12px;
1147
1141
  margin-left: 4px;
1148
1142
  vertical-align: middle;
@@ -1173,8 +1167,8 @@
1173
1167
  .sun-editor .se-block-submenu-item .se-list-icon,
1174
1168
  .sun-editor .se-block-submenu-item svg {
1175
1169
  display: inline-block;
1176
- width: 16px;
1177
- height: 16px;
1170
+ width: var(--se-icon-size-command-menu);
1171
+ height: var(--se-icon-size-command-menu);
1178
1172
  margin: 0 6px 0 0;
1179
1173
  flex-shrink: 0;
1180
1174
  vertical-align: middle;
@@ -1319,6 +1313,7 @@
1319
1313
  font-size: var(--se-main-font-size);
1320
1314
  height: var(--se-input-btn-size);
1321
1315
  border: 0;
1316
+ border-radius: var(--se-border-radius);
1322
1317
  }
1323
1318
  .sun-editor .se-btn-input input:hover {
1324
1319
  border: 1px solid var(--se-main-border-color);
@@ -1390,7 +1385,7 @@
1390
1385
  border-radius: var(--se-border-radius-lg);
1391
1386
  padding: 4px;
1392
1387
  background-color: var(--se-main-background-color);
1393
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
1388
+ box-shadow: var(--se-shadow-layer);
1394
1389
  outline: 0 none;
1395
1390
  }
1396
1391
 
@@ -1697,7 +1692,7 @@
1697
1692
  background-clip: padding-box;
1698
1693
  border: 1px solid var(--se-input-btn-border-color);
1699
1694
  border-radius: var(--se-border-radius);
1700
- box-shadow: 0 6px 12px var(--se-shadow-layer-color);
1695
+ box-shadow: var(--se-shadow-layer);
1701
1696
  }
1702
1697
 
1703
1698
  .sun-editor .se-selector-table .se-table-size {
@@ -2117,22 +2112,16 @@
2117
2112
  -moz-backface-visibility: hidden;
2118
2113
  }
2119
2114
 
2120
- .sun-editor .se-wrapper .se-placeholder-line {
2121
- position: relative;
2122
- }
2123
-
2124
2115
  .sun-editor .se-wrapper .se-placeholder-line::before {
2125
2116
  content: attr(data-se-placeholder-line);
2126
- position: absolute;
2127
- inset: 0;
2128
- box-sizing: border-box;
2129
- padding: inherit;
2117
+ display: inline-block;
2118
+ width: 0;
2119
+ overflow: visible;
2130
2120
  color: var(--se-main-outline-color);
2131
2121
  pointer-events: none;
2122
+ user-select: none;
2123
+ -webkit-user-select: none;
2132
2124
  white-space: nowrap;
2133
- overflow: hidden;
2134
- text-overflow: ellipsis;
2135
- text-align: inherit;
2136
2125
  }
2137
2126
 
2138
2127
  /** --- status bar */
@@ -2282,7 +2271,7 @@
2282
2271
  border: 1px solid rgba(0, 0, 0, 0.2);
2283
2272
  border-radius: var(--se-border-radius-lg);
2284
2273
  outline: 0;
2285
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
2274
+ box-shadow: var(--se-shadow-layer);
2286
2275
  }
2287
2276
  .sun-editor .se-modal-area .se-modal-inner .se-modal-content.se-modal-show {
2288
2277
  display: block;
@@ -2418,7 +2407,8 @@
2418
2407
  padding: 5px 11px;
2419
2408
  margin: 0 0 10px 0 !important;
2420
2409
  font-size: var(--se-modal-input-font-size);
2421
- font-weight: normal;
2410
+ font-weight: 500;
2411
+ color: #fff;
2422
2412
  line-height: 1.4;
2423
2413
  text-align: center;
2424
2414
  white-space: nowrap;
@@ -2895,7 +2885,7 @@
2895
2885
  }
2896
2886
 
2897
2887
  .sun-editor .se-controller:not(.se-resizing-container) {
2898
- box-shadow: 0 5px 10px var(--se-shadow-controller-color);
2888
+ box-shadow: var(--se-shadow-controller);
2899
2889
  }
2900
2890
 
2901
2891
  .sun-editor-editable[contenteditable='true'] figure a,
@@ -3291,7 +3281,7 @@
3291
3281
  padding: 4px 0;
3292
3282
  margin: 0;
3293
3283
  border: 1px solid var(--se-main-divider-color);
3294
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
3284
+ box-shadow: var(--se-shadow-layer);
3295
3285
  outline: 0 none;
3296
3286
  }
3297
3287
 
@@ -3424,7 +3414,7 @@
3424
3414
  border: 1px solid var(--se-main-divider-color);
3425
3415
  border-radius: var(--se-border-radius-lg);
3426
3416
  padding: 4px 0;
3427
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
3417
+ box-shadow: var(--se-shadow-layer);
3428
3418
  z-index: 1;
3429
3419
  }
3430
3420
 
@@ -3516,7 +3506,7 @@
3516
3506
  border: 1px solid rgba(0, 0, 0, 0.2);
3517
3507
  border-radius: var(--se-border-radius-lg);
3518
3508
  outline: 0;
3519
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
3509
+ box-shadow: var(--se-shadow-layer);
3520
3510
  }
3521
3511
 
3522
3512
  /* --- browser - header */
@@ -3898,7 +3888,7 @@
3898
3888
  margin: auto;
3899
3889
  top: 35%;
3900
3890
  border-radius: var(--se-border-radius);
3901
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
3891
+ box-shadow: var(--se-shadow-layer);
3902
3892
  word-break: break-all;
3903
3893
  background-color: var(--se-main-background-color);
3904
3894
  padding: 15px;
@@ -3955,7 +3945,7 @@
3955
3945
  margin: auto;
3956
3946
  top: 35%;
3957
3947
  border-radius: var(--se-border-radius);
3958
- box-shadow: 0 3px 9px var(--se-shadow-layer-color);
3948
+ box-shadow: var(--se-shadow-layer);
3959
3949
  word-break: break-all;
3960
3950
  background-color: var(--se-main-font-color);
3961
3951
  color: var(--se-main-background-color);
@@ -4022,7 +4012,7 @@
4022
4012
  opacity: 0;
4023
4013
  z-index: 1;
4024
4014
  line-height: 1.5;
4025
- transition: opacity 0.5s;
4015
+ transition: opacity 0.13s ease;
4026
4016
  margin: 0;
4027
4017
  padding: 0;
4028
4018
  bottom: auto;
@@ -4408,29 +4398,39 @@
4408
4398
  }
4409
4399
 
4410
4400
  /* side button */
4411
- .sun-editor.se-rtl button.se-side-btn {
4401
+ .sun-editor.se-rtl .se-side-btn {
4412
4402
  float: right;
4413
4403
  }
4414
- .sun-editor.se-rtl button.se-side-btn-b {
4404
+ .sun-editor.se-rtl .se-side-btn-b {
4415
4405
  float: right;
4416
4406
  margin-right: 0;
4417
4407
  border-radius: var(--se-border-radius) 0 0 var(--se-border-radius);
4418
4408
  }
4419
- .sun-editor.se-rtl button.se-side-btn-a {
4409
+ .sun-editor.se-rtl .se-side-btn-a {
4420
4410
  float: right;
4421
4411
  margin-left: 0;
4422
4412
  border-radius: 0 var(--se-border-radius) var(--se-border-radius) 0;
4423
4413
  }
4424
- .sun-editor.se-rtl button.se-side-btn-after {
4414
+ .sun-editor.se-rtl .se-side-btn-after {
4425
4415
  margin-right: 0;
4426
4416
  border-radius: var(--se-border-radius) 0 0 var(--se-border-radius);
4427
4417
  }
4428
- .sun-editor.se-rtl button.se-side-btn-before {
4418
+ .sun-editor.se-rtl .se-side-btn-before {
4429
4419
  float: right;
4430
4420
  margin-left: 0;
4431
4421
  border-radius: 0 var(--se-border-radius) var(--se-border-radius) 0;
4432
4422
  }
4433
4423
 
4424
+ /* side button - input */
4425
+ .sun-editor.se-rtl .se-menu-list li:has(> button.se-side-btn-before) .se-btn-input input {
4426
+ border-top-right-radius: 0;
4427
+ border-bottom-right-radius: 0;
4428
+ }
4429
+ .sun-editor.se-rtl .se-menu-list li:has(> button.se-side-btn-after) .se-btn-input input {
4430
+ border-top-left-radius: 0;
4431
+ border-bottom-left-radius: 0;
4432
+ }
4433
+
4434
4434
  /* after, before button:hover - on, active - border */
4435
4435
  .sun-editor.se-rtl .se-menu-list li .se-side-btn-after.on:enabled,
4436
4436
  .sun-editor.se-rtl .se-menu-list li .se-side-btn-after.active:enabled {
@@ -229,13 +229,14 @@ export default class OptionProvider {
229
229
  }
230
230
 
231
231
  if (diff.has('placeholder_line')) {
232
- const phLineText = newRootOptions.get('placeholder_line');
233
- fc.set('placeholder_line', phLineText);
232
+ const phLine = newRootOptions.get('placeholder_line');
233
+ fc.set('placeholder_line', phLine);
234
234
 
235
235
  const marked = fc.get('wysiwyg').querySelector('.se-placeholder-line');
236
236
  if (marked) {
237
- if (phLineText) {
238
- marked.setAttribute('data-se-placeholder-line', phLineText);
237
+ const phText = ui.resolveLinePlaceholder(marked, phLine);
238
+ if (phText) {
239
+ marked.setAttribute('data-se-placeholder-line', phText);
239
240
  } else {
240
241
  marked.classList.remove('se-placeholder-line');
241
242
  marked.removeAttribute('data-se-placeholder-line');
@@ -138,7 +138,6 @@ export const A = {
138
138
  * @returns {Action}
139
139
  */
140
140
  backspaceEmptyLineMergePrev: (formatEl, prev) => ({ t: 'backspace.emptyLine.mergePrev', p: { formatEl, prev } }),
141
-
142
141
  /**
143
142
  * @param {Node} rowEndBr - the `<br>` the caret sits on (end of the empty row)
144
143
  * @param {Node} rowStartBr - the `<br>` that starts the empty row (removed to merge it up)
@@ -148,7 +147,6 @@ export const A = {
148
147
  t: 'backspace.brline.rowMerge',
149
148
  p: { rowEndBr, rowStartBr },
150
149
  }),
151
-
152
150
  /**
153
151
  * @description Soft line break merge (Backspace): the caret sits in the zero-width anchor right after a soft-break `<br>` (a Shift+Enter).
154
152
  * @param {Node} zws - the zero-width Text node holding the caret (its `previousSibling` is the `<br>`)
@@ -187,13 +185,11 @@ export const A = {
187
185
  * @returns {Action}
188
186
  */
189
187
  deleteEmptyLineMergeNext: (formatEl, next) => ({ t: 'delete.emptyLine.mergeNext', p: { formatEl, next } }),
190
-
191
188
  /**
192
189
  * @param {Node} rowEndBr - the `<br>` the caret sits on (end of the empty row); removed to pull the next row up
193
190
  * @returns {Action}
194
191
  */
195
192
  deleteBrLineRowMerge: (rowEndBr) => ({ t: 'delete.brline.rowMerge', p: { rowEndBr } }),
196
-
197
193
  /**
198
194
  * @description Soft line break merge (Delete): the caret sits just before a soft-break `<br>`.
199
195
  * @param {Node} br - the soft-break `<br>` to remove
@@ -201,6 +197,14 @@ export const A = {
201
197
  */
202
198
  deleteSoftBreakMerge: (br) => ({ t: 'delete.softBreak.merge', p: { br } }),
203
199
 
200
+ // === line ===
201
+ /**
202
+ * @param {HTMLElement} into - Line that receives the content (caret lands at its original end).
203
+ * @param {HTMLElement} from - Line whose content is moved into `into`, then removed.
204
+ * @returns {Action}
205
+ */
206
+ mergeLineInto: (into, from) => ({ t: 'line.merge', p: { into, from } }),
207
+
204
208
  // === tab ===
205
209
  /**
206
210
  * @param {Range} range
@@ -250,6 +250,39 @@ export default {
250
250
  caretToLineEdge(ports, next, false);
251
251
  },
252
252
 
253
+ /**
254
+ * @action mergeLineInto — Merge one line into another across a block boundary.
255
+ */
256
+ 'line.merge': ({ ports }, { into, from }) => {
257
+ const format = ports.format;
258
+ const container = from.parentElement;
259
+
260
+ stripTrailingBreaks(into);
261
+ const focusNode = into.lastChild;
262
+ stripTrailingBreaks(from);
263
+
264
+ while (from.firstChild) into.appendChild(from.firstChild);
265
+ dom.utils.removeItem(from);
266
+
267
+ let block = container;
268
+ while (
269
+ block &&
270
+ block !== into.parentElement &&
271
+ format.isBlock(block) &&
272
+ !format.isClosureBlock(block) &&
273
+ dom.check.isZeroWidth(block)
274
+ ) {
275
+ const parent = block.parentElement;
276
+ dom.utils.removeItem(block);
277
+ block = parent;
278
+ }
279
+
280
+ if (!into.firstChild) into.appendChild(dom.utils.createElement('BR'));
281
+
282
+ if (focusNode) caretToNodeEnd(ports, focusNode);
283
+ else ports.selection.setRange(into, 0, into, 0);
284
+ },
285
+
253
286
  /** @action deleteBrLineRowMerge — remove an empty row inside a brLine (PRE), pull the next row up */
254
287
  'delete.brline.rowMerge': ({ ports }, { rowEndBr }) => {
255
288
  let next = rowEndBr.nextSibling;
@@ -462,7 +495,8 @@ export default {
462
495
  shouldDelete: true,
463
496
  skipHistory: true,
464
497
  });
465
- edge.cc.insertBefore(newEl, edge.ec);
498
+
499
+ edge.cc.insertBefore(newEl, edge.sc ? edge.sc.nextSibling : edge.cc.firstChild);
466
500
  }
467
501
 
468
502
  newEl.innerHTML = '<br>';
@@ -596,15 +630,17 @@ export default {
596
630
  dom.utils.copyTagAttributes(newFormat, formatEl, ctx.options.get('lineAttrReset'));
597
631
 
598
632
  let child = focusBR;
599
- let sNode = selectionNode;
600
- do {
601
- if (!dom.check.isBreak(sNode) && sNode.nodeType === 1) {
602
- const f = /** @type {HTMLElement} */ (sNode.cloneNode(false));
603
- f.appendChild(child);
604
- child = f;
605
- }
606
- sNode = sNode.parentElement;
607
- } while (formatEl !== sNode && formatEl.contains(sNode));
633
+ if (!(formatEndEdge && ctx.options.get('lineBreakClearStyle'))) {
634
+ let sNode = selectionNode;
635
+ do {
636
+ if (!dom.check.isBreak(sNode) && sNode.nodeType === 1) {
637
+ const f = /** @type {HTMLElement} */ (sNode.cloneNode(false));
638
+ f.appendChild(child);
639
+ child = f;
640
+ }
641
+ sNode = sNode.parentElement;
642
+ } while (formatEl !== sNode && formatEl.contains(sNode));
643
+ }
608
644
 
609
645
  newFormat.appendChild(child);
610
646
  formatEl.parentNode.insertBefore(
@@ -755,6 +791,19 @@ function caretToNodeEnd(ports, node) {
755
791
  ports.selection.setRange(node, offset, node, offset);
756
792
  }
757
793
 
794
+ /**
795
+ * @description Removes trailing `<br>` elements and zero-width text nodes from `el`.
796
+ * @param {HTMLElement} el
797
+ */
798
+ function stripTrailingBreaks(el) {
799
+ let last = el.lastChild;
800
+ while (last && (dom.check.isBreak(last) || (last.nodeType === 3 && dom.check.isZeroWidth(last)))) {
801
+ const prev = last.previousSibling;
802
+ dom.utils.removeItem(last);
803
+ last = prev;
804
+ }
805
+ }
806
+
758
807
  /**
759
808
  * @param {HTMLElement} formatEl - Format element
760
809
  * @returns {Node}
@@ -34,6 +34,7 @@ import { useEnterFromBeforeInput } from './reducers/keydown.reducer';
34
34
  * @property {(...args: Parameters<Format['isNormalLine']>) => ReturnType<Format['isNormalLine']>} isNormalLine
35
35
  * @property {(...args: Parameters<Format['isBrLine']>) => ReturnType<Format['isBrLine']>} isBrLine
36
36
  * @property {(...args: Parameters<Format['isClosureBrLine']>) => ReturnType<Format['isClosureBrLine']>} isClosureBrLine
37
+ * @property {(...args: Parameters<Format['isBlock']>) => ReturnType<Format['isBlock']>} isBlock
37
38
  * @property {(...args: Parameters<Format['isClosureBlock']>) => ReturnType<Format['isClosureBlock']>} isClosureBlock
38
39
  * @property {(...args: Parameters<Format['isEdgeLine']>) => ReturnType<Format['isEdgeLine']>} isEdgeLine
39
40
  * @property {(...args: Parameters<Format['removeBlock']>) => ReturnType<Format['removeBlock']>} removeBlock
@@ -124,6 +125,7 @@ export function makePorts(inst, { _styleNodes }) {
124
125
  isNormalLine: (n) => format.isNormalLine(n),
125
126
  isBrLine: (n) => format.isBrLine(n),
126
127
  isClosureBrLine: (n) => format.isClosureBrLine(n),
128
+ isBlock: (n) => format.isBlock(n),
127
129
  isClosureBlock: (n) => format.isClosureBlock(n),
128
130
  isEdgeLine: (node, offset, dir) => format.isEdgeLine(node, offset, dir),
129
131
  removeBlock: (n, p) => format.removeBlock(n, p),