@threadlabs/looma 0.18.6 → 0.19.1

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 (220) hide show
  1. package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +34 -3
  2. package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +55 -0
  3. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +7 -2
  4. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +29 -0
  5. package/components/ui-tree-item/ui-tree-item.html +5 -5
  6. package/components/ui-tree-item/ui-tree-item.js +20 -3
  7. package/dist/index.js +1 -1
  8. package/editor/chunks/extensions.js +24 -3
  9. package/editor/extensions/preset.d.ts +2 -0
  10. package/editor/extensions/table-commands.d.ts +1 -1
  11. package/editor/table-overlay.d.ts +8 -3
  12. package/package.json +2 -1
  13. package/styles/ui-editor-table-overlay.css +30 -0
  14. package/styles/ui-editor-table-toolbar.css +7 -2
  15. package/styles/ui-tree-item.css +5 -5
  16. package/vanilla/UiEditorTableOverlay.d.ts +1 -1
  17. package/vanilla/UiEditorTableOverlay.js +9 -3
  18. package/vue/UiEditorTableOverlay.d.ts +8 -0
  19. package/vue/UiEditorTableOverlay.vue +45 -2
  20. package/vue/UiEditorTableToolbar.vue +7 -2
  21. package/vue/UiTreeItem.vue +5 -5
  22. package/vue/chunks/1c.js +106 -0
  23. package/vue/chunks/UiEditorTableOverlay.js +14 -4
  24. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  25. package/vue/chunks/UiTreeItem.js +1 -1
  26. package/vue/chunks/abnf.js +61 -0
  27. package/vue/chunks/accesslog.js +72 -0
  28. package/vue/chunks/actionscript.js +139 -0
  29. package/vue/chunks/ada.js +183 -0
  30. package/vue/chunks/angelscript.js +141 -0
  31. package/vue/chunks/apache.js +88 -0
  32. package/vue/chunks/applescript.js +99 -0
  33. package/vue/chunks/arcade.js +405 -0
  34. package/vue/chunks/arduino.js +871 -0
  35. package/vue/chunks/armasm.js +65 -0
  36. package/vue/chunks/asciidoc.js +181 -0
  37. package/vue/chunks/aspectj.js +202 -0
  38. package/vue/chunks/autohotkey.js +54 -0
  39. package/vue/chunks/autoit.js +132 -0
  40. package/vue/chunks/avrasm.js +46 -0
  41. package/vue/chunks/awk.js +58 -0
  42. package/vue/chunks/axapta.js +167 -0
  43. package/vue/chunks/bash.js +358 -0
  44. package/vue/chunks/basic.js +221 -0
  45. package/vue/chunks/bnf.js +27 -0
  46. package/vue/chunks/brainfuck.js +40 -0
  47. package/vue/chunks/c.js +265 -0
  48. package/vue/chunks/cal.js +136 -0
  49. package/vue/chunks/capnproto.js +86 -0
  50. package/vue/chunks/ceylon.js +125 -0
  51. package/vue/chunks/clean.js +54 -0
  52. package/vue/chunks/clojure-repl.js +17 -0
  53. package/vue/chunks/clojure.js +133 -0
  54. package/vue/chunks/cmake.js +23 -0
  55. package/vue/chunks/coffeescript.js +291 -0
  56. package/vue/chunks/coq.js +436 -0
  57. package/vue/chunks/cos.js +77 -0
  58. package/vue/chunks/cpp.js +492 -0
  59. package/vue/chunks/crmsh.js +80 -0
  60. package/vue/chunks/crystal.js +260 -0
  61. package/vue/chunks/csharp.js +376 -0
  62. package/vue/chunks/csp.js +52 -0
  63. package/vue/chunks/css.js +890 -0
  64. package/vue/chunks/d.js +184 -0
  65. package/vue/chunks/dart.js +226 -0
  66. package/vue/chunks/delphi.js +227 -0
  67. package/vue/chunks/diff.js +40 -0
  68. package/vue/chunks/django.js +43 -0
  69. package/vue/chunks/dns.js +66 -0
  70. package/vue/chunks/dockerfile.js +35 -0
  71. package/vue/chunks/dos.js +147 -0
  72. package/vue/chunks/dsconfig.js +55 -0
  73. package/vue/chunks/dts.js +119 -0
  74. package/vue/chunks/dust.js +33 -0
  75. package/vue/chunks/ebnf.js +40 -0
  76. package/vue/chunks/elixir.js +241 -0
  77. package/vue/chunks/elm.js +103 -0
  78. package/vue/chunks/erb.js +17 -0
  79. package/vue/chunks/erlang-repl.js +41 -0
  80. package/vue/chunks/erlang.js +236 -0
  81. package/vue/chunks/excel.js +562 -0
  82. package/vue/chunks/fix.js +30 -0
  83. package/vue/chunks/flix.js +60 -0
  84. package/vue/chunks/fortran.js +543 -0
  85. package/vue/chunks/fsharp.js +440 -0
  86. package/vue/chunks/gams.js +134 -0
  87. package/vue/chunks/gauss.js +167 -0
  88. package/vue/chunks/gcode.js +131 -0
  89. package/vue/chunks/gherkin.js +41 -0
  90. package/vue/chunks/glsl.js +25 -0
  91. package/vue/chunks/gml.js +3119 -0
  92. package/vue/chunks/go.js +140 -0
  93. package/vue/chunks/golo.js +72 -0
  94. package/vue/chunks/gradle.js +181 -0
  95. package/vue/chunks/graphql.js +68 -0
  96. package/vue/chunks/groovy.js +159 -0
  97. package/vue/chunks/haml.js +96 -0
  98. package/vue/chunks/handlebars.js +191 -0
  99. package/vue/chunks/haskell.js +149 -0
  100. package/vue/chunks/haxe.js +142 -0
  101. package/vue/chunks/hsp.js +45 -0
  102. package/vue/chunks/http.js +77 -0
  103. package/vue/chunks/hy.js +92 -0
  104. package/vue/chunks/inform7.js +45 -0
  105. package/vue/chunks/ini.js +94 -0
  106. package/vue/chunks/irpf90.js +52 -0
  107. package/vue/chunks/isbl.js +112 -0
  108. package/vue/chunks/java.js +237 -0
  109. package/vue/chunks/javascript.js +570 -0
  110. package/vue/chunks/jboss-cli.js +46 -0
  111. package/vue/chunks/json.js +45 -0
  112. package/vue/chunks/julia-repl.js +18 -0
  113. package/vue/chunks/julia.js +353 -0
  114. package/vue/chunks/kotlin.js +216 -0
  115. package/vue/chunks/lasso.js +127 -0
  116. package/vue/chunks/latex.js +206 -0
  117. package/vue/chunks/ldif.js +25 -0
  118. package/vue/chunks/leaf.js +72 -0
  119. package/vue/chunks/less.js +953 -0
  120. package/vue/chunks/lisp.js +119 -0
  121. package/vue/chunks/livecodeserver.js +86 -0
  122. package/vue/chunks/livescript.js +309 -0
  123. package/vue/chunks/llvm.js +77 -0
  124. package/vue/chunks/lsl.js +55 -0
  125. package/vue/chunks/lua.js +49 -0
  126. package/vue/chunks/makefile.js +62 -0
  127. package/vue/chunks/markdown.js +194 -0
  128. package/vue/chunks/mathematica.js +7317 -0
  129. package/vue/chunks/matlab.js +70 -0
  130. package/vue/chunks/maxima.js +38 -0
  131. package/vue/chunks/mel.js +24 -0
  132. package/vue/chunks/mercury.js +62 -0
  133. package/vue/chunks/mipsasm.js +53 -0
  134. package/vue/chunks/mizar.js +10 -0
  135. package/vue/chunks/mojolicious.js +27 -0
  136. package/vue/chunks/monkey.js +158 -0
  137. package/vue/chunks/moonscript.js +102 -0
  138. package/vue/chunks/n1ql.js +346 -0
  139. package/vue/chunks/nestedtext.js +66 -0
  140. package/vue/chunks/nginx.js +127 -0
  141. package/vue/chunks/nim.js +174 -0
  142. package/vue/chunks/nix.js +334 -0
  143. package/vue/chunks/node-repl.js +20 -0
  144. package/vue/chunks/nsis.js +515 -0
  145. package/vue/chunks/objectivec.js +235 -0
  146. package/vue/chunks/ocaml.js +52 -0
  147. package/vue/chunks/openscad.js +66 -0
  148. package/vue/chunks/oxygene.js +58 -0
  149. package/vue/chunks/parser3.js +40 -0
  150. package/vue/chunks/perl.js +454 -0
  151. package/vue/chunks/pf.js +30 -0
  152. package/vue/chunks/pgsql.js +196 -0
  153. package/vue/chunks/php-template.js +43 -0
  154. package/vue/chunks/php.js +527 -0
  155. package/vue/chunks/plaintext.js +10 -0
  156. package/vue/chunks/pony.js +60 -0
  157. package/vue/chunks/powershell.js +208 -0
  158. package/vue/chunks/processing.js +407 -0
  159. package/vue/chunks/profile.js +38 -0
  160. package/vue/chunks/prolog.js +62 -0
  161. package/vue/chunks/properties.js +45 -0
  162. package/vue/chunks/protobuf.js +63 -0
  163. package/vue/chunks/puppet.js +87 -0
  164. package/vue/chunks/purebasic.js +42 -0
  165. package/vue/chunks/python-repl.js +19 -0
  166. package/vue/chunks/python.js +357 -0
  167. package/vue/chunks/q.js +21 -0
  168. package/vue/chunks/qml.js +134 -0
  169. package/vue/chunks/r.js +145 -0
  170. package/vue/chunks/reasonml.js +134 -0
  171. package/vue/chunks/rib.js +16 -0
  172. package/vue/chunks/roboconf.js +54 -0
  173. package/vue/chunks/routeros.js +109 -0
  174. package/vue/chunks/rsl.js +138 -0
  175. package/vue/chunks/ruby.js +322 -0
  176. package/vue/chunks/ruleslanguage.js +26 -0
  177. package/vue/chunks/rust.js +296 -0
  178. package/vue/chunks/sas.js +533 -0
  179. package/vue/chunks/scala.js +173 -0
  180. package/vue/chunks/scheme.js +118 -0
  181. package/vue/chunks/scilab.js +45 -0
  182. package/vue/chunks/scss.js +900 -0
  183. package/vue/chunks/shell.js +18 -0
  184. package/vue/chunks/smali.js +113 -0
  185. package/vue/chunks/smalltalk.js +59 -0
  186. package/vue/chunks/sml.js +49 -0
  187. package/vue/chunks/sqf.js +2599 -0
  188. package/vue/chunks/sql.js +624 -0
  189. package/vue/chunks/stan.js +471 -0
  190. package/vue/chunks/stata.js +33 -0
  191. package/vue/chunks/step21.js +53 -0
  192. package/vue/chunks/stylus.js +925 -0
  193. package/vue/chunks/subunit.js +34 -0
  194. package/vue/chunks/swift.js +699 -0
  195. package/vue/chunks/taggerscript.js +37 -0
  196. package/vue/chunks/tap.js +30 -0
  197. package/vue/chunks/tcl.js +165 -0
  198. package/vue/chunks/thrift.js +65 -0
  199. package/vue/chunks/tp.js +155 -0
  200. package/vue/chunks/twig.js +232 -0
  201. package/vue/chunks/typescript.js +672 -0
  202. package/vue/chunks/vala.js +39 -0
  203. package/vue/chunks/vbnet.js +86 -0
  204. package/vue/chunks/vbscript-html.js +14 -0
  205. package/vue/chunks/vbscript.js +200 -0
  206. package/vue/chunks/verilog.js +534 -0
  207. package/vue/chunks/vhdl.js +190 -0
  208. package/vue/chunks/vim.js +54 -0
  209. package/vue/chunks/wasm.js +111 -0
  210. package/vue/chunks/wren.js +229 -0
  211. package/vue/chunks/x86asm.js +64 -0
  212. package/vue/chunks/xl.js +192 -0
  213. package/vue/chunks/xml.js +175 -0
  214. package/vue/chunks/xquery.js +303 -0
  215. package/vue/chunks/yaml.js +159 -0
  216. package/vue/chunks/zephir.js +74 -0
  217. package/vue/components.css +204 -170
  218. package/vue/editor/LoomaEditor.d.ts +7 -7
  219. package/vue/editor/code-language-catalog.d.ts +196 -0
  220. package/vue/editor/index.js +290 -20
