@k2b/ui 0.30.0 → 0.32.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 (186) hide show
  1. package/README.md +1 -1
  2. package/dist/base.css +2 -0
  3. package/dist/browser/actions/Toolbar.js +2 -0
  4. package/dist/browser/actions/Toolbar.js.map +1 -1
  5. package/dist/browser/actions/ViewTabs.js +8 -0
  6. package/dist/browser/actions/ViewTabs.js.map +1 -1
  7. package/dist/browser/chat/ChatDecision.js +28 -2
  8. package/dist/browser/chat/ChatDecision.js.map +1 -1
  9. package/dist/browser/content/Chart.js +104 -55
  10. package/dist/browser/content/Chart.js.map +1 -1
  11. package/dist/browser/content/ChartExplorer.js +5 -3
  12. package/dist/browser/content/ChartExplorer.js.map +1 -1
  13. package/dist/browser/content/MapAttribution.js +27 -0
  14. package/dist/browser/content/MapAttribution.js.map +1 -0
  15. package/dist/browser/content/MapView.js +553 -0
  16. package/dist/browser/content/MapView.js.map +1 -0
  17. package/dist/browser/content/MapViewFrame.js +395 -0
  18. package/dist/browser/content/MapViewFrame.js.map +1 -0
  19. package/dist/browser/content/chart-inspection-core.js +4 -1
  20. package/dist/browser/content/chart-inspection-core.js.map +1 -1
  21. package/dist/browser/content/code-highlight.js +66 -0
  22. package/dist/browser/content/code-highlight.js.map +1 -1
  23. package/dist/browser/content/index.js +6 -0
  24. package/dist/browser/content/index.js.map +1 -1
  25. package/dist/browser/content/map-palette.js +39 -0
  26. package/dist/browser/content/map-palette.js.map +1 -0
  27. package/dist/browser/content/map-style.js +276 -0
  28. package/dist/browser/content/map-style.js.map +1 -0
  29. package/dist/browser/content/map-view-geometry.js +269 -0
  30. package/dist/browser/content/map-view-geometry.js.map +1 -0
  31. package/dist/browser/content/map-view-live.js +65 -0
  32. package/dist/browser/content/map-view-live.js.map +1 -0
  33. package/dist/browser/content/map-view-messages.js +53 -0
  34. package/dist/browser/content/map-view-messages.js.map +1 -0
  35. package/dist/browser/content/map-view-types.js +2 -0
  36. package/dist/browser/content/map-view-types.js.map +1 -0
  37. package/dist/browser/content/map-view-worker.js +10 -0
  38. package/dist/browser/feedback/dialog-core.js +19 -0
  39. package/dist/browser/feedback/dialog-core.js.map +1 -1
  40. package/dist/browser/inputs/AutocompleteEditor.js +167 -104
  41. package/dist/browser/inputs/AutocompleteEditor.js.map +1 -1
  42. package/dist/browser/inputs/RichSelect.js +1474 -0
  43. package/dist/browser/inputs/RichSelect.js.map +1 -0
  44. package/dist/browser/inputs/TextInput.js +95 -60
  45. package/dist/browser/inputs/TextInput.js.map +1 -1
  46. package/dist/browser/inputs/choice.js +19 -8
  47. package/dist/browser/inputs/choice.js.map +1 -1
  48. package/dist/browser/inputs/index.js +1 -0
  49. package/dist/browser/inputs/index.js.map +1 -1
  50. package/dist/browser/inputs/rich-select-messages.js +46 -0
  51. package/dist/browser/inputs/rich-select-messages.js.map +1 -0
  52. package/dist/browser/layout/AppWorkspace.js +194 -144
  53. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  54. package/dist/browser/layout/DetailPanel.js +10 -1
  55. package/dist/browser/layout/DetailPanel.js.map +1 -1
  56. package/dist/browser/layout/Navigation.js +14 -5
  57. package/dist/browser/layout/Navigation.js.map +1 -1
  58. package/dist/browser/layout/PageTitle.js +9 -3
  59. package/dist/browser/layout/PageTitle.js.map +1 -1
  60. package/dist/browser/layout/ResizeSeparator.js +8 -2
  61. package/dist/browser/layout/ResizeSeparator.js.map +1 -1
  62. package/dist/browser/layout/app-workspace-controller.js +301 -34
  63. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  64. package/dist/browser/layout/app-workspace-width.js +26 -0
  65. package/dist/browser/layout/app-workspace-width.js.map +1 -0
  66. package/dist/browser/layout/index.js +1 -1
  67. package/dist/browser/layout/index.js.map +1 -1
  68. package/dist/browser/layout/navigation-model.js.map +1 -1
  69. package/dist/browser/layout/resize-separator.js +25 -1
  70. package/dist/browser/layout/resize-separator.js.map +1 -1
  71. package/dist/browser/surfaces/Skeleton.js +14 -0
  72. package/dist/browser/surfaces/Skeleton.js.map +1 -0
  73. package/dist/browser/surfaces/index.js +1 -0
  74. package/dist/browser/surfaces/index.js.map +1 -1
  75. package/dist/licenses/maplibre-gl.txt +116 -0
  76. package/dist/ssr/actions/Toolbar.js +2 -0
  77. package/dist/ssr/actions/Toolbar.js.map +1 -1
  78. package/dist/ssr/actions/ViewTabs.js +8 -0
  79. package/dist/ssr/actions/ViewTabs.js.map +1 -1
  80. package/dist/ssr/chat/ChatDecision.js +11 -0
  81. package/dist/ssr/chat/ChatDecision.js.map +1 -1
  82. package/dist/ssr/content/Chart.js +27 -7
  83. package/dist/ssr/content/Chart.js.map +1 -1
  84. package/dist/ssr/content/ChartExplorer.js +5 -3
  85. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  86. package/dist/ssr/content/MapAttribution.js +19 -0
  87. package/dist/ssr/content/MapAttribution.js.map +1 -0
  88. package/dist/ssr/content/MapView.js +474 -0
  89. package/dist/ssr/content/MapView.js.map +1 -0
  90. package/dist/ssr/content/MapViewFrame.js +193 -0
  91. package/dist/ssr/content/MapViewFrame.js.map +1 -0
  92. package/dist/ssr/content/chart-inspection-core.js +4 -1
  93. package/dist/ssr/content/chart-inspection-core.js.map +1 -1
  94. package/dist/ssr/content/code-highlight.js +66 -0
  95. package/dist/ssr/content/code-highlight.js.map +1 -1
  96. package/dist/ssr/content/index.js +6 -0
  97. package/dist/ssr/content/index.js.map +1 -1
  98. package/dist/ssr/content/map-palette.js +39 -0
  99. package/dist/ssr/content/map-palette.js.map +1 -0
  100. package/dist/ssr/content/map-style.js +276 -0
  101. package/dist/ssr/content/map-style.js.map +1 -0
  102. package/dist/ssr/content/map-view-geometry.js +269 -0
  103. package/dist/ssr/content/map-view-geometry.js.map +1 -0
  104. package/dist/ssr/content/map-view-live.js +1 -0
  105. package/dist/ssr/content/map-view-messages.js +53 -0
  106. package/dist/ssr/content/map-view-messages.js.map +1 -0
  107. package/dist/ssr/content/map-view-types.js +2 -0
  108. package/dist/ssr/content/map-view-types.js.map +1 -0
  109. package/dist/ssr/feedback/dialog-core.js +19 -0
  110. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  111. package/dist/ssr/inputs/AutocompleteEditor.js +64 -24
  112. package/dist/ssr/inputs/AutocompleteEditor.js.map +1 -1
  113. package/dist/ssr/inputs/RichSelect.js +950 -0
  114. package/dist/ssr/inputs/RichSelect.js.map +1 -0
  115. package/dist/ssr/inputs/TextInput.js +40 -16
  116. package/dist/ssr/inputs/TextInput.js.map +1 -1
  117. package/dist/ssr/inputs/choice.js +19 -8
  118. package/dist/ssr/inputs/choice.js.map +1 -1
  119. package/dist/ssr/inputs/index.js +1 -0
  120. package/dist/ssr/inputs/index.js.map +1 -1
  121. package/dist/ssr/inputs/rich-select-messages.js +46 -0
  122. package/dist/ssr/inputs/rich-select-messages.js.map +1 -0
  123. package/dist/ssr/layout/AppWorkspace.js +53 -16
  124. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  125. package/dist/ssr/layout/DetailPanel.js +1 -1
  126. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  127. package/dist/ssr/layout/Navigation.js +1 -1
  128. package/dist/ssr/layout/Navigation.js.map +1 -1
  129. package/dist/ssr/layout/PageTitle.js +9 -3
  130. package/dist/ssr/layout/PageTitle.js.map +1 -1
  131. package/dist/ssr/layout/ResizeSeparator.js +4 -1
  132. package/dist/ssr/layout/ResizeSeparator.js.map +1 -1
  133. package/dist/ssr/layout/app-workspace-controller.js +301 -34
  134. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  135. package/dist/ssr/layout/app-workspace-width.js +26 -0
  136. package/dist/ssr/layout/app-workspace-width.js.map +1 -0
  137. package/dist/ssr/layout/index.js +1 -1
  138. package/dist/ssr/layout/index.js.map +1 -1
  139. package/dist/ssr/layout/navigation-model.js.map +1 -1
  140. package/dist/ssr/layout/resize-separator.js +25 -1
  141. package/dist/ssr/layout/resize-separator.js.map +1 -1
  142. package/dist/ssr/surfaces/Skeleton.js +8 -0
  143. package/dist/ssr/surfaces/Skeleton.js.map +1 -0
  144. package/dist/ssr/surfaces/index.js +1 -0
  145. package/dist/ssr/surfaces/index.js.map +1 -1
  146. package/dist/styles.css +1 -1
  147. package/dist/types/content/Chart.d.ts +4 -2
  148. package/dist/types/content/ChartExplorer.d.ts +0 -1
  149. package/dist/types/content/MapAttribution.d.ts +6 -0
  150. package/dist/types/content/MapView.d.ts +5 -0
  151. package/dist/types/content/MapViewFrame.d.ts +31 -0
  152. package/dist/types/content/code-highlight.d.ts +3 -1
  153. package/dist/types/content/index.d.ts +7 -0
  154. package/dist/types/content/map-palette.d.ts +58 -0
  155. package/dist/types/content/map-style.d.ts +29 -0
  156. package/dist/types/content/map-view-geometry.d.ts +55 -0
  157. package/dist/types/content/map-view-live.d.ts +2 -0
  158. package/dist/types/content/map-view-messages.d.ts +21 -0
  159. package/dist/types/content/map-view-types.d.ts +63 -0
  160. package/dist/types/feedback/dialog-core.d.ts +1 -0
  161. package/dist/types/inputs/AutocompleteEditor.d.ts +6 -1
  162. package/dist/types/inputs/RichSelect.d.ts +93 -0
  163. package/dist/types/inputs/TextInput.d.ts +5 -2
  164. package/dist/types/inputs/choice.d.ts +24 -4
  165. package/dist/types/inputs/index.d.ts +2 -0
  166. package/dist/types/inputs/rich-select-messages.d.ts +21 -0
  167. package/dist/types/layout/AppWorkspace.d.ts +14 -3
  168. package/dist/types/layout/DetailPanel.d.ts +2 -0
  169. package/dist/types/layout/app-workspace-controller.d.ts +2 -0
  170. package/dist/types/layout/app-workspace-width.d.ts +13 -0
  171. package/dist/types/layout/index.d.ts +2 -2
  172. package/dist/types/layout/navigation-model.d.ts +2 -0
  173. package/dist/types/layout/resize-separator.d.ts +1 -0
  174. package/dist/types/surfaces/Skeleton.d.ts +6 -0
  175. package/dist/types/surfaces/index.d.ts +1 -0
  176. package/map-glyphs/IBM Plex Sans Medium/0-255.pbf +0 -0
  177. package/map-glyphs/IBM Plex Sans Medium/1024-1279.pbf +0 -0
  178. package/map-glyphs/IBM Plex Sans Medium/256-511.pbf +0 -0
  179. package/map-glyphs/IBM Plex Sans Medium/512-767.pbf +0 -0
  180. package/map-glyphs/IBM Plex Sans Medium/768-1023.pbf +0 -0
  181. package/map-glyphs/IBM Plex Sans Medium/7680-7935.pbf +0 -0
  182. package/map-glyphs/IBM Plex Sans Medium/8192-8447.pbf +0 -0
  183. package/map-glyphs/IBM Plex Sans Medium/8448-8703.pbf +0 -0
  184. package/map-glyphs/IBM Plex Sans Medium/8704-8959.pbf +0 -0
  185. package/map-glyphs/OFL.txt +93 -0
  186. package/package.json +11 -6
