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 CHANGED
@@ -1056,6 +1056,82 @@ Further things to consider:
1056
1056
  - Avoid CSS selectors like `:last-child` or `:first-child` on nodes (e.g., `[data-type="node"]:last-child`) because `<NodeGap>` elements are inserted in edit mode and will become the actual first or last child. This can cause unexpected layout shifts (e.g., if you have `.paragraph-node:last-child { margin-bottom: 10px }`, the margin won't apply as expected).
1057
1057
  - Avoid adding css `margin` to nodes inside node arrays when using flex or grid layouts. Use `gap` on the container instead. This ensure `NodeGap` and `NodeGapMarkers` render consistently. If you need to add a margin, add it to child element of Node.
1058
1058
 
1059
+ ### Node array CSS tokens
1060
+
1061
+ `<NodeArrayProperty>` renders a `[data-type="node_array"]` element. Two CSS custom properties control how gaps and carets behave inside it:
1062
+
1063
+ **`--row`** — Tell Svedit whether a node array flows horizontally (`1`) or vertically (`0`). Gaps, carets, and markers all switch orientation accordingly. Defaults to `0` on the Svedit canvas. Set it on the node array or an ancestor:
1064
+
1065
+ ```css
1066
+ .my-horizontal-layout :global(.grid-items) {
1067
+ --row: 1;
1068
+ display: flex;
1069
+ flex-wrap: wrap;
1070
+ }
1071
+ ```
1072
+
1073
+ **`--node-caret-boundary`** — Edge gaps (before the first node, after the last) extend outward beyond the container to enlarge their click target. If the node array has neighboring elements (e.g. a preceding node or surrounding UI), the outward extension can overlap them:
1074
+
1075
+ ```
1076
+ Without --node-caret-boundary With --node-caret-boundary
1077
+
1078
+ : . . . . . . . . . . . . . . . :
1079
+ : +---------------------------+ : +-----------------------------+
1080
+ : | Toolbar / UI | : ← overlap | Toolbar / UI |
1081
+ : +---------------------------+ : +-----------------------------+
1082
+ : :
1083
+ : edge gap (unbounded) : boudary → +-----------------------------+
1084
+ : . . . . . . . . . . . . . . . : | : . . . . . . . . . . . . : |
1085
+ | : edge gap (clamped) : |
1086
+ +---------------------------+ | : . . . . . . . . . . . . : |
1087
+ | +---------------------+ | | +-----------------------+ |
1088
+ | | First node | | | | First node | |
1089
+ | +---------------------+ | | +-----------------------+ |
1090
+ | gap between nodes | | gap between nodes |
1091
+ | +---------------------+ | | +-----------------------+ |
1092
+ | | Last node | | | | Last node | |
1093
+ | +---------------------+ | | +-----------------------+ |
1094
+ +---------------------------+ | : . . . . . . . . . . . . : |
1095
+ | : edge gap (clamped) : |
1096
+ : . . . . . . . . . . . . . . . : | : . . . . . . . . . . . . : |
1097
+ : edge gap (unbounded) : boudary → +-----------------------------+
1098
+ : :
1099
+ : +---------------------------+ : +-----------------------------+
1100
+ : | Footer / UI | : ← overlap | Footer / UI |
1101
+ : +---------------------------+ : +-----------------------------+
1102
+ : . . . . . . . . . . . . . . . :
1103
+ ```
1104
+
1105
+ Set `--node-caret-boundary` to the `anchor-name` of a parent element to clamp edge gaps to that element's edges:
1106
+
1107
+ ```css
1108
+ .editor-wrapper {
1109
+ anchor-name: --editor-boundary;
1110
+ padding: 24px;
1111
+ }
1112
+ .editor-wrapper [data-type="node_array"] {
1113
+ --node-caret-boundary: --editor-boundary;
1114
+ }
1115
+ ```
1116
+
1117
+ When set, edge gaps clamp to the boundary element's edges instead. When not set, gaps extend to the containing block edge (default).
1118
+
1119
+ Since `--node-caret-boundary` inherits to nested node arrays, you may need to unset it on inner containers that should not be clamped:
1120
+
1121
+ ```css
1122
+ .inner-container [data-type="node_array"] {
1123
+ --node-caret-boundary: initial;
1124
+ }
1125
+ ```
1126
+
1127
+ For per-axis control, use `--node-caret-boundary-x` (left/right) and `--node-caret-boundary-y` (top/bottom). They take precedence over `--node-caret-boundary` when set:
1128
+
1129
+ ```css
1130
+ .editor-wrapper [data-type="node_array"] {
1131
+ --node-caret-boundary-x: --editor-boundary;
1132
+ }
1133
+ ```
1134
+
1059
1135
  ## Beyond the README
1060
1136
 
1061
1137
  The source code is compact and readable — less than 3000 LOC across a handful of files. We encourage you to explore it. The files in [`src/lib`](./src/lib) are the library code, while the files in [`src/routes`](./src/routes) are example code you can copy and adapt to your needs.
@@ -1080,9 +1156,22 @@ You can preview the production build with `npm run preview`.
1080
1156
 
1081
1157
  ## Contributing
1082
1158
 
1083
- At the very moment, the best way to help is to donate or to sponsor us, so we can buy time to work on this exclusively for a couple of more months. Please get in touch personally.
1159
+ Contributions are very welcome! Bug reports, bug fixes, and small PRs (a couple of lines of code) don't need any ceremony — just go for it. What follows applies to larger changes and new features.
1160
+
1161
+ I take long-term maintainability very seriously. Much like the SQLite project, I prioritize minimalism and code quality over features. This means I may decline pull requests — even good ones — if they don't fit my vision for Svedit at a given point in time. I'll always try to articulate my reasons, but sometimes it comes down to intuition more than logic. Please don't take it personally — it doesn't mean your idea is bad, just that I don't see it belonging in core right now.
1162
+
1163
+ ### How to contribute a feature
1164
+
1165
+ 1. **Start with your requirements.** Open an issue describing what you need and why. Wait for a green light that this is something that belongs in core before writing code.
1166
+ 2. **Explore approaches.** For non-trivial features, there may be multiple ways to solve the problem. Discuss trade-offs before committing to one direction — I may ask you to explore alternatives first.
1167
+ 3. **Prove feasibility.** Make a small PR that solves the root of the problem — no optimizations, no UX polish. This lets me evaluate the impact on the library and give you early feedback if your approach conflicts with Svedit's design decisions.
1168
+ 4. **Plan the finish.** Once feasibility is approved, outline the remaining steps and wait for another green light. Then we iterate together until it's ready to merge.
1169
+
1170
+ **Please don't work on a feature for a long time without checking in regularly.** I have a much lower tolerance for complexity than most developers, and I need to be able to digest changes in small pieces. Going off and making a large rewrite without involving me will likely be frustrating for both of us.
1171
+
1172
+ ### Sponsorship
1084
1173
 
1085
- Find my contact details [here](https://editable.website).
1174
+ Another great way to help is to donate or sponsor the project, so I can buy more dedicated development time. Email me at michael@letsken.com.
1086
1175
 
1087
1176
  ## Beta version
1088
1177
 
package/dist/Node.svelte CHANGED
@@ -1,53 +1,32 @@
1
1
  <script>
2
2
  import { getContext } from 'svelte';
3
- import DefaultNodeGap from './NodeGap.svelte';
4
3
 
5
4
  /** @import { NodeProps } from './types.d.ts'; */
6
5
 
7
6
  const svedit = getContext('svedit');
8
- let NodeGap = $derived(svedit.session.config.system_components?.NodeGap ?? DefaultNodeGap);
9
7
 
10
8
  /** @type {NodeProps} */
11
9
  let { path, children, tag = 'div', class: css_class, style = '', ...rest } = $props();
12
10
 
13
11
  let node = $derived(svedit.session.get(path));
14
12
 
15
- // NodeArrayProperty sets this context — its presence means we're in a node_array.
16
- // Using context avoids expensive session.inspect() and session.get() calls
17
- // that would create O(N) reactive subscriptions to the parent array.
18
13
  const node_array_meta = getContext('node_array_meta');
19
- const is_inside_node_array = !!node_array_meta;
20
- let child_index = $derived(parseInt(String(path.at(-1)), 10));
21
- let is_first_node_array_child = $derived(is_inside_node_array && child_index === 0);
22
- let is_last_node_array_child = $derived(
23
- is_inside_node_array && child_index === node_array_meta.length - 1
24
- );
25
-
26
- // Drives lazy anchor positioning for node gaps. Gaps are always in the
27
- // DOM (stable structure for selection), but only anchor-positioned when
28
- // near the viewport to avoid O(N) layout cost.
29
- let is_near_viewport = $derived(
30
- svedit.is_near_viewport?.(path) ?? true
31
- );
14
+ let child_index = $derived(node_array_meta ? parseInt(String(path.at(-1)), 10) : -1);
15
+ let is_first = $derived(node_array_meta && child_index === 0);
16
+ let is_last = $derived(node_array_meta && child_index === node_array_meta.length - 1);
32
17
  </script>
33
18
 
34
19
  <svelte:element
35
20
  id={node.id}
36
21
  this={tag}
37
- class={css_class}
22
+ class="{css_class}{is_first ? ' first' : ''}{is_last ? ' last' : ''}"
38
23
  data-node-id={node.id}
39
24
  data-path={path.join('.')}
40
25
  data-type="node"
41
26
  style="anchor-name: --{path.join('-')};{style}"
42
27
  {...rest}
43
28
  >
44
- {#if svedit.editable && is_first_node_array_child}
45
- <NodeGap {path} type="gap-before" positioned={is_near_viewport} />
46
- {/if}
47
29
  {@render children()}
48
- {#if svedit.editable && is_inside_node_array}
49
- <NodeGap {path} type="gap-after" last={is_last_node_array_child} positioned={is_near_viewport} />
50
- {/if}
51
30
  </svelte:element>
52
31
 
53
32
  <style>
@@ -1 +1 @@
1
- {"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;AAoDA,yFAAmD"}
1
+ {"version":3,"file":"Node.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Node.svelte.js"],"names":[],"mappings":";;;;;AA8BA,yFAAmD"}
@@ -20,8 +20,6 @@
20
20
  .map(/** @param {string} node_id */ (node_id) => svedit.session.get(node_id))
21
21
  );
22
22
 
23
- // Expose array metadata to child Node.svelte instances via context,
24
- // avoiding expensive session.inspect() / session.get() calls per node.
25
23
  setContext('node_array_meta', {
26
24
  get length() { return nodes.length; }
27
25
  });
@@ -52,9 +50,17 @@
52
50
  <!-- Sibling (not child) of .empty-node-array so its .svedit-selectable
53
51
  resolves anchor positioning against the shared containing block,
54
52
  not the placeholder which inherits .node positioning styles. -->
55
- <NodeGap path={[...path, 0]} type="gap-before" empty />
53
+ <NodeGap array_path={path} offset={0} count={0} empty />
56
54
  {/if}
57
55
  {#each nodes as node, index (index)}
56
+ {#if svedit.editable}
57
+ <NodeGap
58
+ array_path={path}
59
+ offset={index}
60
+ count={nodes.length}
61
+ positioned={svedit.is_near_viewport?.([...path, index]) ?? true}
62
+ />
63
+ {/if}
58
64
  {@const Component = svedit.session.config.node_components[snake_to_pascal(node.type)]}
59
65
  {#if Component}
60
66
  <Component path={[...path, index]} />
@@ -62,6 +68,14 @@
62
68
  <UnknownNode path={[...path, index]} />
63
69
  {/if}
64
70
  {/each}
71
+ {#if svedit.editable && nodes.length > 0}
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
+ />
78
+ {/if}
65
79
  {#if svedit.editable && NodeGapMarkers}
66
80
  <NodeGapMarkers {path} />
67
81
  {/if}
@@ -1 +1 @@
1
- {"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;AA8DA,mHAAgE"}
1
+ {"version":3,"file":"NodeArrayProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeArrayProperty.svelte.js"],"names":[],"mappings":";;;;;AAkEA,mHAAgE"}
@@ -2,6 +2,11 @@
2
2
  /**
3
3
  * Invisible keyboard caret selectable and gap hit area.
4
4
  *
5
+ * Rendered as a sibling of Node elements inside NodeArrayProperty,
6
+ * interleaved between nodes in DOM order. This ensures:
7
+ * - Correct DOM order for native drag-to-select across nodes
8
+ * - No containing-block issues from transform/filter/will-change on nodes
9
+ *
5
10
  * Always present in the DOM when editable (stable structure for
6
11
  * selection anchoring, scrollTo, etc.). Anchor positioning is
7
12
  * activated lazily via the `positioned` prop — only gaps near the
@@ -15,29 +20,37 @@
15
20
  *
16
21
  * Row/column detection uses var(--row, 1) with the * 99999 multiplier
17
22
  * trick — no @container style() queries, works in all browsers.
18
- *
19
- * Node.svelte does NOT set position: relative, so the selectable's
20
- * containing block is a higher ancestor that contains all sibling
21
- * nodes, making cross-sibling anchor references valid.
22
23
  */
23
- let { path, type, empty = false, last = false, positioned = true } = $props();
24
+ let { array_path, offset, count, empty = false, positioned = true } = $props();
25
+
26
+ let is_first = $derived(offset === 0);
27
+ let is_last = $derived(offset === count);
28
+ let type = $derived(is_first ? 'gap-before' : 'gap-after');
24
29
 
25
30
  let gap_style = $derived.by(() => {
26
- const p = path.join('-');
27
- const arr = path.slice(0, -1).join('-');
28
- const idx = parseInt(String(path.at(-1)), 10);
29
- return `--_pa:--${p};--_next:--${arr}-${idx + 1};--_container:--${arr}`;
31
+ const arr = array_path.join('-');
32
+ const prev_idx = offset - 1;
33
+ const pa = is_first ? `--${arr}-0` : `--${arr}-${prev_idx}`;
34
+ const next = `--${arr}-${offset}`;
35
+ const container = `--${arr}`;
36
+ return `--_pa:${pa};--_next:${next};--_container:${container}`;
30
37
  });
31
38
 
32
- let anchor_name = $derived(`--g-${path.join('-')}-${type}`);
39
+ let anchor_name = $derived.by(() => {
40
+ const arr = array_path.join('-');
41
+ if (is_first) return `--g-${arr}-0-gap-before`;
42
+ return `--g-${arr}-${offset - 1}-gap-after`;
43
+ });
33
44
  </script>
34
45
 
35
46
  <div
36
47
  class="node-gap {type}"
37
48
  class:empty
38
- class:last
49
+ class:last={is_last}
39
50
  class:positioned
40
51
  data-type={type}
52
+ data-gap-array-path={array_path.join('.')}
53
+ data-gap-offset={offset}
41
54
  style={gap_style}
42
55
  >
43
56
  <div class="svedit-selectable" style="anchor-name:{anchor_name}"><br /></div>
@@ -46,10 +59,10 @@
46
59
 
47
60
  <style>
48
61
  .node-gap {
49
- height: 0;
50
- overflow: visible;
51
- outline: none;
52
- pointer-events: none;
62
+ display: contents;
63
+ /* The native browser caret briefly appears inside .svedit-selectable for one frame
64
+ before the model-driven NodeCaret renders at the correct edge position.
65
+ Suppressing it here avoids a flash of the native caret. */
53
66
  caret-color: transparent;
54
67
  }
55
68
 
@@ -58,9 +71,7 @@
58
71
  /* ------------------------------------------------------------------ */
59
72
 
60
73
  .node-gap:not(.positioned) .svedit-selectable {
61
- content-visibility: hidden;
62
- contain-intrinsic-size: 0 0;
63
- position: static;
74
+ position: absolute;
64
75
  pointer-events: none;
65
76
  width: 0;
66
77
  height: 0;
@@ -73,8 +84,21 @@
73
84
 
74
85
  /*
75
86
  * Anchor references resolved from CSS variable names passed via
76
- * inline style (--_pa, --_next, --_container). Same pattern as
77
- * NodeGapMarkers — keeps JS minimal and anchor() in CSS.
87
+ * inline style (--_pa = parent anchor, --_next, --_container).
88
+ * Same pattern as NodeGapMarkers — keeps JS minimal and anchor() in CSS.
89
+ *
90
+ * --_pa references the anchor-name of the node this gap belongs to,
91
+ * allowing us to position relative to that node's edges.
92
+ *
93
+ * Edge gaps extend outward and clamp to the containing block edge
94
+ * (0px floor). This can overlap neighboring elements when the node
95
+ * array isn't alone in its parent. anchor() only sees the border
96
+ * box, so it can't detect margin, gap, or parent padding around
97
+ * the container. --node-caret-boundary lets consumers set an
98
+ * explicit clamp target (a parent element's anchor-name) so edge
99
+ * gaps stop at that boundary instead. --node-caret-boundary-x and
100
+ * --node-caret-boundary-y override per-axis, falling back to
101
+ * --node-caret-boundary when unset.
78
102
  */
79
103
  .node-gap.positioned {
80
104
  --_s-t: anchor(var(--_pa) top);
@@ -82,6 +106,10 @@
82
106
  --_s-l: anchor(var(--_pa) left);
83
107
  --_s-r: anchor(var(--_pa) right);
84
108
  }
109
+ .positioned.gap-before:not(.empty) {
110
+ --_b-t: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) top, 0px);
111
+ --_b-l: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) left, 0px);
112
+ }
85
113
  .positioned.gap-after:not(.last) {
86
114
  --_n-t: anchor(var(--_next) top);
87
115
  --_n-l: anchor(var(--_next) left);
@@ -93,6 +121,10 @@
93
121
  --_c-b: anchor(var(--_container) bottom);
94
122
  --_c-l: anchor(var(--_container) left);
95
123
  --_c-r: anchor(var(--_container) right);
124
+ --_b-b: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) bottom, 0px);
125
+ --_b-r: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) right, 0px);
126
+ --_b-bt: anchor(var(--node-caret-boundary-y, var(--node-caret-boundary, --_no-boundary)) bottom, 99999px);
127
+ --_b-rl: anchor(var(--node-caret-boundary-x, var(--node-caret-boundary, --_no-boundary)) right, 99999px);
96
128
  }
