svedit 0.7.0 → 0.8.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 +91 -2
- package/dist/Node.svelte +4 -25
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +17 -3
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeGap.svelte +64 -27
- package/dist/NodeGap.svelte.d.ts +6 -6
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +36 -10
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/Svedit.svelte +84 -122
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/node_gap_computation.svelte.js +1 -1
- package/dist/transforms.svelte.d.ts +8 -0
- package/dist/transforms.svelte.d.ts.map +1 -1
- package/dist/transforms.svelte.js +13 -0
- package/package.json +1 -1
- package/src/lib/Node.svelte +4 -25
- package/src/lib/NodeArrayProperty.svelte +17 -3
- package/src/lib/NodeGap.svelte +64 -27
- package/src/lib/NodeGapMarkers.svelte +36 -10
- package/src/lib/Svedit.svelte +84 -122
- package/src/lib/node_gap_computation.svelte.js +1 -1
- package/src/lib/transforms.svelte.js +13 -0
|
@@ -66,6 +66,12 @@
|
|
|
66
66
|
* --node-caret-blink-duration
|
|
67
67
|
* --node-caret-animation
|
|
68
68
|
* --node-caret-row-inline-position
|
|
69
|
+
* --node-caret-boundary (anchor-name of a parent element; edge
|
|
70
|
+
* gaps clamp to its edges instead of 0px.
|
|
71
|
+
* Prevents overlap when the node array has
|
|
72
|
+
* neighbors. See NodeGap.svelte for details.)
|
|
73
|
+
* --node-caret-boundary-x (per-axis override; clamps left/right only)
|
|
74
|
+
* --node-caret-boundary-y (per-axis override; clamps top/bottom only)
|
|
69
75
|
*
|
|
70
76
|
* Row/column detection uses var(--row, 1) with the * 99999 multiplier
|
|
71
77
|
* trick throughout. Shorthand:
|
|
@@ -262,9 +268,11 @@
|
|
|
262
268
|
|
|
263
269
|
/* Edge first: column = above first node, row = left of first node */
|
|
264
270
|
.gap-edge.first {
|
|
271
|
+
--_b-t: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) top, 0px);
|
|
272
|
+
--_b-l: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) left, 0px);
|
|
265
273
|
top: min(
|
|
266
274
|
calc(anchor(var(--_a) top) + var(--_C) * 99999px),
|
|
267
|
-
calc(max(
|
|
275
|
+
calc(max(var(--_b-t), calc(anchor(var(--_a) top) - var(--_gm))) + var(--_R) * 99999px)
|
|
268
276
|
);
|
|
269
277
|
bottom: min(
|
|
270
278
|
calc(anchor(var(--_a) bottom) + var(--_C) * 99999px),
|
|
@@ -272,7 +280,7 @@
|
|
|
272
280
|
);
|
|
273
281
|
left: min(
|
|
274
282
|
calc(anchor(var(--_a) left) + var(--_R) * 99999px),
|
|
275
|
-
calc(max(
|
|
283
|
+
calc(max(var(--_b-l), calc(anchor(var(--_a) left) - var(--_gm))) + var(--_C) * 99999px)
|
|
276
284
|
);
|
|
277
285
|
right: min(
|
|
278
286
|
calc(anchor(var(--_a) right) + var(--_R) * 99999px),
|
|
@@ -280,23 +288,40 @@
|
|
|
280
288
|
);
|
|
281
289
|
}
|
|
282
290
|
|
|
283
|
-
/* Edge last: column = below last node, row = right of last node
|
|
291
|
+
/* Edge last: column = below last node, row = right of last node.
|
|
292
|
+
top/left also clamp to boundary - gm so that min-height/min-width
|
|
293
|
+
(which win over bottom/right in overconstrained abs-pos) cannot
|
|
294
|
+
push the element past the boundary. */
|
|
284
295
|
.gap-edge.last {
|
|
296
|
+
--_b-b: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) bottom, 0px);
|
|
297
|
+
--_b-r: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) right, 0px);
|
|
298
|
+
--_b-bt: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) bottom, 99999px);
|
|
299
|
+
--_b-rl: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) right, 99999px);
|
|
285
300
|
top: min(
|
|
286
301
|
calc(anchor(var(--_a) top) + var(--_C) * 99999px),
|
|
287
|
-
calc(
|
|
302
|
+
calc(
|
|
303
|
+
min(
|
|
304
|
+
anchor(var(--_a) bottom),
|
|
305
|
+
calc(var(--_b-bt) - var(--_gm))
|
|
306
|
+
) + var(--_R) * 99999px
|
|
307
|
+
)
|
|
288
308
|
);
|
|
289
309
|
bottom: min(
|
|
290
310
|
calc(anchor(var(--_a) bottom) + var(--_C) * 99999px),
|
|
291
|
-
calc(anchor(var(--_a) bottom) - var(--_gm) + var(--_R) * 99999px)
|
|
311
|
+
calc(max(var(--_b-b), calc(anchor(var(--_a) bottom) - var(--_gm))) + var(--_R) * 99999px)
|
|
292
312
|
);
|
|
293
313
|
left: min(
|
|
294
314
|
calc(anchor(var(--_a) left) + var(--_R) * 99999px),
|
|
295
|
-
calc(
|
|
315
|
+
calc(
|
|
316
|
+
min(
|
|
317
|
+
anchor(var(--_a) right),
|
|
318
|
+
calc(var(--_b-rl) - var(--_gm))
|
|
319
|
+
) + var(--_C) * 99999px
|
|
320
|
+
),
|
|
296
321
|
calc(100% - var(--_gm) + var(--_C) * 99999px)
|
|
297
322
|
);
|
|
298
323
|
right: max(
|
|
299
|
-
calc(
|
|
324
|
+
calc(var(--_b-r) + var(--_C) * -99999px),
|
|
300
325
|
calc(anchor(var(--_a) right) + var(--_R) * -99999px),
|
|
301
326
|
calc(anchor(var(--_a) right) - var(--_gm) + var(--_C) * -99999px),
|
|
302
327
|
calc(anchor(var(--_c) right) + var(--_C) * -99999px)
|
|
@@ -314,6 +339,7 @@
|
|
|
314
339
|
left: min(
|
|
315
340
|
calc(anchor(var(--_a) left) + var(--_R) * 99999px),
|
|
316
341
|
calc(anchor(var(--_a) right) + var(--_C) * 99999px),
|
|
342
|
+
calc(var(--_b-rl) - var(--_gm) + var(--_C) * 99999px),
|
|
317
343
|
calc(
|
|
318
344
|
anchor(var(--_a) right)
|
|
319
345
|
+ (max(0px, anchor(var(--_s) left) - anchor(var(--_f) right))) / 2
|
|
@@ -341,7 +367,7 @@
|
|
|
341
367
|
);
|
|
342
368
|
right: max(
|
|
343
369
|
calc(anchor(var(--_a) right) + var(--_R) * -99999px),
|
|
344
|
-
calc(
|
|
370
|
+
calc(var(--_b-r) + var(--_C) * -99999px),
|
|
345
371
|
calc(
|
|
346
372
|
anchor(var(--_a) right)
|
|
347
373
|
- (
|
|
@@ -450,7 +476,7 @@
|
|
|
450
476
|
outline: 0.1px solid green;
|
|
451
477
|
}
|
|
452
478
|
.gap-marker {
|
|
453
|
-
outline:
|
|
479
|
+
outline: 1px solid blue;
|
|
454
480
|
outline-offset: 0.5px;
|
|
455
481
|
} */
|
|
456
|
-
</style>
|
|
482
|
+
</style>
|
package/src/lib/Svedit.svelte
CHANGED
|
@@ -269,6 +269,10 @@
|
|
|
269
269
|
if (!canvas_el?.contains(range.commonAncestorContainer)) return;
|
|
270
270
|
let selection = __get_selection_from_dom();
|
|
271
271
|
if (selection) {
|
|
272
|
+
// Avoid assigning a new object reference when the selection is
|
|
273
|
+
// structurally identical — prevents a redundant $effect cycle
|
|
274
|
+
// (render_selection → scrollIntoView) on every DOM layout change.
|
|
275
|
+
if (JSON.stringify(selection) === JSON.stringify(session.selection)) return;
|
|
272
276
|
session.selection = selection;
|
|
273
277
|
}
|
|
274
278
|
}
|
|
@@ -573,15 +577,15 @@ ${fallback_html}`;
|
|
|
573
577
|
|
|
574
578
|
let plain_text,
|
|
575
579
|
pasted_json,
|
|
576
|
-
|
|
580
|
+
pasted_media = [];
|
|
577
581
|
|
|
578
582
|
// NOTE: For some reason, await navigator.clipboard.read()
|
|
579
583
|
const clipboard_items = event.clipboardData?.items || [];
|
|
580
584
|
for (const item of clipboard_items || []) {
|
|
581
|
-
if (item.type.startsWith('image/')) {
|
|
585
|
+
if (item.type.startsWith('image/') || item.type.startsWith('video/') || item.type.startsWith('audio/')) {
|
|
582
586
|
const blob = item.getAsFile();
|
|
583
587
|
const data_url = URL.createObjectURL(blob);
|
|
584
|
-
|
|
588
|
+
pasted_media.push({
|
|
585
589
|
blob,
|
|
586
590
|
data_url,
|
|
587
591
|
type: item.type,
|
|
@@ -590,11 +594,12 @@ ${fallback_html}`;
|
|
|
590
594
|
}
|
|
591
595
|
}
|
|
592
596
|
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
597
|
+
if (pasted_media.length > 0) {
|
|
598
|
+
const handle_media_paste = session.config.handle_media_paste || session.config.handle_image_paste
|
|
599
|
+
pasted_json = await handle_media_paste(session, pasted_media);
|
|
600
|
+
console.log('pasted_json_after_media_paste', pasted_json);
|
|
596
601
|
// NOTE: If no pasted_json is returned from the custom handler, we assume that content creation has been
|
|
597
|
-
// handled inside
|
|
602
|
+
// handled inside handle_media_paste already.
|
|
598
603
|
if (!pasted_json) return;
|
|
599
604
|
} else {
|
|
600
605
|
// First try to extract svedit data from HTML format
|
|
@@ -763,6 +768,23 @@ ${fallback_html}`;
|
|
|
763
768
|
canvas_el?.focus();
|
|
764
769
|
}
|
|
765
770
|
|
|
771
|
+
/**
|
|
772
|
+
* When a DOM selection endpoint lands in a sibling NodeGap (not inside
|
|
773
|
+
* a node), resolves the adjacent node element for the walk-up algorithm.
|
|
774
|
+
* @param {HTMLElement} el
|
|
775
|
+
* @returns {HTMLElement | null}
|
|
776
|
+
*/
|
|
777
|
+
function __resolve_node_from_gap(el) {
|
|
778
|
+
const gap = /** @type {HTMLElement | null} */ (el.closest('[data-gap-array-path]'));
|
|
779
|
+
if (!gap) return null;
|
|
780
|
+
const array_path = gap.dataset.gapArrayPath;
|
|
781
|
+
const offset = parseInt(gap.dataset.gapOffset, 10);
|
|
782
|
+
const node_idx = offset > 0 ? offset - 1 : 0;
|
|
783
|
+
return /** @type {HTMLElement | null} */ (
|
|
784
|
+
canvas_el.querySelector(`[data-path="${array_path}.${node_idx}"][data-type="node"]`)
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
766
788
|
/**
|
|
767
789
|
* Extracts a NodeSelection from the current DOM selection.
|
|
768
790
|
*
|
|
@@ -782,56 +804,30 @@ ${fallback_html}`;
|
|
|
782
804
|
if (focus_node.nodeType !== Node.ELEMENT_NODE) focus_node = focus_node.parentElement;
|
|
783
805
|
if (anchor_node.nodeType !== Node.ELEMENT_NODE) anchor_node = anchor_node.parentElement;
|
|
784
806
|
|
|
785
|
-
// EDGE CASE:
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
const node_path = /** @type {DocumentPath} */ (node.dataset.path.split('.'));
|
|
796
|
-
const node_index = parseInt(String(node_path.at(-1)), 10);
|
|
797
|
-
|
|
798
|
-
return {
|
|
799
|
-
type: 'node',
|
|
800
|
-
path: node_path.slice(0, -1),
|
|
801
|
-
anchor_offset: node_index + 1,
|
|
802
|
-
focus_offset: node_index + 1
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// EDGE CASE: Let's check if we are in a gap-before for node_arrays.
|
|
807
|
-
let gap_before_el = focus_node.closest('[data-type="gap-before"]');
|
|
808
|
-
if (gap_before_el && focus_node === anchor_node) {
|
|
809
|
-
const node_array_el = /** @type {HTMLElement} */ (
|
|
810
|
-
gap_before_el.closest('[data-type="node_array"]')
|
|
811
|
-
);
|
|
812
|
-
const node_array_path = node_array_el.dataset.path.split('.');
|
|
807
|
+
// EDGE CASE: Collapsed selection inside a node gap (gap-after or gap-before).
|
|
808
|
+
// Gaps are siblings of nodes with data-gap-array-path and data-gap-offset.
|
|
809
|
+
const gap_el = /** @type {HTMLElement | null} */ (
|
|
810
|
+
focus_node.closest('[data-gap-array-path]')
|
|
811
|
+
);
|
|
812
|
+
if (gap_el && focus_node === anchor_node) {
|
|
813
|
+
const array_path = gap_el.dataset.gapArrayPath.split('.');
|
|
814
|
+
const gap_offset = parseInt(gap_el.dataset.gapOffset, 10);
|
|
813
815
|
return {
|
|
814
816
|
type: 'node',
|
|
815
|
-
path:
|
|
816
|
-
anchor_offset:
|
|
817
|
-
focus_offset:
|
|
817
|
+
path: array_path,
|
|
818
|
+
anchor_offset: gap_offset,
|
|
819
|
+
focus_offset: gap_offset
|
|
818
820
|
};
|
|
819
821
|
}
|
|
820
822
|
|
|
821
|
-
let focus_root =
|
|
822
|
-
focus_node.closest('[data-path][data-type="node"]')
|
|
823
|
-
);
|
|
823
|
+
let focus_root = __resolve_node_from_gap(focus_node)
|
|
824
|
+
?? /** @type {HTMLElement} */ (focus_node.closest('[data-path][data-type="node"]'));
|
|
824
825
|
if (!focus_root) return null;
|
|
825
826
|
|
|
826
|
-
let anchor_root =
|
|
827
|
-
anchor_node.closest('[data-path][data-type="node"]')
|
|
828
|
-
);
|
|
827
|
+
let anchor_root = __resolve_node_from_gap(anchor_node)
|
|
828
|
+
?? /** @type {HTMLElement} */ (anchor_node.closest('[data-path][data-type="node"]'));
|
|
829
829
|
if (!anchor_root) return null;
|
|
830
830
|
|
|
831
|
-
if (!(focus_root && anchor_root)) {
|
|
832
|
-
return null;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
831
|
let focus_root_path = focus_root.dataset.path.split('.');
|
|
836
832
|
let anchor_root_path = anchor_root.dataset.path.split('.');
|
|
837
833
|
let focus_node_depth = focus_root_path.length;
|
|
@@ -871,6 +867,10 @@ ${fallback_html}`;
|
|
|
871
867
|
}
|
|
872
868
|
}
|
|
873
869
|
|
|
870
|
+
// If both paths walked up to the root node, there's no common parent node_array
|
|
871
|
+
// (e.g. selecting between nav and page body). The resulting path would be empty.
|
|
872
|
+
if (anchor_root_path.length <= 1) return null;
|
|
873
|
+
|
|
874
874
|
let anchor_offset = parseInt(anchor_root_path.at(-1));
|
|
875
875
|
let focus_offset = parseInt(focus_root_path.at(-1));
|
|
876
876
|
|
|
@@ -1093,106 +1093,68 @@ ${fallback_html}`;
|
|
|
1093
1093
|
}
|
|
1094
1094
|
|
|
1095
1095
|
function __get_node_element(node_array_path, node_offset) {
|
|
1096
|
-
|
|
1097
|
-
`[data-path="${node_array_path}"][data-type="
|
|
1096
|
+
return canvas_el.querySelector(
|
|
1097
|
+
`[data-path="${node_array_path}.${node_offset}"][data-type="node"]`
|
|
1098
1098
|
);
|
|
1099
|
-
if (!node_array_el) return null;
|
|
1100
|
-
|
|
1101
|
-
const node_elements = node_array_el.children;
|
|
1102
|
-
if (node_elements.length === 0) return null;
|
|
1103
|
-
|
|
1104
|
-
return node_elements[node_offset];
|
|
1105
1099
|
}
|
|
1106
1100
|
|
|
1107
1101
|
function __render_node_selection() {
|
|
1108
1102
|
const selection = /** @type {NodeSelection} */ (session.selection);
|
|
1109
1103
|
const node_array_path = selection.path.join('.');
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
// We need to translate the cusor offset to node offsets now
|
|
1114
|
-
let anchor_node_offset, focus_node_offset;
|
|
1104
|
+
const is_collapsed = selection.anchor_offset === selection.focus_offset;
|
|
1105
|
+
const is_backward = !is_collapsed && selection.anchor_offset > selection.focus_offset;
|
|
1115
1106
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
anchor_node_offset = selection.anchor_offset - 1;
|
|
1121
|
-
focus_node_offset = selection.focus_offset;
|
|
1122
|
-
} else {
|
|
1123
|
-
anchor_node_offset = selection.anchor_offset;
|
|
1124
|
-
focus_node_offset = selection.focus_offset - 1;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
const anchor_node = __get_node_element(node_array_path, anchor_node_offset);
|
|
1128
|
-
const focus_node = __get_node_element(node_array_path, focus_node_offset);
|
|
1107
|
+
const node_array_el = canvas_el.querySelector(
|
|
1108
|
+
`[data-path="${node_array_path}"][data-type="node_array"]`
|
|
1109
|
+
);
|
|
1110
|
+
if (!node_array_el) return;
|
|
1129
1111
|
|
|
1130
|
-
if (!anchor_node || !focus_node) return;
|
|
1131
1112
|
const dom_selection = window.getSelection();
|
|
1132
1113
|
const range = window.document.createRange();
|
|
1133
1114
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
// IMPORTANT: We need to look for direct children of anchor_node to find the right node gap.
|
|
1137
|
-
const gap_selector = selection.anchor_offset === 0
|
|
1138
|
-
? '.gap-before'
|
|
1139
|
-
: '.gap-after';
|
|
1140
|
-
// In empty arrays the node gap is a sibling of the placeholder, not a child
|
|
1141
|
-
const gap_el =
|
|
1142
|
-
anchor_node.querySelector(`:scope > ${gap_selector}`) ??
|
|
1143
|
-
anchor_node.parentElement?.querySelector(`:scope > ${gap_selector}`);
|
|
1115
|
+
const gap_selector = (offset) =>
|
|
1116
|
+
`[data-gap-array-path="${node_array_path}"][data-gap-offset="${offset}"]`;
|
|
1144
1117
|
|
|
1118
|
+
if (is_collapsed) {
|
|
1119
|
+
const gap_el = node_array_el.querySelector(gap_selector(selection.anchor_offset));
|
|
1145
1120
|
if (!gap_el) return;
|
|
1146
|
-
|
|
1147
|
-
|
|
1121
|
+
// Target .svedit-selectable (has a box), not gap_el which is
|
|
1122
|
+
// display:contents and would cause the browser to normalize
|
|
1123
|
+
// the range into the parent, breaking read-back.
|
|
1124
|
+
const selectable = gap_el.querySelector('.svedit-selectable');
|
|
1125
|
+
if (!selectable) return;
|
|
1126
|
+
range.setStart(selectable, 1);
|
|
1127
|
+
range.setEnd(selectable, 1);
|
|
1148
1128
|
dom_selection.removeAllRanges();
|
|
1149
1129
|
dom_selection.addRange(range);
|
|
1150
1130
|
} else {
|
|
1151
|
-
|
|
1131
|
+
const anchor_gap = node_array_el.querySelector(gap_selector(selection.anchor_offset));
|
|
1132
|
+
const focus_gap = node_array_el.querySelector(gap_selector(selection.focus_offset));
|
|
1133
|
+
if (!anchor_gap || !focus_gap) return;
|
|
1134
|
+
const anchor_sel = anchor_gap.querySelector('.svedit-selectable');
|
|
1135
|
+
const focus_sel = focus_gap.querySelector('.svedit-selectable');
|
|
1136
|
+
if (!anchor_sel || !focus_sel) return;
|
|
1137
|
+
|
|
1152
1138
|
if (is_backward) {
|
|
1153
|
-
// Use the last selectable you find
|
|
1154
|
-
const anchor_node_selectable = [...anchor_node.querySelectorAll('.svedit-selectable')].at(
|
|
1155
|
-
-1
|
|
1156
|
-
);
|
|
1157
|
-
// Use the first selectable you find
|
|
1158
|
-
const focus_node_selectable = [...focus_node.querySelectorAll('.svedit-selectable')].at(0);
|
|
1159
|
-
|
|
1160
|
-
range.setStart(focus_node_selectable, 1);
|
|
1161
|
-
range.setEnd(anchor_node_selectable, 1);
|
|
1162
1139
|
dom_selection.removeAllRanges();
|
|
1163
|
-
dom_selection.
|
|
1164
|
-
// Phew, this was a hard nut to crack. But with that code the direction can be reversed.
|
|
1165
|
-
dom_selection.setBaseAndExtent(anchor_node_selectable, 1, focus_node_selectable, 1);
|
|
1140
|
+
dom_selection.setBaseAndExtent(anchor_sel, 1, focus_sel, 1);
|
|
1166
1141
|
} else {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
0
|
|
1170
|
-
);
|
|
1171
|
-
// Use the last selectable you find
|
|
1172
|
-
const focus_node_selectable = [...focus_node.querySelectorAll('.svedit-selectable')].at(-1);
|
|
1173
|
-
|
|
1174
|
-
range.setStart(anchor_node_selectable, 1);
|
|
1175
|
-
range.setEnd(focus_node_selectable, 1);
|
|
1142
|
+
range.setStart(anchor_sel, 1);
|
|
1143
|
+
range.setEnd(focus_sel, 1);
|
|
1176
1144
|
dom_selection.removeAllRanges();
|
|
1177
1145
|
dom_selection.addRange(range);
|
|
1178
1146
|
}
|
|
1179
1147
|
}
|
|
1180
1148
|
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
// Scroll the selection into view
|
|
1149
|
+
node_array_el.focus();
|
|
1150
|
+
const scroll_node_offset = is_collapsed
|
|
1151
|
+
? Math.max(0, selection.anchor_offset - 1)
|
|
1152
|
+
: (is_backward ? selection.focus_offset : selection.anchor_offset);
|
|
1153
|
+
const scroll_node = __get_node_element(node_array_path, scroll_node_offset);
|
|
1154
|
+
if (scroll_node) {
|
|
1188
1155
|
setTimeout(() => {
|
|
1189
|
-
(
|
|
1190
|
-
block: 'nearest',
|
|
1191
|
-
inline: 'nearest'
|
|
1192
|
-
});
|
|
1156
|
+
scroll_node.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
1193
1157
|
}, 0);
|
|
1194
|
-
} else {
|
|
1195
|
-
console.log('No container element found!');
|
|
1196
1158
|
}
|
|
1197
1159
|
}
|
|
1198
1160
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* The viewport tracker drives **lazy positioning**: only near-viewport
|
|
28
28
|
* gaps receive CSS anchor positioning (via NodeGap's `positioned`
|
|
29
|
-
* prop). Off-viewport gaps remain as zero-size
|
|
29
|
+
* prop). Off-viewport gaps remain as zero-size absolute elements with
|
|
30
30
|
* no layout cost. Gap markers (NodeGapMarkers) are only computed for
|
|
31
31
|
* visible indices.
|
|
32
32
|
*
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { split_annotated_text, join_annotated_text, get_char_length } from './utils.js';
|
|
2
2
|
import { get_default_node_type } from './doc_utils.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Set multiple properties on a node via a transaction.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} tr - The transaction
|
|
8
|
+
* @param {Array} path - Path to the node
|
|
9
|
+
* @param {Record<string, any>} properties - Key/value pairs to set
|
|
10
|
+
*/
|
|
11
|
+
export function set_properties(tr, path, properties) {
|
|
12
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
13
|
+
tr.set([...path, key], value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export function break_text_node(tr) {
|
|
5
18
|
// Keep a reference of the original selection (before any transforms are applied)
|
|
6
19
|
const selection = tr.selection;
|