svedit 0.11.1 → 0.13.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.
- package/README.md +260 -82
- package/dist/Command.svelte.d.ts +54 -29
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +286 -283
- package/dist/CustomProperty.svelte +3 -5
- package/dist/CustomProperty.svelte.d.ts +3 -6
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.d.ts +19 -6
- package/dist/KeyMapper.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.js +95 -112
- package/dist/Node.svelte +40 -9
- package/dist/Node.svelte.d.ts +3 -5
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +142 -18
- package/dist/NodeArrayProperty.svelte.d.ts +3 -5
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeCaret.svelte +1 -1
- package/dist/NodeCaret.svelte.d.ts +6 -14
- package/dist/NodeCaret.svelte.d.ts.map +1 -1
- package/dist/NodeGap.svelte +60 -12
- package/dist/NodeGap.svelte.d.ts +7 -14
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +19 -10
- package/dist/NodeGapMarkers.svelte.d.ts +6 -11
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/NodeSelectionMarkers.svelte +20 -6
- package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
- package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.d.ts +85 -116
- package/dist/Session.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.js +543 -571
- package/dist/Svedit.svelte +243 -216
- package/dist/Svedit.svelte.d.ts +30 -10
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
- package/dist/TextProperty.svelte.d.ts +5 -0
- package/dist/TextProperty.svelte.d.ts.map +1 -0
- package/dist/Transaction.svelte.d.ts +101 -126
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +843 -827
- package/dist/UnknownNode.svelte +4 -3
- package/dist/UnknownNode.svelte.d.ts +5 -9
- package/dist/UnknownNode.svelte.d.ts.map +1 -1
- package/dist/doc_utils.d.ts +109 -122
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +726 -698
- package/dist/index.d.ts +20 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -18
- package/dist/node_visibility.svelte.d.ts +132 -5
- package/dist/node_visibility.svelte.d.ts.map +1 -1
- package/dist/node_visibility.svelte.js +535 -642
- package/dist/paste_utils.d.ts +11 -43
- package/dist/paste_utils.d.ts.map +1 -1
- package/dist/paste_utils.js +111 -149
- package/dist/transforms.svelte.d.ts +9 -7
- package/dist/transforms.svelte.d.ts.map +1 -1
- package/dist/transforms.svelte.js +126 -152
- package/dist/types.d.ts +329 -291
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +79 -95
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +392 -305
- package/package.json +69 -67
- package/src/lib/{Command.svelte.js → Command.svelte.ts} +111 -41
- package/src/lib/CustomProperty.svelte +3 -5
- package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
- package/src/lib/Node.svelte +40 -9
- package/src/lib/NodeArrayProperty.svelte +142 -18
- package/src/lib/NodeCaret.svelte +1 -1
- package/src/lib/NodeGap.svelte +60 -12
- package/src/lib/NodeGapMarkers.svelte +19 -10
- package/src/lib/NodeSelectionMarkers.svelte +20 -6
- package/src/lib/Session.svelte.ts +656 -0
- package/src/lib/Svedit.svelte +243 -216
- package/src/lib/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
- package/src/lib/Transaction.svelte.ts +1020 -0
- package/src/lib/UnknownNode.svelte +4 -3
- package/src/lib/doc_utils.ts +1120 -0
- package/src/lib/{index.js → index.ts} +48 -1
- package/src/lib/node_visibility.svelte.ts +658 -0
- package/src/lib/{paste_utils.js → paste_utils.ts} +51 -59
- package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +44 -27
- package/src/lib/{types.d.ts → types.ts} +250 -103
- package/src/lib/utils.ts +660 -0
- package/dist/AnnotatedTextProperty.svelte.d.ts +0 -7
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +0 -1
- package/src/lib/Session.svelte.js +0 -575
- package/src/lib/Transaction.svelte.js +0 -846
- package/src/lib/doc_utils.js +0 -807
- package/src/lib/node_visibility.svelte.js +0 -718
- package/src/lib/utils.js +0 -465
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
3
|
import {
|
|
4
4
|
char_slice,
|
|
5
5
|
get_char_length,
|
|
6
6
|
paths_equal,
|
|
7
7
|
serialize_path,
|
|
8
|
-
get_selection_range
|
|
8
|
+
get_selection_range,
|
|
9
|
+
calculate_fragment_ranges
|
|
9
10
|
} from './utils.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
import type {
|
|
13
|
+
TextPropertyProps,
|
|
14
|
+
Mark,
|
|
15
|
+
Fragment,
|
|
16
|
+
SelectionRange,
|
|
17
|
+
SveditContext
|
|
18
|
+
} from './types.js';
|
|
19
|
+
|
|
20
|
+
const svedit = getContext<SveditContext>('svedit');
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
path,
|
|
24
|
+
class: css_class,
|
|
25
|
+
placeholder = '',
|
|
26
|
+
tag = 'div',
|
|
27
|
+
style = '',
|
|
28
|
+
...rest
|
|
29
|
+
}: TextPropertyProps = $props();
|
|
17
30
|
|
|
18
31
|
let path_str = $derived(serialize_path(path));
|
|
19
32
|
|
|
@@ -23,16 +36,20 @@
|
|
|
23
36
|
);
|
|
24
37
|
});
|
|
25
38
|
|
|
26
|
-
let plain_text = $derived(svedit.session.get(path).
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
39
|
+
let plain_text = $derived(svedit.session.get(path).content);
|
|
40
|
+
// A string has zero grapheme clusters iff it has zero code units, so a
|
|
41
|
+
// plain length check avoids a full Intl.Segmenter pass (this derived
|
|
42
|
+
// re-runs for every text property on every document change).
|
|
43
|
+
let is_empty = $derived(plain_text.length === 0 && !(svedit.is_composing && is_focused));
|
|
30
44
|
|
|
31
|
-
let is_collapsed = $derived(
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
let is_collapsed = $derived.by(() => {
|
|
46
|
+
const selection = svedit.session.selection;
|
|
47
|
+
return (
|
|
48
|
+
is_focused && selection?.type === 'text' && selection.anchor_offset === selection.focus_offset
|
|
49
|
+
);
|
|
50
|
+
});
|
|
34
51
|
|
|
35
|
-
// Get selection highlight range if not
|
|
52
|
+
// Get selection highlight range if it does not touch marks.
|
|
36
53
|
// Only render selection highlight when canvas is NOT focused.
|
|
37
54
|
// This avoids DOM mutations (splitting text nodes for highlight spans)
|
|
38
55
|
// while the user is actively selecting, which would cause selection
|
|
@@ -41,7 +58,7 @@
|
|
|
41
58
|
if (svedit.canvas_focused) return null;
|
|
42
59
|
if (is_collapsed) return null;
|
|
43
60
|
if (!is_focused) return null;
|
|
44
|
-
if (svedit.session.
|
|
61
|
+
if (svedit.session.selected_marks.length > 0) return null;
|
|
45
62
|
const sel = svedit.session.selection;
|
|
46
63
|
if (!sel || sel.type !== 'text') return null;
|
|
47
64
|
return get_selection_range(sel);
|
|
@@ -49,61 +66,57 @@
|
|
|
49
66
|
|
|
50
67
|
let fragments = $derived(
|
|
51
68
|
get_fragments(
|
|
52
|
-
svedit.session.get(path).
|
|
53
|
-
svedit.session.get(path).
|
|
69
|
+
svedit.session.get(path).content,
|
|
70
|
+
svedit.session.get(path).marks,
|
|
54
71
|
selection_highlight_range
|
|
55
72
|
)
|
|
56
73
|
);
|
|
57
74
|
|
|
58
75
|
/**
|
|
59
|
-
* Converts text with
|
|
60
|
-
*
|
|
61
|
-
* @param {Array<Annotation>} annotations - Array of annotations
|
|
62
|
-
* @param {SelectionRange} [selection_highlight_range] - Optional selection highlight range
|
|
63
|
-
* @returns {Array<Fragment>} Array of fragments
|
|
76
|
+
* Converts text with marks into renderable fragments for display.
|
|
77
|
+
* Annotations never fragment the text; they are data-only.
|
|
64
78
|
*/
|
|
65
|
-
function get_fragments(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
function get_fragments(
|
|
80
|
+
text: string,
|
|
81
|
+
marks: Array<Mark>,
|
|
82
|
+
selection_highlight_range?: SelectionRange | null
|
|
83
|
+
): Array<Fragment> {
|
|
84
|
+
// Fast path: no marks and no selection highlight means the whole text
|
|
85
|
+
// is a single content fragment. This runs for EVERY text property on
|
|
86
|
+
// EVERY document change, so skipping the two full Intl.Segmenter passes
|
|
87
|
+
// (get_char_length + char_slice over the whole text) matters at scale.
|
|
88
|
+
if (marks.length === 0 && !selection_highlight_range) {
|
|
89
|
+
return text.length > 0 ? [text] : [];
|
|
90
|
+
}
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
const ranges = calculate_fragment_ranges(
|
|
93
|
+
get_char_length(text),
|
|
94
|
+
marks,
|
|
95
|
+
selection_highlight_range
|
|
96
|
+
);
|
|
97
|
+
const fragments: Array<Fragment> = [];
|
|
75
98
|
|
|
76
99
|
for (const range of ranges) {
|
|
77
|
-
// Add text before this range
|
|
78
|
-
if (range.start_offset > last_index) {
|
|
79
|
-
fragments.push(char_slice(text, last_index, range.start_offset));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
100
|
const content = char_slice(text, range.start_offset, range.end_offset);
|
|
83
101
|
|
|
84
|
-
if ('
|
|
102
|
+
if (range.type === 'mark') {
|
|
85
103
|
const node = svedit.session.get(range.node_id);
|
|
86
|
-
if (!node) throw new Error(`Node not found for
|
|
104
|
+
if (!node) throw new Error(`Node not found for mark ${range.node_id}`);
|
|
87
105
|
|
|
88
106
|
fragments.push({
|
|
89
|
-
type: '
|
|
107
|
+
type: 'mark',
|
|
90
108
|
node,
|
|
91
109
|
content,
|
|
92
|
-
|
|
110
|
+
mark_index: range.mark_index!
|
|
93
111
|
});
|
|
94
|
-
} else {
|
|
112
|
+
} else if (range.type === 'selection_highlight') {
|
|
95
113
|
fragments.push({
|
|
96
114
|
type: 'selection_highlight',
|
|
97
115
|
content
|
|
98
116
|
});
|
|
117
|
+
} else {
|
|
118
|
+
fragments.push(content);
|
|
99
119
|
}
|
|
100
|
-
|
|
101
|
-
last_index = range.end_offset;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Add any remaining text
|
|
105
|
-
if (last_index < get_char_length(text)) {
|
|
106
|
-
fragments.push(char_slice(text, last_index));
|
|
107
120
|
}
|
|
108
121
|
|
|
109
122
|
return fragments;
|
|
@@ -130,12 +143,14 @@
|
|
|
130
143
|
{#if typeof fragment === 'string'}{fragment}{:else if fragment.type === 'selection_highlight'}<span
|
|
131
144
|
class="selection-highlight"
|
|
132
145
|
style="anchor-name: --selection-highlight;">{fragment.content}</span
|
|
133
|
-
>{:else if fragment.type === '
|
|
134
|
-
{@const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
>{:else if fragment.type === 'mark'}
|
|
147
|
+
{@const MarkComponent = svedit.session.config.node_components[fragment.node.type]}
|
|
148
|
+
{#if MarkComponent}
|
|
149
|
+
<MarkComponent
|
|
150
|
+
path={[...path, 'marks', fragment.mark_index, 'node_id']}
|
|
151
|
+
content={fragment.content}
|
|
152
|
+
/>
|
|
153
|
+
{:else}<span class="mark-{fragment.node.type}">{fragment.content}</span>{/if}
|
|
139
154
|
{/if}
|
|
140
155
|
{/each}<!--
|
|
141
156
|
-->{#if !is_focused || !is_empty}<br />{/if}
|