svedit 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +230 -64
  2. package/dist/Command.svelte.d.ts +21 -4
  3. package/dist/Command.svelte.d.ts.map +1 -1
  4. package/dist/Command.svelte.js +85 -27
  5. package/dist/Node.svelte +29 -1
  6. package/dist/Node.svelte.d.ts.map +1 -1
  7. package/dist/NodeArrayProperty.svelte +119 -12
  8. package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
  9. package/dist/NodeGap.svelte +48 -6
  10. package/dist/NodeGap.svelte.d.ts.map +1 -1
  11. package/dist/Session.svelte.d.ts +31 -14
  12. package/dist/Session.svelte.d.ts.map +1 -1
  13. package/dist/Session.svelte.js +155 -77
  14. package/dist/Svedit.svelte +110 -89
  15. package/dist/Svedit.svelte.d.ts.map +1 -1
  16. package/dist/{AnnotatedTextProperty.svelte → TextProperty.svelte} +45 -44
  17. package/dist/TextProperty.svelte.d.ts +7 -0
  18. package/dist/TextProperty.svelte.d.ts.map +1 -0
  19. package/dist/Transaction.svelte.d.ts +78 -40
  20. package/dist/Transaction.svelte.d.ts.map +1 -1
  21. package/dist/Transaction.svelte.js +412 -234
  22. package/dist/doc_utils.d.ts +96 -21
  23. package/dist/doc_utils.d.ts.map +1 -1
  24. package/dist/doc_utils.js +440 -168
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/node_visibility.svelte.d.ts +30 -4
  28. package/dist/node_visibility.svelte.d.ts.map +1 -1
  29. package/dist/node_visibility.svelte.js +327 -363
  30. package/dist/paste_utils.d.ts +3 -2
  31. package/dist/paste_utils.d.ts.map +1 -1
  32. package/dist/paste_utils.js +16 -13
  33. package/dist/transforms.svelte.d.ts.map +1 -1
  34. package/dist/transforms.svelte.js +5 -6
  35. package/dist/types.d.ts +112 -42
  36. package/dist/utils.d.ts +75 -12
  37. package/dist/utils.d.ts.map +1 -1
  38. package/dist/utils.js +292 -78
  39. package/package.json +4 -1
  40. package/src/lib/Command.svelte.js +85 -27
  41. package/src/lib/Node.svelte +29 -1
  42. package/src/lib/NodeArrayProperty.svelte +119 -12
  43. package/src/lib/NodeGap.svelte +48 -6
  44. package/src/lib/Session.svelte.js +155 -77
  45. package/src/lib/Svedit.svelte +110 -89
  46. package/src/lib/{AnnotatedTextProperty.svelte → TextProperty.svelte} +45 -44
  47. package/src/lib/Transaction.svelte.js +412 -234
  48. package/src/lib/doc_utils.js +440 -168
  49. package/src/lib/index.js +1 -1
  50. package/src/lib/node_visibility.svelte.js +327 -363
  51. package/src/lib/paste_utils.js +16 -13
  52. package/src/lib/transforms.svelte.js +5 -6
  53. package/src/lib/types.d.ts +112 -42
  54. package/src/lib/utils.js +292 -78
  55. package/dist/AnnotatedTextProperty.svelte.d.ts +0 -7
  56. package/dist/AnnotatedTextProperty.svelte.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { insert_default_node, break_text_node } from './transforms.svelte.js';
2
- import { can_switch_annotation_type } from './doc_utils.js';
2
+ import { can_switch_mark_type, get_selected_range_types } from './doc_utils.js';
3
3
  import { is_selection_collapsed, get_char_length, char_slice } from './utils.js';
4
4
 
5
5
  /**
@@ -107,10 +107,13 @@ export class SelectParentCommand extends Command {
107
107
  }
108
108
 
109
109
  /**
110
- * Generic command that toggles an annotation on the current text selection.
111
- * Used for simple annotations like bold, italic, highlight, etc.
110
+ * Generic command that toggles a mark on the current text or node selection.
111
+ *
112
+ * Marks are mutually exclusive, so all touched marks compete: same-type marks
113
+ * are removed, a single touched property-less mark may switch type, and mixed
114
+ * touched types disable the command. Annotations never affect mark toggling.
112
115
  */
