@k2b/ui 0.28.0 → 0.29.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 (176) hide show
  1. package/dist/browser/actions/ContextMenu.js +5 -0
  2. package/dist/browser/actions/ContextMenu.js.map +1 -1
  3. package/dist/browser/actions/DragAndDrop.js +89 -0
  4. package/dist/browser/actions/DragAndDrop.js.map +1 -0
  5. package/dist/browser/actions/GestureMenu.js +26 -3
  6. package/dist/browser/actions/GestureMenu.js.map +1 -1
  7. package/dist/browser/actions/drag-and-drop-messages.js +75 -0
  8. package/dist/browser/actions/drag-and-drop-messages.js.map +1 -0
  9. package/dist/browser/actions/drag-and-drop.js +692 -0
  10. package/dist/browser/actions/drag-and-drop.js.map +1 -0
  11. package/dist/browser/actions/index.js +1 -0
  12. package/dist/browser/actions/index.js.map +1 -1
  13. package/dist/browser/chat/ChatComposer.js +385 -265
  14. package/dist/browser/chat/ChatComposer.js.map +1 -1
  15. package/dist/browser/chat/ChatDecision.js +778 -0
  16. package/dist/browser/chat/ChatDecision.js.map +1 -0
  17. package/dist/browser/chat/ChatPrimitives.js +34 -28
  18. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  19. package/dist/browser/chat/ChatTimeline.js.map +1 -1
  20. package/dist/browser/chat/chat-messages.js +6 -0
  21. package/dist/browser/chat/chat-messages.js.map +1 -1
  22. package/dist/browser/chat/command-search.js +10 -3
  23. package/dist/browser/chat/command-search.js.map +1 -1
  24. package/dist/browser/chat/index.js +6 -1
  25. package/dist/browser/chat/index.js.map +1 -1
  26. package/dist/browser/chat/types.js.map +1 -1
  27. package/dist/browser/content/Calendar.js +3 -3
  28. package/dist/browser/content/Calendar.js.map +1 -1
  29. package/dist/browser/content/Chart.js +54 -71
  30. package/dist/browser/content/Chart.js.map +1 -1
  31. package/dist/browser/content/FileBrowser.js +2 -2
  32. package/dist/browser/content/FileBrowser.js.map +1 -1
  33. package/dist/browser/content/FileTree.js +42 -36
  34. package/dist/browser/content/FileTree.js.map +1 -1
  35. package/dist/browser/content/FileTypeIcon.js +72 -0
  36. package/dist/browser/content/FileTypeIcon.js.map +1 -0
  37. package/dist/browser/content/FileView.js +2 -2
  38. package/dist/browser/content/FileView.js.map +1 -1
  39. package/dist/browser/content/ReferenceChip.js +162 -0
  40. package/dist/browser/content/ReferenceChip.js.map +1 -0
  41. package/dist/browser/content/calendar-pointer.js +9 -0
  42. package/dist/browser/content/calendar-pointer.js.map +1 -1
  43. package/dist/browser/content/calendar-time-view.js +6 -3
  44. package/dist/browser/content/calendar-time-view.js.map +1 -1
  45. package/dist/browser/content/index.js +2 -0
  46. package/dist/browser/content/index.js.map +1 -1
  47. package/dist/browser/layout/AppOverview.js +1 -1
  48. package/dist/browser/layout/AppOverview.js.map +1 -1
  49. package/dist/browser/layout/AppWorkspace.js +644 -445
  50. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  51. package/dist/browser/layout/PanelDialog.js +88 -86
  52. package/dist/browser/layout/PanelDialog.js.map +1 -1
  53. package/dist/browser/layout/ResizeSeparator.js +81 -0
  54. package/dist/browser/layout/ResizeSeparator.js.map +1 -0
  55. package/dist/browser/layout/SidebarSwitcher.js +9 -2
  56. package/dist/browser/layout/SidebarSwitcher.js.map +1 -1
  57. package/dist/browser/layout/TabBar.js +15 -12
  58. package/dist/browser/layout/TabBar.js.map +1 -1
  59. package/dist/browser/layout/app-workspace-controller.js +130 -17
  60. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  61. package/dist/browser/layout/index.js +3 -1
  62. package/dist/browser/layout/index.js.map +1 -1
  63. package/dist/browser/layout/resize-separator.js +90 -0
  64. package/dist/browser/layout/resize-separator.js.map +1 -0
  65. package/dist/browser/layout/workspace-messages.js +12 -2
  66. package/dist/browser/layout/workspace-messages.js.map +1 -1
  67. package/dist/browser/surfaces/LinkCard.js +1 -1
  68. package/dist/browser/surfaces/LinkCard.js.map +1 -1
  69. package/dist/browser/surfaces/Placeholder.js +14 -3
  70. package/dist/browser/surfaces/Placeholder.js.map +1 -1
  71. package/dist/browser/surfaces/StatCell.js +23 -0
  72. package/dist/browser/surfaces/StatCell.js.map +1 -1
  73. package/dist/browser/widgets/Widget.js +1 -1
  74. package/dist/browser/widgets/Widget.js.map +1 -1
  75. package/dist/ssr/actions/ContextMenu.js +5 -0
  76. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  77. package/dist/ssr/actions/DragAndDrop.js +63 -0
  78. package/dist/ssr/actions/DragAndDrop.js.map +1 -0
  79. package/dist/ssr/actions/GestureMenu.js +26 -3
  80. package/dist/ssr/actions/GestureMenu.js.map +1 -1
  81. package/dist/ssr/actions/drag-and-drop-messages.js +75 -0
  82. package/dist/ssr/actions/drag-and-drop-messages.js.map +1 -0
  83. package/dist/ssr/actions/drag-and-drop.js +692 -0
  84. package/dist/ssr/actions/drag-and-drop.js.map +1 -0
  85. package/dist/ssr/actions/index.js +1 -0
  86. package/dist/ssr/actions/index.js.map +1 -1
  87. package/dist/ssr/chat/ChatComposer.js +131 -14
  88. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  89. package/dist/ssr/chat/ChatDecision.js +465 -0
  90. package/dist/ssr/chat/ChatDecision.js.map +1 -0
  91. package/dist/ssr/chat/ChatPrimitives.js +2 -2
  92. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  93. package/dist/ssr/chat/ChatTimeline.js.map +1 -1
  94. package/dist/ssr/chat/chat-messages.js +6 -0
  95. package/dist/ssr/chat/chat-messages.js.map +1 -1
  96. package/dist/ssr/chat/command-search.js +10 -3
  97. package/dist/ssr/chat/command-search.js.map +1 -1
  98. package/dist/ssr/chat/index.js +6 -1
  99. package/dist/ssr/chat/index.js.map +1 -1
  100. package/dist/ssr/chat/types.js.map +1 -1
  101. package/dist/ssr/content/Calendar.js +3 -3
  102. package/dist/ssr/content/Calendar.js.map +1 -1
  103. package/dist/ssr/content/Chart.js +19 -23
  104. package/dist/ssr/content/Chart.js.map +1 -1
  105. package/dist/ssr/content/FileBrowser.js +2 -2
  106. package/dist/ssr/content/FileBrowser.js.map +1 -1
  107. package/dist/ssr/content/FileTree.js +21 -14
  108. package/dist/ssr/content/FileTree.js.map +1 -1
  109. package/dist/ssr/content/FileTypeIcon.js +50 -0
  110. package/dist/ssr/content/FileTypeIcon.js.map +1 -0
  111. package/dist/ssr/content/FileView.js +2 -2
  112. package/dist/ssr/content/FileView.js.map +1 -1
  113. package/dist/ssr/content/ReferenceChip.js +121 -0
  114. package/dist/ssr/content/ReferenceChip.js.map +1 -0
  115. package/dist/ssr/content/calendar-pointer.js +9 -0
  116. package/dist/ssr/content/calendar-pointer.js.map +1 -1
  117. package/dist/ssr/content/calendar-time-view.js +6 -3
  118. package/dist/ssr/content/calendar-time-view.js.map +1 -1
  119. package/dist/ssr/content/index.js +2 -0
  120. package/dist/ssr/content/index.js.map +1 -1
  121. package/dist/ssr/layout/AppOverview.js +1 -1
  122. package/dist/ssr/layout/AppOverview.js.map +1 -1
  123. package/dist/ssr/layout/AppWorkspace.js +272 -95
  124. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  125. package/dist/ssr/layout/PanelDialog.js +7 -6
  126. package/dist/ssr/layout/PanelDialog.js.map +1 -1
  127. package/dist/ssr/layout/ResizeSeparator.js +45 -0
  128. package/dist/ssr/layout/ResizeSeparator.js.map +1 -0
  129. package/dist/ssr/layout/SidebarSwitcher.js +9 -2
  130. package/dist/ssr/layout/SidebarSwitcher.js.map +1 -1
  131. package/dist/ssr/layout/TabBar.js +1 -1
  132. package/dist/ssr/layout/TabBar.js.map +1 -1
  133. package/dist/ssr/layout/app-workspace-controller.js +130 -17
  134. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  135. package/dist/ssr/layout/index.js +3 -1
  136. package/dist/ssr/layout/index.js.map +1 -1
  137. package/dist/ssr/layout/resize-separator.js +90 -0
  138. package/dist/ssr/layout/resize-separator.js.map +1 -0
  139. package/dist/ssr/layout/workspace-messages.js +12 -2
  140. package/dist/ssr/layout/workspace-messages.js.map +1 -1
  141. package/dist/ssr/surfaces/LinkCard.js +1 -1
  142. package/dist/ssr/surfaces/LinkCard.js.map +1 -1
  143. package/dist/ssr/surfaces/Placeholder.js +4 -2
  144. package/dist/ssr/surfaces/Placeholder.js.map +1 -1
  145. package/dist/ssr/surfaces/StatCell.js +23 -0
  146. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  147. package/dist/ssr/widgets/Widget.js +1 -1
  148. package/dist/ssr/widgets/Widget.js.map +1 -1
  149. package/dist/styles.css +1 -1
  150. package/dist/types/actions/ContextMenu.d.ts +1 -0
  151. package/dist/types/actions/DragAndDrop.d.ts +29 -0
  152. package/dist/types/actions/drag-and-drop-messages.d.ts +35 -0
  153. package/dist/types/actions/drag-and-drop.d.ts +36 -0
  154. package/dist/types/actions/index.d.ts +2 -0
  155. package/dist/types/chat/ChatComposer.d.ts +19 -1
  156. package/dist/types/chat/ChatDecision.d.ts +133 -0
  157. package/dist/types/chat/ChatPrimitives.d.ts +4 -1
  158. package/dist/types/chat/ChatTimeline.d.ts +2 -1
  159. package/dist/types/chat/chat-messages.d.ts +3 -0
  160. package/dist/types/chat/command-search.d.ts +3 -0
  161. package/dist/types/chat/index.d.ts +23 -1
  162. package/dist/types/chat/types.d.ts +54 -1
  163. package/dist/types/content/Chart.d.ts +9 -3
  164. package/dist/types/content/FileTypeIcon.d.ts +27 -0
  165. package/dist/types/content/ReferenceChip.d.ts +49 -0
  166. package/dist/types/content/calendar-pointer.d.ts +6 -0
  167. package/dist/types/content/index.d.ts +3 -0
  168. package/dist/types/layout/AppWorkspace.d.ts +5 -3
  169. package/dist/types/layout/ResizeSeparator.d.ts +9 -0
  170. package/dist/types/layout/TabBar.d.ts +7 -0
  171. package/dist/types/layout/app-workspace-controller.d.ts +2 -0
  172. package/dist/types/layout/index.d.ts +3 -1
  173. package/dist/types/layout/resize-separator.d.ts +18 -0
  174. package/dist/types/layout/workspace-messages.d.ts +5 -0
  175. package/dist/types/surfaces/StatCell.d.ts +6 -0
  176. package/package.json +2 -2