@@ -29,7 +29,7 @@
29
29
  <state name="activeKey" :value="''"></state>
30
30
  <event
31
31
  name="action"
32
- type="object({ action: add-row-before | add-row-after | add-column-before | add-column-after, boundaryIndex: integer }) | object({ action: select-row | select-column, rowIndex: integer, columnIndex: integer }) | object({ action: open-cell-menu | open-row-menu | open-column-menu, rowIndex: integer, columnIndex: integer, anchor: object({ left: number, top: number, right: number, bottom: number }) })"
32
+ type="object({ action: add-row-before | add-row-after | add-column-before | add-column-after, boundaryIndex: integer }) | object({ action: select-row | select-column, rowIndex: integer, columnIndex: integer }) | object({ action: reorder-row | reorder-column, fromIndex: integer, toIndex: integer }) | object({ action: open-cell-menu | open-row-menu | open-column-menu, rowIndex: integer, columnIndex: integer, anchor: object({ left: number, top: number, right: number, bottom: number }) })"
33
33
  ></event>
34
34
  </defs>
35
35
  <div aria-label="Table controls" :hidden="not open">
@@ -89,7 +89,7 @@
89
89
  data-action="select-row"
90
90
  :data-row-index="hovered.rowIndex"
91
91
  :data-column-index="hovered.columnIndex"