@@ -1,6 +1,7 @@
1
1
  import { suppressTextSelection } from "../internal/text-selection";
2
2
  import { prefersReducedMotion } from "../motion";
3
- import { APP_WORKSPACE_DETAIL_DEFAULT, APP_WORKSPACE_DETAIL_MAX, APP_WORKSPACE_DETAIL_MIN, APP_WORKSPACE_DRAWER_MAX, APP_WORKSPACE_DRAWER_MIN, APP_WORKSPACE_MAIN_MIN, APP_WORKSPACE_PANE_DEFAULT, APP_WORKSPACE_PANE_MAX, APP_WORKSPACE_PANE_MIN, APP_WORKSPACE_SIDEBAR_COLLAPSED, APP_WORKSPACE_SIDEBAR_MAX, APP_WORKSPACE_SIDEBAR_MIN, appWorkspacePanelVariable, appWorkspaceResizeLimits, fitAppWorkspacePaneSizes, normalizeAppWorkspaceLayoutState, resolveAppWorkspaceSidebarWidth, safeAppWorkspacePanelId, shouldCollapseAppWorkspaceSidebar } from "./app-workspace-state";
3
+ 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_MAIN_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, appWorkspacePanelVariable, appWorkspaceResizeLimits, fitAppWorkspacePaneSizes, normalizeAppWorkspaceLayoutState, resolveAppWorkspaceSidebarWidth, safeAppWorkspacePanelId, shouldCollapseAppWorkspaceSidebar } from "./app-workspace-state";
4
+ import { readAppWorkspaceWidth, resolveAppWorkspaceWidth } from "./app-workspace-width";
4
5
  import { installScrollFades } from "./scroll-fade";
