@k2b/ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/browser/chat/ChatComposer.js +261 -274
  2. package/dist/browser/chat/ChatComposer.js.map +1 -1
  3. package/dist/browser/chat/ChatPrimitives.js +9 -6
  4. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  5. package/dist/browser/content/FileView.js +62 -36
  6. package/dist/browser/content/FileView.js.map +1 -1
  7. package/dist/browser/content/MarkdownView.js +9 -3
  8. package/dist/browser/content/MarkdownView.js.map +1 -1
  9. package/dist/browser/content/VideoPlayer.js +279 -0
  10. package/dist/browser/content/VideoPlayer.js.map +1 -0
  11. package/dist/browser/content/file-view-preview.js +2 -2
  12. package/dist/browser/content/file-view-preview.js.map +1 -1
  13. package/dist/browser/content/index.js +2 -0
  14. package/dist/browser/content/index.js.map +1 -1
  15. package/dist/browser/content/markdown-links.js +178 -0
  16. package/dist/browser/content/markdown-links.js.map +1 -0
  17. package/dist/browser/inputs/FileDropTarget.js +129 -0
  18. package/dist/browser/inputs/FileDropTarget.js.map +1 -0
  19. package/dist/browser/inputs/FileInputs.js +37 -11
  20. package/dist/browser/inputs/FileInputs.js.map +1 -1
  21. package/dist/browser/inputs/file-drop.js +338 -0
  22. package/dist/browser/inputs/file-drop.js.map +1 -0
  23. package/dist/browser/inputs/index.js +1 -0
  24. package/dist/browser/inputs/index.js.map +1 -1
  25. package/dist/browser/inputs/markdown/MarkdownEditor.js +72 -78
  26. package/dist/browser/inputs/markdown/MarkdownEditor.js.map +1 -1
  27. package/dist/browser/intl/messages.js +97 -4
  28. package/dist/browser/intl/messages.js.map +1 -1
  29. package/dist/browser/layout/AppWorkspace.js +210 -44
  30. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  31. package/dist/browser/layout/Navigation.js +278 -170
  32. package/dist/browser/layout/Navigation.js.map +1 -1
  33. package/dist/browser/layout/index.js.map +1 -1
  34. package/dist/browser/layout/navigation-model.js +3 -1
  35. package/dist/browser/layout/navigation-model.js.map +1 -1
  36. package/dist/browser/surfaces/NoticeCard.js +47 -44
  37. package/dist/browser/surfaces/NoticeCard.js.map +1 -1
  38. package/dist/ssr/chat/ChatComposer.js +59 -48
  39. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  40. package/dist/ssr/chat/ChatPrimitives.js +9 -6
  41. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  42. package/dist/ssr/content/FileView.js +30 -6
  43. package/dist/ssr/content/FileView.js.map +1 -1
  44. package/dist/ssr/content/MarkdownView.js +9 -3
  45. package/dist/ssr/content/MarkdownView.js.map +1 -1
  46. package/dist/ssr/content/VideoPlayer.js +208 -0
  47. package/dist/ssr/content/VideoPlayer.js.map +1 -0
  48. package/dist/ssr/content/file-view-preview.js +2 -2
  49. package/dist/ssr/content/file-view-preview.js.map +1 -1
  50. package/dist/ssr/content/index.js +2 -0
  51. package/dist/ssr/content/index.js.map +1 -1
  52. package/dist/ssr/content/markdown-links.js +178 -0
  53. package/dist/ssr/content/markdown-links.js.map +1 -0
  54. package/dist/ssr/inputs/FileDropTarget.js +103 -0
  55. package/dist/ssr/inputs/FileDropTarget.js.map +1 -0
  56. package/dist/ssr/inputs/FileInputs.js +32 -10
  57. package/dist/ssr/inputs/FileInputs.js.map +1 -1
  58. package/dist/ssr/inputs/file-drop.js +338 -0
  59. package/dist/ssr/inputs/file-drop.js.map +1 -0
  60. package/dist/ssr/inputs/index.js +1 -0
  61. package/dist/ssr/inputs/index.js.map +1 -1
  62. package/dist/ssr/inputs/markdown/MarkdownEditor.js +14 -2
  63. package/dist/ssr/inputs/markdown/MarkdownEditor.js.map +1 -1
  64. package/dist/ssr/intl/messages.js +97 -4
  65. package/dist/ssr/intl/messages.js.map +1 -1
  66. package/dist/ssr/layout/AppWorkspace.js +173 -27
  67. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  68. package/dist/ssr/layout/Navigation.js +175 -102
  69. package/dist/ssr/layout/Navigation.js.map +1 -1
  70. package/dist/ssr/layout/index.js.map +1 -1
  71. package/dist/ssr/layout/navigation-model.js +3 -1
  72. package/dist/ssr/layout/navigation-model.js.map +1 -1
  73. package/dist/ssr/surfaces/NoticeCard.js +17 -12
  74. package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
  75. package/dist/styles.css +1 -1
  76. package/dist/types/chat/ChatPrimitives.d.ts +1 -1
  77. package/dist/types/content/VideoPlayer.d.ts +41 -0
  78. package/dist/types/content/index.d.ts +2 -0
  79. package/dist/types/content/markdown-links.d.ts +66 -0
  80. package/dist/types/inputs/FileDropTarget.d.ts +23 -0
  81. package/dist/types/inputs/FileInputs.d.ts +2 -0
  82. package/dist/types/inputs/file-drop.d.ts +80 -0
  83. package/dist/types/inputs/index.d.ts +2 -0
  84. package/dist/types/intl/messages.d.ts +47 -0
  85. package/dist/types/layout/AppWorkspace.d.ts +21 -0
  86. package/dist/types/layout/index.d.ts +2 -2
  87. package/dist/types/layout/navigation-model.d.ts +29 -0
  88. package/package.json +1 -1