113
- export class ToggleAnnotationCommand extends Command {
116
+ export class ToggleMarkCommand extends Command {
114
117
  constructor(node_type, context) {
115
118
  super(context);
116
119
  this.node_type = node_type;
@@ -119,34 +122,89 @@ export class ToggleAnnotationCommand extends Command {
119
122
  active = $derived(this.is_active());
120
123
 
121
124
  is_active() {
122
- return this.context.session.active_annotation(this.node_type);
125
+ const selected_marks = this.context.session.selected_marks;
126
+ return (
127
+ selected_marks.length > 0 &&
128
+ selected_marks.every(({ node }) => node?.type === this.node_type)
129
+ );
123
130
  }
124
131
 
125
132
  is_enabled() {
126
133
  const { session, editable } = this.context;
127
- const active_annotation = session.active_annotation();
128
- const has_annotation = session.active_annotation(this.node_type);
129
- const can_switch_annotation =
130
- active_annotation &&
131
- !has_annotation &&
132
- session.available_annotation_types.includes(this.node_type) &&
133
- can_switch_annotation_type(
134
- session.schema,
135
- session.get(active_annotation.node_id),
136
- this.node_type
137
- );
138
- const no_annotation_and_caret_not_collapsed =
139
- !active_annotation && !is_selection_collapsed(session.selection);
134
+ const selection = session.selection;
135
+ const is_valid_selection =
136
+ selection?.type === 'text' ||
137
+ (selection?.type === 'node' && !is_selection_collapsed(selection));
138
+ const mark_type_is_allowed = session.available_mark_types.includes(this.node_type);
139
+ const selected_marks = session.selected_marks;
140
+ const selected_mark_types = get_selected_range_types(selected_marks);
141
+
142
+ if (!editable || !is_valid_selection || !mark_type_is_allowed) return false;
143
+ if (selected_mark_types.size > 1) return false;
144
+
145
+ if (selected_marks.length === 0) {
146
+ return Boolean(selection && !is_selection_collapsed(selection));
147
+ }
140
148
 
141
- return (
142
- editable &&
143
- session.selection?.type === 'text' &&
144
- (has_annotation || can_switch_annotation || no_annotation_and_caret_not_collapsed)
149
+ const first_selected_mark = selected_marks[0];
150
+ const selected_mark_type = first_selected_mark.node.type;
151
+ if (selected_mark_type === this.node_type) return true;
152
+ if (selected_marks.length !== 1) return false;
153
+
154
+ return can_switch_mark_type(session.schema, selected_mark_type, this.node_type);
155
+ }
156
+
157
+ execute() {
158
+ this.context.session.apply(this.context.session.tr.toggle_mark(this.node_type));
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Generic command that toggles an annotation on the current text or node selection.
164
+ *
165
+ * Annotations only compete with touched annotations of the same type: touched
166
+ * same-type annotations are removed, otherwise a new annotation is created.
167
+ * Marks and other annotation types never block the toggle.
168
+ */
169
+ export class ToggleAnnotationCommand extends Command {
170
+ constructor(node_type, context) {
171
+ super(context);
172
+ this.node_type = node_type;
173
+ }
174
+
175
+ active = $derived(this.is_active());
176
+
177
+ // Mirrors the filter in tr.toggle_annotation, so active/enabled match what
178
+ // execute() does.
179
+ relevant_annotations() {
180
+ return this.context.session.selected_annotations.filter(
181
+ ({ node }) => node?.type === this.node_type
145
182
  );
146
183
  }
147
184
 
185
+ is_active() {
186
+ return this.relevant_annotations().length > 0;
187
+ }
188
+
189
+ is_enabled() {
190
+ const { session, editable } = this.context;
191
+ const selection = session.selection;
192
+ const is_valid_selection =
193
+ selection?.type === 'text' ||
194
+ (selection?.type === 'node' && !is_selection_collapsed(selection));
195
+ const annotation_type_is_allowed = session.available_annotation_types.includes(this.node_type);
196
+
197
+ if (!editable || !is_valid_selection || !annotation_type_is_allowed) return false;
198
+
199
+ // Removing touched same-type annotations also works from a collapsed
200
+ // caret inside the annotation; creating a new one requires an expanded
201
+ // selection.
202
+ if (this.relevant_annotations().length > 0) return true;
203
+ return Boolean(selection && !is_selection_collapsed(selection));
204
+ }
205
+
148
206
  execute() {
149
- this.context.session.apply(this.context.session.tr.annotate_text(this.node_type));
207
+ this.context.session.apply(this.context.session.tr.toggle_annotation(this.node_type));
150
208
  }
151
209
  }
152
210
 
@@ -179,7 +237,7 @@ export class AddNewLineCommand extends Command {
179
237
 
180
238
  const collapsed_offset = tr.selection.anchor_offset;
181
239
  const content = tr.get(tr.selection.path);
182
- const text_before_caret = char_slice(content.text, 0, collapsed_offset);
240
+ const text_before_caret = char_slice(content.content, 0, collapsed_offset);
183
241
  const line_start_index = text_before_caret.lastIndexOf('\n') + 1;
184
242
  const current_line_prefix = text_before_caret.slice(line_start_index);
185
243
  const indentation_match = current_line_prefix.match(/^[\t ]*/);
@@ -235,7 +293,7 @@ export class SelectAllCommand extends Command {
235
293
 
236
294
  if (selection.type === 'text') {
237
295
  const text_content = session.get(selection.path);
238
- const text_length = get_char_length(text_content.text);
296
+ const text_length = get_char_length(text_content.content);
239
297
 
240
298
  // Check if all text is already selected
241
299
  const is_all_text_selected =
@@ -278,7 +336,7 @@ export class SelectAllCommand extends Command {
278
336
  // Check if the entire node_array is already selected
279
337
  const is_entire_node_array_selected =
280
338
  Math.min(selection.anchor_offset, selection.focus_offset) === 0 &&
281
- Math.max(selection.anchor_offset, selection.focus_offset) === node_array.length;
339
+ Math.max(selection.anchor_offset, selection.focus_offset) === node_array.nodes.length;
282
340
 
283
341
  if (!is_entire_node_array_selected) {
284
342
  // Select the entire node_array
@@ -286,7 +344,7 @@ export class SelectAllCommand extends Command {
286
344
  type: 'node',
287
345
  path: node_array_path,
288
346
  anchor_offset: 0,
289
- focus_offset: node_array.length
347
+ focus_offset: node_array.nodes.length
290
348
  };
291
349
  } else {
292
350
  // Entire node_array is selected, try to move up to parent node_array
package/dist/Node.svelte CHANGED
@@ -16,17 +16,45 @@
16
16
  let child_index = $derived(node_array_meta ? parseInt(String(path.at(-1)), 10) : -1);
17
17
  let is_first = $derived(node_array_meta && child_index === 0);
18
18
  let is_last = $derived(node_array_meta && child_index === node_array_meta.length - 1);
19
+
20
+ // Marks and annotations covering this node become classes automatically:
21
+ // `mark-<type>`/`anno-<type>`, plus `-start`/`-end` variants on the run's
22
+ // first/last covered node. This allows styling ranges (including
23
+ // annotations, which never render wrapper components) with pure CSS. For
24
+ // richer rendering, node components still receive the `mark` and
25
+ // `annotations` props.
26
+ let range_classes = $derived.by(() => {
27
+ if (child_index < 0 || !node_array_meta?.annotations_for) return '';
28
+ const classes = [];
29
+
30
+ const add_classes = (range, prefix) => {
31
+ const range_type = range?.node?.type;
32
+ if (!range_type) return;
33
+ classes.push(`${prefix}-${range_type}`);
34
+ if (range.is_start) classes.push(`${prefix}-${range_type}-start`);
35
+ if (range.is_end) classes.push(`${prefix}-${range_type}-end`);
36
+ };
37
+
38
+ add_classes(node_array_meta.mark_for?.(child_index), 'mark');
39
+ for (const annotation of node_array_meta.annotations_for(child_index)) {
40
+ add_classes(annotation, 'anno');
41
+ }
42
+ return classes.join(' ');
43
+ });
19
44
  </script>
20
45
 
21
46
  <svelte:element
22
47
  this={tag}
23
48
  id={node.id}
24
- class="node-{node.type} {css_class}{is_first ? ' first' : ''}{is_last ? ' last' : ''}"
49
+ class="node-{node.type} {css_class}{range_classes
50
+ ? ` ${range_classes}`
51
+ : ''}{is_first ? ' first' : ''}{is_last ? ' last' : ''}"
25
52
  data-node-id={node.id}
26
53
  data-path={path_str}
27
54
  data-type="node"
28
55
  style="anchor-name: --{path_str};{style}"
29
56
  {...rest}
57
+ {@attach svedit.visibility_registry.track_node(path_str)}
30
58
  >
31
59
  {@render children()}
32
60
  </svelte:element>
@@ -1 +1 @@
1
- {"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;AAkCA,yFAAmD"}
1
+ {"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;AA6DA,yFAAmD"}
@@ -1,11 +1,11 @@
1
1
  <script>
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
7
 
8
- /** @import { NodeArrayPropertyProps } from './types.d.ts'; */
8
+ /** @import { NodeArrayPropertyProps, NodeArrayAttachmentContext } from './types.d.ts'; */
9
9
 
10
10
  const svedit = getContext('svedit');
11
11
  let NodeGap = $derived(svedit.session.config.system_components?.node_gap ?? DefaultNodeGap);
@@ -20,10 +20,85 @@
20
20
  // NodeGap's should_position_gap call to avoid N+1 joins per render.
21
21
  let path_str = $derived(serialize_path(path));
22
22
 
23
- let node_ids = $derived(svedit.session.get(path));
24
- let nodes = $derived(node_ids.map((id) => svedit.session.get(id)));
23
+ let raw_value = $derived(svedit.session.get(path));
24
+ let node_ids = $derived(raw_value.nodes);
25
+ let marks = $derived(raw_value.marks);
26
+ let annotations = $derived(raw_value.annotations);
25
27
 
26
- // Mirrors the AnnotatedTextProperty pattern: a `focused` class follows
28
+ let fragments = $derived(get_fragments(node_ids, marks));
29
+
30
+ function get_fragments(node_ids, marks) {
31
+ const ranges = calculate_fragment_ranges(node_ids.length, marks);
32
+ let fragments = [];
33
+
34
+ for (const range of ranges) {
35
+ const nodes_slice = node_ids.slice(range.start_offset, range.end_offset);
36
+
37
+ if (range.type === 'mark') {
38
+ const node = svedit.session.get(range.node_id);
39
+ fragments.push({
40
+ type: 'mark',
41
+ node,
42
+ nodes: nodes_slice,
43
+ start_index: range.start_offset,
44
+ mark_index: range.mark_index
45
+ });
46
+ } else if (range.type === 'content') {
47
+ fragments.push({
48
+ type: 'nodes',
49
+ nodes: nodes_slice,
50
+ start_index: range.start_offset
51
+ });
52
+ }
53
+ }
54
+ return fragments;
55
+ }
56
+
57
+ function get_node_key(node_id, index) {
58
+ let occurrence = 0;
59
+ for (let current_index = 0; current_index < index; current_index++) {
60
+ if (node_ids[current_index] === node_id) occurrence += 1;
61
+ }
62
+ return `${node_id}:${occurrence}`;
63
+ }
64
+
65
+ /**
66
+ * @returns {NodeArrayAttachmentContext}
67
+ */
68
+ function get_range_context(range, index, node_index) {
69
+ const node = svedit.session.get(range.node_id);
70
+ const is_start = node_index === range.start_offset;
71
+ const is_end = node_index === range.end_offset - 1;
72
+
73
+ return {
74
+ ...range,
75
+ index,
76
+ node,
77
+ is_start,
78
+ is_middle: !is_start && !is_end,
79
+ is_end
80
+ };
81
+ }
82
+
83
+ function get_covering_ranges(ranges, node_index) {
84
+ return ranges
85
+ .map((range, index) => ({ range, index }))
86
+ .filter(({ range }) => range.start_offset <= node_index && node_index < range.end_offset)
87
+ .map(({ range, index }) => get_range_context(range, index, node_index));
88
+ }
89
+
90
+ function get_annotation_contexts(node_index) {
91
+ return get_covering_ranges(annotations, node_index);
92
+ }
93
+
94
+ function get_mark(node_index) {
95
+ // The single mark wrapping this node, or null. Mark exclusivity
96
+ // guarantees at most one. Annotations never wrap and are exposed via
97
+ // `annotations` instead.
98
+ return get_covering_ranges(marks, node_index)[0] ?? null;
99
+ }
100
+
101
+ // Mirrors the TextProperty pattern: a `focused` class follows
27
102
  // the model selection. Used by the CSS rule below to hide the empty-array
28
103
  // NodeGap while the caret is in the placeholder, so its <br> isn't a
29
104
  // second arrow-key stop (issue #260).
@@ -35,6 +110,15 @@
35
110
  setContext('node_array_meta', {
36
111
  get length() {
37
112
  return node_ids.length;
113
+ },
114
+ // Lets Node self-serve the marks and annotations covering a child, so
115
+ // node wrappers can carry range classes without every node component
116
+ // having to thread the `mark`/`annotations` props through.
117
+ mark_for(node_index) {
118
+ return get_mark(node_index);
119
+ },
120
+ annotations_for(node_index) {
121
+ return get_annotation_contexts(node_index);
38
122
  }
39
123
  });
40
124
  </script>
@@ -48,6 +132,7 @@
48
132
  data-path={path_str}
49
133
  style="anchor-name: --{path_str};{style ? ` ${style}` : ''}"
50
134
  {...rest}
135
+ {@attach svedit.visibility_registry.track_array(path_str)}
51
136
  >
52
137
  {#if node_ids.length === 0 && svedit.editable}
53
138
  <div
@@ -55,17 +140,39 @@
55
140
  data-path={serialize_path([...path, 0])}
56
141
  data-type="node"
57
142
  style="anchor-name: --{serialize_path([...path, 0])};"
143
+ {@attach svedit.visibility_registry.track_node(serialize_path([...path, 0]))}
58
144
  >
59
145
  <NodeGap array_path={path} offset={0} count={0} empty />
60
146
  </div>
61
147
  {/if}
62
- {#each nodes as node, index (index)}
63
- <NodeGap array_path={path} offset={index} count={nodes.length} />
64
- {@const Component = svedit.session.config.node_components[node.type]}
65
- {#if Component}
66
- <Component path={[...path, index]} />
67
- {:else}
68
- <UnknownNode path={[...path, index]} />
148
+ {#snippet render_nodes(nodes_slice, start_index)}
149
+ {#each nodes_slice as id, slice_index (get_node_key(id, start_index + slice_index))}
150
+ {@const index = start_index + slice_index}
151
+ {@const node = svedit.session.get(id)}
152
+ {@const mark = get_mark(index)}
153
+ {@const annotations = get_annotation_contexts(index)}
154
+ <NodeGap array_path={path} offset={index} count={node_ids.length} />
155
+ {@const Component = svedit.session.config.node_components[node.type]}
156
+ {#if Component}
157
+ <Component path={[...path, index]} {mark} {annotations} />
158
+ {:else}
159
+ <UnknownNode path={[...path, index]} />
160
+ {/if}
161
+ {/each}
162
+ {/snippet}
163
+
164
+ {#each fragments as fragment, fragment_index (fragment_index)}
165
+ {#if fragment.type === 'nodes'}
166
+ {@render render_nodes(fragment.nodes, fragment.start_index)}
167
+ {:else if fragment.type === 'mark'}
168
+ {@const MarkComponent = svedit.session.config.node_components[fragment.node.type]}
169
+ {#if MarkComponent}
170
+ <MarkComponent path={[...path, 'marks', fragment.mark_index, 'node_id']}>
171
+ {@render render_nodes(fragment.nodes, fragment.start_index)}
172
+ </MarkComponent>
173
+ {:else}
174
+ {@render render_nodes(fragment.nodes, fragment.start_index)}
175
+ {/if}
69
176
  {/if}
70
177
  {/each}
71
178
  {#if node_ids.length > 0}
@@ -1 +1 @@
1
- {"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;AA8EA,mHAAgE"}
1
+ {"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;AAuLA,mHAAgE"}
@@ -13,6 +13,7 @@
13
13
  <script>
14
14
  import { serialize_path } from './utils.js';
15
15
  import { getContext } from 'svelte';
16
+ import { should_position_gap } from './node_visibility.svelte.js';
16
17
 
17
18
  /**
18
19
  * ┌─────────────────────────────────────────────────────────────────┐
@@ -37,10 +38,13 @@
37
38
  *
38
39
  * Always present in the DOM when editable (stable structure for
39
40
  * selection anchoring, scrollTo, etc.). Anchor positioning is
40
- * activated lazily via the `.positioned` class, toggled imperatively
41
- * by the IO callback / MO / bootstrap in node_visibility — keeps
42
- * NodeGap allocation-free of per-instance Svelte signals, which
43
- * matters at 1000+ NodeGaps per scroll frame.
41
+ * activated lazily via the `.positioned` class, derived reactively
42
+ * from the visibility registry's array_indices / edge_map. Svelte's
43
+ * reactive collections track reads at per-key granularity, so a
44
+ * registry write only
45
+ * re-evaluates the adjacent gaps. Applying the class declaratively
46
+ * (instead of imperative classList toggles) means it survives DOM
47
+ * recreation without a document change (e.g. dev-mode HMR).
44
48
  *
45
49
  * `position-visibility: anchors-visible` does NOT skip anchor
46
50
  * resolution — the browser resolves all anchor() then hides the
@@ -62,6 +66,22 @@
62
66
  let is_last = $derived(offset === count);
63
67
  let type = $derived(is_first ? 'gap-before' : 'gap-after');
64
68
 
69
+ let path_str = $derived(serialize_path(array_path));
70
+ // Two-derived split is load-bearing: get_array_indices lazily
71
+ // creates the set, and Svelte doesn't track deps on state created
72
+ // inside the reading derived — so acquire here, read in `positioned`.
73
+ let near_indices = $derived(svedit.visibility_registry.get_array_indices(path_str));
74
+ let positioned = $derived(
75
+ is_editable &&
76
+ should_position_gap(
77
+ near_indices,
78
+ svedit.visibility_registry.edge_map.get(path_str),
79
+ offset,
80
+ is_last,
81
+ empty
82
+ )
83
+ );
84
+
65
85
  let gap_style = $derived.by(() => {
66
86
  if (!is_editable) return '';
67
87
  const prev_idx = offset - 1;
@@ -87,8 +107,9 @@
87
107
  class:gap-after={!is_first}
88
108
  class:empty
89
109
  class:last={is_last}
110
+ class:positioned
90
111
  data-type={type}
91
- data-gap-array-path={serialize_path(array_path)}
112
+ data-gap-array-path={path_str}
92
113
  data-gap-offset={offset}
93
114
  style={gap_style}
94
115
  >
@@ -111,9 +132,30 @@
111
132
  }
112
133
 
113
134
  /* ------------------------------------------------------------------ */
114
- /* Un-positioned: minimal in-DOM presence, no anchor resolution */
135
+ /* Un-positioned: no layout box at all */
115
136
  /* ------------------------------------------------------------------ */
116
137
 
138
+ /*
139
+ * display: none is load-bearing for large documents. A zero-size
140
+ * absolutely-positioned selectable still belongs to the containing
141
+ * block's out-of-flow list, and the browser lays out EVERY such box
142
+ * on EVERY layout pass — measured ~430ms per pass at 2000 nodes
143
+ * (~3500 gaps) in Chrome vs ~23ms with the boxes removed. That cost
144
+ * hits every keystroke (the per-change reconcile reads a rect for
145
+ * every node, and selection rendering forces layout), every window
146
+ * resize frame, and every scroll-triggered layout.
147
+ *
148
+ * Off-screen gaps therefore contribute no layout box. DOM structure
149
+ * stays stable (the .node-gap wrapper and selectable elements remain),
150
+ * and DOM Ranges may still point into display:none elements, so
151
+ * programmatic node selections targeting off-screen gaps keep working.
152
+ * Empty-array gaps are excluded: they must stay clickable/visible even
153
+ * before reconcile positions them.
154
+ */
155
+ :global(.node-gap:not(.positioned):not(.empty) .svedit-selectable) {
156
+ display: none;
157
+ }
158
+
117
159
  :global(.node-gap:not(.positioned) .svedit-selectable) {
118
160
  position: absolute;
119
161
  pointer-events: none;
@@ -1 +1 @@
1
- {"version":3,"file":"NodeGap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGap.svelte.js"],"names":[],"mappings":";;;;;AAwGA;gBA1CgC,GAAG;YAAU,GAAG;WAAS,GAAG;YAAU,OAAO;WA0CvB;wBA1CpC;IAAE,UAAU,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE"}
1
+ {"version":3,"file":"NodeGap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGap.svelte.js"],"names":[],"mappings":";;;;;AA6HA;gBA1DgC,GAAG;YAAU,GAAG;WAAS,GAAG;YAAU,OAAO;WA0DvB;wBA1DpC;IAAE,UAAU,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE"}
@@ -27,7 +27,24 @@ export default class Session {
27
27
  can_undo: boolean;
28
28
  can_redo: boolean;
29
29
  selected_node: any;
30
+ available_mark_types: any;
30
31
  available_annotation_types: any;
32
+ selected_marks: (Attachment & {
33
+ index: number;
34
+ node: any;
35
+ })[];
36
+ active_mark: Attachment & {
37
+ index: number;
38
+ node: any;
39
+ };
40
+ selected_annotations: (Attachment & {
41
+ index: number;
42
+ node: any;
43
+ })[];
44
+ active_annotation: Attachment & {
45
+ index: number;
46
+ node: any;
47
+ };
31
48
  /**
32
49
  * Sets the selection with validation
33
50
  * @param {Selection | null} value - The new selection
@@ -76,6 +93,7 @@ export default class Session {
76
93
  * @param {object} context - The svedit context with session, editable, canvas, etc.
77
94
  */
78
95
  initialize_commands(context: object): void;
96
+ get_available_mark_types(): any;
79
97
  get_available_annotation_types(): any;
80
98
  get_selected_node(): any;
81
99
  /**
@@ -114,8 +132,8 @@ export default class Session {
114
132
  * session.get(['page_1', 'body', 3, 'list_items', 0]) // => { type: 'list_item', id: 'list_item_1', ... }
115
133
  *
116
134
  * @example
117
- * // Get an annotated text property
118
- * session.get(['page_1', 'cover', 'title']) // => {text: 'Hello world', annotations: []}
135
+ * // Get a text property
136
+ * session.get(['page_1', 'cover', 'title']) // => {content: 'Hello world', marks: [], annotations: []}
119
137
  */
120
138
  get(path: DocumentPath | string): any;
121
139
  /**
@@ -150,7 +168,7 @@ export default class Session {
150
168
  };
151
169
  /**
152
170
  * Determines the kind of a node ('block' for structured blocks, 'text' for pure
153
- * text nodes or 'annotation' for annotation nodes.
171
+ * text nodes, 'mark' for mark nodes or 'annotation' for annotation nodes.
154
172
  * @param {any} node
155
173
  * @returns {NodeKind}
156
174
  */
@@ -169,19 +187,18 @@ export default class Session {
169
187
  * @returns {Selection|null} The next node insert caret selection, or null if none is available
170
188
  */
171
189
  get_next_node_insert_caret(selection?: Selection): Selection | null;
172
- /**
173
- * Returns the annotation object that is currently "under the cursor".
174
- * NOTE: Annotations in Svedit are exclusive, so there can only be one active_annotation
175
- *
176
- * @param {string} [annotation_type] Optional annotation type to filter by
177
- * @returns {Annotation|null}
178
- */
179
- active_annotation(annotation_type?: string): Annotation | null;
180
- get_selected_annotated_text(): {
181
- text: string;
190
+ get_selected_text(): {
191
+ content: string;
192
+ marks: any;
182
193
  annotations: any;
183
194
  nodes: {};
184
195
  };
196
+ get_selected_annotated_nodes(): {
197
+ nodes: {};
198
+ main_nodes: any;
199
+ marks: any;
200
+ annotations: any;
201
+ };
185
202
  get_selected_plain_text(): string;
186
203
  get_selected_nodes(): any;
187
204
  select_parent(): void;
@@ -224,11 +241,11 @@ export type SessionOptions = {
224
241
  };
225
242
  import type { DocumentSchema } from './types.d.ts';
226
243
  import type { Document } from './types.d.ts';
244
+ import type { Attachment } from './types.d.ts';
227
245
  import type { Selection } from './types.d.ts';
228
246
  import Transaction from './Transaction.svelte.js';
229
247
  import type { DocumentPath } from './types.d.ts';
230
248
  import type { NodeKind } from './types.d.ts';
231
- import type { Annotation } from './types.d.ts';
232
249
  import type { DocumentNode } from './types.d.ts';
233
250
  import type { NodeId } from './types.d.ts';
234
251
  //# sourceMappingURL=Session.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Session.svelte.js"],"names":[],"mappings":"AAgCA;;;GAGG;AAEH;IA+BC;;;;;;OAMG;IACH,oBANW,cAAc,OACd,QAAQ,UACR,MAAM,YAEd;QAA4B,SAAS,GAA7B,SAAS;KACnB,EAYA;IA7CD,6BAA6B;IAC7B,QADW,cAAc,CACH;IAEtB,uBAAuB;IACvB,KADW,QAAQ,CACA;IAEnB,kBAAkB;IAClB,QADW,GAAG,CACQ;IAEtB,eAAyB;IACzB,sBAA+B;IAC/B,wBAA2C;IAI3C,aAA0B;IAC1B,WAAwB;IAGxB,kBAA6C;IAC7C,kBAAkE;IAGlE,mBAAmD;IACnD,gCAA6E;IA+B7E;;;;OAIG;IACH,qBAHW,SAAS,GAAG,IAAI,EAM1B;IAhBD;;;OAGG;IACH,iBAFa,SAAS,GAAG,IAAI,CAI5B;IAYD;;;;;;OAMG;IACH,4BAEC;IAED;;;OAGG;IACH,mBAFa,MAAM,CAIlB;IAED;;;;;;;;;;OAUG;IACH,yCAHW,WAAW,QA4BrB;IAED,mBAMC;IAED;;;;;;;;;OASG;IACH,6BAFW,MAAM,QAQhB;IAED,sCAKC;IAGD,yBAkBC;IAED;;;;OAIG;IACH,UAFa,WAAW,CAKvB;IAED;;;;;;;OAOG;IACH,mBAJW,WAAW,cAEnB;QAA0B,KAAK,GAAvB,OAAO;KACjB,QA6CA;IAED,aAgBC;IAED,aAaC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAlBW,YAAY,GAAC,MAAM,GACjB,GAAG,CAmBf;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,cAHW,YAAY,GACV;QAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC,CAIzD;IAED;;;;;OAKG;IACH,WAHW,GAAG,GACD,QAAQ,CAIpB;IAED;;;;;OAKG;IACH,sBAJW,MAAM,cACN,SAAS,GACP,OAAO,CAcnB;IAED;;;;;OAKG;IACH,uCAHW,SAAS,GACP,SAAS,GAAC,IAAI,CAe1B;IAED;;;;;;OAMG;IACH,oCAHW,MAAM,GACJ,UAAU,GAAC,IAAI,CAI3B;IAED;;;;MA6BC;IAGD,kCAOC;IAED,0BAOC;IAED,sBAmCC;IAED;;;;;;;;;;;OAWG;IACH,kBAJW,MAAM,GACJ,KAAK,CAAC,YAAY,CAAC,CAK/B;IAED;;;;;;;OAOG;IACH,WAFa,QAAQ,CAWpB;IAID,gDAEC;IAGD,uCAEC;IAGD;;;OAGG;IACH,8BAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;;CACD;;;;;gBA5hBa,SAAS;;oCAPb,cAAc;8BAAd,cAAc;+BAAd,cAAc;wBA3BA,yBAAyB;kCA2BvC,cAAc;8BAAd,cAAc;gCAAd,cAAc;kCAAd,cAAc;4BAAd,cAAc"}
1
+ {"version":3,"file":"Session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Session.svelte.js"],"names":[],"mappings":"AAsCA;;;GAGG;AAEH;IAqCC;;;;;;OAMG;IACH,oBANW,cAAc,OACd,QAAQ,UACR,MAAM,YAEd;QAA4B,SAAS,GAA7B,SAAS;KACnB,EAaA;IApDD,6BAA6B;IAC7B,QADW,cAAc,CACH;IAEtB,uBAAuB;IACvB,KADW,QAAQ,CACA;IAEnB,kBAAkB;IAClB,QADW,GAAG,CACQ;IAEtB,eAAyB;IACzB,sBAA+B;IAC/B,wBAA2C;IAI3C,aAA0B;IAC1B,WAAwB;IAGxB,kBAA6C;IAC7C,kBAAkE;IAGlE,mBAAmD;IACnD,0BAAiE;IACjE,gCAA6E;IAC7E;;;SAAqF;IACrF;;;MAAyF;IACzF;;;SAAiG;IACjG;;;MAEE;IA+BF;;;;OAIG;IACH,qBAHW,SAAS,GAAG,IAAI,EAM1B;IAhBD;;;OAGG;IACH,iBAFa,SAAS,GAAG,IAAI,CAI5B;IAYD;;;;;;OAMG;IACH,4BAEC;IAED;;;OAGG;IACH,mBAFa,MAAM,CAIlB;IAED;;;;;;;;;;OAUG;IACH,yCAHW,WAAW,QAsCrB;IAED,mBAQC;IAED;;;;;;;;;OASG;IACH,6BAFW,MAAM,QAQhB;IAED,gCAIC;IAED,sCAIC;IAGD,yBAkBC;IAED;;;;OAIG;IACH,UAFa,WAAW,CAKvB;IAED;;;;;;;OAOG;IACH,mBAJW,WAAW,cAEnB;QAA0B,KAAK,GAAvB,OAAO;KACjB,QAmDA;IAED,aAkBC;IAED,aAaC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAlBW,YAAY,GAAC,MAAM,GACjB,GAAG,CAmBf;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,cAHW,YAAY,GACV;QAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC,CAIzD;IAED;;;;;OAKG;IACH,WAHW,GAAG,GACD,QAAQ,CAIpB;IAED;;;;;OAKG;IACH,sBAJW,MAAM,cACN,SAAS,GACP,OAAO,CAcnB;IAED;;;;;OAKG;IACH,uCAHW,SAAS,GACP,SAAS,GAAC,IAAI,CAe1B;IAED;;;;;MAqCC;IAED;;;;;MAqCC;IAGD,kCAOC;IAED,0BASC;IAED,sBAmCC;IAED;;;;;;;;;;;OAWG;IACH,kBAJW,MAAM,GACJ,KAAK,CAAC,YAAY,CAAC,CAS/B;IAED;;;;;;;OAOG;IACH,WAFa,QAAQ,CAWpB;IAID,gDAEC;IAGD,uCAEC;IAGD;;;OAGG;IACH,8BAHW,MAAM,GACJ,MAAM,EAAE,CAKpB;;CACD;;;;;gBApmBa,SAAS;;oCAPb,cAAc;8BAAd,cAAc;gCAAd,cAAc;+BAAd,cAAc;wBAjCA,yBAAyB;kCAiCvC,cAAc;8BAAd,cAAc;kCAAd,cAAc;4BAAd,cAAc"}