@@ -6,8 +6,36 @@ const HANDLE_SELECTOR = "[data-app-workspace-resize]";
6
6
  const ROOT_SELECTOR = "[data-k2b-app-workspace]";
7
7
  const LAYOUT_REGION_SELECTOR = [ROOT_SELECTOR, ".k2b-app-workspace__sidebar", ".k2b-app-workspace__main-pane", ".k2b-app-workspace__detail", ".k2b-app-workspace__drawer", HANDLE_SELECTOR].join(",");
8
8
  const TOGGLE_SELECTOR = "[data-app-workspace-sidebar-toggle]";
9
- /** The width range in which the sidebar is an overlay; it matches the compact media query in the stylesheet. */
10
- const COMPACT_QUERY = "(min-width: 48rem) and (max-width: 63.999rem)";
9
+ /** Touch-tablet gestures remain viewport decisions. */
10
+ const TOUCH_DRAWER_QUERY = "(min-width: 48rem) and (max-width: 63.999rem) and (pointer: coarse)";
11
+ const DETAIL_TOGGLE_SELECTOR = "[data-app-workspace-detail-toggle]";
12
+ const DETAIL_DRAWER_SELECTOR = '.k2b-app-workspace__detail[data-workspace-detail-drawer="open"]';
13
+ const cssState = (element, name) => getComputedStyle(element).getPropertyValue(name).trim();
14
+ const detailWaiting = aside => cssState(aside, "--k2b-workspace-detail-waiting") === "1";
15
+ const detailDrawer = aside => aside.dataset.workspaceDetailDrawer === "open";
16
+ const detailToggle = aside => {
17
+ const root = workspaceRoot(aside);
18
+ return rootElements(root ?? aside, DETAIL_TOGGLE_SELECTOR).find(toggle => toggle.getAttribute("aria-controls") === aside.id);
19
+ };
20
+ // The trail's registered property accepts a length, not the drawer's percentage
21
+ // cap. Only a user-opened drawer needs this measurement; the first frame stays CSS-owned.
22
+ const detailDrawerContent = aside => aside.closest(".k2b-app-workspace__content");
23
+ const syncDetailDrawerSpace = aside => detailDrawerContent(aside)?.style.setProperty("--k2b-detail-space", `${aside.getBoundingClientRect().width}px`);
24
+ const parkDetailDrawer = (aside, focus = false) => {
25
+ if (!detailDrawer(aside)) return;
26
+ delete aside.dataset.workspaceDetailDrawer;
27
+ detailDrawerContent(aside)?.style.removeProperty("--k2b-detail-space");
28
+ const toggle = detailToggle(aside);
29
+ toggle?.setAttribute("aria-expanded", "false");
30
+ if (focus) toggle?.focus({
31
+ preventScroll: true
32
+ });
33
+ };
34
+
35
+ /** Parks open waiting-detail drawers; focus follows only when it was inside one. */
36
+ export const parkWorkspaceDetailDrawers = (root = document) => {
37
+ for (const aside of root.querySelectorAll(DETAIL_DRAWER_SELECTOR)) parkDetailDrawer(aside, aside.contains(aside.ownerDocument.activeElement));
38
+ };
11
39
  const FOCUSABLE_SELECTOR = ["a[href]", "button:not([disabled])", "input:not([disabled]):not([type='hidden'])", "select:not([disabled])", "textarea:not([disabled])", "[tabindex]:not([tabindex='-1'])"].join(",");
