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.
Files changed (93) hide show
  1. package/README.md +260 -82
  2. package/dist/Command.svelte.d.ts +54 -29
  3. package/dist/Command.svelte.d.ts.map +1 -1
  4. package/dist/Command.svelte.js +286 -283
  5. package/dist/CustomProperty.svelte +3 -5
  6. package/dist/CustomProperty.svelte.d.ts +3 -6
  7. package/dist/CustomProperty.svelte.d.ts.map +1 -1
  8. package/dist/KeyMapper.svelte.d.ts +19 -6
  9. package/dist/KeyMapper.svelte.d.ts.map +1 -1
  10. package/dist/KeyMapper.svelte.js +95 -112
  11. package/dist/Node.svelte +40 -9
  12. package/dist/Node.svelte.d.ts +3 -5
  13. package/dist/Node.svelte.d.ts.map +1 -1
  14. package/dist/NodeArrayProperty.svelte +142 -18
  15. package/dist/NodeArrayProperty.svelte.d.ts +3 -5
  16. package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
  17. package/dist/NodeCaret.svelte +1 -1
  18. package/dist/NodeCaret.svelte.d.ts +6 -14
  19. package/dist/NodeCaret.svelte.d.ts.map +1 -1
  20. package/dist/NodeGap.svelte +60 -12
  21. package/dist/NodeGap.svelte.d.ts +7 -14
  22. package/dist/NodeGap.svelte.d.ts.map +1 -1
  23. package/dist/NodeGapMarkers.svelte +19 -10
  24. package/dist/NodeGapMarkers.svelte.d.ts +6 -11
  25. package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
  26. package/dist/NodeSelectionMarkers.svelte +20 -6
  27. package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
  28. package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
  29. package/dist/Session.svelte.d.ts +85 -116
  30. package/dist/Session.svelte.d.ts.map +1 -1
  31. package/dist/Session.svelte.js +543 -571
  32. package/dist/Svedit.svelte +243 -216
  33. package/dist/Svedit.svelte.d.ts +30 -10
  34. package/dist/Svedit.svelte.d.ts.map +1 -1
  35. package/dist/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
  36. package/dist/TextProperty.svelte.d.ts +5 -0
  37. package/dist/TextProperty.svelte.d.ts.map +1 -0
  38. package/dist/Transaction.svelte.d.ts +101 -126
  39. package/dist/Transaction.svelte.d.ts.map +1 -1
  40. package/dist/Transaction.svelte.js +843 -827
  41. package/dist/UnknownNode.svelte +4 -3
  42. package/dist/UnknownNode.svelte.d.ts +5 -9
  43. package/dist/UnknownNode.svelte.d.ts.map +1 -1
  44. package/dist/doc_utils.d.ts +109 -122
  45. package/dist/doc_utils.d.ts.map +1 -1
  46. package/dist/doc_utils.js +726 -698
  47. package/dist/index.d.ts +20 -17
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -18
  50. package/dist/node_visibility.svelte.d.ts +132 -5
  51. package/dist/node_visibility.svelte.d.ts.map +1 -1
  52. package/dist/node_visibility.svelte.js +535 -642
  53. package/dist/paste_utils.d.ts +11 -43
  54. package/dist/paste_utils.d.ts.map +1 -1
  55. package/dist/paste_utils.js +111 -149
  56. package/dist/transforms.svelte.d.ts +9 -7
  57. package/dist/transforms.svelte.d.ts.map +1 -1
  58. package/dist/transforms.svelte.js +126 -152
  59. package/dist/types.d.ts +329 -291
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +1 -0
  62. package/dist/utils.d.ts +79 -95
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +392 -305
  65. package/package.json +69 -67
  66. package/src/lib/{Command.svelte.js → Command.svelte.ts} +111 -41
  67. package/src/lib/CustomProperty.svelte +3 -5
  68. package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
  69. package/src/lib/Node.svelte +40 -9
  70. package/src/lib/NodeArrayProperty.svelte +142 -18
  71. package/src/lib/NodeCaret.svelte +1 -1
  72. package/src/lib/NodeGap.svelte +60 -12
  73. package/src/lib/NodeGapMarkers.svelte +19 -10
  74. package/src/lib/NodeSelectionMarkers.svelte +20 -6
  75. package/src/lib/Session.svelte.ts +656 -0
  76. package/src/lib/Svedit.svelte +243 -216
  77. package/src/lib/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
  78. package/src/lib/Transaction.svelte.ts +1020 -0
  79. package/src/lib/UnknownNode.svelte +4 -3
  80. package/src/lib/doc_utils.ts +1120 -0
  81. package/src/lib/{index.js → index.ts} +48 -1
  82. package/src/lib/node_visibility.svelte.ts +658 -0
  83. package/src/lib/{paste_utils.js → paste_utils.ts} +51 -59
  84. package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +44 -27
  85. package/src/lib/{types.d.ts → types.ts} +250 -103
  86. package/src/lib/utils.ts +660 -0
  87. package/dist/AnnotatedTextProperty.svelte.d.ts +0 -7
  88. package/dist/AnnotatedTextProperty.svelte.d.ts.map +0 -1
  89. package/src/lib/Session.svelte.js +0 -575
  90. package/src/lib/Transaction.svelte.js +0 -846
  91. package/src/lib/doc_utils.js +0 -807
  92. package/src/lib/node_visibility.svelte.js +0 -718
  93. package/src/lib/utils.js +0 -465
