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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CustomProperty.svelte.
|
|
1
|
+
{"version":3,"file":"CustomProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CustomProperty.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAuBtD,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A command as required by the key mapper: anything with is_enabled/execute.
|
|
3
|
+
*/
|
|
4
|
+
type KeymapCommand = {
|
|
5
|
+
is_enabled: () => boolean;
|
|
6
|
+
execute: () => unknown;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A keymap maps key combo strings to arrays of commands.
|
|
10
|
+
*/
|
|
11
|
+
export type Keymap = Record<string, KeymapCommand[]>;
|
|
1
12
|
/**
|
|
2
13
|
* Validates and defines a keymap.
|
|
3
14
|
* Throws an error if any key combo is invalid.
|
|
4
15
|
* Valid formats: 'meta+e,ctrl+e' or 'meta+shift+a'
|
|
5
16
|
* Invalid: 'meta+e+a' (only one non-modifier key allowed)
|
|
6
17
|
*/
|
|
7
|
-
export function define_keymap(keymap:
|
|
18
|
+
export declare function define_keymap<K extends Keymap>(keymap: K): K;
|
|
8
19
|
/**
|
|
9
20
|
* KeyMapper manages keyboard shortcuts using a stack-based scope system.
|
|
10
21
|
*
|
|
@@ -17,20 +28,22 @@ export function define_keymap(keymap: any): any;
|
|
|
17
28
|
* mapper.push_scope(editor_keymap); // When editor gains focus
|
|
18
29
|
* mapper.pop_scope(); // When editor loses focus
|
|
19
30
|
*/
|
|
20
|
-
export class KeyMapper {
|
|
21
|
-
scope_stack:
|
|
31
|
+
export declare class KeyMapper {
|
|
32
|
+
scope_stack: Keymap[];
|
|
22
33
|
skip_onkeydown: boolean;
|
|
34
|
+
constructor();
|
|
23
35
|
/**
|
|
24
36
|
* Push a new scope onto the stack (becomes highest priority)
|
|
25
37
|
*/
|
|
26
|
-
push_scope(keymap:
|
|
38
|
+
push_scope(keymap: Keymap): void;
|
|
27
39
|
/**
|
|
28
40
|
* Pop the most recent scope from the stack
|
|
29
41
|
*/
|
|
30
|
-
pop_scope():
|
|
42
|
+
pop_scope(): Keymap | undefined;
|
|
31
43
|
/**
|
|
32
44
|
* Handle keyboard event by trying scopes from top to bottom
|
|
33
45
|
*/
|
|
34
|
-
handle_keydown(event:
|
|
46
|
+
handle_keydown(event: KeyboardEvent): void;
|
|
35
47
|
}
|
|
48
|
+
export {};
|
|
36
49
|
//# sourceMappingURL=KeyMapper.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyMapper.svelte.d.ts","sourceRoot":"","sources":["../src/lib/KeyMapper.svelte.
|
|
1
|
+
{"version":3,"file":"KeyMapper.svelte.d.ts","sourceRoot":"","sources":["../src/lib/KeyMapper.svelte.ts"],"names":[],"mappings":"AAUA;;GAEG;AACH,KAAK,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAA;CAAE,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAgB5D;AA2ED;;;;;;;;;;;GAWG;AACH,qBAAa,SAAS;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;;IAOxB;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKhC;;OAEG;IACH,SAAS,IAAI,MAAM,GAAG,SAAS;IAM/B;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;CAW1C"}
|
package/dist/KeyMapper.svelte.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { is_virtual_keyboard_active } from './utils.js';
|
|
2
|
-
|
|
3
2
|
const MODIFIER_KEYS = ['meta', 'ctrl', 'alt', 'shift'];
|
|
4
3
|
const MODIFIER_EVENT_KEYS = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
meta: 'metaKey',
|
|
5
|
+
ctrl: 'ctrlKey',
|
|
6
|
+
alt: 'altKey',
|
|
7
|
+
shift: 'shiftKey'
|
|
9
8
|
};
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Validates and defines a keymap.
|
|
13
11
|
* Throws an error if any key combo is invalid.
|
|
@@ -15,58 +13,48 @@ const MODIFIER_EVENT_KEYS = {
|
|
|
15
13
|
* Invalid: 'meta+e+a' (only one non-modifier key allowed)
|
|
16
14
|
*/
|
|
17
15
|
export function define_keymap(keymap) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return keymap;
|
|
16
|
+
for (const [key_combo] of Object.entries(keymap)) {
|
|
17
|
+
const alternatives = key_combo.split(',');
|
|
18
|
+
for (const alternative of alternatives) {
|
|
19
|
+
const parts = alternative.trim().toLowerCase().split('+');
|
|
20
|
+
const non_modifiers = parts.filter((part) => !MODIFIER_KEYS.includes(part));
|
|
21
|
+
if (non_modifiers.length !== 1) {
|
|
22
|
+
throw new Error(`Invalid key combo: "${alternative}". Must have exactly one non-modifier key. Found: ${non_modifiers.length}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return keymap;
|
|
33
27
|
}
|
|
34
|
-
|
|
35
28
|
/**
|
|
36
29
|
* Matches a keyboard event against a key combo string.
|
|
37
30
|
* Example: 'meta+e,ctrl+e' matches either (metaKey && key==='e') OR (ctrlKey && key==='e')
|
|
38
31
|
*/
|
|
39
32
|
function matches_key_combo(key_combo, event, virtual_keyboard_active = false) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Check if the key matches
|
|
65
|
-
const key_matches = event.key.toLowerCase() === non_modifier;
|
|
66
|
-
return modifiers_match && no_extra_modifiers && key_matches;
|
|
67
|
-
});
|
|
33
|
+
const alternatives = key_combo.split(',');
|
|
34
|
+
return alternatives.some((alternative) => {
|
|
35
|
+
const parts = alternative.trim().toLowerCase().split('+');
|
|
36
|
+
const modifiers = parts.filter((part) => MODIFIER_KEYS.includes(part));
|
|
37
|
+
const non_modifier = parts.find((part) => !MODIFIER_KEYS.includes(part));
|
|
38
|
+
if (virtual_keyboard_active && modifiers.length > 0) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// Check if all specified modifiers are pressed
|
|
42
|
+
const modifiers_match = modifiers.every((mod) => event[MODIFIER_EVENT_KEYS[mod]]);
|
|
43
|
+
// Check if no unspecified modifiers are pressed
|
|
44
|
+
// NOTE: On virtual keyboards (iOS / Anroid) modifier keys are just ignored, because
|
|
45
|
+
// they must be assumed accidental (e.g. Shift activated as a result of auto-capitalization)
|
|
46
|
+
const no_extra_modifiers = virtual_keyboard_active
|
|
47
|
+
? true
|
|
48
|
+
: MODIFIER_KEYS.every((mod) => {
|
|
49
|
+
if (modifiers.includes(mod))
|
|
50
|
+
return true; // This modifier is expected
|
|
51
|
+
return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
|
|
52
|
+
});
|
|
53
|
+
// Check if the key matches
|
|
54
|
+
const key_matches = event.key.toLowerCase() === non_modifier;
|
|
55
|
+
return modifiers_match && no_extra_modifiers && key_matches;
|
|
56
|
+
});
|
|
68
57
|
}
|
|
69
|
-
|
|
70
58
|
/**
|
|
71
59
|
* Handles a key map by matching keyboard event against registered key combos
|
|
72
60
|
* and executing the first enabled command.
|
|
@@ -75,32 +63,27 @@ function matches_key_combo(key_combo, event, virtual_keyboard_active = false) {
|
|
|
75
63
|
* but don't crash the application.
|
|
76
64
|
*/
|
|
77
65
|
function handle_key_map(key_map, event) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
66
|
+
const virtual_keyboard_active = is_virtual_keyboard_active();
|
|
67
|
+
for (const [key_combo, commands] of Object.entries(key_map)) {
|
|
68
|
+
if (matches_key_combo(key_combo, event, virtual_keyboard_active)) {
|
|
69
|
+
// Find the first enabled command and execute it
|
|
70
|
+
const enabled_command = commands.find((cmd) => cmd.is_enabled());
|
|
71
|
+
if (enabled_command) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
// Execute command (may be sync or async)
|
|
74
|
+
const result = enabled_command.execute();
|
|
75
|
+
// If it's a promise, handle errors (fire-and-forget)
|
|
76
|
+
if (result instanceof Promise) {
|
|
77
|
+
result.catch((err) => {
|
|
78
|
+
console.error('Command execution failed:', err);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
102
86
|
}
|
|
103
|
-
|
|
104
87
|
/**
|
|
105
88
|
* KeyMapper manages keyboard shortcuts using a stack-based scope system.
|
|
106
89
|
*
|
|
@@ -114,40 +97,40 @@ function handle_key_map(key_map, event) {
|
|
|
114
97
|
* mapper.pop_scope(); // When editor loses focus
|
|
115
98
|
*/
|
|
116
99
|
export class KeyMapper {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
100
|
+
scope_stack;
|
|
101
|
+
skip_onkeydown;
|
|
102
|
+
constructor() {
|
|
103
|
+
this.scope_stack = [];
|
|
104
|
+
this.skip_onkeydown = false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Push a new scope onto the stack (becomes highest priority)
|
|
108
|
+
*/
|
|
109
|
+
push_scope(keymap) {
|
|
110
|
+
// console.log('pushed keymap', keymap);
|
|
111
|
+
this.scope_stack.push(keymap);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Pop the most recent scope from the stack
|
|
115
|
+
*/
|
|
116
|
+
pop_scope() {
|
|
117
|
+
const keymap = this.scope_stack.pop();
|
|
118
|
+
// console.log('popped keymap', keymap);
|
|
119
|
+
return keymap;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Handle keyboard event by trying scopes from top to bottom
|
|
123
|
+
*/
|
|
124
|
+
handle_keydown(event) {
|
|
125
|
+
// Key handling temporarily disabled (e.g. while character composition takes place)
|
|
126
|
+
if (this.skip_onkeydown)
|
|
127
|
+
return;
|
|
128
|
+
// console.log('KeyMapper.handle_keydown', event);
|
|
129
|
+
// Try from most specific (top of stack) to most general (bottom)
|
|
130
|
+
for (let i = this.scope_stack.length - 1; i >= 0; i--) {
|
|
131
|
+
if (handle_key_map(this.scope_stack[i], event)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
153
136
|
}
|
package/dist/Node.svelte
CHANGED
|
@@ -1,32 +1,63 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
3
|
import { serialize_path } from './utils.js';
|
|
4
|
+
import type {
|
|
5
|
+
NodeArrayAttachmentContext,
|
|
6
|
+
NodeArrayRenderContext,
|
|
7
|
+
NodeProps,
|
|
8
|
+
SveditRenderContext
|
|
9
|
+
} from './types.js';
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
const svedit = getContext<SveditRenderContext>('svedit');
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** @type {NodeProps} */
|
|
10
|
-
let { path, children, tag = 'div', class: css_class, style = '', ...rest } = $props();
|
|
13
|
+
let { path, children, tag = 'div', class: css_class, style = '', ...rest }: NodeProps = $props();
|
|
11
14
|
|
|
12
15
|
let node = $derived(svedit.session.get(path));
|
|
13
16
|
let path_str = $derived(serialize_path(path));
|
|
14
17
|
|
|
15
|
-
const node_array_meta = getContext('node_array_meta');
|
|
16
|
-
let child_index = $derived(node_array_meta ?
|
|
18
|
+
const node_array_meta = getContext<NodeArrayRenderContext | undefined>('node_array_meta');
|
|
19
|
+
let child_index = $derived(node_array_meta ? (path.at(-1) as number) : -1);
|
|
17
20
|
let is_first = $derived(node_array_meta && child_index === 0);
|
|
18
21
|
let is_last = $derived(node_array_meta && child_index === node_array_meta.length - 1);
|
|
22
|
+
|
|
23
|
+
// Marks and annotations covering this node become classes automatically:
|
|
24
|
+
// `mark-<type>`/`anno-<type>`, plus `-start`/`-end` variants on the run's
|
|
25
|
+
// first/last covered node. This allows styling ranges (including
|
|
26
|
+
// annotations, which never render wrapper components) with pure CSS. For
|
|
27
|
+
// richer rendering, node components still receive the `mark` and
|
|
28
|
+
// `annotations` props.
|
|
29
|
+
let range_classes = $derived.by(() => {
|
|
30
|
+
if (child_index < 0 || !node_array_meta?.annotations_for) return '';
|
|
31
|
+
const classes: string[] = [];
|
|
32
|
+
|
|
33
|
+
const add_classes = (range: NodeArrayAttachmentContext | null | undefined, prefix: string) => {
|
|
34
|
+
const range_type = range?.node?.type;
|
|
35
|
+
if (!range_type) return;
|
|
36
|
+
classes.push(`${prefix}-${range_type}`);
|
|
37
|
+
if (range.is_start) classes.push(`${prefix}-${range_type}-start`);
|
|
38
|
+
if (range.is_end) classes.push(`${prefix}-${range_type}-end`);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
add_classes(node_array_meta.mark_for?.(child_index), 'mark');
|
|
42
|
+
for (const annotation of node_array_meta.annotations_for(child_index)) {
|
|
43
|
+
add_classes(annotation, 'anno');
|
|
44
|
+
}
|
|
45
|
+
return classes.join(' ');
|
|
46
|
+
});
|
|
19
47
|
</script>
|
|
20
48
|
|
|
21
49
|
<svelte:element
|
|
22
50
|
this={tag}
|
|
23
51
|
id={node.id}
|
|
24
|
-
class="node-{node.type} {css_class}{
|
|
52
|
+
class="node-{node.type} {css_class}{range_classes ? ` ${range_classes}` : ''}{is_first
|
|
53
|
+
? ' first'
|
|
54
|
+
: ''}{is_last ? ' last' : ''}"
|
|
25
55
|
data-node-id={node.id}
|
|
26
56
|
data-path={path_str}
|
|
27
57
|
data-type="node"
|
|
28
58
|
style="anchor-name: --{path_str};{style}"
|
|
29
59
|
{...rest}
|
|
60
|
+
{@attach svedit.visibility_registry.track_node(path_str)}
|
|
30
61
|
>
|
|
31
62
|
{@render children()}
|
|
32
63
|
</svelte:element>
|
package/dist/Node.svelte.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import type { NodeProps } from './types.js';
|
|
2
|
+
declare const Node: import("svelte").Component<NodeProps, {}, "">;
|
|
3
|
+
type Node = ReturnType<typeof Node>;
|
|
1
4
|
export default Node;
|
|
2
|
-
type Node = {
|
|
3
|
-
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
-
$set?(props: Partial<NodeProps>): void;
|
|
5
|
-
};
|
|
6
|
-
declare const Node: import("svelte").Component<import("./types.d.ts").NodeProps, {}, "">;
|
|
7
5
|
//# sourceMappingURL=Node.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.
|
|
1
|
+
{"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,SAAS,EAET,MAAM,YAAY,CAAC;AAuDrB,QAAA,MAAM,IAAI,+CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
|
|
@@ -1,29 +1,121 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, setContext } from 'svelte';
|
|
3
3
|
import UnknownNode from './UnknownNode.svelte';
|
|
4
|
-
import { serialize_path } from './utils.js';
|
|
4
|
+
import { serialize_path, calculate_fragment_ranges } from './utils.js';
|
|
5
5
|
import DefaultNodeGap from './NodeGap.svelte';
|
|
6
6
|
import DefaultNodeGapMarkers from './NodeGapMarkers.svelte';
|
|
7
|
+
import type {
|
|
8
|
+
NodeArrayPropertyProps,
|
|
9
|
+
NodeArrayAttachmentContext,
|
|
10
|
+
NodeArrayFragment,
|
|
11
|
+
NodeId,
|
|
12
|
+
Attachment,
|
|
13
|
+
Mark,
|
|
14
|
+
NodeArrayRenderContext,
|
|
15
|
+
SveditRenderContext
|
|
16
|
+
} from './types.js';
|
|
7
17
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const svedit = getContext('svedit');
|
|
18
|
+
const svedit = getContext<SveditRenderContext>('svedit');
|
|
11
19
|
let NodeGap = $derived(svedit.session.config.system_components?.node_gap ?? DefaultNodeGap);
|
|
12
20
|
let NodeGapMarkers = $derived(
|
|
13
21
|
svedit.session.config.system_components?.node_gap_markers ?? DefaultNodeGapMarkers
|
|
14
22
|
);
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
let {
|
|
25
|
+
path,
|
|
26
|
+
tag = 'div',
|
|
27
|
+
class: css_class,
|
|
28
|
+
style = '',
|
|
29
|
+
...rest
|
|
30
|
+
}: NodeArrayPropertyProps = $props();
|
|
18
31
|
|
|
19
32
|
// Pre-joined once per path change; reused by data-path and by every
|
|
20
33
|
// NodeGap's should_position_gap call to avoid N+1 joins per render.
|
|
21
34
|
let path_str = $derived(serialize_path(path));
|
|
22
35
|
|
|
23
|
-
let
|
|
24
|
-
let
|
|
36
|
+
let raw_value = $derived(svedit.session.get(path));
|
|
37
|
+
let node_ids = $derived(raw_value.nodes);
|
|
38
|
+
let marks = $derived(raw_value.marks);
|
|
39
|
+
let annotations = $derived(raw_value.annotations);
|
|
40
|
+
|
|
41
|
+
let fragments = $derived(get_fragments(node_ids, marks));
|
|
42
|
+
|
|
43
|
+
function get_fragments(node_ids: NodeId[], marks: Mark[]): NodeArrayFragment[] {
|
|
44
|
+
const ranges = calculate_fragment_ranges(node_ids.length, marks);
|
|
45
|
+
const fragments: NodeArrayFragment[] = [];
|
|
46
|
+
|
|
47
|
+
for (const range of ranges) {
|
|
48
|
+
const nodes_slice = node_ids.slice(range.start_offset, range.end_offset);
|
|
49
|
+
|
|
50
|
+
if (range.type === 'mark') {
|
|
51
|
+
const node = svedit.session.get(range.node_id);
|
|
52
|
+
fragments.push({
|
|
53
|
+
type: 'mark',
|
|
54
|
+
node,
|
|
55
|
+
nodes: nodes_slice,
|
|
56
|
+
start_index: range.start_offset,
|
|
57
|
+
mark_index: range.mark_index!
|
|
58
|
+
});
|
|
59
|
+
} else if (range.type === 'content') {
|
|
60
|
+
fragments.push({
|
|
61
|
+
type: 'nodes',
|
|
62
|
+
nodes: nodes_slice,
|
|
63
|
+
start_index: range.start_offset
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return fragments;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function get_node_key(node_id: NodeId, index: number): string {
|
|
71
|
+
let occurrence = 0;
|
|
72
|
+
for (let current_index = 0; current_index < index; current_index++) {
|
|
73
|
+
if (node_ids[current_index] === node_id) occurrence += 1;
|
|
74
|
+
}
|
|
75
|
+
return `${node_id}:${occurrence}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function get_range_context(
|
|
79
|
+
range: Attachment,
|
|
80
|
+
index: number,
|
|
81
|
+
node_index: number
|
|
82
|
+
): NodeArrayAttachmentContext {
|
|
83
|
+
const node = svedit.session.get(range.node_id);
|
|
84
|
+
const is_start = node_index === range.start_offset;
|
|
85
|
+
const is_end = node_index === range.end_offset - 1;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...range,
|
|
89
|
+
index,
|
|
90
|
+
node,
|
|
91
|
+
is_start,
|
|
92
|
+
is_middle: !is_start && !is_end,
|
|
93
|
+
is_end
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function get_covering_ranges(
|
|
98
|
+
ranges: Attachment[],
|
|
99
|
+
node_index: number
|
|
100
|
+
): NodeArrayAttachmentContext[] {
|
|
101
|
+
return ranges
|
|
102
|
+
.map((range, index) => ({ range, index }))
|
|
103
|
+
.filter(({ range }) => range.start_offset <= node_index && node_index < range.end_offset)
|
|
104
|
+
.map(({ range, index }) => get_range_context(range, index, node_index));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function get_annotation_contexts(node_index: number): NodeArrayAttachmentContext[] {
|
|
108
|
+
return get_covering_ranges(annotations, node_index);
|
|
109
|
+
}
|
|
25
110
|
|
|
26
|
-
|
|
111
|
+
function get_mark(node_index: number): NodeArrayAttachmentContext | null {
|
|
112
|
+
// The single mark wrapping this node, or null. Mark exclusivity
|
|
113
|
+
// guarantees at most one. Annotations never wrap and are exposed via
|
|
114
|
+
// `annotations` instead.
|
|
115
|
+
return get_covering_ranges(marks, node_index)[0] ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Mirrors the TextProperty pattern: a `focused` class follows
|
|
27
119
|
// the model selection. Used by the CSS rule below to hide the empty-array
|
|
28
120
|
// NodeGap while the caret is in the placeholder, so its <br> isn't a
|
|
29
121
|
// second arrow-key stop (issue #260).
|
|
@@ -32,9 +124,18 @@
|
|
|
32
124
|
serialize_path(svedit.session.selection.path) === path_str
|
|
33
125
|
);
|
|
34
126
|
|
|
35
|
-
setContext('node_array_meta', {
|
|
127
|
+
setContext<NodeArrayRenderContext>('node_array_meta', {
|
|
36
128
|
get length() {
|
|
37
129
|
return node_ids.length;
|
|
130
|
+
},
|
|
131
|
+
// Lets Node self-serve the marks and annotations covering a child, so
|
|
132
|
+
// node wrappers can carry range classes without every node component
|
|
133
|
+
// having to thread the `mark`/`annotations` props through.
|
|
134
|
+
mark_for(node_index: number) {
|
|
135
|
+
return get_mark(node_index);
|
|
136
|
+
},
|
|
137
|
+
annotations_for(node_index: number) {
|
|
138
|
+
return get_annotation_contexts(node_index);
|
|
38
139
|
}
|
|
39
140
|
});
|
|
40
141
|
</script>
|
|
@@ -48,6 +149,7 @@
|
|
|
48
149
|
data-path={path_str}
|
|
49
150
|
style="anchor-name: --{path_str};{style ? ` ${style}` : ''}"
|
|
50
151
|
{...rest}
|
|
152
|
+
{@attach svedit.visibility_registry.track_array(path_str)}
|
|
51
153
|
>
|
|
52
154
|
{#if node_ids.length === 0 && svedit.editable}
|
|
53
155
|
<div
|
|
@@ -55,17 +157,39 @@
|
|
|
55
157
|
data-path={serialize_path([...path, 0])}
|
|
56
158
|
data-type="node"
|
|
57
159
|
style="anchor-name: --{serialize_path([...path, 0])};"
|
|
160
|
+
{@attach svedit.visibility_registry.track_node(serialize_path([...path, 0]))}
|
|
58
161
|
>
|
|
59
162
|
<NodeGap array_path={path} offset={0} count={0} empty />
|
|
60
163
|
</div>
|
|
61
164
|
{/if}
|
|
62
|
-
{#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<
|
|
165
|
+
{#snippet render_nodes(nodes_slice: NodeId[], start_index: number)}
|
|
166
|
+
{#each nodes_slice as id, slice_index (get_node_key(id, start_index + slice_index))}
|
|
167
|
+
{@const index = start_index + slice_index}
|
|
168
|
+
{@const node = svedit.session.get(id)}
|
|
169
|
+
{@const mark = get_mark(index)}
|
|
170
|
+
{@const annotations = get_annotation_contexts(index)}
|
|
171
|
+
<NodeGap array_path={path} offset={index} count={node_ids.length} />
|
|
172
|
+
{@const Component = svedit.session.config.node_components[node.type]}
|
|
173
|
+
{#if Component}
|
|
174
|
+
<Component path={[...path, index]} {mark} {annotations} />
|
|
175
|
+
{:else}
|
|
176
|
+
<UnknownNode path={[...path, index]} />
|
|
177
|
+
{/if}
|
|
178
|
+
{/each}
|
|
179
|
+
{/snippet}
|
|
180
|
+
|
|
181
|
+
{#each fragments as fragment, fragment_index (fragment_index)}
|
|
182
|
+
{#if fragment.type === 'nodes'}
|
|
183
|
+
{@render render_nodes(fragment.nodes, fragment.start_index)}
|
|
184
|
+
{:else if fragment.type === 'mark'}
|
|
185
|
+
{@const MarkComponent = svedit.session.config.node_components[fragment.node.type]}
|
|
186
|
+
{#if MarkComponent}
|
|
187
|
+
<MarkComponent path={[...path, 'marks', fragment.mark_index, 'node_id']}>
|
|
188
|
+
{@render render_nodes(fragment.nodes, fragment.start_index)}
|
|
189
|
+
</MarkComponent>
|
|
190
|
+
{:else}
|
|
191
|
+
{@render render_nodes(fragment.nodes, fragment.start_index)}
|
|
192
|
+
{/if}
|
|
69
193
|
{/if}
|
|
70
194
|
{/each}
|
|
71
195
|
{#if node_ids.length > 0}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import type { NodeArrayPropertyProps } from './types.js';
|
|
2
|
+
declare const NodeArrayProperty: import("svelte").Component<NodeArrayPropertyProps, {}, "">;
|
|
3
|
+
type NodeArrayProperty = ReturnType<typeof NodeArrayProperty>;
|
|
1
4
|
export default NodeArrayProperty;
|
|
2
|
-
type NodeArrayProperty = {
|
|
3
|
-
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
-
$set?(props: Partial<NodeArrayPropertyProps>): void;
|
|
5
|
-
};
|
|
6
|
-
declare const NodeArrayProperty: import("svelte").Component<import("./types.d.ts").NodeArrayPropertyProps, {}, "">;
|
|
7
5
|
//# sourceMappingURL=NodeArrayProperty.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.
|
|
1
|
+
{"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,sBAAsB,EAQtB,MAAM,YAAY,CAAC;AAwLrB,QAAA,MAAM,iBAAiB,4DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
package/dist/NodeCaret.svelte
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
export default NodeCaret;
|
|
2
|
-
type NodeCaret = SvelteComponent<{
|
|
3
|
-
[x: string]: never;
|
|
4
|
-
}, {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
}, {}> & {
|
|
7
|
-
$$bindings?: string;
|
|
8
|
-
};
|
|
9
|
-
declare const NodeCaret: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
-
[x: string]: never;
|
|
11
|
-
}, {
|
|
12
|
-
[evt: string]: CustomEvent<any>;
|
|
13
|
-
}, {}, {}, string>;
|
|
14
1
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
-
new (options: import(
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
16
3
|
$$bindings?: Bindings;
|
|
17
4
|
} & Exports;
|
|
18
5
|
(internal: unknown, props: {
|
|
@@ -24,4 +11,9 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
|
|
|
24
11
|
};
|
|
25
12
|
z_$$bindings?: Bindings;
|
|
26
13
|
}
|
|
14
|
+
declare const NodeCaret: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type NodeCaret = InstanceType<typeof NodeCaret>;
|
|
18
|
+
export default NodeCaret;
|
|
27
19
|
//# sourceMappingURL=NodeCaret.svelte.d.ts.map
|