12
40
  /** Text entry, menus, dialogs, and open popups keep their own keys. */
13
41
  const KEY_OWNER_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox'], [role='combobox'], [role='searchbox'], [role='spinbutton'], [role='menu'], [role='listbox'], [role='dialog'], dialog, [aria-haspopup]:not([aria-haspopup='false'])[aria-expanded='true']";
@@ -59,8 +87,9 @@ const controlledPanel = (root, handle) => {
59
87
  };
60
88
  const flexibleLevel = pane => {
61
89
  if (!pane.hasAttribute("data-workspace-level")) return false;
62
- const widthClass = window.matchMedia("(min-width: 80rem)").matches ? "3" : window.matchMedia("(min-width: 48rem)").matches ? "2" : "1";
63
- return pane.dataset.workspaceLevelLast?.split(" ").includes(widthClass) ?? false;
90
+ const root = workspaceRoot(pane);
91
+ const widthClass = root ? cssState(root, "--k2b-workspace-level-class") : "";
92
+ return Boolean(widthClass && pane.dataset.workspaceLevelLast?.split(" ").includes(widthClass));
64
93
  };
65
94
  const levelsMain = handle => handle.closest(".k2b-app-workspace__main")?.hasAttribute("data-workspace-levels") ?? false;
66
95
  const controlsResizableRegion = (root, handle, kind) => {
@@ -228,7 +257,6 @@ export const installAppWorkspaceController = (options = {}) => {
228
257
  const ownerDocument = eventRoot.ownerDocument ?? document;
229
258
  const ownerWindow = ownerDocument.defaultView ?? window;
230
259
  let overlay = null;
231
- const compact = typeof window.matchMedia === "function" ? window.matchMedia(COMPACT_QUERY) : null;
232
260
  // A server-rendered head names the sidebar before the sidebar renders; the button learns its real id now.
233
261
  for (const toggle of eventRoot.querySelectorAll(TOGGLE_SELECTOR)) overlayTarget(toggle);
234
262
  const openOverlay = target => {
@@ -269,12 +297,28 @@ export const installAppWorkspaceController = (options = {}) => {
269
297
  };
270
298
  const onClick = event => {
271
299
  const target = eventElement(event);
300
+ const detailButton = target?.closest(DETAIL_TOGGLE_SELECTOR);
301
+ if (detailButton) {
302
+ const root = workspaceRoot(detailButton);
303
+ const aside = root ? controlledPanel(root, detailButton) : null;
304
+ if (!aside || aside.hidden || !detailWaiting(aside) || claimedToggleClicks.has(event)) return;
305
+ claimedToggleClicks.add(event);
306
+ if (detailDrawer(aside)) parkDetailDrawer(aside);else {
307
+ aside.dataset.workspaceDetailDrawer = "open";
308
+ detailButton.setAttribute("aria-expanded", "true");
309
+ syncDetailDrawerSpace(aside);
310
+ if (event.detail === 0) requestTitle(aside);else detailButton.focus({
311
+ preventScroll: true
312
+ });
313
+ }
314
+ return;
315
+ }
272
316
  const toggle = target?.closest(TOGGLE_SELECTOR);
273
317
  if (toggle) {
274
318
  const next = overlayTarget(toggle);
275
319
  if (!next || claimedToggleClicks.has(event)) return;
276
320
  claimedToggleClicks.add(event);
277
- if (overlay?.sidebar === next.sidebar) closeOverlay("button");else if (compact?.matches) openOverlay(next);
321
+ if (overlay?.sidebar === next.sidebar) closeOverlay("button");else if (cssState(next.root, "--k2b-workspace-sidebar-form") === "overlay") openOverlay(next);
278
322
  return;
279
323
  }
280
324
  if (!overlay || !target) return;
@@ -310,6 +354,11 @@ export const installAppWorkspaceController = (options = {}) => {
310
354
  // Dialogs and popovers keep their own Tab order.
311
355
  const page = phone?.matches ? allRoots().flatMap(openDetails).find(phonePage) : undefined;
312
356
  if (!page || ownerDocument.querySelector("dialog[open], [aria-modal='true']") || target.closest("[popover]")) return;
357
+ if (target !== ownerDocument.body) {
358
+ const rect = target.getBoundingClientRect();
359
+ const hit = ownerDocument.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
360
+ if (hit && target.contains(hit)) return;
361
+ }
313
362
  const stops = tabStopsIn(page);
314
363
  event.preventDefault();
315
364
  (event.shiftKey ? stops.at(-1) : stops[0])?.focus();
@@ -334,12 +383,37 @@ export const installAppWorkspaceController = (options = {}) => {
334
383
  first.focus();
335
384
  }
336
385
  };
337
- const onCompactChange = () => {
338
- if (!compact?.matches) closeOverlay("none");
386
+ // Whether each open detail waited at the last check, so a column that starts
387
+ // waiting under focus hands that focus to its Details button.
388
+ const detailWasWaiting = new WeakMap();
389
+ const syncHostState = () => {
390
+ if (overlay && cssState(overlay.root, "--k2b-workspace-sidebar-form") !== "overlay") closeOverlay("none");
391
+ for (const root of allRoots()) {
392
+ // A drawer removed with its panel leaves no space behind in Content.
393
+ for (const content of rootElements(root, ".k2b-app-workspace__content")) if (content.style.getPropertyValue("--k2b-detail-space") && !content.querySelector(DETAIL_DRAWER_SELECTOR)) content.style.removeProperty("--k2b-detail-space");
394
+ for (const aside of rootElements(root, detailSelector)) {
395
+ if (aside.hidden) {
396
+ detailWasWaiting.delete(aside);
397
+ parkDetailDrawer(aside);
398
+ continue;
399
+ }
400
+ const waiting = detailWaiting(aside);
401
+ const before = detailWasWaiting.get(aside);
402
+ detailWasWaiting.set(aside, waiting);
403
+ if (!waiting) {
404
+ // The Details button hides with the waiting; its focus moves to the returning column's title.
405
+ const toggleFocused = detailToggle(aside)?.contains(ownerDocument.activeElement) ?? false;
406
+ parkDetailDrawer(aside);
407
+ if (toggleFocused) focusTitle(aside);
408
+ } else if (detailDrawer(aside)) syncDetailDrawerSpace(aside);else if (before !== true && aside.contains(ownerDocument.activeElement)) detailToggle(aside)?.focus({
409
+ preventScroll: true
410
+ });
411
+ }
412
+ }
339
413
  };
340
414
  const phone = typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 47.999rem)") : null;
341
415
  const wide = typeof window.matchMedia === "function" ? window.matchMedia("(min-width: 48rem)") : null;
342
- const touchDrawer = typeof window.matchMedia === "function" ? window.matchMedia(`${COMPACT_QUERY} and (pointer: coarse)`) : null;
416
+ const touchDrawer = typeof window.matchMedia === "function" ? window.matchMedia(TOUCH_DRAWER_QUERY) : null;
343
417
  const detailSelector = ".k2b-app-workspace__detail";
344
418
  const grabberSelector = ".k2b-app-workspace__detail-grabber";
345
419
  const detailHidden = new WeakMap();
@@ -499,7 +573,7 @@ export const installAppWorkspaceController = (options = {}) => {
499
573
  delete aside.dataset.workspaceDetailDragging;
500
574
  };
501
575
  const anchorItem = (root, aside, snapshot) => {
502
- if (!wide?.matches || touchDrawer?.matches && closable(aside) || !snapshot?.item.isConnected) return;
576
+ if (!wide?.matches || touchDrawer?.matches && closable(aside) || detailDrawer(aside) || !snapshot?.item.isConnected) return;
503
577
  if (!mainElement(root)?.contains(snapshot.item) || snapshot.item.closest(ROOT_SELECTOR) !== root) return;
504
578
  const scroller = snapshot.scroller;
505
579
  if (scroller?.isConnected && scroller.scrollTop === snapshot.scrollTop) scroller.scrollTop += snapshot.item.getBoundingClientRect().top - snapshot.top;
@@ -605,6 +679,7 @@ export const installAppWorkspaceController = (options = {}) => {
605
679
  const followedInteraction = snapshot?.fresh ?? false;
606
680
  if (snapshot) snapshot.fresh = false;
607
681
  if (aside.hidden) {
682
+ parkDetailDrawer(aside);
608
683
  if (pendingTitle?.aside === aside) cancelTitle();
609
684
  if (panelStep === aside) panelStep = null;
610
685
  if (root.hasAttribute("data-workspace-motion") && !prefersReducedMotion() && wide?.matches) {
@@ -633,6 +708,7 @@ export const installAppWorkspaceController = (options = {}) => {
633
708
  if (detailDrag?.aside === aside) finishDetailDrag(true);
634
709
  return;
635
710
  }
711
+ if (detailWaiting(aside) && !detailDrawer(aside)) return;
636
712
  anchorItem(root, aside, snapshot);
637
713
  if (phone?.matches) setDetent(aside, false);
638
714
  if (followedInteraction && snapshot) {
@@ -653,13 +729,18 @@ export const installAppWorkspaceController = (options = {}) => {
653
729
  const root = target.closest(ROOT_SELECTOR);
654
730
  let aside = target.closest(detailSelector);
655
731
  if (aside?.hidden || aside && (!eventRoot.contains(aside) || aside.closest(ROOT_SELECTOR) !== root)) aside = null;
656
- if (!aside && root && observedRoots.has(root) && mainElement(root)?.contains(target)) aside = openDetails(root)[0] ?? null;
732
+ if (!aside && root && observedRoots.has(root) && (mainElement(root)?.contains(target) || target.closest(DETAIL_TOGGLE_SELECTOR))) aside = openDetails(root).find(isVisible) ?? null;
657
733
  if (!aside && target === ownerDocument.body) {
658
- const open = allRoots().flatMap(openDetails);
734
+ const open = allRoots().flatMap(openDetails).filter(isVisible);
659
735
  if (open.length === 1) aside = open[0];
660
736
  }
661
737
  const owningRoot = aside?.closest(ROOT_SELECTOR);
662
- if (!aside || !owningRoot) return;
738
+ if (!aside || !owningRoot || !isVisible(aside)) return;
739
+ if (event.key === "Escape" && detailDrawer(aside)) {
740
+ parkDetailDrawer(aside, true);
741
+ event.preventDefault();
742
+ return;
743
+ }
663
744
  if (event.key === "Escape" && closable(aside)) requestClose(aside);else if ((event.key.toLowerCase() === "j" || event.key.toLowerCase() === "k") && aside.hasAttribute("data-workspace-detail-steppable")) {
664
745
  const main = mainElement(owningRoot);
665
746
  const inMain = remembered.get(owningRoot)?.item.contains(target) || main?.contains(target);
@@ -1196,14 +1277,43 @@ export const installAppWorkspaceController = (options = {}) => {
1196
1277
  reconcileFrame = requestAnimationFrame(() => {
1197
1278
  reconcileFrame = null;
1198
1279
  reconcile();
1280
+ // Reconciling can restore a drawer's persisted width; its trail space follows.
1281
+ for (const root of allRoots()) for (const aside of rootElements(root, DETAIL_DRAWER_SELECTOR)) syncDetailDrawerSpace(aside);
1199
1282
  });
1200
1283
  };
1201
- const onResize = () => scheduleReconcile();
1284
+ const onResize = () => {
1285
+ syncHostState();
1286
+ scheduleReconcile();
1287
+ };
1202
1288
  const observedRoots = new Set();
1203
1289
  const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
1290
+ syncHostState();
1204
1291
  scheduleReconcile();
1205
1292
  });
1293
+ // A host can change its width while a workspace with a fixed width keeps
1294
+ // its own, so the controller also watches each root's nearest named host.
1295
+ const observedHosts = new Set();
1296
+ // Found once per root: reading styles on every mutation would cost a style pass.
1297
+ const rootHosts = new WeakMap();
1298
+ const hostOf = root => {
1299
+ if (rootHosts.has(root)) return rootHosts.get(root);
1300
+ let host = null;
1301
+ for (let element = root.parentElement; element && !host; element = element.parentElement) if (getComputedStyle(element).containerName.split(/\s+/).includes("k2b-app-area")) host = element;
1302
+ rootHosts.set(root, host);
1303
+ return host;
1304
+ };
1206
1305
  const syncObservedRoots = () => {
1306
+ const hosts = new Set(allRoots().map(hostOf).filter(host => host !== null));
1307
+ observedHosts.forEach(host => {
1308
+ if (hosts.has(host)) return;
1309
+ resizeObserver?.unobserve(host);
1310
+ observedHosts.delete(host);
1311
+ });
1312
+ hosts.forEach(host => {
1313
+ if (observedHosts.has(host)) return;
1314
+ observedHosts.add(host);
1315
+ resizeObserver?.observe(host);
1316
+ });
1207
1317
  for (const elements of [touchedDetails, titleTabIndices, motionRoots]) {
1208
1318
  elements.forEach(element => {
1209
1319
  if (!element.isConnected) elements.delete(element);
@@ -1265,6 +1375,8 @@ export const installAppWorkspaceController = (options = {}) => {
1265
1375
  const changed = records.some(record => record.type === "attributes" ? record.target instanceof Element && record.target.matches(LAYOUT_REGION_SELECTOR) : [...Array.from(record.addedNodes), ...Array.from(record.removedNodes)].some(layoutNode));
1266
1376
  if (!changed) return;
1267
1377
  syncObservedRoots();
1378
+ // A new width token or a removed panel can end waiting without any box resizing.
1379
+ if (records.some(record => record.type === "childList" || record.attributeName === "data-width")) syncHostState();
1268
1380
  scheduleReconcile();
1269
1381
  });
1270
1382
  eventRoot.addEventListener("pointerdown", onPointerDown);
@@ -1289,7 +1401,7 @@ export const installAppWorkspaceController = (options = {}) => {
1289
1401
  });
1290
1402
  eventRoot.addEventListener("touchend", onDetailTouchEnd);
1291
1403
  eventRoot.addEventListener("touchcancel", onDetailTouchEnd);
1292
- compact?.addEventListener("change", onCompactChange);
1404
+ phone?.addEventListener("change", syncHostState);
1293
1405
  phone?.addEventListener("change", syncPhonePages);
1294
1406
  eventRoot.addEventListener("keydown", onKeyDown);
1295
1407
  eventRoot.addEventListener("focusin", onFocusIn);
@@ -1299,7 +1411,7 @@ export const installAppWorkspaceController = (options = {}) => {
1299
1411
  mutationObserver?.observe(eventRoot, {
1300
1412
  attributes: true,
1301
1413
  attributeOldValue: true,
1302
- attributeFilter: ["hidden", "data-workspace-level-shown"],
1414
+ attributeFilter: ["hidden", "data-workspace-level-shown", "data-width"],
1303
1415
  childList: true,
1304
1416
  subtree: true
1305
1417
  });
@@ -1348,11 +1460,12 @@ export const installAppWorkspaceController = (options = {}) => {
1348
1460
  mutationObserver?.disconnect();
1349
1461
  resizeObserver?.disconnect();
1350
1462
  observedRoots.clear();
1463
+ observedHosts.clear();
1351
1464
  closeOverlay("none");
1352
1465
  eventRoot.removeEventListener("pointerdown", onPointerDown);
1353
1466
  eventRoot.removeEventListener("click", onClick);
1354
1467
  ownerDocument.removeEventListener("keydown", onOverlayKeyDown);
1355
- compact?.removeEventListener("change", onCompactChange);
1468
+ phone?.removeEventListener("change", syncHostState);
1356
1469
  eventRoot.removeEventListener("keydown", onKeyDown);
1357
1470
  eventRoot.removeEventListener("focusin", onFocusIn);
1358
1471
  window.removeEventListener("resize", onResize);