svedit 0.9.0 → 0.11.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 +476 -364
- package/dist/AnnotatedTextProperty.svelte +40 -10
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.d.ts +0 -1
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +48 -6
- package/dist/CustomProperty.svelte +20 -9
- package/dist/CustomProperty.svelte.d.ts +2 -1
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/Node.svelte +7 -5
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +59 -48
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeCaret.svelte +0 -7
- package/dist/NodeGap.svelte +208 -118
- package/dist/NodeGap.svelte.d.ts +0 -2
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +251 -84
- package/dist/NodeGapMarkers.svelte.d.ts +2 -0
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/NodeSelectionMarkers.svelte +4 -3
- package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.d.ts +12 -1
- package/dist/Session.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.js +40 -3
- package/dist/Svedit.svelte +496 -221
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +24 -5
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +103 -61
- package/dist/doc_utils.d.ts +69 -3
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +276 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -1
- package/dist/node_visibility.svelte.d.ts +9 -0
- package/dist/node_visibility.svelte.d.ts.map +1 -0
- package/dist/node_visibility.svelte.js +718 -0
- package/dist/paste_utils.d.ts +46 -0
- package/dist/paste_utils.d.ts.map +1 -0
- package/dist/paste_utils.js +167 -0
- package/dist/styles/svedit-animations.css +8 -0
- package/dist/styles/svedit-colors.css +1 -0
- package/dist/types.d.ts +46 -3
- package/dist/utils.d.ts +52 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +109 -3
- package/package.json +3 -2
- package/src/lib/AnnotatedTextProperty.svelte +40 -10
- package/src/lib/Command.svelte.js +48 -6
- package/src/lib/CustomProperty.svelte +20 -9
- package/src/lib/Node.svelte +7 -5
- package/src/lib/NodeArrayProperty.svelte +59 -48
- package/src/lib/NodeCaret.svelte +0 -7
- package/src/lib/NodeGap.svelte +208 -118
- package/src/lib/NodeGapMarkers.svelte +251 -84
- package/src/lib/NodeSelectionMarkers.svelte +4 -3
- package/src/lib/Session.svelte.js +40 -3
- package/src/lib/Svedit.svelte +496 -221
- package/src/lib/Transaction.svelte.js +103 -61
- package/src/lib/doc_utils.js +276 -10
- package/src/lib/index.js +6 -1
- package/src/lib/node_visibility.svelte.js +718 -0
- package/src/lib/paste_utils.js +167 -0
- package/src/lib/styles/svedit-animations.css +8 -0
- package/src/lib/styles/svedit-colors.css +1 -0
- package/src/lib/types.d.ts +46 -3
- package/src/lib/utils.js +109 -3
- package/dist/node_gap_computation.svelte.d.ts +0 -5
- package/dist/node_gap_computation.svelte.d.ts.map +0 -1
- package/dist/node_gap_computation.svelte.js +0 -621
- package/src/lib/node_gap_computation.svelte.js +0 -621
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
char_slice,
|
|
5
|
+
get_char_length,
|
|
6
|
+
paths_equal,
|
|
7
|
+
serialize_path,
|
|
8
|
+
get_selection_range
|
|
9
|
+
} from './utils.js';
|
|
4
10
|
|
|
5
11
|
/** @import { AnnotatedTextPropertyProps, Annotation, Fragment, SelectionRange } from './types.d.ts'; */
|
|
6
12
|
|
|
@@ -9,10 +15,11 @@
|
|
|
9
15
|
/** @type {AnnotatedTextPropertyProps} */
|
|
10
16
|
let { path, class: css_class, placeholder = '', tag = 'div', style = '', ...rest } = $props();
|
|
11
17
|
|
|
18
|
+
let path_str = $derived(serialize_path(path));
|
|
19
|
+
|
|
12
20
|
let is_focused = $derived.by(() => {
|
|
13
21
|
return (
|
|
14
|
-
svedit.session.selection?.type === 'text' &&
|
|
15
|
-
path.join('.') === svedit.session.selection?.path.join('.')
|
|
22
|
+
svedit.session.selection?.type === 'text' && paths_equal(path, svedit.session.selection.path)
|
|
16
23
|
);
|
|
17
24
|
});
|
|
18
25
|
|
|
@@ -111,12 +118,11 @@
|
|
|
111
118
|
<svelte:element
|
|
112
119
|
this={tag}
|
|
113
120
|
data-type="text"
|
|
114
|
-
data-path={
|
|
115
|
-
style="anchor-name: --{
|
|
121
|
+
data-path={path_str}
|
|
122
|
+
style="anchor-name: --{path_str};{style}"
|
|
116
123
|
class="text svedit-selectable {css_class}"
|
|
117
124
|
class:empty={is_empty}
|
|
118
125
|
class:focused={is_focused}
|
|
119
|
-
class:editable={svedit.editable}
|
|
120
126
|
{placeholder}
|
|
121
127
|
{...rest}
|
|
122
128
|
>
|
|
@@ -125,8 +131,7 @@
|
|
|
125
131
|
class="selection-highlight"
|
|
126
132
|
style="anchor-name: --selection-highlight;">{fragment.content}</span
|
|
127
133
|
>{:else if fragment.type === 'annotation'}
|
|
128
|
-
{@const AnnotationComponent =
|
|
129
|
-
svedit.session.config.node_components[snake_to_pascal(fragment.node.type)]}
|
|
134
|
+
{@const AnnotationComponent = svedit.session.config.node_components[fragment.node.type]}
|
|
130
135
|
<AnnotationComponent
|
|
131
136
|
path={[...path, 'annotations', fragment.annotation_index, 'node_id']}
|
|
132
137
|
content={fragment.content}
|
|
@@ -137,11 +142,13 @@
|
|
|
137
142
|
</svelte:element>
|
|
138
143
|
|
|
139
144
|
<style>
|
|
140
|
-
|
|
145
|
+
/* Editable text base layout; :where() allows easy override without specificity conflicts. */
|
|
146
|
+
:where(.text) {
|
|
141
147
|
white-space: pre-wrap;
|
|
142
148
|
overflow-wrap: anywhere;
|
|
143
149
|
box-sizing: content-box;
|
|
144
150
|
}
|
|
151
|
+
|
|
145
152
|
/* We switch from ::before to ::after when the element is focused. So the the caret is always before the placeholder. */
|
|
146
153
|
[placeholder].empty:not(.focused)::before,
|
|
147
154
|
[placeholder].empty.focused::after {
|
|
@@ -150,13 +157,36 @@
|
|
|
150
157
|
color: color-mix(in oklch, currentcolor 50%, transparent);
|
|
151
158
|
}
|
|
152
159
|
|
|
160
|
+
/* A virtual caret: to fix the caret vertical alignment issue in Chrome and Firefox for empty focused contenteditable with placeholders */
|
|
161
|
+
/* Browser BUG: iOS Safari only considers the caret color set on the top contenteditable element, not on nested elements (e.g. the second selector doesn't work in iOS Safari) */
|
|
162
|
+
:global(.svedit.editable .svedit-canvas:has([placeholder].empty.focused)),
|
|
163
|
+
:global(.svedit.editable) [placeholder].empty.focused {
|
|
164
|
+
caret-color: transparent !important;
|
|
165
|
+
}
|
|
166
|
+
:global(.svedit.editable) [placeholder].empty.focused::before {
|
|
167
|
+
content: '';
|
|
168
|
+
/* we limit width & height to avoid layout shifts in case the text has a lower natural height */
|
|
169
|
+
width: 0px;
|
|
170
|
+
height: 1cap;
|
|
171
|
+
display: inline-block;
|
|
172
|
+
/* we use box-shadow to draw the caret shape, matching the native caret */
|
|
173
|
+
box-shadow:
|
|
174
|
+
0 -0.4cap 0 0.65px var(--svedit-caret-color, AccentColor),
|
|
175
|
+
0 0 0 0.65px var(--svedit-caret-color, AccentColor),
|
|
176
|
+
0 0.4cap 0 0.65px var(--svedit-caret-color, AccentColor);
|
|
177
|
+
animation: var(
|
|
178
|
+
--node-caret-animation,
|
|
179
|
+
node-caret-blink var(--node-caret-blink-duration, 1.1s) ease-in-out infinite
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
/* Hide flickering: in Chrome, the caret jumps from end of placeholder string to start of text property when we focus */
|
|
154
184
|
.text:not(.focused) {
|
|
155
185
|
caret-color: transparent;
|
|
156
186
|
}
|
|
157
187
|
|
|
158
188
|
/* Disable text-transform when editable and focused so users see original text */
|
|
159
|
-
.
|
|
189
|
+
:global(.svedit.editable) .text.focused {
|
|
160
190
|
text-transform: none !important;
|
|
161
191
|
}
|
|
162
192
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnnotatedTextProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AnnotatedTextProperty.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"AnnotatedTextProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AnnotatedTextProperty.svelte.js"],"names":[],"mappings":";;;;;AAwIA,2HAAoE"}
|
package/dist/Command.svelte.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Command.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Command.svelte.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Command.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Command.svelte.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAOC;;;;OAIG;IACH,qBAFW,GAAG,EAIb;IAbD;;;OAGG;IACH,kBAAwC;IAQvC,aAAsB;IAGvB;;;;;OAKG;IACH,cAFa,OAAO,CAInB;IAED;;;;;;OAMG;IACH,WAFa,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;;GAGG;AACH;IASC,gBAEC;CACD;AAED;;;GAGG;AACH;IACC,0CAGC;IADA,eAA0B;IAG3B,YAAoC;IAEpC,iBAEC;IAED,kBAqBC;IAED,gBAEC;CACD;AAED;;;;GAIG;AACH;IACC,kBAUC;IAED,gBAqBC;CACD;AAED;;;;GAIG;AACH;IAaC,gBAKC;CACD;AAED;;;GAGG;AACH;IACC,kBAEC;IAED,gBAwGC;CACD;AAED;;;GAGG;AACH;IAUC,gBAIC;CACD"}
|
package/dist/Command.svelte.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { insert_default_node, break_text_node } from './transforms.svelte.js';
|
|
2
|
-
import {
|
|
2
|
+
import { can_switch_annotation_type } from './doc_utils.js';
|
|
3
|
+
import { is_selection_collapsed, is_mobile_browser, get_char_length, char_slice } from './utils.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Base class for commands that can be executed in response to user actions
|
|
@@ -93,7 +94,11 @@ export class RedoCommand extends Command {
|
|
|
93
94
|
*/
|
|
94
95
|
export class SelectParentCommand extends Command {
|
|
95
96
|
is_enabled() {
|
|
96
|
-
return
|
|
97
|
+
return (
|
|
98
|
+
this.context.editable &&
|
|
99
|
+
this.context.session.selection &&
|
|
100
|
+
this.context.session.selection.path.length > 3
|
|
101
|
+
);
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
execute() {
|
|
@@ -119,14 +124,24 @@ export class ToggleAnnotationCommand extends Command {
|
|
|
119
124
|
|
|
120
125
|
is_enabled() {
|
|
121
126
|
const { session, editable } = this.context;
|
|
127
|
+
const active_annotation = session.active_annotation();
|
|
122
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
|
+
);
|
|
123
138
|
const no_annotation_and_caret_not_collapsed =
|
|
124
|
-
!
|
|
139
|
+
!active_annotation && !is_selection_collapsed(session.selection);
|
|
125
140
|
|
|
126
141
|
return (
|
|
127
142
|
editable &&
|
|
128
143
|
session.selection?.type === 'text' &&
|
|
129
|
-
(has_annotation || no_annotation_and_caret_not_collapsed)
|
|
144
|
+
(has_annotation || can_switch_annotation || no_annotation_and_caret_not_collapsed)
|
|
130
145
|
);
|
|
131
146
|
}
|
|
132
147
|
|
|
@@ -154,7 +169,26 @@ export class AddNewLineCommand extends Command {
|
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
execute() {
|
|
157
|
-
|
|
172
|
+
const session = this.context.session;
|
|
173
|
+
const selection = session.selection;
|
|
174
|
+
if (!selection || selection.type !== 'text') return;
|
|
175
|
+
if (!session.inspect(selection.path).allow_newlines) return;
|
|
176
|
+
|
|
177
|
+
const tr = session.tr;
|
|
178
|
+
if (selection.anchor_offset !== selection.focus_offset) {
|
|
179
|
+
tr.delete_selection();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const collapsed_offset = tr.selection.anchor_offset;
|
|
183
|
+
const content = tr.get(tr.selection.path);
|
|
184
|
+
const text_before_caret = char_slice(content.text, 0, collapsed_offset);
|
|
185
|
+
const line_start_index = text_before_caret.lastIndexOf('\n') + 1;
|
|
186
|
+
const current_line_prefix = text_before_caret.slice(line_start_index);
|
|
187
|
+
const indentation_match = current_line_prefix.match(/^[\t ]*/);
|
|
188
|
+
const indentation = indentation_match ? indentation_match[0] : '';
|
|
189
|
+
|
|
190
|
+
tr.insert_text(`\n${indentation}`);
|
|
191
|
+
session.apply(tr);
|
|
158
192
|
}
|
|
159
193
|
}
|
|
160
194
|
|
|
@@ -165,7 +199,15 @@ export class AddNewLineCommand extends Command {
|
|
|
165
199
|
*/
|
|
166
200
|
export class BreakTextNodeCommand extends Command {
|
|
167
201
|
is_enabled() {
|
|
168
|
-
|
|
202
|
+
const session = this.context.session;
|
|
203
|
+
const selection = session.selection;
|
|
204
|
+
if (!this.context.editable || selection?.type !== 'text') return false;
|
|
205
|
+
|
|
206
|
+
const owner_node = session.get(selection.path.slice(0, -1));
|
|
207
|
+
const owner_node_schema = owner_node ? session.schema[owner_node.type] : null;
|
|
208
|
+
if (!owner_node_schema || owner_node_schema.kind !== 'text') return false;
|
|
209
|
+
|
|
210
|
+
return session.inspect(selection.path.slice(0, -2))?.type === 'node_array';
|
|
169
211
|
}
|
|
170
212
|
|
|
171
213
|
execute() {
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import {
|
|
2
|
+
import { serialize_path } from './utils.js';
|
|
3
3
|
|
|
4
4
|
/** @import { CustomPropertyProps } from './types.d.ts'; */
|
|
5
|
-
const svedit = getContext('svedit');
|
|
6
5
|
|
|
7
6
|
/** @type {CustomPropertyProps} */
|
|
8
7
|
let { path, tag = 'div', class: css_class, children, style, ...rest } = $props();
|
|
8
|
+
let path_str = $derived(serialize_path(path));
|
|
9
|
+
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
<svelte:element
|
|
12
13
|
this={tag}
|
|
13
14
|
class={css_class}
|
|
14
15
|
data-type="property"
|
|
15
|
-
data-path={
|
|
16
|
-
style="anchor-name: --{
|
|
16
|
+
data-path={path_str}
|
|
17
|
+
style="anchor-name: --{path_str};{style ? ` ${style}` : ''}"
|
|
17
18
|
{...rest}
|
|
18
19
|
>
|
|
19
|
-
|
|
20
|
-
<div class="
|
|
21
|
-
|
|
22
|
-
</div>
|
|
23
|
-
{/if}
|
|
20
|
+
<div class="property-selectable">
|
|
21
|
+
<div class="svedit-selectable"><br /></div>
|
|
22
|
+
</div>
|
|
24
23
|
|
|
25
24
|
{@render children()}
|
|
26
25
|
</svelte:element>
|
|
@@ -37,5 +36,17 @@
|
|
|
37
36
|
bottom: 0;
|
|
38
37
|
z-index: 1;
|
|
39
38
|
outline: none;
|
|
39
|
+
/* Position the hidden selectable element at the bottom so the
|
|
40
|
+
browser's native scroll-to-caret ensures the full property
|
|
41
|
+
is visible, not just the top edge. */
|
|
42
|
+
display: none;
|
|
43
|
+
align-items: flex-end;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
}
|
|
46
|
+
:global(.svedit.editable) .property-selectable {
|
|
47
|
+
display: flex;
|
|
48
|
+
}
|
|
49
|
+
.svedit-selectable {
|
|
50
|
+
caret-color: transparent;
|
|
40
51
|
}
|
|
41
52
|
</style>
|
|
@@ -3,5 +3,6 @@ type CustomProperty = {
|
|
|
3
3
|
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
4
|
$set?(props: Partial<CustomPropertyProps>): void;
|
|
5
5
|
};
|
|
6
|
-
declare const CustomProperty: import("svelte").Component<
|
|
6
|
+
declare const CustomProperty: import("svelte").Component<CustomPropertyProps, {}, "">;
|
|
7
|
+
import type { CustomPropertyProps } from './types.d.ts';
|
|
7
8
|
//# sourceMappingURL=CustomProperty.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CustomProperty.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"CustomProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CustomProperty.svelte.js"],"names":[],"mappings":";;;;;AA4BA,sFAA6D;yCApBnB,cAAc"}
|
package/dist/Node.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
|
+
import { serialize_path } from './utils.js';
|
|
3
4
|
|
|
4
5
|
/** @import { NodeProps } from './types.d.ts'; */
|
|
5
6
|
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
let { path, children, tag = 'div', class: css_class, style = '', ...rest } = $props();
|
|
10
11
|
|
|
11
12
|
let node = $derived(svedit.session.get(path));
|
|
13
|
+
let path_str = $derived(serialize_path(path));
|
|
12
14
|
|
|
13
15
|
const node_array_meta = getContext('node_array_meta');
|
|
14
16
|
let child_index = $derived(node_array_meta ? parseInt(String(path.at(-1)), 10) : -1);
|
|
@@ -17,13 +19,13 @@
|
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
21
|
<svelte:element
|
|
20
|
-
id={node.id}
|
|
21
22
|
this={tag}
|
|
22
|
-
|
|
23
|
+
id={node.id}
|
|
24
|
+
class="node-{node.type} {css_class}{is_first ? ' first' : ''}{is_last ? ' last' : ''}"
|
|
23
25
|
data-node-id={node.id}
|
|
24
|
-
data-path={
|
|
26
|
+
data-path={path_str}
|
|
25
27
|
data-type="node"
|
|
26
|
-
style="anchor-name: --{
|
|
28
|
+
style="anchor-name: --{path_str};{style}"
|
|
27
29
|
{...rest}
|
|
28
30
|
>
|
|
29
31
|
{@render children()}
|
|
@@ -35,4 +37,4 @@
|
|
|
35
37
|
/* For developers who need to position their node with `position: absolute` or `position: relative`, they need to wrap their node in a div */
|
|
36
38
|
position: static !important;
|
|
37
39
|
}
|
|
38
|
-
</style>
|
|
40
|
+
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;AAkCA,yFAAmD"}
|
|
@@ -1,82 +1,93 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext, setContext } from 'svelte';
|
|
3
3
|
import UnknownNode from './UnknownNode.svelte';
|
|
4
|
-
import {
|
|
4
|
+
import { serialize_path } from './utils.js';
|
|
5
5
|
import DefaultNodeGap from './NodeGap.svelte';
|
|
6
6
|
import DefaultNodeGapMarkers from './NodeGapMarkers.svelte';
|
|
7
7
|
|
|
8
8
|
/** @import { NodeArrayPropertyProps } from './types.d.ts'; */
|
|
9
9
|
|
|
10
10
|
const svedit = getContext('svedit');
|
|
11
|
-
let NodeGap = $derived(svedit.session.config.system_components?.
|
|
12
|
-
let NodeGapMarkers = $derived(
|
|
11
|
+
let NodeGap = $derived(svedit.session.config.system_components?.node_gap ?? DefaultNodeGap);
|
|
12
|
+
let NodeGapMarkers = $derived(
|
|
13
|
+
svedit.session.config.system_components?.node_gap_markers ?? DefaultNodeGapMarkers
|
|
14
|
+
);
|
|
13
15
|
|
|
14
16
|
/** @type {NodeArrayPropertyProps} */
|
|
15
17
|
let { path, tag = 'div', class: css_class, style = '', ...rest } = $props();
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Pre-joined once per path change; reused by data-path and by every
|
|
20
|
+
// NodeGap's should_position_gap call to avoid N+1 joins per render.
|
|
21
|
+
let path_str = $derived(serialize_path(path));
|
|
22
|
+
|
|
23
|
+
let node_ids = $derived(svedit.session.get(path));
|
|
24
|
+
let nodes = $derived(node_ids.map((id) => svedit.session.get(id)));
|
|
25
|
+
|
|
26
|
+
// Mirrors the AnnotatedTextProperty pattern: a `focused` class follows
|
|
27
|
+
// the model selection. Used by the CSS rule below to hide the empty-array
|
|
28
|
+
// NodeGap while the caret is in the placeholder, so its <br> isn't a
|
|
29
|
+
// second arrow-key stop (issue #260).
|
|
30
|
+
let is_focused = $derived(
|
|
31
|
+
svedit.session.selection?.type === 'node' &&
|
|
32
|
+
serialize_path(svedit.session.selection.path) === path_str
|
|
21
33
|
);
|
|
22
34
|
|
|
23
35
|
setContext('node_array_meta', {
|
|
24
|
-
get length() {
|
|
36
|
+
get length() {
|
|
37
|
+
return node_ids.length;
|
|
38
|
+
}
|
|
25
39
|
});
|
|
26
40
|
</script>
|
|
41
|
+
|
|
27
42
|
<!-- we use the anchor of node_array in Overlays.svelte to position the last insertion point in a horizontal layout based on the right edge of the container -->
|
|
28
|
-
<svelte:element
|
|
29
|
-
this={tag}
|
|
30
|
-
class={css_class}
|
|
31
|
-
|
|
32
|
-
data-
|
|
33
|
-
|
|
43
|
+
<svelte:element
|
|
44
|
+
this={tag}
|
|
45
|
+
class={css_class}
|
|
46
|
+
class:focused={is_focused}
|
|
47
|
+
data-type="node_array"
|
|
48
|
+
data-path={path_str}
|
|
49
|
+
style="anchor-name: --{path_str};{style ? ` ${style}` : ''}"
|
|
50
|
+
{...rest}
|
|
34
51
|
>
|
|
35
|
-
{#if
|
|
36
|
-
<!--
|
|
37
|
-
Experimental: We'll let .empty-node-array act like a node, so the existing
|
|
38
|
-
code paths for selection mapping will work as expected.
|
|
39
|
-
|
|
40
|
-
TODO: Need to figure out a way to make .empty-node-array customizable.
|
|
41
|
-
-->
|
|
52
|
+
{#if node_ids.length === 0 && svedit.editable}
|
|
42
53
|
<div
|
|
43
|
-
class="
|
|
44
|
-
data-path={[...path, 0]
|
|
54
|
+
class="empty-node-placeholder"
|
|
55
|
+
data-path={serialize_path([...path, 0])}
|
|
45
56
|
data-type="node"
|
|
46
|
-
style="anchor-name: --{[...path, 0]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<!-- Sibling (not child) of .empty-node-array so its .svedit-selectable
|
|
51
|
-
resolves anchor positioning against the shared containing block,
|
|
52
|
-
not the placeholder which inherits .node positioning styles. -->
|
|
53
|
-
<NodeGap array_path={path} offset={0} count={0} empty />
|
|
57
|
+
style="anchor-name: --{serialize_path([...path, 0])};"
|
|
58
|
+
>
|
|
59
|
+
<NodeGap array_path={path} offset={0} count={0} empty />
|
|
60
|
+
</div>
|
|
54
61
|
{/if}
|
|
55
62
|
{#each nodes as node, index (index)}
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
array_path={path}
|
|
59
|
-
offset={index}
|
|
60
|
-
count={nodes.length}
|
|
61
|
-
positioned={svedit.is_near_viewport?.([...path, index]) ?? true}
|
|
62
|
-
/>
|
|
63
|
-
{/if}
|
|
64
|
-
{@const Component = svedit.session.config.node_components[snake_to_pascal(node.type)]}
|
|
63
|
+
<NodeGap array_path={path} offset={index} count={nodes.length} />
|
|
64
|
+
{@const Component = svedit.session.config.node_components[node.type]}
|
|
65
65
|
{#if Component}
|
|
66
66
|
<Component path={[...path, index]} />
|
|
67
67
|
{:else}
|
|
68
68
|
<UnknownNode path={[...path, index]} />
|
|
69
69
|
{/if}
|
|
70
70
|
{/each}
|
|
71
|
-
{#if
|
|
72
|
-
<NodeGap
|
|
73
|
-
array_path={path}
|
|
74
|
-
offset={nodes.length}
|
|
75
|
-
count={nodes.length}
|
|
76
|
-
positioned={svedit.is_near_viewport?.([...path, nodes.length - 1]) ?? true}
|
|
77
|
-
/>
|
|
71
|
+
{#if node_ids.length > 0}
|
|
72
|
+
<NodeGap array_path={path} offset={node_ids.length} count={node_ids.length} />
|
|
78
73
|
{/if}
|
|
79
74
|
{#if svedit.editable && NodeGapMarkers}
|
|
80
|
-
<NodeGapMarkers {path} />
|
|
75
|
+
<NodeGapMarkers {path} count={node_ids.length} />
|
|
81
76
|
{/if}
|
|
82
77
|
</svelte:element>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
/* position: relative makes this the containing block for the gap's
|
|
81
|
+
.svedit-selectable, which fills it via inset:0. You may override to
|
|
82
|
+
position: absolute (e.g. so an empty array doesn't occupy flow space)
|
|
83
|
+
— still a containing block; then also set position: relative on the
|
|
84
|
+
parent node-array container so this placeholder's inset:0 resolves
|
|
85
|
+
against it. */
|
|
86
|
+
:where(.empty-node-placeholder) {
|
|
87
|
+
position: relative;
|
|
88
|
+
inset: 0;
|
|
89
|
+
min-height: 40px;
|
|
90
|
+
min-width: 24px;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;AA8EA,mHAAgE"}
|
package/dist/NodeCaret.svelte
CHANGED
|
@@ -11,13 +11,6 @@
|
|
|
11
11
|
<div class="caret" role="none"></div>
|
|
12
12
|
|
|
13
13
|
<style>
|
|
14
|
-
@keyframes node-caret-blink {
|
|
15
|
-
0%, 60% { opacity: 1; }
|
|
16
|
-
68% { opacity: 0; }
|
|
17
|
-
88% { opacity: 0; }
|
|
18
|
-
100% { opacity: 1; }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
14
|
.caret {
|
|
22
15
|
--_R: var(--row, 1);
|
|
23
16
|
--_C: calc(1 - var(--row, 1));
|