5
6
  const HANDLE_SELECTOR = "[data-app-workspace-resize]";
6
7
  const ROOT_SELECTOR = "[data-k2b-app-workspace]";
@@ -25,7 +26,7 @@ const parkDetailDrawer = (aside, focus = false) => {
25
26
  detailDrawerContent(aside)?.style.removeProperty("--k2b-detail-space");
26
27
  const toggle = detailToggle(aside);
27
28
  toggle?.setAttribute("aria-expanded", "false");
28
- if (focus) toggle?.focus({
29
+ if (focus) detailReturnControl(aside)?.focus({
29
30
  preventScroll: true
30
31
  });
31
32
  };
@@ -65,6 +66,8 @@ const sidebarHideable = root => sidebarElement(root)?.dataset.workspaceHideable
65
66
  * stays application state, so the controller only requests the change.
66
67
  */
67
68
  export const APP_WORKSPACE_SIDEBAR_HIDE_EVENT = "k2b-app-workspace-sidebar-hide";
69
+ /** Asks the owning Detail to reopen its current available object. */
70
+ export const APP_WORKSPACE_DETAIL_OPEN_EVENT = "k2b-app-workspace-detail-open";
68
71
  /** Asks the owning `AppWorkspace.Detail` to close; its `onClose` decides. */
69
72
  export const APP_WORKSPACE_DETAIL_CLOSE_EVENT = "k2b-app-workspace-detail-close";
70
73
  /**
@@ -79,6 +82,15 @@ const previewSidebarHidden = (root, hidden) => {
79
82
  if (hidden) sidebar.dataset.workspaceHidePreview = "true";else delete sidebar.dataset.workspaceHidePreview;
80
83
  };
81
84
  const isVisible = element => Boolean(element && !element.hidden && getComputedStyle(element).display !== "none");
85
+ // A pane-bound head can be absent on a phone. The visible control in Main
86
+ // that names this same detail remains its return target, within the same root.
87
+ const detailReturnControl = aside => {
88
+ const root = workspaceRoot(aside);
89
+ if (!root) return undefined;
90
+ const shared = detailToggle(aside);
91
+ if (isVisible(shared ?? null) && shared && !shared.matches(":disabled")) return shared;
92
+ return rootElements(root, DETAIL_REQUEST_CONTROL_SELECTOR).find(control => control.matches(FOCUSABLE_SELECTOR) && !aside.contains(control) && !control.matches(":disabled, [inert]") && !control.closest("[inert]") && control.getAttribute("aria-controls")?.split(/\s+/).includes(aside.id) && control.getClientRects().length > 0);
93
+ };
82
94
  const elementSize = (element, kind) => {
83
95
  if (!isVisible(element)) return 0;
84
96
  const rect = element.getBoundingClientRect();
@@ -121,8 +133,9 @@ const sizeLimits = (root, handle, kind, reserveMainMinimum = true) => {
121
133
  const content = kind === "detail" ? controlled?.closest(".k2b-app-workspace__content") : null;
122
134
  const defaultMin = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MIN : kind === "pane" ? APP_WORKSPACE_PANE_MIN : kind === "detail" ? APP_WORKSPACE_DETAIL_MIN : APP_WORKSPACE_DRAWER_MIN;
123
135
  const defaultMax = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MAX : kind === "pane" ? APP_WORKSPACE_PANE_MAX : kind === "detail" ? APP_WORKSPACE_DETAIL_MAX : APP_WORKSPACE_DRAWER_MAX;
136
+ const mainSpec = readAppWorkspaceWidth(mainElement(root)?.dataset.workspaceMainMin);
124
137
  const mainMinValue = cssState(root, "--k2b-workspace-main-min");
125
- const mainMin = mainMinValue.endsWith("px") ? Number.parseFloat(mainMinValue) : APP_WORKSPACE_MAIN_MIN;
138
+ const mainMin = typeof mainSpec === "object" ? resolveAppWorkspaceWidth(mainSpec, root.getBoundingClientRect().width) : mainMinValue.endsWith("px") ? Number.parseFloat(mainMinValue) : APP_WORKSPACE_MAIN_MIN;
126
139
  const mainExtra = Math.max(0, (Number.isFinite(mainMin) ? mainMin : APP_WORKSPACE_MAIN_MIN) - APP_WORKSPACE_MAIN_MIN);
127
140
  const limits = appWorkspaceResizeLimits({
128
141
  kind,
@@ -213,18 +226,39 @@ const reconcilePanes = (root, layoutState, active = null) => {
213
226
  if (!isVisible(panel)) return [];
214
227
  const min = numberData(handle, "workspaceMinSize", APP_WORKSPACE_PANE_MIN);
215
228
  const max = Math.max(min, numberData(handle, "workspaceMaxSize", APP_WORKSPACE_PANE_MAX));
216
- const preferred = active?.moved && active.root === root && active.kind === "pane" && active.handle === handle ? active.liveSize : layoutState.paneWidths?.[panelId(handle)] ?? numberData(handle, "workspaceDefaultSize", elementSize(panel, "pane"));
217
- const desired = clamp(preferred, min, max);
229
+ const spec = readAppWorkspaceWidth(handle.dataset.workspaceDefaultSize) ?? elementSize(panel, "pane");
230
+ const preferred = active?.moved && active.root === root && active.kind === "pane" && active.handle === handle ? active.liveSize : layoutState.paneWidths?.[panelId(handle)];
231
+ const cssDefault = preferred === undefined && typeof spec !== "number";
232
+ if (cssDefault && root.style.getPropertyValue(appWorkspacePanelVariable("pane", panelId(handle))))
233
+ // Drop a previous live width (or an inherited SSR preference after reset).
234
+ root.style.setProperty(appWorkspacePanelVariable("pane", panelId(handle)), "initial");
235
+ // CSS has already applied this default and the pane-fit clamp, including
236
+ // subpixels. Reserve exactly that box when fitting other preferences;
237
+ // measuring must not turn a responsive default into a stored px width.
238
+ const requested = preferred ?? resolveAppWorkspaceWidth(spec, root.getBoundingClientRect().width);
239
+ const desired = cssDefault ? elementSize(panel, "pane") : clamp(requested, min, max);
218
240
  return [{
219
241
  desired,
220
242
  handle,
221
- min
243
+ min: cssDefault ? desired : min,
244
+ cssDefault,
245
+ requested: clamp(requested, min, max),
246
+ configuredMin: min
222
247
  }];
223
248
  });
224
249
  if (!fits.length) return;
225
- const sizes = fitAppWorkspacePaneSizes(fits, main.getBoundingClientRect().width - fixedWidth - APP_WORKSPACE_MAIN_MIN);
250
+ const budget = main.getBoundingClientRect().width - fixedWidth - APP_WORKSPACE_MAIN_MIN;
251
+ // Each share default clamps only against Main, not its resizable or non-resizable siblings. When panes
252
+ // together leave Main's primary region less than its minimum, fit them like stored widths, allowing subpixels.
253
+ if (fits.reduce((total, fit) => total + fit.desired, 0) > budget + 0.5) for (const fit of fits) if (fit.cssDefault) Object.assign(fit, {
254
+ cssDefault: false,
255
+ desired: fit.requested,
256
+ min: fit.configuredMin
257
+ });
258
+ const sizes = fitAppWorkspacePaneSizes(fits, budget);
226
259
  fits.forEach((fit, index) => {
227
- root.style.setProperty(appWorkspacePanelVariable("pane", panelId(fit.handle)), `${sizes[index]}px`);
260
+ // Defaults stay CSS-owned; fit only saved or live preferences around them.
261
+ if (!fit.cssDefault) root.style.setProperty(appWorkspacePanelVariable("pane", panelId(fit.handle)), `${sizes[index]}px`);
228
262
  });
229
263
  fits.forEach((fit, index) => updateHandleValue(root, fit.handle, "pane", sizes[index]));
230
264
  };
@@ -313,14 +347,26 @@ export const installAppWorkspaceController = (options = {}) => {
313
347
  if (detailButton) {
314
348
  const root = workspaceRoot(detailButton);
315
349
  const aside = root ? controlledPanel(root, detailButton) : null;
316
- if (!aside || aside.hidden || !detailWaiting(aside) || claimedToggleClicks.has(event)) return;
350
+ if (!aside || detailButton.matches(":disabled") || claimedToggleClicks.has(event)) return;
351
+ const reopenable = aside.dataset.workspaceDetailReopen === "true";
352
+ if (!reopenable && (aside.hidden || !detailWaiting(aside))) return;
317
353
  claimedToggleClicks.add(event);
318
- if (detailDrawer(aside)) parkDetailDrawer(aside);else {
354
+ if (aside.hidden) {
355
+ aside.dispatchEvent(new CustomEvent(APP_WORKSPACE_DETAIL_OPEN_EVENT));
356
+ // The app may reject opening (for example while navigating). Never
357
+ // override hidden ourselves or reveal retained, unavailable contents.
358
+ if (aside.hidden) return;
359
+ if (wide?.matches && detailWaiting(aside)) openDetailDrawer(aside);
360
+ if (event.detail === 0 || phone?.matches) requestTitle(aside);else detailButton.focus({
361
+ preventScroll: true
362
+ });
363
+ } else if (reopenable && (!wide?.matches || !detailWaiting(aside))) requestClose(aside);else if (detailDrawer(aside)) parkDetailDrawer(aside);else {
319
364
  openDetailDrawer(aside);
320
365
  if (event.detail === 0) requestTitle(aside);else detailButton.focus({
321
366
  preventScroll: true
322
367
  });
323
368
  }
369
+ syncDetailRequestControls();
324
370
  return;
325
371
  }
326
372
  const toggle = target?.closest(TOGGLE_SELECTOR);
@@ -396,10 +442,88 @@ export const installAppWorkspaceController = (options = {}) => {
396
442
  // Whether each open detail waited at the last check, so a column that starts
397
443
  // waiting under focus hands that focus to its Details button.
398
444
  const detailWasWaiting = new WeakMap();
445
+ const detailRequestExpanded = new Map();
446
+ const restoreDetailRequestExpanded = (control, state) => {
447
+ // A newer application render already owns the current value.
448
+ if (control.getAttribute("aria-expanded") !== state.written) return;
449
+ if (state.rendered === null) control.removeAttribute("aria-expanded");else control.setAttribute("aria-expanded", state.aside.hidden || !state.aside.isConnected ? "false" : state.rendered);
450
+ };
451
+ const requestedWaitingDetail = control => {
452
+ if (!wide?.matches || control.matches(DETAIL_TOGGLE_SELECTOR) || !eventRoot.contains(control)) return null;
453
+ const root = workspaceRoot(control);
454
+ const region = control.closest(".k2b-app-workspace__main, .k2b-app-workspace__detail");
455
+ if (!root || !region || region.closest(ROOT_SELECTOR) !== root) return null;
456
+ const ids = control.getAttribute("aria-controls")?.split(/\s+/);
457
+ return rootElements(root, detailSelector).find(aside => !aside.hidden && ids?.includes(aside.id) && detailWaiting(aside)) ?? null;
458
+ };
459
+ const syncDetailRequestControls = () => {
460
+ for (const root of allRoots()) {
461
+ for (const aside of rootElements(root, detailSelector)) {
462
+ const toggle = detailToggle(aside);
463
+ if (toggle?.dataset.workspaceDetailReopen !== "true") continue;
464
+ const expanded = !aside.hidden && (!wide?.matches || !detailWaiting(aside) || detailDrawer(aside));
465
+ const value = String(expanded);
466
+ if (toggle.getAttribute("aria-expanded") !== value) toggle.setAttribute("aria-expanded", value);
467
+ }
468
+ }
469
+ const waitingControls = new Map();
470
+ for (const root of allRoots()) {
471
+ const waitingDetails = wide?.matches ? rootElements(root, detailSelector).filter(aside => !aside.hidden && detailWaiting(aside)) : [];
472
+ if (!waitingDetails.length) continue;
473
+ // Only a control that names a Detail can request it.
474
+ for (const control of rootElements(root, "[aria-controls]")) {
475
+ if (control.matches(DETAIL_TOGGLE_SELECTOR) || !eventRoot.contains(control)) continue;
476
+ const region = control.closest(".k2b-app-workspace__main, .k2b-app-workspace__detail");
477
+ if (!region || region.closest(ROOT_SELECTOR) !== root) continue;
478
+ const ids = control.getAttribute("aria-controls")?.split(/\s+/);
479
+ const aside = waitingDetails.find(aside => ids?.includes(aside.id));
480
+ if (aside) waitingControls.set(control, aside);
481
+ }
482
+ }
483
+ detailRequestExpanded.forEach((state, control) => {
484
+ if (waitingControls.has(control)) return;
485
+ restoreDetailRequestExpanded(control, state);
486
+ detailRequestExpanded.delete(control);
487
+ });
488
+ waitingControls.forEach((aside, control) => {
489
+ const current = control.getAttribute("aria-expanded");
490
+ const state = detailRequestExpanded.get(control);
491
+ const written = String(detailDrawer(aside));
492
+ // Keep the application's latest value to restore when waiting ends.
493
+ const rendered = !state || current !== state.written ? current : state.rendered;
494
+ detailRequestExpanded.set(control, {
495
+ aside,
496
+ rendered,
497
+ written
498
+ });
499
+ if (current !== written) control.setAttribute("aria-expanded", written);
500
+ });
501
+ };
502
+ const onDetailRequestClick = event => {
503
+ if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
504
+ const control = eventElement(event)?.closest(DETAIL_REQUEST_CONTROL_SELECTOR);
505
+ const aside = control ? requestedWaitingDetail(control) : null;
506
+ if (!control || !aside) return;
507
+ // The application keeps its Detail open; only its waiting drawer changes.
508
+ event.preventDefault();
509
+ event.stopImmediatePropagation();
510
+ cancelPendingDetail();
511
+ cancelTitle();
512
+ clearActivation();
513
+ // A control inside the drawer leaves with it, so focus goes to Details.
514
+ if (detailDrawer(aside)) parkDetailDrawer(aside, aside.contains(ownerDocument.activeElement));else {
515
+ openDetailDrawer(aside);
516
+ if (event.detail === 0) requestTitle(aside);else control.focus({
517
+ preventScroll: true
518
+ });
519
+ }
520
+ syncDetailRequestControls();
521
+ };
399
522
  const openDetailDrawer = aside => {
400
523
  aside.dataset.workspaceDetailDrawer = "open";
401
524
  detailToggle(aside)?.setAttribute("aria-expanded", "true");
402
525
  syncDetailDrawerSpace(aside);
526
+ syncDetailRequestControls();
403
527
  };
404
528
  // A sidebar drag changes the room beside it without resizing the workspace,
405
529
  // so it rechecks waiting once per frame.
@@ -416,7 +540,63 @@ export const installAppWorkspaceController = (options = {}) => {
416
540
  if (args[2] === "sidebar") scheduleHostSync();
417
541
  return size;
418
542
  };
543
+ // A responsive application control and the shared control can name the same
544
+ // Detail. Remember actual focus, not an old selection, before CSS hides it.
545
+ let detailControlFocus = null;
546
+ const controlledDetail = control => {
547
+ if (!control.matches(FOCUSABLE_SELECTOR) || control.matches(HANDLE_SELECTOR) || !eventRoot.contains(control)) return null;
548
+ const root = workspaceRoot(control);
549
+ if (!root) return null;
550
+ const ids = control.getAttribute("aria-controls")?.split(/\s+/);
551
+ const aside = rootElements(root, detailSelector).find(aside => ids?.includes(aside.id));
552
+ return aside ? {
553
+ control,
554
+ aside,
555
+ root
556
+ } : null;
557
+ };
558
+ const detailControlVisible = control => control.isConnected && control.getClientRects().length > 0 && getComputedStyle(control).visibility === "visible";
559
+ const detailControlUsable = control => detailControlVisible(control) && !control.matches(":disabled, [aria-disabled='true']") && !control.closest("[inert], [hidden], [aria-hidden='true']");
560
+ const onDetailControlFocusOut = () => {
561
+ // Explicit blur of a visible control is not a responsive focus loss.
562
+ if (detailControlFocus && detailControlVisible(detailControlFocus.control)) detailControlFocus = null;
563
+ };
564
+ const rescueDetailControlFocus = () => {
565
+ const previous = detailControlFocus;
566
+ if (!previous) return;
567
+ const {
568
+ control,
569
+ aside,
570
+ root
571
+ } = previous;
572
+ const active = ownerDocument.activeElement;
573
+ if (!control.isConnected || !aside.isConnected || workspaceRoot(control) !== root || workspaceRoot(aside) !== root || active !== ownerDocument.body && !control.contains(active)) {
574
+ detailControlFocus = null;
575
+ return;
576
+ }
577
+ const shared = detailToggle(aside);
578
+ if (shared?.dataset.workspaceDetailReopen === "true" && phone?.matches && phonePage(aside) && !aside.hidden && !aside.contains(control)) {
579
+ // WebKit can update matchMedia after resize has already focused the now
580
+ // visible application control. Move into the page before Main becomes inert.
581
+ detailControlFocus = null;
582
+ requestTitle(aside);
583
+ return;
584
+ }
585
+ if (detailControlVisible(control)) return;
586
+ detailControlFocus = null;
587
+ if (shared?.dataset.workspaceDetailReopen !== "true") {
588
+ // Selection-only details keep their existing returning-column title target.
589
+ if (control === shared && !aside.hidden && !detailWaiting(aside)) requestTitle(aside);
590
+ return;
591
+ }
592
+ const candidates = shared ? [shared, ...rootElements(root, "[aria-controls]")] : rootElements(root, "[aria-controls]");
593
+ const replacement = candidates.find(candidate => candidate !== control && detailControlUsable(candidate) && controlledDetail(candidate)?.aside === aside);
594
+ replacement?.focus({
595
+ preventScroll: true
596
+ });
597
+ };
419
598
  const syncHostState = () => {
599
+ rescueDetailControlFocus();
420
600
  if (pendingDetail && !canOpenPendingDetail(pendingDetail.aside)) cancelPendingDetail();
421
601
  if (overlay && sidebarForm(overlay.root) !== "overlay") closeOverlay("none");
422
602
  for (const root of allRoots()) {
@@ -432,15 +612,18 @@ export const installAppWorkspaceController = (options = {}) => {
432
612
  const before = detailWasWaiting.get(aside);
433
613
  detailWasWaiting.set(aside, waiting);
434
614
  if (!waiting) {
435
- // The Details button hides with the waiting; its focus moves to the returning column's title.
436
- const toggleFocused = detailToggle(aside)?.contains(ownerDocument.activeElement) ?? false;
615
+ // Rescue focus only when the legacy waiting-only button disappears.
616
+ // A persistent head control stays a valid focus target after resizing.
617
+ const toggle = detailToggle(aside);
618
+ const toggleFocused = toggle?.contains(ownerDocument.activeElement) ?? false;
437
619
  parkDetailDrawer(aside);
438
- if (toggleFocused) focusTitle(aside);
439
- } else if (detailDrawer(aside)) syncDetailDrawerSpace(aside);else if (before !== true && aside.contains(ownerDocument.activeElement)) detailToggle(aside)?.focus({
620
+ if (toggle && toggleFocused && !isVisible(toggle)) focusTitle(aside);
621
+ } else if (detailDrawer(aside)) syncDetailDrawerSpace(aside);else if (before !== true && aside.contains(ownerDocument.activeElement)) detailReturnControl(aside)?.focus({
440
622
  preventScroll: true
441
623
  });
442
624
  }
443
625
  }
626
+ syncDetailRequestControls();
444
627
  };
445
628
  const phone = typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 47.999rem)") : null;
446
629
  const wide = typeof window.matchMedia === "function" ? window.matchMedia("(min-width: 48rem)") : null;
@@ -554,6 +737,8 @@ export const installAppWorkspaceController = (options = {}) => {
554
737
  const root = target?.closest(ROOT_SELECTOR);
555
738
  if (!target || !root || !observedRoots.has(root)) return;
556
739
  lastFocus.set(root, target);
740
+ const control = target.closest("[aria-controls]");
741
+ detailControlFocus = control ? controlledDetail(control) : null;
557
742
  const previous = remembered.get(root);
558
743
  // Pointer focus follows pointerdown; keeping that snapshot also keeps the
559
744
  // interaction that opened the panel rather than treating focus as a new one.
@@ -600,6 +785,7 @@ export const installAppWorkspaceController = (options = {}) => {
600
785
  // that opens later than that does not take the drawer or the focus.
601
786
  let lastChosen = null;
602
787
  const cancelTitleOnInteraction = event => {
788
+ if (detailControlFocus && !(event.target instanceof Node && detailControlFocus.control.contains(event.target))) detailControlFocus = null;
603
789
  if (event.type === "pointerdown" || event.type === "keydown") clearActivation();
604
790
  if (lastChosen && !(event.target instanceof Node && lastChosen.item.contains(event.target))) {
605
791
  lastChosen.chosen = false;
@@ -861,7 +1047,13 @@ export const installAppWorkspaceController = (options = {}) => {
861
1047
  pendingAnchors.set(root, cancel);
862
1048
  } else anchorItem(root, aside, snapshot);
863
1049
  const focused = ownerDocument.activeElement;
864
- if (snapshot?.item.isConnected && (aside.contains(lastFocus.get(root) ?? null) || aside.contains(focused) || focused === ownerDocument.body)) snapshot.item.focus({
1050
+ const toggle = detailToggle(aside);
1051
+ const returnControl = detailReturnControl(aside);
1052
+ if (toggle?.dataset.workspaceDetailReopen === "true" && returnControl && (aside.contains(focused) || focused === ownerDocument.body && aside.contains(lastFocus.get(root) ?? null))) {
1053
+ returnControl.focus({
1054
+ preventScroll: true
1055
+ });
1056
+ } else if (snapshot?.item.isConnected && (aside.contains(lastFocus.get(root) ?? null) || aside.contains(focused) || focused === ownerDocument.body)) snapshot.item.focus({
865
1057
  preventScroll: true
866
1058
  });
867
1059
  clearDrag(aside);
@@ -903,6 +1095,7 @@ export const installAppWorkspaceController = (options = {}) => {
903
1095
  if (!aside || !owningRoot || !isVisible(aside)) return;
904
1096
  if (event.key === "Escape" && detailDrawer(aside)) {
905
1097
  parkDetailDrawer(aside, true);
1098
+ syncDetailRequestControls();
906
1099
  event.preventDefault();
907
1100
  return;
908
1101
  }
@@ -1137,29 +1330,86 @@ export const installAppWorkspaceController = (options = {}) => {
1137
1330
  const handle = resizeHandle(event);
1138
1331
  const root = handle?.closest(ROOT_SELECTOR);
1139
1332
  const kind = handle ? resizeKind(handle) : null;
1140
- if (!handle || !root || kind !== "detail" && kind !== "pane" || !workspaceResizable(root) || !controlsResizableRegion(root, handle, kind)) return;
1141
- const key = kind === "pane" ? "paneWidths" : "detailWidths";
1142
- const widths = {
1143
- ...layoutState[key]
1144
- };
1145
- delete widths[panelId(handle)];
1146
- layoutState = {
1147
- ...layoutState,
1148
- [key]: widths
1149
- };
1150
- const variable = appWorkspacePanelVariable(kind, panelId(handle));
1151
- root.style.removeProperty(variable);
1152
- // An SSR ancestor may still supply the old preference until the next request.
1153
- if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, `${numberData(handle, "workspaceDefaultSize", kind === "pane" ? APP_WORKSPACE_PANE_DEFAULT : APP_WORKSPACE_DETAIL_DEFAULT)}px`);
1154
- // The siblings fit again around the default width, also in a Main without levels.
1155
- if (kind === "pane") reconcilePanes(root, layoutState);
1333
+ if (!handle || !root || !kind || !workspaceResizable(root) || !controlsResizableRegion(root, handle, kind)) return;
1334
+ if (active) return;
1335
+ if (kind === "sidebar") {
1336
+ const {
1337
+ sidebarWidth: _width,
1338
+ sidebarCollapsed: _collapsed,
1339
+ ...remaining
1340
+ } = layoutState;
1341
+ layoutState = remaining;
1342
+ } else {
1343
+ const key = kind === "pane" ? "paneWidths" : kind === "drawer" ? "drawerHeights" : "detailWidths";
1344
+ const widths = {
1345
+ ...layoutState[key]
1346
+ };
1347
+ delete widths[panelId(handle)];
1348
+ layoutState = {
1349
+ ...layoutState,
1350
+ [key]: widths
1351
+ };
1352
+ }
1353
+ const regions = resetRegions.get(root) ?? new Map();
1354
+ regions.set(resizeVariable(handle, kind), {
1355
+ kind,
1356
+ id: panelId(handle),
1357
+ configuration: handle
1358
+ });
1359
+ resetRegions.set(root, regions);
1360
+ writeState();
1361
+ if (kind === "sidebar" || kind === "pane") reconcilePanes(root, layoutState);
1156
1362
  updateHandleValue(root, handle, kind, currentSize(root, handle, kind));
1157
- options.writeState?.(layoutState);
1158
1363
  };
1159
1364
  const stopScrollFades = installScrollFades(eventRoot);
1160
1365
  let layoutState = normalizeAppWorkspaceLayoutState(options.readState?.()) ?? {
1161
1366
  version: 2
1162
1367
  };
1368
+ const resetRegions = new WeakMap();
1369
+ const resizeVariable = (handle, kind) => kind === "sidebar" ? "--k2b-workspace-sidebar-width" : appWorkspacePanelVariable(kind, panelId(handle));
1370
+ const defaultSize = (handle, kind) => readAppWorkspaceWidth(handle.dataset.workspaceDefaultSize) ?? (kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_DEFAULT : kind === "pane" ? APP_WORKSPACE_PANE_DEFAULT : kind === "drawer" ? APP_WORKSPACE_DRAWER_DEFAULT : APP_WORKSPACE_DETAIL_DEFAULT);
1371
+ const restoreResetDefaults = root => {
1372
+ for (const [variable, region] of resetRegions.get(root) ?? []) {
1373
+ const {
1374
+ kind,
1375
+ id
1376
+ } = region;
1377
+ const persisted = kind === "sidebar" ? layoutState.sidebarWidth : layoutState[kind === "pane" ? "paneWidths" : kind === "drawer" ? "drawerHeights" : "detailWidths"]?.[id];
1378
+ if (persisted !== undefined || kind === "sidebar" && layoutState.sidebarCollapsed) continue;
1379
+ const handle = rootElements(root, HANDLE_SELECTOR).find(handle => resizeKind(handle) === kind && panelId(handle) === id);
1380
+ // Keep the latest handle's configuration across unmounts. Limits still
1381
+ // measure today's workspace, so a missing handle cannot freeze its fit.
1382
+ if (handle) region.configuration = handle;
1383
+ const spec = defaultSize(region.configuration, kind);
1384
+ // Store the configured default, not a temporary fit. CSS/relayout owns
1385
+ // the available space, so a numeric level can grow back when room returns.
1386
+ if (typeof spec === "number") {
1387
+ // Sidebar and drawer have no CSS fit. Keep their configured default
1388
+ // in the map, but render within today's bounds and recover it later.
1389
+ if (kind === "sidebar" || kind === "drawer") {
1390
+ const {
1391
+ min,
1392
+ max
1393
+ } = sizeLimits(root, region.configuration, kind);
1394
+ root.style.setProperty(variable, `${clamp(spec, min, max)}px`);
1395
+ } else root.style.setProperty(variable, `${spec}px`);
1396
+ } else {
1397
+ root.style.removeProperty(variable);
1398
+ if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, "initial");
1399
+ }
1400
+ if (kind === "sidebar") delete root.dataset.sidebarCollapsed;
1401
+ }
1402
+ };
1403
+ const writeState = () => {
1404
+ options.writeState?.(layoutState);
1405
+ // Keep reset defaults through reactive writes and temporary handle unmounts,
1406
+ // without saving a width that would mask a changed application default.
1407
+ for (const root of roots()) {
1408
+ if (!workspaceResizable(root)) continue;
1409
+ restoreResetDefaults(root);
1410
+ reconcilePanes(root, layoutState);
1411
+ }
1412
+ };
1163
1413
  let active = null;
1164
1414
  let resizeFrame = null;
1165
1415
  let pendingClient = null;
@@ -1181,7 +1431,9 @@ export const installAppWorkspaceController = (options = {}) => {
1181
1431
  }
1182
1432
  };
1183
1433
  }
1184
- options.writeState?.(layoutState);
1434
+ const root = workspaceRoot(handle);
1435
+ if (root) resetRegions.get(root)?.delete(resizeVariable(handle, kind));
1436
+ writeState();
1185
1437
  };
1186
1438
 
1187
1439
  // A level resize starts from what is on screen. When the stored widths do
@@ -1322,6 +1574,11 @@ export const installAppWorkspaceController = (options = {}) => {
1322
1574
  const kind = handle ? resizeKind(handle) : null;
1323
1575
  const root = handle ? workspaceRoot(handle) : null;
1324
1576
  if (!handle || !kind || !root || !workspaceResizable(root) || !controlsResizableRegion(root, handle, kind)) return;
1577
+ if (event.key === "Enter" && !event.defaultPrevented && !event.isComposing && !event.altKey && !event.ctrlKey && !event.metaKey && !active) {
1578
+ event.preventDefault();
1579
+ onPanelDoubleClick(event);
1580
+ return;
1581
+ }
1325
1582
  const current = currentSize(root, handle, kind);
1326
1583
  const {
1327
1584
  min,
@@ -1400,6 +1657,7 @@ export const installAppWorkspaceController = (options = {}) => {
1400
1657
  // resizing; without this the same workspace still had persisted sizes
1401
1658
  // forced onto it on load and on every window resize.
1402
1659
  if (!workspaceResizable(root)) return;
1660
+ restoreResetDefaults(root);
1403
1661
  const handles = rootElements(root, HANDLE_SELECTOR);
1404
1662
  // Chrome establishes the space available to the work area. Auxiliary
1405
1663
  // panes are then fitted together into what remains, so DOM order cannot
@@ -1529,6 +1787,7 @@ export const installAppWorkspaceController = (options = {}) => {
1529
1787
  };
1530
1788
  const layoutNode = node => node instanceof Element && (node.matches(LAYOUT_REGION_SELECTOR) || Boolean(node.querySelector(LAYOUT_REGION_SELECTOR)));
1531
1789
  const mutationObserver = typeof MutationObserver === "undefined" ? null : new MutationObserver(records => {
1790
+ if (records.some(record => record.type === "childList" || ["hidden", "aria-controls", "aria-expanded", "data-workspace-detail-drawer"].includes(record.attributeName ?? ""))) syncDetailRequestControls();
1532
1791
  syncPhonePages();
1533
1792
  if (pendingTitle && (pendingTitle.aside.hidden || !pendingTitle.aside.isConnected)) cancelTitle();
1534
1793
  if (pendingDetail && (!canOpenPendingDetail(pendingDetail.aside) || !pendingDetail.snapshot.chosen || !pendingDetail.snapshot.item.isConnected)) cancelPendingDetail();
@@ -1553,8 +1812,10 @@ export const installAppWorkspaceController = (options = {}) => {
1553
1812
  ownerDocument.addEventListener("keydown", onOverlayKeyDown);
1554
1813
  ownerWindow.addEventListener("keydown", onDetailKeyDown);
1555
1814
  ownerDocument.addEventListener("focusin", cancelTitleOnInteraction, true);
1815
+ ownerDocument.addEventListener("focusout", onDetailControlFocusOut, true);
1556
1816
  ownerDocument.addEventListener("pointerdown", cancelTitleOnInteraction, true);
1557
1817
  ownerDocument.addEventListener("keydown", cancelTitleOnInteraction, true);
1818
+ ownerDocument.addEventListener("click", onDetailRequestClick, true);
1558
1819
  ownerDocument.addEventListener("click", onChoiceClick, true);
1559
1820
  ownerDocument.addEventListener("keyup", endActivation, true);
1560
1821
  ownerDocument.addEventListener("pointerup", endActivation, true);
@@ -1581,10 +1842,11 @@ export const installAppWorkspaceController = (options = {}) => {
1581
1842
  window.addEventListener("resize", onResize);
1582
1843
  syncPhonePages();
1583
1844
  syncObservedRoots();
1845
+ syncDetailRequestControls();
1584
1846
  mutationObserver?.observe(eventRoot, {
1585
1847
  attributes: true,
1586
1848
  attributeOldValue: true,
1587
- attributeFilter: ["hidden", "data-workspace-level-shown", "data-width", "data-workspace-main-min"],
1849
+ attributeFilter: ["hidden", "aria-controls", "aria-expanded", "data-workspace-detail-drawer", "data-workspace-level-shown", "data-width", "data-workspace-main-min", "data-workspace-default-size"],
1588
1850
  childList: true,
1589
1851
  subtree: true
1590
1852
  });
@@ -1611,6 +1873,8 @@ export const installAppWorkspaceController = (options = {}) => {
1611
1873
  titleTabIndices.forEach(title => title.removeAttribute("tabindex"));
1612
1874
  ownerWindow.removeEventListener("keydown", onDetailKeyDown);
1613
1875
  ownerDocument.removeEventListener("focusin", cancelTitleOnInteraction, true);
1876
+ ownerDocument.removeEventListener("focusout", onDetailControlFocusOut, true);
1877
+ detailControlFocus = null;
1614
1878
  ownerDocument.removeEventListener("pointerdown", cancelTitleOnInteraction, true);
1615
1879
  ownerDocument.removeEventListener("keydown", cancelTitleOnInteraction, true);
1616
1880
  clearActivation();
@@ -1638,6 +1902,9 @@ export const installAppWorkspaceController = (options = {}) => {
1638
1902
  if (reconcileFrame !== null) cancelAnimationFrame(reconcileFrame);
1639
1903
  if (hostSyncFrame !== null) cancelAnimationFrame(hostSyncFrame);
1640
1904
  mutationObserver?.disconnect();
1905
+ detailRequestExpanded.forEach((state, control) => restoreDetailRequestExpanded(control, state));
1906
+ detailRequestExpanded.clear();
1907
+ ownerDocument.removeEventListener("click", onDetailRequestClick, true);
1641
1908
  resizeObserver?.disconnect();
1642
1909
  observedRoots.clear();
1643
1910
  observedHosts.clear();