@@ -44,7 +44,7 @@ var _tmpl$ = /*#__PURE__*/_$template(`<button type=button class=k2b-app-workspac
44
44
  _tmpl$24 = /*#__PURE__*/_$template(`<span class=k2b-app-workspace__sidebar-item-meta>`),
45
45
  _tmpl$25 = /*#__PURE__*/_$template(`<a class=k2b-app-workspace__sidebar-item-action>`),
46
46
  _tmpl$26 = /*#__PURE__*/_$template(`<button type=button class=k2b-app-workspace__sidebar-item-action>`),
47
- _tmpl$27 = /*#__PURE__*/_$template(`<span class=k2b-app-workspace__sidebar-item-icon aria-hidden=true><i>`),
47
+ _tmpl$27 = /*#__PURE__*/_$template(`<span class=k2b-app-workspace__sidebar-item-icon><i>`),
48
48
  _tmpl$28 = /*#__PURE__*/_$template(`<span class=k2b-app-workspace__sidebar-item-label><span class=k2b-app-workspace__sidebar-item-label-text>`),
49
49
  _tmpl$29 = /*#__PURE__*/_$template(`<span>`),
50
50
  _tmpl$30 = /*#__PURE__*/_$template(`<span class=k2b-app-workspace__nav-tree-toggle data-k2b-nav-tree-toggle aria-hidden=true><i>`),
@@ -55,7 +55,7 @@ var _tmpl$ = /*#__PURE__*/_$template(`<button type=button class=k2b-app-workspac
55
55
  _tmpl$35 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
56
56
  _tmpl$36 = /*#__PURE__*/_$template(`<div data-k2b-app-workspace>`);
57
57
  import { Link } from "@k2b/ssr/nav";
58
- import { children, createContext, createMemo, createSignal, createUniqueId, For, mergeProps, onCleanup, onMount, Show, useContext } from "solid-js";
58
+ import { children, createContext, createEffect, createMemo, createSignal, createUniqueId, For, mergeProps, onCleanup, onMount, Show, useContext } from "solid-js";
59
59
  import { useUiMessages } from "../intl/messages";
60
60
  import { APP_WORKSPACE_SIDEBAR_HIDE_EVENT, installAppWorkspaceController } from "./app-workspace-controller";
61
61
  import { APP_WORKSPACE_DETAIL_DEFAULT, APP_WORKSPACE_DETAIL_MAX, APP_WORKSPACE_DETAIL_MIN, APP_WORKSPACE_DRAWER_DEFAULT, APP_WORKSPACE_DRAWER_MAX, APP_WORKSPACE_DRAWER_MIN, APP_WORKSPACE_PANE_DEFAULT, APP_WORKSPACE_PANE_MAX, APP_WORKSPACE_PANE_MIN, APP_WORKSPACE_SIDEBAR_COLLAPSED, APP_WORKSPACE_SIDEBAR_DEFAULT, APP_WORKSPACE_SIDEBAR_MAX, APP_WORKSPACE_SIDEBAR_MIN, appWorkspaceLayoutStyle, appWorkspacePanelVariable } from "./app-workspace-state";
@@ -84,10 +84,11 @@ const sidebarChildSlot = value => Boolean(value && typeof value === "object" &&
84
84
  const sidebarItemSlot = value => Boolean(value && typeof value === "object" && "kind" in value && [SIDEBAR_ITEM_ICON, SIDEBAR_ITEM_LABEL, SIDEBAR_ITEM_META, SIDEBAR_ITEM_ACTION].includes(value.kind));
85
85
  const navTreeItemSlot = value => Boolean(value && typeof value === "object" && "kind" in value && value.kind === NAV_TREE_ITEM);
86
86
  function ResizeHandle(props) {
87
+ const messages = useUiMessages();
87
88
  return (() => {
88
89
  var _el$ = _$getNextElement(_tmpl$);
89
90
  _$effect(_p$ => {
90
- var _v$ = props.label ?? (props.kind === "sidebar" ? "Resize navigation" : props.kind === "pane" ? "Resize workspace pane" : props.kind === "detail" ? "Resize detail panel" : "Resize bottom drawer"),
91
+ var _v$ = props.label ?? (props.kind === "sidebar" ? messages().resizeNavigation : props.kind === "pane" ? messages().resizeWorkspacePane : props.kind === "detail" ? messages().resizeDetailPanel : messages().resizeBottomDrawer),
91
92
  _v$2 = props.controls,
92
93
  _v$3 = props.kind === "drawer" ? "horizontal" : "vertical",
93
94
  _v$4 = props.minSize,
@@ -135,6 +136,9 @@ function ResizeHandle(props) {
135
136
  return _el$;
136
137
  })();
137
138
  }
139
+
140
+ /** One requested place for an item among its unchanged siblings. */
141
+
138
142
  const modeAttrs = mode => mode && mode !== "always" ? {
139
143
  "data-sidebar-mode": mode
140
144
  } : {};
@@ -146,6 +150,13 @@ const scrollAttrs = key => key ? {
146
150
  "data-scroll-preserve": key
147
151
  } : {};
148
152
  const iconClass = (icon, fallback = "ti-circle") => icon?.startsWith("ti ") ? icon : `ti ${icon || fallback}`;
153
+ /** A named icon is an image screen readers announce; any other icon is decoration. */
154
+ const iconAccessibility = label => label ? {
155
+ role: "img",
156
+ "aria-label": label
157
+ } : {
158
+ "aria-hidden": true
159
+ };
149
160
  function AppWorkspaceMainPane(props) {
150
161
  return {
151
162
  kind: MAIN_PANE,
@@ -1153,6 +1164,27 @@ const AppWorkspaceNavTreeItem = props => ({
1153
1164
  kind: NAV_TREE_ITEM,
1154
1165
  ...props
1155
1166
  });
1167
+ const NAV_TREE_DRAG_TYPE = "application/x-k2b-nav-tree-item";
1168
+ const callDragHandler = (handler, event) => {
1169
+ if (typeof handler === "function") handler(event);else handler?.[0](handler[1], event);
1170
+ };
1171
+
1172
+ /**
1173
+ * The move that puts the sibling at `from` into the gap before `slot`
1174
+ * (`siblings.length` is the gap after the last one), or `null` when its place
1175
+ * would not change or it would pass an item that is not movable.
1176
+ */
1177
+ const navTreeMove = (siblings, from, slot, parentId) => {
1178
+ if (from < 0 || slot < 0 || slot > siblings.length || slot === from || slot === from + 1) return null;
1179
+ const passed = slot < from ? siblings.slice(slot, from) : siblings.slice(from + 1, slot);
1180
+ if (passed.some(sibling => !sibling.movable)) return null;
1181
+ return {
1182
+ id: siblings[from].id,
1183
+ parentId,
1184
+ beforeId: siblings[slot]?.id ?? null,
1185
+ afterId: siblings[slot - 1]?.id ?? null
1186
+ };
1187
+ };
1156
1188
  const AppWorkspaceNavTree = props => {
1157
1189
  let root;
1158
1190
  const resolved = children(() => props.children);
@@ -1197,16 +1229,48 @@ const AppWorkspaceNavTree = props => {
1197
1229
  });
1198
1230
  };
1199
1231
 
1232
+ // Reordering: the dragged item and the gap the pointer marks. The application renders the new
1233
+ // order; the item moved by keyboard takes focus back when its row mounts or changes place.
1234
+ const [dragged, setDragged] = createSignal(null);
1235
+ const [dropTarget, setDropTarget] = createSignal(null);
1236
+ let claimedDragOver = null;
1237
+ let refocusId = null;
1238
+ const endDrag = () => {
1239
+ setDragged(null);
1240
+ setDropTarget(null);
1241
+ };
1242
+ const requestMove = (move, keepFocus) => {
1243
+ if (!move || !props.onMove) return;
1244
+ refocusId = keepFocus ? move.id : null;
1245
+ props.onMove(move);
1246
+ };
1247
+
1200
1248
  // Items arrive through an accessor so a changed nested list reconciles rows by identity instead of rebuilding the list.
1201
- const renderItems = (value, depth, parentId) => _$createComponent(For, {
1249
+ const renderItems = (siblings, depth, parentId) => _$createComponent(For, {
1202
1250
  get each() {
1203
- return navTreeItems(value());
1251
+ return siblings();
1204
1252
  },
1205
- children: item => {
1253
+ children: (item, place) => {
1206
1254
  const nested = createMemo(() => navTreeItems(item.children));
1207
1255
  const hasChildren = () => nested().length > 0;
1208
1256
  const selected = () => props.selectedId === item.id;
1257
+ const siblingIndex = id => siblings().findIndex(sibling => sibling.id === id);
1258
+ const movable = () => Boolean(props.onMove && item.movable && !item.disabled);
1209
1259
  let treeItem;
1260
+ const ownRow = () => treeItem?.firstElementChild;
1261
+
1262
+ // A new row for the moved item, or the same row at its new place: moving a focused row drops its focus.
1263
+ createEffect(() => {
1264
+ place();
1265
+ if (refocusId !== item.id) return;
1266
+ queueMicrotask(() => {
1267
+ if (refocusId !== item.id || !treeItem?.isConnected) return;
1268
+ const active = treeItem.ownerDocument.activeElement;
1269
+ if (!active || active === treeItem.ownerDocument.body) treeItem.focus({
1270
+ preventScroll: true
1271
+ });
1272
+ });
1273
+ });
1210
1274
  const activate = event => {
1211
1275
  if (item.disabled) return;
1212
1276
  treeItem?.focus({
@@ -1239,6 +1303,13 @@ const AppWorkspaceNavTree = props => {
1239
1303
  };
1240
1304
  const onKeyDown = event => {
1241
1305
  if (event.target !== event.currentTarget) return;
1306
+ if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && (event.key === "ArrowUp" || event.key === "ArrowDown")) {
1307
+ if (!movable()) return;
1308
+ event.preventDefault();
1309
+ const from = siblingIndex(item.id);
1310
+ requestMove(navTreeMove(siblings(), from, event.key === "ArrowUp" ? from - 1 : from + 2, parentId ?? null), true);
1311
+ return;
1312
+ }
1242
1313
  if (event.altKey || event.ctrlKey || event.metaKey) return;
1243
1314
  const items = treeItemElements();
1244
1315
  const index = items.indexOf(event.currentTarget);
@@ -1294,16 +1365,13 @@ const AppWorkspaceNavTree = props => {
1294
1365
  get children() {
1295
1366
  var _el$55 = _$getNextElement(_tmpl$27),
1296
1367
  _el$56 = _el$55.firstChild;
1297
- _$effect(_p$ => {
1298
- var _v$55 = usesIconDisclosure() ? "" : undefined,
1299
- _v$56 = iconClass(rowIcon());
1300
- _v$55 !== _p$.e && _$setAttribute(_el$55, "data-k2b-nav-tree-toggle", _p$.e = _v$55);
1301
- _v$56 !== _p$.t && _$className(_el$56, _p$.t = _v$56);
1302
- return _p$;
1303
- }, {
1304
- e: undefined,
1305
- t: undefined
1306
- });
1368
+ _$spread(_el$55, _$mergeProps({
1369
+ get ["data-k2b-nav-tree-toggle"]() {
1370
+ return usesIconDisclosure() ? "" : undefined;
1371
+ }
1372
+ }, () => iconAccessibility(item.iconLabel)), false, true);
1373
+ _$effect(() => _$className(_el$56, iconClass(rowIcon())));
1374
+ _$runHydrationEvents();
1307
1375
  return _el$55;
1308
1376
  }
1309
1377
  }), (() => {
@@ -1319,10 +1387,10 @@ const AppWorkspaceNavTree = props => {
1319
1387
  var _el$59 = _$getNextElement(_tmpl$29);
1320
1388
  _$insert(_el$59, () => item.meta);
1321
1389
  _$effect(_p$ => {
1322
- var _v$57 = `k2b-app-workspace__sidebar-item-meta ${hasChildren() ? "" : "k2b-app-workspace__nav-tree-leaf-meta"}`,
1323
- _v$58 = item.metaVisibility === "hover" ? "hover" : undefined;
1324
- _v$57 !== _p$.e && _$className(_el$59, _p$.e = _v$57);
1325
- _v$58 !== _p$.t && _$setAttribute(_el$59, "data-visibility", _p$.t = _v$58);
1390
+ var _v$55 = `k2b-app-workspace__sidebar-item-meta ${hasChildren() ? "" : "k2b-app-workspace__nav-tree-leaf-meta"}`,
1391
+ _v$56 = item.metaVisibility === "hover" ? "hover" : undefined;
1392
+ _v$55 !== _p$.e && _$className(_el$59, _p$.e = _v$55);
1393
+ _v$56 !== _p$.t && _$setAttribute(_el$59, "data-visibility", _p$.t = _v$56);
1326
1394
  return _p$;
1327
1395
  }, {
1328
1396
  e: undefined,
@@ -1393,16 +1461,68 @@ const AppWorkspaceNavTree = props => {
1393
1461
  },
1394
1462
  children: rowContent
1395
1463
  });
1464
+
1465
+ // A gap is offered only next to a sibling's own row, never over its sub-items.
1466
+ const dropMove = event => {
1467
+ const drag = dragged();
1468
+ const row = ownRow();
1469
+ if (!drag || !event.dataTransfer?.types.includes(NAV_TREE_DRAG_TYPE)) return null;
1470
+ if (drag.parentId !== (parentId ?? null) || drag.id === item.id || !row?.contains(event.target)) return null;
1471
+ const rect = row.getBoundingClientRect();
1472
+ const side = event.clientY < rect.top + rect.height / 2 ? "before" : "after";
1473
+ const slot = siblingIndex(item.id) + (side === "after" ? 1 : 0);
1474
+ const move = navTreeMove(siblings(), siblingIndex(drag.id), slot, parentId ?? null);
1475
+ return move ? {
1476
+ side,
1477
+ move
1478
+ } : null;
1479
+ };
1480
+ const onDragStart = event => {
1481
+ const row = ownRow();
1482
+ if (!movable() || !row?.contains(event.target)) return;
1483
+ event.stopPropagation();
1484
+ setDragged({
1485
+ id: item.id,
1486
+ parentId: parentId ?? null
1487
+ });
1488
+ if (!event.dataTransfer) return;
1489
+ event.dataTransfer.effectAllowed = "move";
1490
+ event.dataTransfer.setData(NAV_TREE_DRAG_TYPE, item.id);
1491
+ const rect = row.getBoundingClientRect();
1492
+ event.dataTransfer.setDragImage(row, event.clientX - rect.left, event.clientY - rect.top);
1493
+ };
1494
+ const onDragOver = event => {
1495
+ callDragHandler(item.onDragOver, event);
1496
+ const target = dropMove(event);
1497
+ if (!target) return;
1498
+ event.preventDefault();
1499
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
1500
+ claimedDragOver = event;
1501
+ setDropTarget({
1502
+ id: item.id,
1503
+ side: target.side
1504
+ });
1505
+ };
1506
+ const onDrop = event => {
1507
+ callDragHandler(item.onDrop, event);
1508
+ const target = dropMove(event);
1509
+ if (!target) return;
1510
+ event.preventDefault();
1511
+ event.stopPropagation();
1512
+ endDrag();
1513
+ requestMove(target.move, false);
1514
+ };
1396
1515
  return (() => {
1397
1516
  var _el$62 = _$getNextElement(_tmpl$32),
1398
1517
  _el$64 = _el$62.firstChild,
1399
1518
  [_el$65, _co$0] = _$getNextMarker(_el$64.nextSibling),
1400
1519
  _el$66 = _el$65.nextSibling,
1401
1520
  [_el$67, _co$1] = _$getNextMarker(_el$66.nextSibling);
1402
- _$addEventListener(_el$62, "drop", item.onDrop);
1521
+ _el$62.addEventListener("drop", onDrop);
1403
1522
  _$addEventListener(_el$62, "dragleave", item.onDragLeave);
1404
- _$addEventListener(_el$62, "dragover", item.onDragOver);
1523
+ _el$62.addEventListener("dragover", onDragOver);
1405
1524
  _$addEventListener(_el$62, "dragenter", item.onDragEnter);
1525
+ _el$62.addEventListener("dragstart", onDragStart);
1406
1526
  _el$62.$$keydown = onKeyDown;
1407
1527
  _el$62.addEventListener("focus", () => setFocusedId(item.id));
1408
1528
  var _ref$3 = treeItem;
@@ -1421,39 +1541,85 @@ const AppWorkspaceNavTree = props => {
1421
1541
  }
1422
1542
  }), _el$67, _co$1);
1423
1543
  _$effect(_p$ => {
1424
- var _v$59 = selected(),
1544
+ var _v$57 = siblings().length,
1545
+ _v$58 = place() + 1,
1546
+ _v$59 = selected(),
1425
1547
  _v$60 = hasChildren() ? isExpanded(item.id) : undefined,
1426
1548
  _v$61 = item.disabled ? "true" : undefined,
1427
1549
  _v$62 = item.disabled ? -1 : tabStopId() === item.id ? 0 : -1,
1428
- _v$63 = item.id;
1429
- _v$59 !== _p$.e && _$setAttribute(_el$62, "aria-selected", _p$.e = _v$59);
1430
- _v$60 !== _p$.t && _$setAttribute(_el$62, "aria-expanded", _p$.t = _v$60);
1431
- _v$61 !== _p$.a && _$setAttribute(_el$62, "aria-disabled", _p$.a = _v$61);
1432
- _v$62 !== _p$.o && _$setAttribute(_el$62, "tabindex", _p$.o = _v$62);
1433
- _v$63 !== _p$.i && _$setAttribute(_el$62, "data-k2b-nav-tree-id", _p$.i = _v$63);
1550
+ _v$63 = movable() ? true : undefined,
1551
+ _v$64 = item.id,
1552
+ _v$65 = dragged()?.id === item.id ? "" : undefined,
1553
+ _v$66 = dropTarget()?.id === item.id ? dropTarget().side : undefined,
1554
+ _v$67 = String(props.indented === false ? 0 : depth);
1555
+ _v$57 !== _p$.e && _$setAttribute(_el$62, "aria-setsize", _p$.e = _v$57);
1556
+ _v$58 !== _p$.t && _$setAttribute(_el$62, "aria-posinset", _p$.t = _v$58);
1557
+ _v$59 !== _p$.a && _$setAttribute(_el$62, "aria-selected", _p$.a = _v$59);
1558
+ _v$60 !== _p$.o && _$setAttribute(_el$62, "aria-expanded", _p$.o = _v$60);
1559
+ _v$61 !== _p$.i && _$setAttribute(_el$62, "aria-disabled", _p$.i = _v$61);
1560
+ _v$62 !== _p$.n && _$setAttribute(_el$62, "tabindex", _p$.n = _v$62);
1561
+ _v$63 !== _p$.s && _$setAttribute(_el$62, "draggable", _p$.s = _v$63);
1562
+ _v$64 !== _p$.h && _$setAttribute(_el$62, "data-k2b-nav-tree-id", _p$.h = _v$64);
1563
+ _v$65 !== _p$.r && _$setAttribute(_el$62, "data-k2b-nav-tree-dragging", _p$.r = _v$65);
1564
+ _v$66 !== _p$.d && _$setAttribute(_el$62, "data-k2b-nav-tree-drop", _p$.d = _v$66);
1565
+ _v$67 !== _p$.l && _$setStyleProperty(_el$62, "--k2b-nav-tree-depth", _p$.l = _v$67);
1434
1566
  return _p$;
1435
1567
  }, {
1436
1568
  e: undefined,
1437
1569
  t: undefined,
1438
1570
  a: undefined,
1439
1571
  o: undefined,
1440
- i: undefined
1572
+ i: undefined,
1573
+ n: undefined,
1574
+ s: undefined,
1575
+ h: undefined,
1576
+ r: undefined,
1577
+ d: undefined,
1578
+ l: undefined
1441
1579
  });
1442
1580
  _$runHydrationEvents();
1443
1581
  return _el$62;
1444
1582
  })();
1445
1583
  }
1446
1584
  });
1585
+
1586
+ // Any other click or focus is the person's choice; only the moved item itself keeps its claim.
1587
+ const forgetRefocus = event => {
1588
+ if (event.type === "pointerdown" || event.target?.dataset?.k2bNavTreeId !== refocusId) refocusId = null;
1589
+ };
1590
+ // A row replaced during a drag never receives its dragend; the next pointer movement means the drag is over.
1591
+ const forgetDrag = () => {
1592
+ if (dragged()) endDrag();
1593
+ };
1594
+ onMount(() => {
1595
+ const doc = root?.ownerDocument;
1596
+ doc?.addEventListener("pointerdown", forgetRefocus, true);
1597
+ doc?.addEventListener("focusin", forgetRefocus, true);
1598
+ doc?.addEventListener("pointermove", forgetDrag, true);
1599
+ onCleanup(() => {
1600
+ doc?.removeEventListener("pointerdown", forgetRefocus, true);
1601
+ doc?.removeEventListener("focusin", forgetRefocus, true);
1602
+ doc?.removeEventListener("pointermove", forgetDrag, true);
1603
+ });
1604
+ });
1447
1605
  return (() => {
1448
1606
  var _el$68 = _$getNextElement(_tmpl$33);
1607
+ _el$68.addEventListener("dragend", endDrag);
1608
+ _el$68.addEventListener("dragleave", event => {
1609
+ if (!root?.contains(event.relatedTarget)) setDropTarget(null);
1610
+ });
1611
+ _el$68.addEventListener("dragover", event => {
1612
+ // Runs after the rows: a position no row claimed shows no gap.
1613
+ if (claimedDragOver !== event) setDropTarget(null);
1614
+ });
1449
1615
  var _ref$4 = root;
1450
1616
  typeof _ref$4 === "function" ? _$use(_ref$4, _el$68) : root = _el$68;
1451
1617
  _$insert(_el$68, () => renderItems(roots, 0));
1452
1618
  _$effect(_p$ => {
1453
- var _v$64 = `k2b-app-workspace__nav-tree ${props.class ?? ""}`,
1454
- _v$65 = props.ariaLabel;
1455
- _v$64 !== _p$.e && _$className(_el$68, _p$.e = _v$64);
1456
- _v$65 !== _p$.t && _$setAttribute(_el$68, "aria-label", _p$.t = _v$65);
1619
+ var _v$68 = `k2b-app-workspace__nav-tree ${props.class ?? ""}`,
1620
+ _v$69 = props.ariaLabel;
1621
+ _v$68 !== _p$.e && _$className(_el$68, _p$.e = _v$68);
1622
+ _v$69 !== _p$.t && _$setAttribute(_el$68, "aria-label", _p$.t = _v$69);
1457
1623
  return _p$;
1458
1624
  }, {
1459
1625
  e: undefined,
@@ -1581,16 +1747,16 @@ const AppWorkspace = props => {
1581
1747
  typeof _ref$5 === "function" ? _$use(_ref$5, _el$77) : root = _el$77;
1582
1748
  _$insert(_el$77, () => props.children);
1583
1749
  _$effect(_p$ => {
1584
- var _v$66 = `k2b-app-workspace ${props.class ?? ""}`,
1585
- _v$67 = props.mobileSurface,
1586
- _v$68 = initialLayoutState()?.sidebarCollapsed === undefined ? undefined : String(initialLayoutState()?.sidebarCollapsed),
1587
- _v$69 = props.resizable === false ? "false" : "true",
1588
- _v$70 = appWorkspaceLayoutStyle(initialLayoutState());
1589
- _v$66 !== _p$.e && _$className(_el$77, _p$.e = _v$66);
1590
- _v$67 !== _p$.t && _$setAttribute(_el$77, "data-mobile-surface", _p$.t = _v$67);
1591
- _v$68 !== _p$.a && _$setAttribute(_el$77, "data-sidebar-collapsed", _p$.a = _v$68);
1592
- _v$69 !== _p$.o && _$setAttribute(_el$77, "data-workspace-resizable", _p$.o = _v$69);
1593
- _p$.i = _$style(_el$77, _v$70, _p$.i);
1750
+ var _v$70 = `k2b-app-workspace ${props.class ?? ""}`,
1751
+ _v$71 = props.mobileSurface,
1752
+ _v$72 = initialLayoutState()?.sidebarCollapsed === undefined ? undefined : String(initialLayoutState()?.sidebarCollapsed),
1753
+ _v$73 = props.resizable === false ? "false" : "true",
1754
+ _v$74 = appWorkspaceLayoutStyle(initialLayoutState());
1755
+ _v$70 !== _p$.e && _$className(_el$77, _p$.e = _v$70);
1756
+ _v$71 !== _p$.t && _$setAttribute(_el$77, "data-mobile-surface", _p$.t = _v$71);
1757
+ _v$72 !== _p$.a && _$setAttribute(_el$77, "data-sidebar-collapsed", _p$.a = _v$72);
1758
+ _v$73 !== _p$.o && _$setAttribute(_el$77, "data-workspace-resizable", _p$.o = _v$73);
1759
+ _p$.i = _$style(_el$77, _v$74, _p$.i);
1594
1760
  return _p$;
1595
1761
  }, {
1596
1762
  e: undefined,