92
- title="Row actions"
92
+ title="Drag row to reorder; click for actions"
93
93
  aria-label="Row actions"
94
94
  >
95
95
  <span class="glyph"><ui-icon name="grip-vertical"></ui-icon></span>
@@ -101,7 +101,7 @@
101
101
  data-action="select-column"
102
102
  :data-row-index="hovered.rowIndex"
103
103
  :data-column-index="hovered.columnIndex"
104
- title="Column actions"
104
+ title="Drag column to reorder; click for actions"
105
105
  aria-label="Column actions"
106
106
  >
107
107
  <span class="glyph"><ui-icon name="grip-horizontal"></ui-icon></span>
@@ -121,6 +121,7 @@
121
121
  >
122
122
  <ui-icon name="chevron-down"></ui-icon>
123
123
  </button>
124
+ <span class="drop-indicator" data-drop-indicator hidden aria-hidden="true"></span>
124
125
  </div>
125
126
  <style>
126
127
  :host {
@@ -346,6 +347,12 @@
346
347
 
347
348
  .selector {
348
349
  border-radius: var(--ui-radius-round);
350
+ touch-action: none;
351
+ cursor: grab;
352
+ }
353
+
354
+ .selector:active {
355
+ cursor: grabbing;
349
356
  }
350
357
 
351
358
  .selector::before {
@@ -431,6 +438,30 @@
431
438
  --_ui-default-icon-stroke-width: 2;
432
439
  }
433
440
 
441
+ .drop-indicator {
442
+ position: absolute;
443
+ z-index: 3;
444
+ background: var(--ui-accent-solid);
445
+ box-shadow: 0 0 0 2px var(--ui-surface);
446
+ pointer-events: none;
447
+ }
448
+
449
+ .drop-indicator.row {
450
+ left: 0;
451
+ width: 100%;
452
+ height: 2px;
453
+ }
454
+
455
+ .drop-indicator.column {
456
+ top: 0;
457
+ width: 2px;
458
+ height: 100%;
459
+ }
460
+
461
+ .drop-indicator[hidden] {
462
+ display: none;
463
+ }
464
+
434
465
  :host[hidden] {
435
466
  display: none;
436
467
  }
@@ -52,6 +52,52 @@ function createProximity(scope, selector = ".handle[data-ui-affordance]", radius
52
52
  export default function controller(host) {
53
53
  const element = host.element;
54
54
  const proximity = createProximity(element);
55
+ let drag = null;
56
+ let suppressClick = false;
57
+ const indicator = () => element.querySelector("[data-drop-indicator]");
58
+ const clearDrag = () => {
59
+ drag = null;
60
+ const guide = indicator();
61
+ if (guide) {
62
+ guide.hidden = true;
63
+ guide.classList.remove("row", "column");
64
+ }
65
+ };
66
+ const onPointerDown = (event) => {
67
+ const button = event.target.closest?.("button.selector[data-action]");
68
+ if (!button || event.button !== 0) return;
69
+ const axis = button.dataset.action === "select-row" ? "row" : "column";
70
+ drag = { pointerId: event.pointerId, axis, from: Number(button.dataset[axis === "row" ? "rowIndex" : "columnIndex"]), x: event.clientX, y: event.clientY, to: null };
71
+ button.setPointerCapture(event.pointerId);
72
+ event.preventDefault();
73
+ };
74
+ const onPointerMove = (event) => {
75
+ if (!drag || event.pointerId !== drag.pointerId) return;
76
+ if (Math.hypot(event.clientX - drag.x, event.clientY - drag.y) < 5 && drag.to === null) return;
77
+ const rect = element.getBoundingClientRect();
78
+ const values = boundaries(drag.axis);
79
+ const coordinate = drag.axis === "row" ? event.clientY - rect.top : event.clientX - rect.left;
80
+ const found = values.findIndex((_, index) => index < values.length - 1 && coordinate < values[index + 1]);
81
+ const to = found < 0 ? values.length - 2 : found;
82
+ drag.to = to;
83
+ const guide = indicator();
84
+ if (!guide) return;
85
+ guide.hidden = false;
86
+ guide.classList.toggle("row", drag.axis === "row");
87
+ guide.classList.toggle("column", drag.axis === "column");
88
+ const boundary = to > drag.from ? values[to + 1] : values[to];
89
+ if (drag.axis === "row") guide.style.top = `${boundary}px`;
90
+ else guide.style.left = `${boundary}px`;
91
+ };
92
+ const onPointerUp = (event) => {
93
+ if (!drag || event.pointerId !== drag.pointerId) return;
94
+ const { axis, from, to } = drag;
95
+ clearDrag();
96
+ if (to === null) return;
97
+ suppressClick = true;
98
+ setTimeout(() => { suppressClick = false; }, 0);
99
+ if (to !== from) host.dispatch("action", { action: axis === "row" ? "reorder-row" : "reorder-column", fromIndex: from, toIndex: to });
100
+ };
55
101
  const boundaries = (axis) => {
56
102
  const values = host.state.geometry?.[axis === "row" ? "rowBoundaries" : "columnBoundaries"];
57
103
  if (Array.isArray(values) && values.length >= 2 && values.every(Number.isFinite)) return values;
@@ -89,6 +135,7 @@ export default function controller(host) {
89
135
  queueMicrotask(proximity.refresh);
90
136
  });
91
137
  const onClick = (event) => {
138
+ if (suppressClick) { event.preventDefault(); return; }
92
139
  const button = event.target.closest?.("button[data-action]");
93
140
  if (!button) return;
94
141
  const action = button.dataset.action;
@@ -106,6 +153,10 @@ export default function controller(host) {
106
153
  const enter = (event) => { host.state.activeKey = keyOf(event.target); };
107
154
  const leave = (event) => { host.state.activeKey = keyOf(event.relatedTarget); };
108
155
  element.addEventListener("click", onClick);
156
+ element.addEventListener("pointerdown", onPointerDown);
157
+ document.addEventListener("pointermove", onPointerMove, true);
158
+ document.addEventListener("pointerup", onPointerUp, true);
159
+ document.addEventListener("pointercancel", clearDrag, true);
109
160
  element.addEventListener("pointerover", enter);
110
161
  element.addEventListener("pointerout", leave);
111
162
  element.addEventListener("focusin", enter);
@@ -114,6 +165,10 @@ export default function controller(host) {
114
165
  stop();
115
166
  proximity.destroy();
116
167
  element.removeEventListener("click", onClick);
168
+ element.removeEventListener("pointerdown", onPointerDown);
169
+ document.removeEventListener("pointermove", onPointerMove, true);
170
+ document.removeEventListener("pointerup", onPointerUp, true);
171
+ document.removeEventListener("pointercancel", clearDrag, true);
117
172
  element.removeEventListener("pointerover", enter);
118
173
  element.removeEventListener("pointerout", leave);
119
174
  element.removeEventListener("focusin", enter);
@@ -211,10 +211,12 @@
211
211
  }
212
212
 
213
213
  .menu {
214
+ box-sizing: border-box;
214
215
  position: absolute;
215
216
  top: calc(100% + var(--ui-space-2));
216
217
  right: 0;
217
218
  min-width: 12rem;
219
+ max-width: min(17rem, calc(100vw - 24px));
218
220
  padding: var(--ui-space-1);
219
221
  background: var(--ui-surface);
220
222
  border: 1px solid var(--ui-border-strong);
@@ -224,6 +226,9 @@
224
226
  flex-direction: column;
225
227
  gap: 2px;
226
228
  z-index: 2;
229
+ overflow-y: auto;
230
+ overscroll-behavior: contain;
231
+ scrollbar-width: thin;
227
232
  }
228
233
 
229
234
  .menu-section + .menu-section {
@@ -250,7 +255,7 @@
250
255
 
251
256
  .swatches {
252
257
  display: grid;
253
- grid-template-columns: repeat(3, minmax(0, 1fr));
258
+ grid-template-columns: repeat(2, minmax(0, 1fr));
254
259
  gap: 2px;
255
260
  }
256
261
 
@@ -264,7 +269,7 @@
264
269
  height: 14px;
265
270
  flex: 0 0 14px;
266
271
  border: 1px solid color-mix(in srgb, var(--ui-border) 80%, #000);
267
- border-radius: 3px;
272
+ border-radius: var(--ui-radius-round);
268
273
  background: var(--_swatch);
269
274
  }
270
275
 
@@ -9,6 +9,20 @@ const overflowSections = [
9
9
  // Lists the actions the table selection permits; the overflow menu opens and closes here.
10
10
  export default function controller(host) {
11
11
  const element = host.element;
12
+ const positionMenu = () => {
13
+ const button = element.querySelector(".more");
14
+ const menu = element.querySelector(".menu");
15
+ if (!button || !menu) return;
16
+ const rect = button.getBoundingClientRect();
17
+ const viewport = window.visualViewport;
18
+ const top = viewport?.offsetTop ?? 0;
19
+ const bottom = top + (viewport?.height ?? window.innerHeight);
20
+ const above = rect.top - top - 20;
21
+ const below = bottom - rect.bottom - 20;
22
+ menu.style.maxHeight = `${Math.max(80, Math.floor(Math.max(above, below)))}px`;
23
+ menu.style.top = below >= above ? "calc(100% + var(--ui-space-2))" : "auto";
24
+ menu.style.bottom = below >= above ? "auto" : "calc(100% + var(--ui-space-2))";
25
+ };
12
26
  const stop = host.effect(() => {
13
27
  const enabled = new Set(Array.isArray(host.state.actions) ? host.state.actions : []);
14
28
  const alignment = host.state.cellAlignment === "center" || host.state.cellAlignment === "right" ? host.state.cellAlignment : "left";
@@ -36,6 +50,7 @@ export default function controller(host) {
36
50
  if (!action) return;
37
51
  if (action === "toggle-overflow") {
38
52
  host.state.overflowOpen = !host.state.overflowOpen;
53
+ if (host.state.overflowOpen) requestAnimationFrame(positionMenu);
39
54
  return;
40
55
  }
41
56
  host.state.overflowOpen = false;
@@ -44,11 +59,25 @@ export default function controller(host) {
44
59
  const onOutside = (event) => {
45
60
  if (host.state.overflowOpen && !event.composedPath().includes(element)) host.state.overflowOpen = false;
46
61
  };
62
+ const onKeydown = (event) => {
63
+ if (event.key === "Escape" && host.state.overflowOpen) {
64
+ host.state.overflowOpen = false;
65
+ event.preventDefault();
66
+ event.stopPropagation();
67
+ element.querySelector(".more")?.focus();
68
+ }
69
+ };
47
70
  element.addEventListener("click", onClick);
48
71
  document.addEventListener("pointerdown", onOutside, true);
72
+ document.addEventListener("keydown", onKeydown, true);
73
+ window.addEventListener("resize", positionMenu);
74
+ window.visualViewport?.addEventListener("resize", positionMenu);
49
75
  return () => {
50
76
  stop();
51
77
  element.removeEventListener("click", onClick);
52
78
  document.removeEventListener("pointerdown", onOutside, true);
79
+ document.removeEventListener("keydown", onKeydown, true);
80
+ window.removeEventListener("resize", positionMenu);
81
+ window.visualViewport?.removeEventListener("resize", positionMenu);
53
82
  };
54
83
  }
@@ -324,7 +324,7 @@
324
324
  padding: var(--_ui-tree-item-label-padding-block, var(--ui-space-1)) var(--_ui-tree-item-label-padding-inline, var(--ui-space-2));
325
325
  /* The length of the fade before the controls. The label's one flex child lays out no gap; this
326
326
  is where the controller reads the length in pixels to stop a sliding name before the fade. */
327
- column-gap: var(--ui-space-3);
327
+ column-gap: var(--ui-space-1);
328
328
  }
329
329
 
330
330
  /* Slotted label content fills the cell, so a link is the row's hit area rather than sizing to
@@ -352,7 +352,7 @@
352
352
  text-overflow: clip;
353
353
  mask-image: linear-gradient(
354
354
  to right,
355
- #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
355
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-1)),
356
356
  transparent calc(100% - var(--_tree-actions-width, 1.75rem))
357
357
  );
358
358
  }
@@ -361,7 +361,7 @@
361
361
  .row:focus-within > .label:dir(rtl) {
362
362
  mask-image: linear-gradient(
363
363
  to left,
364
- #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
364
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-1)),
365
365
  transparent calc(100% - var(--_tree-actions-width, 1.75rem))
366
366
  );
367
367
  }
@@ -388,7 +388,7 @@
388
388
  to right,
389
389
  transparent var(--_marquee-icon-width, 0px),
390
390
  #000 calc(var(--_marquee-lead, 0px) + var(--_ui-tree-item-label-padding-inline, var(--ui-space-2))),
391
- #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
391
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-1)),
392
392
  transparent calc(100% - var(--_tree-actions-width, 1.75rem))
393
393
  );
394
394
  }
@@ -398,7 +398,7 @@
398
398
  to left,
399
399
  transparent var(--_marquee-icon-width, 0px),
400
400
  #000 calc(var(--_marquee-lead, 0px) + var(--_ui-tree-item-label-padding-inline, var(--ui-space-2))),
401
- #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
401
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-1)),
402
402
  transparent calc(100% - var(--_tree-actions-width, 1.75rem))
403
403
  );
404
404
  }
@@ -112,17 +112,34 @@ export default function controller(host) {
112
112
  // The tree sets this for its items; an item's own prop overrides it either way.
113
113
  const marqueeWanted = () => host.state.marquee
114
114
  || getComputedStyle(element).getPropertyValue("--_ui-default-tree-item-marquee").trim() === "1";
115
+ // A slotted link may fill the row and have padding past its name. Measure the rendered letters,
116
+ // since moving that link's box to the actions would send a short name too far (or move it at all).
117
+ const textEdge = (rightToLeft) => {
118
+ const walker = element.ownerDocument.createTreeWalker(labelText, NodeFilter.SHOW_TEXT);
119
+ const range = element.ownerDocument.createRange();
120
+ let edge = rightToLeft ? Infinity : -Infinity;
121
+ while (walker.nextNode()) {
122
+ if (!walker.currentNode.textContent?.trim()) continue;
123
+ range.selectNodeContents(walker.currentNode);
124
+ for (const rect of range.getClientRects()) {
125
+ if (rect.width === 0 || rect.height === 0) continue;
126
+ edge = rightToLeft ? Math.min(edge, rect.left) : Math.max(edge, rect.right);
127
+ }
128
+ }
129
+ return Number.isFinite(edge) ? edge : rightToLeft
130
+ ? labelText.getBoundingClientRect().left : labelText.getBoundingClientRect().right;
131
+ };
115
132
  const startMarquee = () => {
116
133
  if (!labelText || !label || !marqueeWanted()) return;
117
134
  labelText.style.flex = "none";
118
135
  labelText.style.inlineSize = "max-content";
119
- const text = labelText.getBoundingClientRect();
136
+ const rightToLeft = getComputedStyle(element).direction === "rtl";
137
+ const end = textEdge(rightToLeft);
120
138
  labelText.style.flex = labelText.style.inlineSize = "";
121
139
  const cell = label.getBoundingClientRect();
122
140
  const icon = leading?.getBoundingClientRect();
123
141
  const stop = (actions?.offsetWidth ?? 0) + parseFloat(getComputedStyle(label).columnGap);
124
- const rightToLeft = getComputedStyle(element).direction === "rtl";
125
- const distance = Math.ceil(rightToLeft ? cell.left + stop - text.left : text.right - (cell.right - stop));
142
+ const distance = Math.ceil(rightToLeft ? cell.left + stop - end : end - (cell.right - stop));
126
143
  if (distance <= 0) return;
127
144
  const lead = icon?.width ? (rightToLeft ? icon.right - cell.right : cell.left - icon.left) : 0;
128
145
  row.style.setProperty("--_marquee-lead", `${Math.max(0, lead)}px`);