svedit 0.6.0 → 0.6.2
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/AnnotatedTextProperty.svelte +18 -12
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -1
- package/dist/Svedit.svelte +53 -59
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/AnnotatedTextProperty.svelte +18 -12
- package/src/lib/Svedit.svelte +53 -59
|
@@ -16,8 +16,18 @@
|
|
|
16
16
|
);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
let plain_text = $derived(svedit.session.get(path).text);
|
|
20
|
+
let is_empty = $derived(
|
|
21
|
+
get_char_length(plain_text) === 0 && !(svedit.is_composing && is_focused)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
let is_collapsed = $derived(
|
|
25
|
+
is_focused && svedit.session.selection?.anchor_offset == svedit.session.selection?.focus_offset
|
|
26
|
+
);
|
|
27
|
+
|
|
19
28
|
// Get selection highlight range if not inside an annotation
|
|
20
29
|
let selection_highlight_range = $derived.by(() => {
|
|
30
|
+
if (is_collapsed) return null;
|
|
21
31
|
if (!is_focused) return null;
|
|
22
32
|
if (svedit.session.active_annotation()) return null;
|
|
23
33
|
const sel = svedit.session.selection;
|
|
@@ -25,6 +35,14 @@
|
|
|
25
35
|
return get_selection_range(sel);
|
|
26
36
|
});
|
|
27
37
|
|
|
38
|
+
let fragments = $derived(
|
|
39
|
+
get_fragments(
|
|
40
|
+
svedit.session.get(path).text,
|
|
41
|
+
svedit.session.get(path).annotations,
|
|
42
|
+
selection_highlight_range
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* Converts text with annotations into renderable fragments for display.
|
|
30
48
|
* @param {string} text - The plain text content
|
|
@@ -78,18 +96,6 @@
|
|
|
78
96
|
|
|
79
97
|
return fragments;
|
|
80
98
|
}
|
|
81
|
-
|
|
82
|
-
let fragments = $derived(
|
|
83
|
-
get_fragments(
|
|
84
|
-
svedit.session.get(path).text,
|
|
85
|
-
svedit.session.get(path).annotations,
|
|
86
|
-
selection_highlight_range
|
|
87
|
-
)
|
|
88
|
-
);
|
|
89
|
-
let plain_text = $derived(svedit.session.get(path).text);
|
|
90
|
-
let is_empty = $derived(
|
|
91
|
-
get_char_length(plain_text) === 0 && !(svedit.is_composing && is_focused)
|
|
92
|
-
);
|
|
93
99
|
</script>
|
|
94
100
|
|
|
95
101
|
<!-- ATTENTION: The comments are needed to prevent unwanted text nodes with whitespace. -->
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnnotatedTextProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AnnotatedTextProperty.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"AnnotatedTextProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AnnotatedTextProperty.svelte.js"],"names":[],"mappings":";;;;;AA6HA,2HAAoE"}
|
package/dist/Svedit.svelte
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
snake_to_pascal,
|
|
5
5
|
get_char_length,
|
|
6
6
|
utf16_to_char_offset,
|
|
7
|
-
char_to_utf16_offset
|
|
8
|
-
get_char_at
|
|
7
|
+
char_to_utf16_offset
|
|
9
8
|
} from './utils.js';
|
|
10
9
|
|
|
11
10
|
/** @import {
|
|
@@ -68,7 +67,9 @@
|
|
|
68
67
|
const key_mapper = getContext('key_mapper');
|
|
69
68
|
|
|
70
69
|
// Initialize commands and keymap on the session
|
|
71
|
-
|
|
70
|
+
$effect(() => {
|
|
71
|
+
session.initialize_commands(context);
|
|
72
|
+
});
|
|
72
73
|
|
|
73
74
|
// Handle focus - push session's keymap onto stack
|
|
74
75
|
function handle_canvas_focus() {
|
|
@@ -949,36 +950,44 @@ ${fallback_html}`;
|
|
|
949
950
|
if (!path) return null;
|
|
950
951
|
|
|
951
952
|
// EDGE CASE 1B: Cursor after trailing <br> at end of text
|
|
953
|
+
//
|
|
954
|
+
// AnnotatedTextProperty renders a trailing <br> for non-empty or non-focused text.
|
|
955
|
+
// When the user places their cursor after this <br>, focusNode is the container
|
|
956
|
+
// element (not a text node), and normal processing would return position 0.
|
|
957
|
+
// We detect this and return text_length instead.
|
|
952
958
|
const text_content = session.get(path).text;
|
|
953
959
|
const text_length = get_char_length(text_content);
|
|
954
|
-
|
|
955
|
-
|
|
960
|
+
const child_nodes = focus_root.childNodes;
|
|
961
|
+
|
|
962
|
+
if (
|
|
963
|
+
focus_node === anchor_node &&
|
|
964
|
+
focus_node === focus_root &&
|
|
965
|
+
focus_root.dataset?.type === 'text' &&
|
|
966
|
+
!focus_root.classList.contains('empty')
|
|
967
|
+
) {
|
|
968
|
+
// Find the last non-comment child node (comments are inserted by Svelte)
|
|
969
|
+
let last_element_index = child_nodes.length - 1;
|
|
970
|
+
while (last_element_index >= 0 && child_nodes[last_element_index].nodeType === Node.COMMENT_NODE) {
|
|
971
|
+
last_element_index--;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Check if cursor is at or after the trailing <br>
|
|
956
975
|
if (
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
!focus_root.classList.contains('empty') &&
|
|
961
|
-
last_char === '\n'
|
|
976
|
+
last_element_index >= 0 &&
|
|
977
|
+
child_nodes[last_element_index].nodeName === 'BR' &&
|
|
978
|
+
focus_offset_in_node >= last_element_index
|
|
962
979
|
) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
) {
|
|
970
|
-
return {
|
|
971
|
-
type: 'text',
|
|
972
|
-
path,
|
|
973
|
-
anchor_offset: text_length,
|
|
974
|
-
focus_offset: text_length
|
|
975
|
-
};
|
|
976
|
-
}
|
|
980
|
+
return {
|
|
981
|
+
type: 'text',
|
|
982
|
+
path,
|
|
983
|
+
anchor_offset: text_length,
|
|
984
|
+
focus_offset: text_length
|
|
985
|
+
};
|
|
977
986
|
}
|
|
978
987
|
}
|
|
979
988
|
|
|
980
|
-
let
|
|
981
|
-
let
|
|
989
|
+
let start_offset = 0;
|
|
990
|
+
let end_offset = 0;
|
|
982
991
|
let current_offset = 0;
|
|
983
992
|
|
|
984
993
|
function processNode(node) {
|
|
@@ -988,12 +997,12 @@ ${fallback_html}`;
|
|
|
988
997
|
if (node === range.startContainer) {
|
|
989
998
|
// Convert UTF-16 offset to character offset
|
|
990
999
|
const char_start_offset = utf16_to_char_offset(nodeText, range.startOffset);
|
|
991
|
-
|
|
1000
|
+
start_offset = current_offset + char_start_offset;
|
|
992
1001
|
}
|
|
993
1002
|
if (node === range.endContainer) {
|
|
994
1003
|
// Convert UTF-16 offset to character offset
|
|
995
1004
|
const char_end_offset = utf16_to_char_offset(nodeText, range.endOffset);
|
|
996
|
-
|
|
1005
|
+
end_offset = current_offset + char_end_offset;
|
|
997
1006
|
}
|
|
998
1007
|
current_offset += nodeCharLength;
|
|
999
1008
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
@@ -1001,7 +1010,7 @@ ${fallback_html}`;
|
|
|
1001
1010
|
processNode(childNode);
|
|
1002
1011
|
}
|
|
1003
1012
|
}
|
|
1004
|
-
return
|
|
1013
|
+
return end_offset !== 0;
|
|
1005
1014
|
}
|
|
1006
1015
|
|
|
1007
1016
|
// Process nodes to find offsets
|
|
@@ -1012,21 +1021,17 @@ ${fallback_html}`;
|
|
|
1012
1021
|
// Check if it's a backward selection
|
|
1013
1022
|
// When range is provided, we can't detect backward selection from the range alone
|
|
1014
1023
|
// since ranges are always normalized (start before end)
|
|
1015
|
-
const is_backward = dom_selection
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
// Swap offsets if it's a backward selection
|
|
1021
|
-
if (is_backward) {
|
|
1022
|
-
[anchor_offset, focus_offset] = [focus_offset, anchor_offset];
|
|
1023
|
-
}
|
|
1024
|
+
const is_backward = dom_selection ? __is_dom_selection_backwards() : false;
|
|
1025
|
+
|
|
1026
|
+
// Assign to anchor/focus based on direction
|
|
1027
|
+
const anchor_offset = is_backward ? end_offset : start_offset;
|
|
1028
|
+
const focus_offset = is_backward ? start_offset : end_offset;
|
|
1024
1029
|
|
|
1025
1030
|
return {
|
|
1026
1031
|
type: 'text',
|
|
1027
1032
|
path,
|
|
1028
|
-
anchor_offset
|
|
1029
|
-
focus_offset
|
|
1033
|
+
anchor_offset,
|
|
1034
|
+
focus_offset
|
|
1030
1035
|
};
|
|
1031
1036
|
}
|
|
1032
1037
|
|
|
@@ -1163,8 +1168,6 @@ ${fallback_html}`;
|
|
|
1163
1168
|
`[data-path="${selection.path.join('.')}"][data-type="text"]`
|
|
1164
1169
|
);
|
|
1165
1170
|
const empty_text = session.get(selection.path).text.length === 0;
|
|
1166
|
-
|
|
1167
|
-
const range = window.document.createRange();
|
|
1168
1171
|
const dom_selection = window.getSelection();
|
|
1169
1172
|
let current_offset = 0;
|
|
1170
1173
|
/** @type {HTMLElement | Text} */
|
|
@@ -1241,24 +1244,14 @@ ${fallback_html}`;
|
|
|
1241
1244
|
|
|
1242
1245
|
// Set the range if both start and end were found
|
|
1243
1246
|
if (anchor_node && focus_node) {
|
|
1244
|
-
// Always set range in document order (start to end)
|
|
1245
|
-
if (is_backward) {
|
|
1246
|
-
range.setStart(focus_node, focus_node_offset);
|
|
1247
|
-
range.setEnd(anchor_node, anchor_node_offset);
|
|
1248
|
-
} else {
|
|
1249
|
-
range.setStart(anchor_node, anchor_node_offset);
|
|
1250
|
-
range.setEnd(focus_node, focus_node_offset);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
1247
|
dom_selection.removeAllRanges();
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1248
|
+
// NOTE: Only using setBaseAndExtent() will preserve selection direction
|
|
1249
|
+
dom_selection.setBaseAndExtent(
|
|
1250
|
+
anchor_node,
|
|
1251
|
+
anchor_node_offset,
|
|
1252
|
+
focus_node,
|
|
1253
|
+
focus_node_offset
|
|
1254
|
+
);
|
|
1262
1255
|
el.focus(); // needed?
|
|
1263
1256
|
|
|
1264
1257
|
// Scroll the selection into view
|
|
@@ -1323,6 +1316,7 @@ ${fallback_html}`;
|
|
|
1323
1316
|
onfocus={handle_canvas_focus}
|
|
1324
1317
|
onblur={handle_canvas_blur}
|
|
1325
1318
|
contenteditable={editable ? 'true' : 'false'}
|
|
1319
|
+
tabindex="-1"
|
|
1326
1320
|
{autocapitalize}
|
|
1327
1321
|
{spellcheck}
|
|
1328
1322
|
{...{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Svedit.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Svedit.svelte.js"],"names":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"Svedit.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Svedit.svelte.js"],"names":[],"mappings":";;;;;;;AA+yCA;;eAAqD;iCAxxC1C,cAAc"}
|
package/package.json
CHANGED
|
@@ -16,8 +16,18 @@
|
|
|
16
16
|
);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
let plain_text = $derived(svedit.session.get(path).text);
|
|
20
|
+
let is_empty = $derived(
|
|
21
|
+
get_char_length(plain_text) === 0 && !(svedit.is_composing && is_focused)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
let is_collapsed = $derived(
|
|
25
|
+
is_focused && svedit.session.selection?.anchor_offset == svedit.session.selection?.focus_offset
|
|
26
|
+
);
|
|
27
|
+
|
|
19
28
|
// Get selection highlight range if not inside an annotation
|
|
20
29
|
let selection_highlight_range = $derived.by(() => {
|
|
30
|
+
if (is_collapsed) return null;
|
|
21
31
|
if (!is_focused) return null;
|
|
22
32
|
if (svedit.session.active_annotation()) return null;
|
|
23
33
|
const sel = svedit.session.selection;
|
|
@@ -25,6 +35,14 @@
|
|
|
25
35
|
return get_selection_range(sel);
|
|
26
36
|
});
|
|
27
37
|
|
|
38
|
+
let fragments = $derived(
|
|
39
|
+
get_fragments(
|
|
40
|
+
svedit.session.get(path).text,
|
|
41
|
+
svedit.session.get(path).annotations,
|
|
42
|
+
selection_highlight_range
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* Converts text with annotations into renderable fragments for display.
|
|
30
48
|
* @param {string} text - The plain text content
|
|
@@ -78,18 +96,6 @@
|
|
|
78
96
|
|
|
79
97
|
return fragments;
|
|
80
98
|
}
|
|
81
|
-
|
|
82
|
-
let fragments = $derived(
|
|
83
|
-
get_fragments(
|
|
84
|
-
svedit.session.get(path).text,
|
|
85
|
-
svedit.session.get(path).annotations,
|
|
86
|
-
selection_highlight_range
|
|
87
|
-
)
|
|
88
|
-
);
|
|
89
|
-
let plain_text = $derived(svedit.session.get(path).text);
|
|
90
|
-
let is_empty = $derived(
|
|
91
|
-
get_char_length(plain_text) === 0 && !(svedit.is_composing && is_focused)
|
|
92
|
-
);
|
|
93
99
|
</script>
|
|
94
100
|
|
|
95
101
|
<!-- ATTENTION: The comments are needed to prevent unwanted text nodes with whitespace. -->
|
package/src/lib/Svedit.svelte
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
snake_to_pascal,
|
|
5
5
|
get_char_length,
|
|
6
6
|
utf16_to_char_offset,
|
|
7
|
-
char_to_utf16_offset
|
|
8
|
-
get_char_at
|
|
7
|
+
char_to_utf16_offset
|
|
9
8
|
} from './utils.js';
|
|
10
9
|
|
|
11
10
|
/** @import {
|
|
@@ -68,7 +67,9 @@
|
|
|
68
67
|
const key_mapper = getContext('key_mapper');
|
|
69
68
|
|
|
70
69
|
// Initialize commands and keymap on the session
|
|
71
|
-
|
|
70
|
+
$effect(() => {
|
|
71
|
+
session.initialize_commands(context);
|
|
72
|
+
});
|
|
72
73
|
|
|
73
74
|
// Handle focus - push session's keymap onto stack
|
|
74
75
|
function handle_canvas_focus() {
|
|
@@ -949,36 +950,44 @@ ${fallback_html}`;
|
|
|
949
950
|
if (!path) return null;
|
|
950
951
|
|
|
951
952
|
// EDGE CASE 1B: Cursor after trailing <br> at end of text
|
|
953
|
+
//
|
|
954
|
+
// AnnotatedTextProperty renders a trailing <br> for non-empty or non-focused text.
|
|
955
|
+
// When the user places their cursor after this <br>, focusNode is the container
|
|
956
|
+
// element (not a text node), and normal processing would return position 0.
|
|
957
|
+
// We detect this and return text_length instead.
|
|
952
958
|
const text_content = session.get(path).text;
|
|
953
959
|
const text_length = get_char_length(text_content);
|
|
954
|
-
|
|
955
|
-
|
|
960
|
+
const child_nodes = focus_root.childNodes;
|
|
961
|
+
|
|
962
|
+
if (
|
|
963
|
+
focus_node === anchor_node &&
|
|
964
|
+
focus_node === focus_root &&
|
|
965
|
+
focus_root.dataset?.type === 'text' &&
|
|
966
|
+
!focus_root.classList.contains('empty')
|
|
967
|
+
) {
|
|
968
|
+
// Find the last non-comment child node (comments are inserted by Svelte)
|
|
969
|
+
let last_element_index = child_nodes.length - 1;
|
|
970
|
+
while (last_element_index >= 0 && child_nodes[last_element_index].nodeType === Node.COMMENT_NODE) {
|
|
971
|
+
last_element_index--;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Check if cursor is at or after the trailing <br>
|
|
956
975
|
if (
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
!focus_root.classList.contains('empty') &&
|
|
961
|
-
last_char === '\n'
|
|
976
|
+
last_element_index >= 0 &&
|
|
977
|
+
child_nodes[last_element_index].nodeName === 'BR' &&
|
|
978
|
+
focus_offset_in_node >= last_element_index
|
|
962
979
|
) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
) {
|
|
970
|
-
return {
|
|
971
|
-
type: 'text',
|
|
972
|
-
path,
|
|
973
|
-
anchor_offset: text_length,
|
|
974
|
-
focus_offset: text_length
|
|
975
|
-
};
|
|
976
|
-
}
|
|
980
|
+
return {
|
|
981
|
+
type: 'text',
|
|
982
|
+
path,
|
|
983
|
+
anchor_offset: text_length,
|
|
984
|
+
focus_offset: text_length
|
|
985
|
+
};
|
|
977
986
|
}
|
|
978
987
|
}
|
|
979
988
|
|
|
980
|
-
let
|
|
981
|
-
let
|
|
989
|
+
let start_offset = 0;
|
|
990
|
+
let end_offset = 0;
|
|
982
991
|
let current_offset = 0;
|
|
983
992
|
|
|
984
993
|
function processNode(node) {
|
|
@@ -988,12 +997,12 @@ ${fallback_html}`;
|
|
|
988
997
|
if (node === range.startContainer) {
|
|
989
998
|
// Convert UTF-16 offset to character offset
|
|
990
999
|
const char_start_offset = utf16_to_char_offset(nodeText, range.startOffset);
|
|
991
|
-
|
|
1000
|
+
start_offset = current_offset + char_start_offset;
|
|
992
1001
|
}
|
|
993
1002
|
if (node === range.endContainer) {
|
|
994
1003
|
// Convert UTF-16 offset to character offset
|
|
995
1004
|
const char_end_offset = utf16_to_char_offset(nodeText, range.endOffset);
|
|
996
|
-
|
|
1005
|
+
end_offset = current_offset + char_end_offset;
|
|
997
1006
|
}
|
|
998
1007
|
current_offset += nodeCharLength;
|
|
999
1008
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
@@ -1001,7 +1010,7 @@ ${fallback_html}`;
|
|
|
1001
1010
|
processNode(childNode);
|
|
1002
1011
|
}
|
|
1003
1012
|
}
|
|
1004
|
-
return
|
|
1013
|
+
return end_offset !== 0;
|
|
1005
1014
|
}
|
|
1006
1015
|
|
|
1007
1016
|
// Process nodes to find offsets
|
|
@@ -1012,21 +1021,17 @@ ${fallback_html}`;
|
|
|
1012
1021
|
// Check if it's a backward selection
|
|
1013
1022
|
// When range is provided, we can't detect backward selection from the range alone
|
|
1014
1023
|
// since ranges are always normalized (start before end)
|
|
1015
|
-
const is_backward = dom_selection
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
// Swap offsets if it's a backward selection
|
|
1021
|
-
if (is_backward) {
|
|
1022
|
-
[anchor_offset, focus_offset] = [focus_offset, anchor_offset];
|
|
1023
|
-
}
|
|
1024
|
+
const is_backward = dom_selection ? __is_dom_selection_backwards() : false;
|
|
1025
|
+
|
|
1026
|
+
// Assign to anchor/focus based on direction
|
|
1027
|
+
const anchor_offset = is_backward ? end_offset : start_offset;
|
|
1028
|
+
const focus_offset = is_backward ? start_offset : end_offset;
|
|
1024
1029
|
|
|
1025
1030
|
return {
|
|
1026
1031
|
type: 'text',
|
|
1027
1032
|
path,
|
|
1028
|
-
anchor_offset
|
|
1029
|
-
focus_offset
|
|
1033
|
+
anchor_offset,
|
|
1034
|
+
focus_offset
|
|
1030
1035
|
};
|
|
1031
1036
|
}
|
|
1032
1037
|
|
|
@@ -1163,8 +1168,6 @@ ${fallback_html}`;
|
|
|
1163
1168
|
`[data-path="${selection.path.join('.')}"][data-type="text"]`
|
|
1164
1169
|
);
|
|
1165
1170
|
const empty_text = session.get(selection.path).text.length === 0;
|
|
1166
|
-
|
|
1167
|
-
const range = window.document.createRange();
|
|
1168
1171
|
const dom_selection = window.getSelection();
|
|
1169
1172
|
let current_offset = 0;
|
|
1170
1173
|
/** @type {HTMLElement | Text} */
|
|
@@ -1241,24 +1244,14 @@ ${fallback_html}`;
|
|
|
1241
1244
|
|
|
1242
1245
|
// Set the range if both start and end were found
|
|
1243
1246
|
if (anchor_node && focus_node) {
|
|
1244
|
-
// Always set range in document order (start to end)
|
|
1245
|
-
if (is_backward) {
|
|
1246
|
-
range.setStart(focus_node, focus_node_offset);
|
|
1247
|
-
range.setEnd(anchor_node, anchor_node_offset);
|
|
1248
|
-
} else {
|
|
1249
|
-
range.setStart(anchor_node, anchor_node_offset);
|
|
1250
|
-
range.setEnd(focus_node, focus_node_offset);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
1247
|
dom_selection.removeAllRanges();
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1248
|
+
// NOTE: Only using setBaseAndExtent() will preserve selection direction
|
|
1249
|
+
dom_selection.setBaseAndExtent(
|
|
1250
|
+
anchor_node,
|
|
1251
|
+
anchor_node_offset,
|
|
1252
|
+
focus_node,
|
|
1253
|
+
focus_node_offset
|
|
1254
|
+
);
|
|
1262
1255
|
el.focus(); // needed?
|
|
1263
1256
|
|
|
1264
1257
|
// Scroll the selection into view
|
|
@@ -1323,6 +1316,7 @@ ${fallback_html}`;
|
|
|
1323
1316
|
onfocus={handle_canvas_focus}
|
|
1324
1317
|
onblur={handle_canvas_blur}
|
|
1325
1318
|
contenteditable={editable ? 'true' : 'false'}
|
|
1319
|
+
tabindex="-1"
|
|
1326
1320
|
{autocapitalize}
|
|
1327
1321
|
{spellcheck}
|
|
1328
1322
|
{...{
|