97
129
 
98
130
  .positioned .svedit-selectable {
@@ -171,28 +203,33 @@
171
203
  );
172
204
  min-height: calc(var(--_gm) * var(--_C));
173
205
  min-width: calc(var(--_gm) * var(--_R));
206
+ /* min-height: max(calc(var(--_gm) * var(--_C)), calc(anchor-size(var(--_pa) height, 100%) * var(--_R)));
207
+ min-width: max(calc(var(--_gm) * var(--_R)), calc(anchor-size(var(--_pa) width, 100%) * var(--_C))); */
174
208
  }
175
209
 
176
- /* After last node: col extends down, row extends right */
210
+ /* After last node: col extends down, row extends right.
211
+ top also clamps to boundary_bottom - eg so that min-height
212
+ (which wins over bottom in overconstrained abs-pos) cannot
213
+ push the element past the boundary. */
177
214
  .positioned.gap-after.last .svedit-selectable {
178
215
  top: min(
179
- calc(var(--_s-b) + var(--_R) * 99999px),
216
+ calc(min(var(--_s-b), calc(var(--_b-bt) - var(--_eg))) + var(--_R) * 99999px),
180
217
  calc(var(--_s-t) + var(--_C) * 99999px)
181
218
  );
182
219
  bottom: min(
183
- calc(var(--_s-b) - var(--_eg) + var(--_R) * 99999px),
220
+ calc(max(var(--_b-b), var(--_s-b) - var(--_eg)) + var(--_R) * 99999px),
184
221
  calc(var(--_s-b) + var(--_C) * 99999px)
185
222
  );
186
223
  left: min(
187
224
  calc(var(--_s-l) + var(--_R) * 99999px),
188
- calc(var(--_s-r) + var(--_C) * 99999px),
225
+ calc(min(var(--_s-r), calc(var(--_b-rl) - var(--_eg))) + var(--_C) * 99999px),
189
226
  calc(100% - var(--_eg) + var(--_C) * 99999px)
190
227
  );
191
228
  right: min(
192
229
  calc(var(--_s-r) + var(--_R) * 99999px),
193
230
  calc(
194
231
  max(
195
- 0px,
232
+ var(--_b-r),
196
233
  min(
197
234
  calc(var(--_c-r) - var(--_eg)),
198
235
  calc(var(--_s-r) - var(--_eg))
@@ -209,7 +246,7 @@
209
246
  .positioned.gap-before:not(.empty) .svedit-selectable {
210
247
  top: min(
211
248
  calc(
212
- max(0px, var(--_s-t) - var(--_eg))
249
+ max(var(--_b-t), var(--_s-t) - var(--_eg))
213
250
  + var(--_R) * 99999px
214
251
  ),
215
252
  calc(var(--_s-t) + var(--_C) * 99999px)
@@ -221,7 +258,7 @@
221
258
  left: min(
222
259
  calc(var(--_s-l) + var(--_R) * 99999px),
223
260
  calc(
224
- max(0px, var(--_s-l) - var(--_eg))
261
+ max(var(--_b-l), var(--_s-l) - var(--_eg))
225
262
  + var(--_C) * 99999px
226
263
  )
227
264
  );
@@ -4,17 +4,17 @@ type NodeGap = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  };
6
6
  declare const NodeGap: import("svelte").Component<{
7
- path: any;
8
- type: any;
7
+ array_path: any;
8
+ offset: any;
9
+ count: any;
9
10
  empty?: boolean;
10
- last?: boolean;
11
11
  positioned?: boolean;
12
12
  }, {}, "">;
13
13
  type $$ComponentProps = {
14
- path: any;
15
- type: any;
14
+ array_path: any;
15
+ offset: any;
16
+ count: any;
16
17
  empty?: boolean;
17
- last?: boolean;
18
18
  positioned?: boolean;
19
19
  };
20
20
  //# sourceMappingURL=NodeGap.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeGap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGap.svelte.js"],"names":[],"mappings":";;;;;AA8CA;UArB0B,GAAG;UAAQ,GAAG;YAAU,OAAO;WAAS,OAAO;iBAAe,OAAO;WAqBzC;wBArBpC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE"}
1
+ {"version":3,"file":"NodeGap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGap.svelte.js"],"names":[],"mappings":";;;;;AAyDA;gBA/BgC,GAAG;YAAU,GAAG;WAAS,GAAG;YAAU,OAAO;iBAAe,OAAO;WA+B7C;wBA/BpC;IAAE,UAAU,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE"}
@@ -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(0px, calc(anchor(var(--_a) top) - var(--_gm))) + var(--_R) * 99999px)
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(0px, calc(anchor(var(--_a) left) - var(--_gm))) + var(--_C) * 99999px)
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(anchor(var(--_a) bottom) + var(--_R) * 99999px)
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(anchor(var(--_a) right) + var(--_C) * 99999px),
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(0px + var(--_C) * -99999px),
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(0px + var(--_C) * -99999px),
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: 0.1px solid blue;
479
+ outline: 1px solid blue;
454
480
  outline-offset: 0.5px;
455
481
  } */
456
- </style>
482
+ </style>
@@ -1 +1 @@
1
- {"version":3,"file":"NodeGapMarkers.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGapMarkers.svelte.js"],"names":[],"mappings":";;;;;AA2CA;UAtB0B,GAAG;WAsBgC;wBAtB3C;IAAE,IAAI,EAAE,GAAG,CAAA;CAAE"}
1
+ {"version":3,"file":"NodeGapMarkers.svelte.d.ts","sourceRoot":"","sources":["../src/lib/NodeGapMarkers.svelte.js"],"names":[],"mappings":";;;;;AA0CA;UArB0B,GAAG;WAqBgC;wBArB3C;IAAE,IAAI,EAAE,GAAG,CAAA;CAAE"}