suneditor 3.1.1 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/suneditor-contents.min.css +1 -1
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +10 -7
- package/src/assets/design/size.css +2 -1
- package/src/assets/suneditor.css +75 -23
- package/src/core/editor.js +1 -0
- package/src/core/event/actions/index.js +2 -1
- package/src/core/event/effects/keydown.registry.js +3 -1
- package/src/core/event/effects/ruleHelpers.js +30 -1
- package/src/core/event/handlers/handler_ww_dragDrop.js +20 -0
- package/src/core/event/rules/keydown.rule.arrow.js +22 -16
- package/src/core/event/rules/keydown.rule.backspace.js +18 -5
- package/src/core/event/rules/keydown.rule.delete.js +7 -5
- package/src/core/event/rules/keydown.rule.enter.js +33 -3
- package/src/core/logic/panel/menu.js +4 -0
- package/src/core/logic/shell/ui.js +52 -3
- package/src/core/schema/options.js +1 -1
- package/src/core/section/constructor.js +44 -13
- package/src/core/section/documentType.js +55 -29
- package/src/modules/contract/Browser.js +3 -0
- package/src/modules/contract/Controller.js +3 -0
- package/src/modules/contract/Figure.js +23 -4
- package/src/modules/contract/Modal.js +2 -0
- package/src/plugins/dropdown/table/services/table.style.js +21 -12
- package/types/core/event/actions/index.d.ts +1 -1
- package/types/core/event/effects/keydown.registry.d.ts +1 -1
- package/types/core/event/effects/ruleHelpers.d.ts +12 -0
- package/types/core/logic/shell/ui.d.ts +2 -2
- package/types/core/schema/options.d.ts +2 -2
- package/types/core/section/documentType.d.ts +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suneditor",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Vanilla JavaScript based WYSIWYG web editor",
|
|
5
5
|
"author": "Yi JiHong",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,16 +49,19 @@
|
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=14.0.0"
|
|
51
51
|
},
|
|
52
|
+
"overrides": {
|
|
53
|
+
"uuid": ">=14.0.0"
|
|
54
|
+
},
|
|
52
55
|
"scripts": {
|
|
53
56
|
"dev": "webpack-dev-server --config webpack/dev.js",
|
|
54
57
|
"start": "npm run dev",
|
|
55
58
|
"build:dev": "cross-env NODE_ENV=development webpack --config webpack/builder.js && cross-env NODE_ENV=development webpack --config webpack/builder-contents.js && rm -f dist/_suneditor-contents.js",
|
|
56
59
|
"build:prod": "cross-env NODE_ENV=production webpack --config webpack/builder.js && cross-env NODE_ENV=production webpack --config webpack/builder-contents.js && rm -f dist/_suneditor-contents.js",
|
|
57
60
|
"lint:type": "npx tsc --noEmit",
|
|
58
|
-
"lint:fix-js": "npx eslint
|
|
61
|
+
"lint:fix-js": "npx eslint src/ --fix",
|
|
59
62
|
"lint:fix-ts": "npx eslint types/ --fix",
|
|
60
|
-
"lint:fix-all": "npx eslint
|
|
61
|
-
"lint": "npx eslint
|
|
63
|
+
"lint:fix-all": "npx eslint src/ types/ --fix",
|
|
64
|
+
"lint": "npx eslint src/ types/ && npm run lint:type && npm run check:arch",
|
|
62
65
|
"ts-build": "npm run check:inject && (npx tsc || true) && barrelsby --config .barrelsby.json && node scripts/ts-build/format-index.cjs && node scripts/ts-build/fix-langs.cjs && node scripts/ts-build/wrap-dts.cjs && node scripts/ts-build/rename-index.cjs && node scripts/ts-build/interfaces-convert.cjs && node scripts/ts-build/gen-options-dts.cjs && node scripts/ts-build/gen-css-dts.cjs && node scripts/ts-build/inject-typedef-import.cjs && npm run lint:fix-ts",
|
|
63
66
|
"test": "jest --silent",
|
|
64
67
|
"test:watch": "jest --watch",
|
|
@@ -125,13 +128,13 @@
|
|
|
125
128
|
"webpack": "^5.102.0",
|
|
126
129
|
"webpack-bundle-analyzer": "^4.10.2",
|
|
127
130
|
"webpack-cli": "^6.0.1",
|
|
128
|
-
"webpack-dev-server": "^5.2.
|
|
131
|
+
"webpack-dev-server": "^5.2.3",
|
|
129
132
|
"webpack-merge": "^6.0.1"
|
|
130
133
|
},
|
|
131
134
|
"browserslist": [
|
|
132
135
|
"chrome >= 119",
|
|
133
136
|
"edge >= 119",
|
|
134
|
-
"firefox >=
|
|
137
|
+
"firefox >= 125",
|
|
135
138
|
"safari >= 17.2",
|
|
136
139
|
"opera >= 105",
|
|
137
140
|
"samsung >= 23",
|
|
@@ -168,4 +171,4 @@
|
|
|
168
171
|
"web editor",
|
|
169
172
|
"browser editor"
|
|
170
173
|
]
|
|
171
|
-
}
|
|
174
|
+
}
|
package/src/assets/suneditor.css
CHANGED
|
@@ -963,6 +963,18 @@
|
|
|
963
963
|
bottom: 0px;
|
|
964
964
|
}
|
|
965
965
|
|
|
966
|
+
/* Bottom toolbar: tooltip appears above */
|
|
967
|
+
.sun-editor .se-toolbar.se-toolbar-bottom .se-tooltip .se-tooltip-inner {
|
|
968
|
+
top: auto;
|
|
969
|
+
bottom: 120%;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
.sun-editor .se-toolbar.se-toolbar-bottom .se-tooltip .se-tooltip-inner .se-tooltip-text::after {
|
|
973
|
+
top: 100%;
|
|
974
|
+
bottom: auto;
|
|
975
|
+
border-color: var(--se-main-font-color) transparent transparent transparent;
|
|
976
|
+
}
|
|
977
|
+
|
|
966
978
|
/* toolbar arrow up */
|
|
967
979
|
.sun-editor .se-toolbar .se-arrow.se-arrow-up {
|
|
968
980
|
border-bottom-color: var(--se-main-divider-color);
|
|
@@ -1137,6 +1149,26 @@
|
|
|
1137
1149
|
z-index: 2147483646;
|
|
1138
1150
|
}
|
|
1139
1151
|
|
|
1152
|
+
/** --- popover UA override ---------------------------------------------------- */
|
|
1153
|
+
.sun-editor .se-controller[popover],
|
|
1154
|
+
.sun-editor.sun-editor-carrier-wrapper .se-drag-cursor[popover] {
|
|
1155
|
+
inset: unset;
|
|
1156
|
+
margin: 0;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.sun-editor .se-modal-area[popover],
|
|
1160
|
+
.sun-editor .se-back-wrapper[popover],
|
|
1161
|
+
.sun-editor .se-loading-box[popover],
|
|
1162
|
+
.sun-editor .se-toast[popover],
|
|
1163
|
+
.sun-editor .se-browser[popover],
|
|
1164
|
+
.sun-editor .se-menu-tray[popover] {
|
|
1165
|
+
background: none;
|
|
1166
|
+
border: none;
|
|
1167
|
+
padding: 0;
|
|
1168
|
+
overflow: visible;
|
|
1169
|
+
color: inherit;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1140
1172
|
/** --- dropdown layer ---------------------------------------------------------- */
|
|
1141
1173
|
.sun-editor .se-dropdown {
|
|
1142
1174
|
overflow-x: hidden;
|
|
@@ -1156,7 +1188,7 @@
|
|
|
1156
1188
|
height: auto;
|
|
1157
1189
|
z-index: 5;
|
|
1158
1190
|
border: 1px solid var(--se-main-divider-color);
|
|
1159
|
-
border-radius: var(--se-border-radius);
|
|
1191
|
+
border-radius: var(--se-border-radius-lg);
|
|
1160
1192
|
padding: 4px;
|
|
1161
1193
|
background-color: var(--se-main-background-color);
|
|
1162
1194
|
box-shadow: 0 3px 9px var(--se-shadow-layer-color);
|
|
@@ -1189,7 +1221,8 @@
|
|
|
1189
1221
|
|
|
1190
1222
|
/* dropdown layer - basic list */
|
|
1191
1223
|
.sun-editor .se-list-inner .se-list-basic li {
|
|
1192
|
-
width:
|
|
1224
|
+
width: auto;
|
|
1225
|
+
max-width: 100%;
|
|
1193
1226
|
}
|
|
1194
1227
|
.sun-editor .se-list-inner input {
|
|
1195
1228
|
border-radius: 0;
|
|
@@ -1563,6 +1596,19 @@
|
|
|
1563
1596
|
stroke-width: 2px;
|
|
1564
1597
|
}
|
|
1565
1598
|
|
|
1599
|
+
.sun-editor .se-list-layer .se-selector-color .se-color-pallet:first-child li:first-child button {
|
|
1600
|
+
border-top-left-radius: var(--se-border-radius);
|
|
1601
|
+
}
|
|
1602
|
+
.sun-editor .se-list-layer .se-selector-color .se-color-pallet:first-child li:last-child button {
|
|
1603
|
+
border-top-right-radius: var(--se-border-radius);
|
|
1604
|
+
}
|
|
1605
|
+
.sun-editor .se-list-layer .se-selector-color .se-color-pallet:last-of-type li:first-child button {
|
|
1606
|
+
border-bottom-left-radius: var(--se-border-radius);
|
|
1607
|
+
}
|
|
1608
|
+
.sun-editor .se-list-layer .se-selector-color .se-color-pallet:last-of-type li:last-child button {
|
|
1609
|
+
border-bottom-right-radius: var(--se-border-radius);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1566
1612
|
/* --- hue slider -------------------------------------------------------------- */
|
|
1567
1613
|
.sun-editor .se-hue-slider {
|
|
1568
1614
|
padding: 14px;
|
|
@@ -1997,7 +2043,7 @@
|
|
|
1997
2043
|
-webkit-background-clip: padding-box;
|
|
1998
2044
|
background-clip: padding-box;
|
|
1999
2045
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
2000
|
-
border-radius: var(--se-border-radius);
|
|
2046
|
+
border-radius: var(--se-border-radius-lg);
|
|
2001
2047
|
outline: 0;
|
|
2002
2048
|
box-shadow: 0 3px 9px var(--se-shadow-layer-color);
|
|
2003
2049
|
}
|
|
@@ -2542,6 +2588,12 @@
|
|
|
2542
2588
|
display: none;
|
|
2543
2589
|
overflow: visible;
|
|
2544
2590
|
z-index: 2147483646;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
.sun-editor .se-controller[popover]:popover-open {
|
|
2594
|
+
position: absolute;
|
|
2595
|
+
inset: unset;
|
|
2596
|
+
margin: 0;
|
|
2545
2597
|
padding: 2px 2px 0 2px;
|
|
2546
2598
|
margin: 0;
|
|
2547
2599
|
border: 1px solid var(--se-controller-border-color);
|
|
@@ -2558,6 +2610,7 @@
|
|
|
2558
2610
|
color: var(--se-controller-color);
|
|
2559
2611
|
-webkit-background-clip: padding-box;
|
|
2560
2612
|
background-clip: padding-box;
|
|
2613
|
+
border-radius: var(--se-border-radius);
|
|
2561
2614
|
line-break: auto;
|
|
2562
2615
|
}
|
|
2563
2616
|
|
|
@@ -2582,13 +2635,13 @@
|
|
|
2582
2635
|
|
|
2583
2636
|
.sun-editor-editable[contenteditable='true'] figure figcaption {
|
|
2584
2637
|
display: block;
|
|
2585
|
-
z-index:
|
|
2638
|
+
z-index: 11;
|
|
2586
2639
|
}
|
|
2587
2640
|
|
|
2588
2641
|
.sun-editor-editable[contenteditable='true']:not(.se-read-only) figure:not(.se-input-component)::after {
|
|
2589
2642
|
position: absolute;
|
|
2590
2643
|
content: '';
|
|
2591
|
-
z-index:
|
|
2644
|
+
z-index: 10;
|
|
2592
2645
|
top: 0;
|
|
2593
2646
|
left: 0;
|
|
2594
2647
|
right: 0;
|
|
@@ -2960,10 +3013,10 @@
|
|
|
2960
3013
|
overflow-x: visible;
|
|
2961
3014
|
overflow: visible;
|
|
2962
3015
|
background-color: var(--se-main-background-color);
|
|
2963
|
-
border-radius: var(--se-border-radius);
|
|
2964
|
-
padding:
|
|
3016
|
+
border-radius: var(--se-border-radius-lg);
|
|
3017
|
+
padding: 4px 0;
|
|
2965
3018
|
margin: 0;
|
|
2966
|
-
border: 1px solid var(--se-main-
|
|
3019
|
+
border: 1px solid var(--se-main-divider-color);
|
|
2967
3020
|
box-shadow: 0 3px 9px var(--se-shadow-layer-color);
|
|
2968
3021
|
outline: 0 none;
|
|
2969
3022
|
}
|
|
@@ -2990,8 +3043,10 @@
|
|
|
2990
3043
|
min-height: 28px;
|
|
2991
3044
|
font-size: var(--se-btn-font-size);
|
|
2992
3045
|
padding: 0 6px;
|
|
2993
|
-
margin:
|
|
3046
|
+
margin: 1px 4px;
|
|
2994
3047
|
cursor: pointer;
|
|
3048
|
+
border-radius: var(--se-border-radius);
|
|
3049
|
+
transition: background-color 0.08s ease;
|
|
2995
3050
|
}
|
|
2996
3051
|
|
|
2997
3052
|
.sun-editor .se-select-menu .se-select-item button {
|
|
@@ -3011,21 +3066,12 @@
|
|
|
3011
3066
|
|
|
3012
3067
|
.sun-editor .se-select-menu.se-select-menu-mouse-move .se-select-item:hover,
|
|
3013
3068
|
.sun-editor .se-select-menu:not(.se-select-menu-mouse-move) .se-select-item.active {
|
|
3014
|
-
|
|
3015
|
-
outline: 1px solid var(--se-active-color) !important;
|
|
3016
|
-
box-shadow: 0 0 0 0.3rem var(--se-active-light3-color);
|
|
3017
|
-
transition: box-shadow 0.1s ease-in-out;
|
|
3069
|
+
background-color: var(--se-hover-light-color);
|
|
3018
3070
|
}
|
|
3019
3071
|
|
|
3020
3072
|
.sun-editor .se-select-menu.se-select-menu-mouse-move .se-select-item:active,
|
|
3021
3073
|
.sun-editor .se-select-menu.se-select-menu-mouse-move .se-select-item.__se__active {
|
|
3022
|
-
background-color: var(--se-
|
|
3023
|
-
border-color: var(--se-active-light4-color) !important;
|
|
3024
|
-
outline: 1px solid var(--se-active-color) !important;
|
|
3025
|
-
box-shadow: none;
|
|
3026
|
-
transition:
|
|
3027
|
-
background-color 0.1s ease-in-out,
|
|
3028
|
-
box-shadow 0.1s ease-in-out;
|
|
3074
|
+
background-color: var(--se-hover-light2-color);
|
|
3029
3075
|
}
|
|
3030
3076
|
|
|
3031
3077
|
.sun-editor .se-modal-form-files .se-select-menu {
|
|
@@ -3086,7 +3132,7 @@
|
|
|
3086
3132
|
-webkit-background-clip: padding-box;
|
|
3087
3133
|
background-clip: padding-box;
|
|
3088
3134
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
3089
|
-
border-radius: var(--se-border-radius);
|
|
3135
|
+
border-radius: var(--se-border-radius-lg);
|
|
3090
3136
|
outline: 0;
|
|
3091
3137
|
box-shadow: 0 3px 9px var(--se-shadow-layer-color);
|
|
3092
3138
|
}
|
|
@@ -3943,6 +3989,12 @@
|
|
|
3943
3989
|
.sun-editor.se-rtl .se-find-replace-row {
|
|
3944
3990
|
justify-content: flex-start;
|
|
3945
3991
|
}
|
|
3992
|
+
.sun-editor.se-rtl .se-find-replace-input {
|
|
3993
|
+
direction: rtl;
|
|
3994
|
+
}
|
|
3995
|
+
.sun-editor.se-rtl .se-input-form {
|
|
3996
|
+
direction: rtl;
|
|
3997
|
+
}
|
|
3946
3998
|
|
|
3947
3999
|
/* statusbar */
|
|
3948
4000
|
.sun-editor.se-rtl .se-status-bar {
|
|
@@ -4527,8 +4579,8 @@
|
|
|
4527
4579
|
.sun-editor-editable[contenteditable='true'] .se-page-break::after {
|
|
4528
4580
|
content: '⎯⎯⎯⎯⎯⎯';
|
|
4529
4581
|
position: absolute;
|
|
4530
|
-
top:
|
|
4531
|
-
background:
|
|
4582
|
+
top: 0px;
|
|
4583
|
+
background: transparent;
|
|
4532
4584
|
padding: 0 5px;
|
|
4533
4585
|
color: var(--se-doc-info-page-background-color);
|
|
4534
4586
|
font-size: var(--se-main-font-size);
|
package/src/core/editor.js
CHANGED
|
@@ -245,6 +245,7 @@ class Editor {
|
|
|
245
245
|
converter._setAutoHeightStyle(targetOptions.get('height'));
|
|
246
246
|
frame.contentDocument.body.className = originOptions.get('_editableClass');
|
|
247
247
|
frame.contentDocument.body.setAttribute('contenteditable', 'true');
|
|
248
|
+
if (originOptions.get('_rtl')) frame.contentDocument.body.dir = 'rtl';
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
/**
|
|
@@ -203,9 +203,10 @@ export const A = {
|
|
|
203
203
|
* @param {Node} selectionNode
|
|
204
204
|
* @param {boolean} formatStartEdge
|
|
205
205
|
* @param {boolean} formatEndEdge
|
|
206
|
+
* @param {boolean} [bidiSwapped]
|
|
206
207
|
* @returns {Action}
|
|
207
208
|
*/
|
|
208
|
-
enterFormatBreakAtEdge: (formatEl, selectionNode, formatStartEdge, formatEndEdge) => ({ t: 'enter.format.breakAtEdge', p: { formatEl, selectionNode, formatStartEdge, formatEndEdge } }),
|
|
209
|
+
enterFormatBreakAtEdge: (formatEl, selectionNode, formatStartEdge, formatEndEdge, bidiSwapped) => ({ t: 'enter.format.breakAtEdge', p: { formatEl, selectionNode, formatStartEdge, formatEndEdge, bidiSwapped } }),
|
|
209
210
|
/**
|
|
210
211
|
* @param {Element} formatEl
|
|
211
212
|
* @param {Range} range
|
|
@@ -411,7 +411,7 @@ export default {
|
|
|
411
411
|
},
|
|
412
412
|
|
|
413
413
|
/** @action enterFormatBreakAtEdge */
|
|
414
|
-
'enter.format.breakAtEdge': ({ ports, ctx }, { formatEl, selectionNode, formatStartEdge, formatEndEdge }) => {
|
|
414
|
+
'enter.format.breakAtEdge': ({ ports, ctx }, { formatEl, selectionNode, formatStartEdge, formatEndEdge, bidiSwapped }) => {
|
|
415
415
|
const focusBR = dom.utils.createElement('BR');
|
|
416
416
|
const newFormat = dom.utils.createElement(formatEl.nodeName, null, focusBR);
|
|
417
417
|
|
|
@@ -432,6 +432,8 @@ export default {
|
|
|
432
432
|
formatEl.parentNode.insertBefore(newFormat, formatStartEdge && !formatEndEdge ? formatEl : formatEl.nextElementSibling);
|
|
433
433
|
if (formatEndEdge) {
|
|
434
434
|
ports.selection.setRange(focusBR, 1, focusBR, 1);
|
|
435
|
+
} else if (bidiSwapped) {
|
|
436
|
+
ports.selection.setRange(formatEl, 1, formatEl, 1);
|
|
435
437
|
} else {
|
|
436
438
|
const firstEl = formatEl.firstChild || formatEl;
|
|
437
439
|
ports.selection.setRange(firstEl, 0, firstEl, 0);
|
|
@@ -145,4 +145,33 @@ function setDefaultLine(ports, lineTagName) {
|
|
|
145
145
|
return ports.setDefaultLine(lineTagName);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
/**
|
|
149
|
+
* @description Detects if a detected logical edge is incorrect due to bidi text direction mismatch in RTL mode.
|
|
150
|
+
* When LTR text (numbers, Latin) is inside an RTL line, the browser may place the caret at offset 0
|
|
151
|
+
* for the visual end or offset=length for the visual start. This function compares the caret's visual
|
|
152
|
+
* position against the content boundaries to detect such mismatches.
|
|
153
|
+
* @param {Range} range - The current collapsed range
|
|
154
|
+
* @param {HTMLElement} formatEl - The format/line element
|
|
155
|
+
* @param {'front'|'end'} detectedEdge - The edge detected by logical offset check
|
|
156
|
+
* @param {Document} doc - The document object
|
|
157
|
+
* @returns {boolean} true if the detected edge doesn't match the visual position (bidi mismatch)
|
|
158
|
+
*/
|
|
159
|
+
function isRtlBidiMismatch(range, formatEl, detectedEdge, doc) {
|
|
160
|
+
if (!range.collapsed || !formatEl) return false;
|
|
161
|
+
|
|
162
|
+
const caretRect = range.getBoundingClientRect();
|
|
163
|
+
if (caretRect.height <= 0) return false;
|
|
164
|
+
|
|
165
|
+
const contentRange = doc.createRange();
|
|
166
|
+
contentRange.selectNodeContents(formatEl);
|
|
167
|
+
|
|
168
|
+
const contentRect = contentRange.getBoundingClientRect();
|
|
169
|
+
if (contentRect.width <= 2) return false;
|
|
170
|
+
|
|
171
|
+
// In RTL: content left = visual end, content right = visual start
|
|
172
|
+
// 'front' mismatch: logically at front (offset 0) but caret at left = visual end
|
|
173
|
+
// 'end' mismatch: logically at end (offset=length) but caret at right = visual start
|
|
174
|
+
return detectedEdge === 'front' ? caretRect.left <= contentRect.left + 2 : caretRect.left >= contentRect.right - 2;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { hardDelete, cleanRemovedTags, isUneditableNode, setDefaultLine, isRtlBidiMismatch };
|
|
@@ -46,7 +46,17 @@ export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar
|
|
|
46
46
|
dragCursor.style.top = `${rect.top + _w.scrollY + _offset.y - 5 + frameY}px`;
|
|
47
47
|
dragCursor.style.height = `${rect.height + 10}px`;
|
|
48
48
|
dragCursor.style.display = 'block';
|
|
49
|
+
try {
|
|
50
|
+
dragCursor.showPopover?.();
|
|
51
|
+
} catch {
|
|
52
|
+
// ignore
|
|
53
|
+
}
|
|
49
54
|
} else {
|
|
55
|
+
try {
|
|
56
|
+
dragCursor.hidePopover?.();
|
|
57
|
+
} catch {
|
|
58
|
+
// ignore
|
|
59
|
+
}
|
|
50
60
|
dragCursor.style.display = 'none';
|
|
51
61
|
}
|
|
52
62
|
}
|
|
@@ -56,6 +66,11 @@ export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar
|
|
|
56
66
|
* @param {HTMLElement} dragCursor - Drag cursor element
|
|
57
67
|
*/
|
|
58
68
|
export function OnDragEnd_wysiwyg(dragCursor) {
|
|
69
|
+
try {
|
|
70
|
+
dragCursor.hidePopover?.();
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore
|
|
73
|
+
}
|
|
59
74
|
dragCursor.style.display = 'none';
|
|
60
75
|
}
|
|
61
76
|
|
|
@@ -109,6 +124,11 @@ export function OnDrop_wysiwyg(fc, dragCursor, e) {
|
|
|
109
124
|
this.$.selection.setRange(sc, so, ec, eo);
|
|
110
125
|
return this._dataTransferAction('drop', e, dataTransfer, fc);
|
|
111
126
|
} finally {
|
|
127
|
+
try {
|
|
128
|
+
dragCursor.hidePopover?.();
|
|
129
|
+
} catch {
|
|
130
|
+
// ignore
|
|
131
|
+
}
|
|
112
132
|
dragCursor.style.display = 'none';
|
|
113
133
|
}
|
|
114
134
|
}
|
|
@@ -20,6 +20,10 @@ import { A } from '../actions';
|
|
|
20
20
|
export function reduceArrowDown(actions, ports, ctx) {
|
|
21
21
|
const { component } = ports;
|
|
22
22
|
const { formatEl, range, selectionNode, keyCode } = ctx;
|
|
23
|
+
const rtl = ctx.options.get('_rtl');
|
|
24
|
+
|
|
25
|
+
// In RTL, ArrowLeft is forward (toward end on the left), ArrowRight is backward (toward start on the right)
|
|
26
|
+
const hDir = keyCode === 'ArrowLeft' ? (rtl ? 'forward' : 'back') : keyCode === 'ArrowRight' ? (rtl ? 'back' : 'forward') : null;
|
|
23
27
|
|
|
24
28
|
// next component
|
|
25
29
|
let cmponentInfo = null;
|
|
@@ -30,12 +34,24 @@ export function reduceArrowDown(actions, ports, ctx) {
|
|
|
30
34
|
}
|
|
31
35
|
break;
|
|
32
36
|
case 'ArrowLeft' /** left key */:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
case 'ArrowRight' /** right key */:
|
|
38
|
+
if (hDir === 'forward') {
|
|
39
|
+
if (dom.check.isEdgePoint(selectionNode, range.endOffset, 'end')) {
|
|
40
|
+
const nextEl = selectionNode.nextElementSibling || dom.query.getNextDeepestNode(selectionNode);
|
|
41
|
+
if (nextEl) {
|
|
42
|
+
if (component.is(nextEl)) cmponentInfo = component.get(nextEl);
|
|
43
|
+
} else if (component.is(formatEl.nextElementSibling)) {
|
|
44
|
+
cmponentInfo = component.get(formatEl.nextElementSibling);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else if (hDir === 'back') {
|
|
48
|
+
if (dom.check.isEdgePoint(selectionNode, range.startOffset, 'front')) {
|
|
49
|
+
const prevEl = selectionNode.previousElementSibling || dom.query.getPreviousDeepestNode(selectionNode);
|
|
50
|
+
if (prevEl) {
|
|
51
|
+
if (component.is(prevEl)) cmponentInfo = component.get(prevEl);
|
|
52
|
+
} else if (component.is(formatEl.previousElementSibling)) {
|
|
53
|
+
cmponentInfo = component.get(formatEl.previousElementSibling);
|
|
54
|
+
}
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
break;
|
|
@@ -44,16 +60,6 @@ export function reduceArrowDown(actions, ports, ctx) {
|
|
|
44
60
|
cmponentInfo = component.get(formatEl.nextElementSibling);
|
|
45
61
|
}
|
|
46
62
|
break;
|
|
47
|
-
case 'ArrowRight' /** right key */:
|
|
48
|
-
if (dom.check.isEdgePoint(selectionNode, range.endOffset, 'end')) {
|
|
49
|
-
const nextEl = selectionNode.nextElementSibling || dom.query.getNextDeepestNode(selectionNode);
|
|
50
|
-
if (nextEl) {
|
|
51
|
-
if (component.is(nextEl)) cmponentInfo = component.get(nextEl);
|
|
52
|
-
} else if (component.is(formatEl.nextElementSibling)) {
|
|
53
|
-
cmponentInfo = component.get(formatEl.nextElementSibling);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
break;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
if (cmponentInfo && !cmponentInfo.options?.isInputComponent) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dom } from '../../../helper';
|
|
2
|
-
import { cleanRemovedTags, hardDelete, isUneditableNode, setDefaultLine } from '../effects/ruleHelpers';
|
|
2
|
+
import { cleanRemovedTags, hardDelete, isUneditableNode, setDefaultLine, isRtlBidiMismatch } from '../effects/ruleHelpers';
|
|
3
3
|
import { A } from '../actions';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -22,6 +22,8 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
22
22
|
let { formatEl } = ctx;
|
|
23
23
|
|
|
24
24
|
const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
|
|
25
|
+
// RTL bidi guard: if offset 0 is actually at the visual end due to LTR text in RTL line, skip front-edge handling
|
|
26
|
+
const bidiNotFront = options.get('_rtl') && !selectRange && range.startOffset === 0 && isRtlBidiMismatch(range, formatEl, 'front', fc.get('_wd'));
|
|
25
27
|
|
|
26
28
|
actions.push(A.componentDeselect());
|
|
27
29
|
actions.push(A.cacheStyleNode());
|
|
@@ -54,6 +56,7 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
54
56
|
// closure, default
|
|
55
57
|
if (
|
|
56
58
|
!selectRange &&
|
|
59
|
+
!bidiNotFront &&
|
|
57
60
|
!formatEl.previousElementSibling &&
|
|
58
61
|
range.startOffset === 0 &&
|
|
59
62
|
!selectionNode.previousSibling &&
|
|
@@ -87,7 +90,7 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
87
90
|
|
|
88
91
|
// clean remove tag
|
|
89
92
|
const startCon = range.startContainer;
|
|
90
|
-
if (formatEl && !formatEl.previousElementSibling && range.startOffset === 0 && startCon.nodeType === 3 && dom.check.isZeroWidth(startCon)) {
|
|
93
|
+
if (formatEl && !bidiNotFront && !formatEl.previousElementSibling && range.startOffset === 0 && startCon.nodeType === 3 && dom.check.isZeroWidth(startCon)) {
|
|
91
94
|
if (cleanRemovedTags(ports, startCon, formatEl) === true) return true;
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -118,7 +121,7 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
// format attributes
|
|
121
|
-
if (!selectRange && format.isEdgeLine(range.startContainer, range.startOffset, 'front')) {
|
|
124
|
+
if (!selectRange && !bidiNotFront && format.isEdgeLine(range.startContainer, range.startOffset, 'front')) {
|
|
122
125
|
if (format.isLine(formatEl.previousElementSibling)) {
|
|
123
126
|
actions.push(A.cacheFormatAttrsTemp(formatEl.previousElementSibling.attributes));
|
|
124
127
|
}
|
|
@@ -134,7 +137,7 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
134
137
|
dom.check.isList(rangeEl) &&
|
|
135
138
|
(dom.check.isListCell(rangeEl.parentElement) || formatEl.previousElementSibling) &&
|
|
136
139
|
(selectionNode === formatEl || (selectionNode.nodeType === 3 && (!selectionNode.previousSibling || dom.check.isList(selectionNode.previousSibling)))) &&
|
|
137
|
-
(format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null) ? rangeEl.contains(range.startContainer) : range.startOffset === 0 && range.collapsed)
|
|
140
|
+
(format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null) ? rangeEl.contains(range.startContainer) : range.startOffset === 0 && range.collapsed && !bidiNotFront)
|
|
138
141
|
) {
|
|
139
142
|
if (range.startContainer !== range.endContainer) {
|
|
140
143
|
actions.push(A.prevent());
|
|
@@ -163,7 +166,7 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
// detach range
|
|
166
|
-
if (!selectRange && range.startOffset === 0) {
|
|
169
|
+
if (!selectRange && range.startOffset === 0 && !bidiNotFront) {
|
|
167
170
|
let detach = true;
|
|
168
171
|
let comm = commonCon;
|
|
169
172
|
while (comm && comm !== rangeEl && !dom.check.isWysiwygFrame(comm)) {
|
|
@@ -185,6 +188,16 @@ export function reduceBackspaceDown(actions, ports, ctx) {
|
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
|
|
191
|
+
// empty line adjacent to component (offset-independent — handles RTL caret at offset 1)
|
|
192
|
+
if (!selectRange && formatEl && dom.check.isEmptyLine(formatEl) && component.is(formatEl.previousElementSibling)) {
|
|
193
|
+
const fileComponentInfo = component.get(formatEl.previousElementSibling);
|
|
194
|
+
if (fileComponentInfo) {
|
|
195
|
+
actions.push(A.preventStop());
|
|
196
|
+
actions.push(A.backspaceComponentRemove(false, formatEl.firstChild, formatEl, fileComponentInfo));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
// component
|
|
189
202
|
if (!selectRange && formatEl && (range.startOffset === 0 || (selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : false))) {
|
|
190
203
|
const isList = dom.check.isListCell(formatEl);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dom } from '../../../helper';
|
|
2
|
-
import { hardDelete, isUneditableNode } from '../effects/ruleHelpers';
|
|
2
|
+
import { hardDelete, isUneditableNode, isRtlBidiMismatch } from '../effects/ruleHelpers';
|
|
3
3
|
import { A } from '../actions';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -18,10 +18,12 @@ import { A } from '../actions';
|
|
|
18
18
|
*/
|
|
19
19
|
export function reduceDeleteDown(actions, ports, ctx) {
|
|
20
20
|
const { format, component } = ports;
|
|
21
|
-
const { range, selectionNode } = ctx;
|
|
21
|
+
const { fc, range, selectionNode } = ctx;
|
|
22
22
|
let { formatEl } = ctx;
|
|
23
23
|
|
|
24
24
|
const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
|
|
25
|
+
// RTL bidi guard: if offset=length is actually at the visual start due to LTR text in RTL line, skip end-edge handling
|
|
26
|
+
const bidiNotEnd = ctx.options.get('_rtl') && !selectRange && range.endOffset >= (range.endContainer.textContent?.length || 0) && isRtlBidiMismatch(range, formatEl, 'end', fc.get('_wd'));
|
|
25
27
|
|
|
26
28
|
actions.push(A.componentDeselect());
|
|
27
29
|
actions.push(A.cacheStyleNode());
|
|
@@ -31,7 +33,7 @@ export function reduceDeleteDown(actions, ports, ctx) {
|
|
|
31
33
|
return true;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
if (!selectRange && format.isEdgeLine(range.endContainer, range.endOffset, 'end') && !formatEl.nextSibling) {
|
|
36
|
+
if (!selectRange && !bidiNotEnd && format.isEdgeLine(range.endContainer, range.endOffset, 'end') && !formatEl.nextSibling) {
|
|
35
37
|
actions.push(A.preventStop());
|
|
36
38
|
return false;
|
|
37
39
|
}
|
|
@@ -107,7 +109,7 @@ export function reduceDeleteDown(actions, ports, ctx) {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// format attributes
|
|
110
|
-
if (!selectRange && format.isEdgeLine(range.endContainer, range.endOffset, 'end')) {
|
|
112
|
+
if (!selectRange && !bidiNotEnd && format.isEdgeLine(range.endContainer, range.endOffset, 'end')) {
|
|
111
113
|
if (format.isLine(formatEl.nextElementSibling)) {
|
|
112
114
|
actions.push(A.cacheFormatAttrsTemp(formatEl.attributes));
|
|
113
115
|
}
|
|
@@ -122,7 +124,7 @@ export function reduceDeleteDown(actions, ports, ctx) {
|
|
|
122
124
|
(selectionNode === formatEl ||
|
|
123
125
|
(selectionNode.nodeType === 3 &&
|
|
124
126
|
(!selectionNode.nextSibling || dom.check.isList(selectionNode.nextSibling)) &&
|
|
125
|
-
(format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null) ? rangeEl.contains(range.endContainer) : range.endOffset === selectionNode.textContent.length && range.collapsed)))
|
|
127
|
+
(format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null) ? rangeEl.contains(range.endContainer) : range.endOffset === selectionNode.textContent.length && range.collapsed && !bidiNotEnd)))
|
|
126
128
|
) {
|
|
127
129
|
actions.push(A.deleteListRemoveNested(range, formatEl, rangeEl));
|
|
128
130
|
return true;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dom } from '../../../helper';
|
|
2
|
+
import { isRtlBidiMismatch } from '../effects/ruleHelpers';
|
|
2
3
|
import { A } from '../actions';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -40,8 +41,24 @@ export function reduceEnterDown(actions, ports, ctx) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
if (!shift) {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
let formatEndEdge = format.isEdgeLine(range.endContainer, range.endOffset, 'end');
|
|
45
|
+
let formatStartEdge = format.isEdgeLine(range.startContainer, range.startOffset, 'front');
|
|
46
|
+
|
|
47
|
+
// RTL bidi edge correction: when LTR text (e.g. numbers) is inside an RTL line,
|
|
48
|
+
// the browser places the caret at the logically opposite offset at bidi boundaries.
|
|
49
|
+
let bidiSwapped = false;
|
|
50
|
+
if (ctx.options.get('_rtl') && formatEl && range.collapsed && formatStartEdge !== formatEndEdge) {
|
|
51
|
+
const _wd = ctx.fc.get('_wd');
|
|
52
|
+
if (formatStartEdge && isRtlBidiMismatch(range, formatEl, 'front', _wd)) {
|
|
53
|
+
formatStartEdge = false;
|
|
54
|
+
formatEndEdge = true;
|
|
55
|
+
bidiSwapped = true;
|
|
56
|
+
} else if (formatEndEdge && isRtlBidiMismatch(range, formatEl, 'end', _wd)) {
|
|
57
|
+
formatEndEdge = false;
|
|
58
|
+
formatStartEdge = true;
|
|
59
|
+
bidiSwapped = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
|
|
46
63
|
// add default format line
|
|
47
64
|
if (formatEndEdge && (/^H[1-6]$/i.test(formatEl.nodeName) || /^HR$/i.test(formatEl.nodeName))) {
|
|
@@ -115,7 +132,7 @@ export function reduceEnterDown(actions, ports, ctx) {
|
|
|
115
132
|
// set format attrs - edge
|
|
116
133
|
if (range.collapsed && (formatStartEdge || formatEndEdge)) {
|
|
117
134
|
ports.enterPrevent(e);
|
|
118
|
-
actions.push(A.enterFormatBreakAtEdge(formatEl, selectionNode, formatStartEdge, formatEndEdge));
|
|
135
|
+
actions.push(A.enterFormatBreakAtEdge(formatEl, selectionNode, formatStartEdge, formatEndEdge, bidiSwapped));
|
|
119
136
|
actions.push(A.enterScrollTo(range));
|
|
120
137
|
return true;
|
|
121
138
|
}
|
|
@@ -125,6 +142,19 @@ export function reduceEnterDown(actions, ports, ctx) {
|
|
|
125
142
|
|
|
126
143
|
/** @type {HTMLElement} */
|
|
127
144
|
if (selectRange) {
|
|
145
|
+
// RTL bidi edge correction for selection ranges (formatStartEdge only)
|
|
146
|
+
// formatEndEdge insert-after behavior is already correct for RTL selections
|
|
147
|
+
if (ctx.options.get('_rtl') && formatStartEdge && !formatEndEdge) {
|
|
148
|
+
const _wd = ctx.fc.get('_wd');
|
|
149
|
+
const testRange = _wd.createRange();
|
|
150
|
+
testRange.setStart(range.startContainer, range.startOffset);
|
|
151
|
+
testRange.collapse(true);
|
|
152
|
+
if (isRtlBidiMismatch(testRange, formatEl, 'front', _wd)) {
|
|
153
|
+
formatStartEdge = false;
|
|
154
|
+
formatEndEdge = true;
|
|
155
|
+
bidiSwapped = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
128
158
|
actions.push(A.enterFormatBreakWithSelection(formatEl, range, formatStartEdge, formatEndEdge));
|
|
129
159
|
} else {
|
|
130
160
|
actions.push(A.enterFormatBreakAtCursor(formatEl, range));
|
|
@@ -122,6 +122,7 @@ class Menu {
|
|
|
122
122
|
this.#setMenuPosition(btnEl, menu);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
this.#context.get('menuTray').showPopover?.();
|
|
125
126
|
this.#bindClose_dropdown_mouse = this.#eventManager.addGlobalEvent('mousedown', this.#globalEventHandler.mousedown, false);
|
|
126
127
|
if (this.#dropdownCommands.includes(dropdownName)) {
|
|
127
128
|
this.menus = converter.nodeListToArray(menu.querySelectorAll('[data-command]'));
|
|
@@ -162,6 +163,7 @@ class Menu {
|
|
|
162
163
|
}
|
|
163
164
|
this.currentDropdownActiveButton = null;
|
|
164
165
|
this.#$.ui.preventToolbarHide(false);
|
|
166
|
+
this.#context.get('menuTray').hidePopover?.();
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
this.#store.set('_preventBlur', false);
|
|
@@ -209,6 +211,7 @@ class Menu {
|
|
|
209
211
|
this.#setMenuPosition(button, this.currentContainer);
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
this.#context.get('menuTray').showPopover?.();
|
|
212
215
|
this.#bindClose_cons_mouse = this.#eventManager.addGlobalEvent('mousedown', this.#globalEventHandler.containerDown, false);
|
|
213
216
|
|
|
214
217
|
if (this.#$.plugins[containerName].on) this.#$.plugins[containerName].on(button);
|
|
@@ -229,6 +232,7 @@ class Menu {
|
|
|
229
232
|
dom.utils.removeClass(this.currentContainerActiveButton, 'on');
|
|
230
233
|
this.currentContainerActiveButton = null;
|
|
231
234
|
this.#$.ui.preventToolbarHide(false);
|
|
235
|
+
this.#context.get('menuTray').hidePopover?.();
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
this.#store.set('_preventBlur', false);
|