@@ -1 +1 @@
1
- {"version":3,"file":"CustomProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CustomProperty.svelte.js"],"names":[],"mappings":";;;;;AA4BA,sFAA6D;yCApBnB,cAAc"}
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: any): any;
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: any[];
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: any): void;
38
+ push_scope(keymap: Keymap): void;
27
39
  /**
28
40
  * Pop the most recent scope from the stack
29
41
  */
30
- pop_scope(): any;
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: any): void;
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.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,gDAgBC;AAuED;;;;;;;;;;;GAWG;AACH;IAEE,mBAAqB;IACrB,wBAA2B;IAG5B;;OAEG;IACH,8BAGC;IAED;;OAEG;IACH,iBAIC;IAED;;OAEG;IACH,iCAUC;CACD"}
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"}
@@ -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
- meta: 'metaKey',
6
- ctrl: 'ctrlKey',
7
- alt: 'altKey',
8
- shift: 'shiftKey'
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
- for (const [key_combo] of Object.entries(keymap)) {
19
- const alternatives = key_combo.split(',');
20
-
21
- for (const alternative of alternatives) {
22
- const parts = alternative.trim().toLowerCase().split('+');
23
- const non_modifiers = parts.filter((part) => !MODIFIER_KEYS.includes(part));
24
-
25
- if (non_modifiers.length !== 1) {
26
- throw new Error(
27
- `Invalid key combo: "${alternative}". Must have exactly one non-modifier key. Found: ${non_modifiers.length}`
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
- const alternatives = key_combo.split(',');
41
-
42
- return alternatives.some((alternative) => {
43
- const parts = alternative.trim().toLowerCase().split('+');
44
- const modifiers = parts.filter((part) => MODIFIER_KEYS.includes(part));
45
- const non_modifier = parts.find((part) => !MODIFIER_KEYS.includes(part));
46
-
47
- if (virtual_keyboard_active && modifiers.length > 0) {
48
- return false;
49
- }
50
-
51
- // Check if all specified modifiers are pressed
52
- const modifiers_match = modifiers.every((mod) => event[MODIFIER_EVENT_KEYS[mod]]);
53
-
54
- // Check if no unspecified modifiers are pressed
55
- // NOTE: On virtual keyboards (iOS / Anroid) modifier keys are just ignored, because
56
- // they must be assumed accidental (e.g. Shift activated as a result of auto-capitalization)
57
- const no_extra_modifiers = virtual_keyboard_active
58
- ? true
59
- : MODIFIER_KEYS.every((mod) => {
60
- if (modifiers.includes(mod)) return true; // This modifier is expected
61
- return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
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
- const virtual_keyboard_active = is_virtual_keyboard_active();
79
-
80
- for (const [key_combo, commands] of Object.entries(key_map)) {
81
- if (matches_key_combo(key_combo, event, virtual_keyboard_active)) {
82
- // Find the first enabled command and execute it
83
- const enabled_command = commands.find((cmd) => cmd.is_enabled());
84
- if (enabled_command) {
85
- event.preventDefault();
86
-
87
- // Execute command (may be sync or async)
88
- const result = enabled_command.execute();
89
-
90
- // If it's a promise, handle errors (fire-and-forget)
91
- if (result instanceof Promise) {
92
- result.catch((err) => {
93
- console.error('Command execution failed:', err);
94
- });
95
- }
96
-
97
- return true;
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
- constructor() {
118
- this.scope_stack = [];
119
- this.skip_onkeydown = false;
120
- }
121
-
122
- /**
123
- * Push a new scope onto the stack (becomes highest priority)
124
- */
125
- push_scope(keymap) {
126
- // console.log('pushed keymap', keymap);
127
- this.scope_stack.push(keymap);
128
- }
129
-
130
- /**
131
- * Pop the most recent scope from the stack
132
- */
133
- pop_scope() {
134
- const keymap = this.scope_stack.pop();
135
- // console.log('popped keymap', keymap);
136
- return keymap;
137
- }
138
-
139
- /**
140
- * Handle keyboard event by trying scopes from top to bottom
141
- */
142
- handle_keydown(event) {
143
- // Key handling temporarily disabled (e.g. while character composition takes place)
144
- if (this.skip_onkeydown) return;
145
- // console.log('KeyMapper.handle_keydown', event);
146
- // Try from most specific (top of stack) to most general (bottom)
147
- for (let i = this.scope_stack.length - 1; i >= 0; i--) {
148
- if (handle_key_map(this.scope_stack[i], event)) {
149
- return;
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
- /** @import { NodeProps } from './types.d.ts'; */
11
+ const svedit = getContext<SveditRenderContext>('svedit');
6
12
 
7
- const svedit = getContext('svedit');
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 ? parseInt(String(path.at(-1)), 10) : -1);
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}{is_first ? ' first' : ''}{is_last ? ' last' : ''}"
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>
@@ -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.js"],"names":[],"mappings":";;;;;AAkCA,yFAAmD"}
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
- /** @import { NodeArrayPropertyProps } from './types.d.ts'; */
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
- /** @type {NodeArrayPropertyProps} */
17
- let { path, tag = 'div', class: css_class, style = '', ...rest } = $props();
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 node_ids = $derived(svedit.session.get(path));
24
- let nodes = $derived(node_ids.map((id) => svedit.session.get(id)));
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
- // Mirrors the AnnotatedTextProperty pattern: a `focused` class follows
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
- {#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]} />
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.js"],"names":[],"mappings":";;;;;AA8EA,mHAAgE"}
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"}
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  /**
3
3
  * Visual insertion caret rendered inside the active insertion marker.
4
4
  *
@@ -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("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
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