@k2b/ui 0.26.0 → 0.27.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 (65) hide show
  1. package/dist/browser/chat/ChatComposer.js +811 -406
  2. package/dist/browser/chat/ChatComposer.js.map +1 -1
  3. package/dist/browser/chat/chat-behavior.js +4 -0
  4. package/dist/browser/chat/chat-behavior.js.map +1 -1
  5. package/dist/browser/chat/chat-messages.js +14 -0
  6. package/dist/browser/chat/chat-messages.js.map +1 -1
  7. package/dist/browser/chat/command-search.js +170 -0
  8. package/dist/browser/chat/command-search.js.map +1 -0
  9. package/dist/browser/chat/composer-document.js +1 -1
  10. package/dist/browser/chat/composer-document.js.map +1 -1
  11. package/dist/browser/chat/index.js.map +1 -1
  12. package/dist/browser/content/CalendarDayView.js +28 -3
  13. package/dist/browser/content/CalendarDayView.js.map +1 -1
  14. package/dist/browser/inputs/Combobox.js +4 -2
  15. package/dist/browser/inputs/Combobox.js.map +1 -1
  16. package/dist/browser/inputs/SelectChip.js +43 -12
  17. package/dist/browser/inputs/SelectChip.js.map +1 -1
  18. package/dist/browser/layout/AppWorkspace.js +708 -515
  19. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  20. package/dist/browser/layout/BottomSheet.js +8 -0
  21. package/dist/browser/layout/BottomSheet.js.map +1 -1
  22. package/dist/browser/layout/app-workspace-controller.js +155 -23
  23. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  24. package/dist/browser/layout/app-workspace-state.js +5 -1
  25. package/dist/browser/layout/app-workspace-state.js.map +1 -1
  26. package/dist/browser/layout/index.js +1 -1
  27. package/dist/browser/layout/index.js.map +1 -1
  28. package/dist/ssr/chat/ChatComposer.js +410 -93
  29. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  30. package/dist/ssr/chat/chat-behavior.js +4 -0
  31. package/dist/ssr/chat/chat-behavior.js.map +1 -1
  32. package/dist/ssr/chat/chat-messages.js +14 -0
  33. package/dist/ssr/chat/chat-messages.js.map +1 -1
  34. package/dist/ssr/chat/command-search.js +170 -0
  35. package/dist/ssr/chat/command-search.js.map +1 -0
  36. package/dist/ssr/chat/composer-document.js +1 -1
  37. package/dist/ssr/chat/composer-document.js.map +1 -1
  38. package/dist/ssr/chat/index.js.map +1 -1
  39. package/dist/ssr/content/CalendarDayView.js +28 -3
  40. package/dist/ssr/content/CalendarDayView.js.map +1 -1
  41. package/dist/ssr/inputs/Combobox.js +4 -2
  42. package/dist/ssr/inputs/Combobox.js.map +1 -1
  43. package/dist/ssr/inputs/SelectChip.js +35 -11
  44. package/dist/ssr/inputs/SelectChip.js.map +1 -1
  45. package/dist/ssr/layout/AppWorkspace.js +242 -105
  46. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  47. package/dist/ssr/layout/BottomSheet.js +8 -0
  48. package/dist/ssr/layout/BottomSheet.js.map +1 -1
  49. package/dist/ssr/layout/app-workspace-controller.js +155 -23
  50. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  51. package/dist/ssr/layout/app-workspace-state.js +5 -1
  52. package/dist/ssr/layout/app-workspace-state.js.map +1 -1
  53. package/dist/ssr/layout/index.js +1 -1
  54. package/dist/ssr/layout/index.js.map +1 -1
  55. package/dist/styles.css +1 -1
  56. package/dist/types/chat/ChatComposer.d.ts +44 -2
  57. package/dist/types/chat/chat-behavior.d.ts +4 -0
  58. package/dist/types/chat/chat-messages.d.ts +7 -0
  59. package/dist/types/chat/command-search.d.ts +39 -0
  60. package/dist/types/chat/index.d.ts +1 -1
  61. package/dist/types/inputs/SelectChip.d.ts +4 -0
  62. package/dist/types/layout/AppWorkspace.d.ts +13 -1
  63. package/dist/types/layout/BottomSheet.d.ts +8 -0
  64. package/dist/types/layout/index.d.ts +1 -1
  65. package/package.json +4 -4
@@ -28,6 +28,14 @@ export const bottomSheetOptions = {
28
28
  contentClassName: "k2b-panel-dialog-viewport k2b-bottom-sheet-viewport",
29
29
  initialFocus: sheetInitialFocus
30
30
  };
31
+
32
+ /** A sheet on phones and a centered dialog from 48rem up, for decisions and forms; menus keep
33
+ * `bottomSheetOptions`. The stylesheet picks the mode with the phone media query, so the first
34
+ * frame is already right and a rotation or resize switches it in place, without a remount. */
35
+ export const bottomSheetAdaptiveOptions = {
36
+ ...bottomSheetOptions,
37
+ panelClassName: `${bottomSheetOptions.panelClassName} is-adaptive`
38
+ };
31
39
  function BottomSheetRoot(props) {
32
40
  const messages = useUiMessages();
33
41
  let pointer;
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["layout/BottomSheet.tsx"],"sourcesContent":["import { type JSX, Show } from \"solid-js\";\nimport type { OpenDialogOptions } from \"../feedback/dialog-core\";\nimport { suppressTextSelection } from \"../internal/text-selection\";\nimport { useUiMessages } from \"../intl/messages\";\nimport PanelDialog from \"./PanelDialog\";\n\n/** Initial focus goes to the first input of the top sheet, otherwise to the\n * dialog element itself. Native `showModal()` focuses the first focusable\n * descendant, which is the drag handle; iOS shows a ring for that programmatic\n * focus, so the sheet moves it to the ring-less dialog before the next paint. */\nconst sheetInitialFocus = (dialog: HTMLDialogElement): HTMLElement => {\n const input = dialog.lastElementChild?.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), [role='combobox']:not([disabled]):not([aria-disabled='true'])\",\n );\n if (input) return input;\n dialog.tabIndex = -1;\n return dialog;\n};\n\nexport const bottomSheetOptions = {\n panelClassName: \"k2b-dialog k2b-bottom-sheet-frame\",\n contentClassName: \"k2b-panel-dialog-viewport k2b-bottom-sheet-viewport\",\n initialFocus: sheetInitialFocus,\n} satisfies OpenDialogOptions;\n\nexport type BottomSheetProps = {\n children: JSX.Element;\n /** Pass dialogCore's render context requestDismiss, never the completion callback. */\n onDismiss: () => void | Promise<void>;\n dismissDisabled?: boolean;\n dismissLabel?: string;\n handle?: boolean;\n};\n\nfunction BottomSheetRoot(props: BottomSheetProps) {\n const messages = useUiMessages();\n let pointer: { id: number; y: number; releaseTextSelection: () => void } | undefined;\n let suppressClick = false;\n const dismiss = () => {\n if (!props.dismissDisabled) void props.onDismiss();\n };\n return (\n <PanelDialog>\n <Show when={props.handle !== false}>\n <button\n type=\"button\"\n class=\"k2b-bottom-sheet__handle\"\n aria-label={props.dismissLabel ?? messages().close}\n disabled={props.dismissDisabled}\n onPointerDown={(event) => {\n if (!event.isPrimary || event.button !== 0 || props.dismissDisabled) return;\n suppressClick = false;\n pointer = { id: event.pointerId, y: event.clientY, releaseTextSelection: suppressTextSelection(event.pointerId) };\n event.currentTarget.setPointerCapture(event.pointerId);\n }}\n onPointerUp={(event) => {\n if (pointer?.id !== event.pointerId) return;\n const distance = event.clientY - pointer.y;\n pointer.releaseTextSelection();\n pointer = undefined;\n // A drag must not turn into a second dismiss request through click.\n suppressClick = Math.abs(distance) > 8;\n if (distance > 60) dismiss();\n }}\n onPointerCancel={() => {\n pointer?.releaseTextSelection();\n pointer = undefined;\n suppressClick = true;\n }}\n onLostPointerCapture={() => {\n if (pointer) {\n pointer.releaseTextSelection();\n pointer = undefined;\n suppressClick = true;\n }\n }}\n onClick={(event) => {\n if (event.detail !== 0 && suppressClick) {\n suppressClick = false;\n return;\n }\n dismiss();\n }}\n >\n <span aria-hidden=\"true\" />\n </button>\n </Show>\n {props.children}\n </PanelDialog>\n );\n}\n\n/** Bottom-edge presentation; dialogCore continues to own all modal state. */\nconst BottomSheet = Object.assign(BottomSheetRoot, {\n Header: PanelDialog.Header,\n Body: PanelDialog.Body,\n Footer: PanelDialog.Footer,\n Section: PanelDialog.Section,\n Tabs: PanelDialog.Tabs,\n});\nexport default BottomSheet;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["layout/BottomSheet.tsx"],"sourcesContent":["import { type JSX, Show } from \"solid-js\";\nimport type { OpenDialogOptions } from \"../feedback/dialog-core\";\nimport { suppressTextSelection } from \"../internal/text-selection\";\nimport { useUiMessages } from \"../intl/messages\";\nimport PanelDialog from \"./PanelDialog\";\n\n/** Initial focus goes to the first input of the top sheet, otherwise to the\n * dialog element itself. Native `showModal()` focuses the first focusable\n * descendant, which is the drag handle; iOS shows a ring for that programmatic\n * focus, so the sheet moves it to the ring-less dialog before the next paint. */\nconst sheetInitialFocus = (dialog: HTMLDialogElement): HTMLElement => {\n const input = dialog.lastElementChild?.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), [role='combobox']:not([disabled]):not([aria-disabled='true'])\",\n );\n if (input) return input;\n dialog.tabIndex = -1;\n return dialog;\n};\n\nexport const bottomSheetOptions = {\n panelClassName: \"k2b-dialog k2b-bottom-sheet-frame\",\n contentClassName: \"k2b-panel-dialog-viewport k2b-bottom-sheet-viewport\",\n initialFocus: sheetInitialFocus,\n} satisfies OpenDialogOptions;\n\n/** A sheet on phones and a centered dialog from 48rem up, for decisions and forms; menus keep\n * `bottomSheetOptions`. The stylesheet picks the mode with the phone media query, so the first\n * frame is already right and a rotation or resize switches it in place, without a remount. */\nexport const bottomSheetAdaptiveOptions = {\n ...bottomSheetOptions,\n panelClassName: `${bottomSheetOptions.panelClassName} is-adaptive`,\n} satisfies OpenDialogOptions;\n\nexport type BottomSheetProps = {\n children: JSX.Element;\n /** Pass dialogCore's render context requestDismiss, never the completion callback. */\n onDismiss: () => void | Promise<void>;\n dismissDisabled?: boolean;\n dismissLabel?: string;\n handle?: boolean;\n};\n\nfunction BottomSheetRoot(props: BottomSheetProps) {\n const messages = useUiMessages();\n let pointer: { id: number; y: number; releaseTextSelection: () => void } | undefined;\n let suppressClick = false;\n const dismiss = () => {\n if (!props.dismissDisabled) void props.onDismiss();\n };\n return (\n <PanelDialog>\n <Show when={props.handle !== false}>\n <button\n type=\"button\"\n class=\"k2b-bottom-sheet__handle\"\n aria-label={props.dismissLabel ?? messages().close}\n disabled={props.dismissDisabled}\n onPointerDown={(event) => {\n if (!event.isPrimary || event.button !== 0 || props.dismissDisabled) return;\n suppressClick = false;\n pointer = { id: event.pointerId, y: event.clientY, releaseTextSelection: suppressTextSelection(event.pointerId) };\n event.currentTarget.setPointerCapture(event.pointerId);\n }}\n onPointerUp={(event) => {\n if (pointer?.id !== event.pointerId) return;\n const distance = event.clientY - pointer.y;\n pointer.releaseTextSelection();\n pointer = undefined;\n // A drag must not turn into a second dismiss request through click.\n suppressClick = Math.abs(distance) > 8;\n if (distance > 60) dismiss();\n }}\n onPointerCancel={() => {\n pointer?.releaseTextSelection();\n pointer = undefined;\n suppressClick = true;\n }}\n onLostPointerCapture={() => {\n if (pointer) {\n pointer.releaseTextSelection();\n pointer = undefined;\n suppressClick = true;\n }\n }}\n onClick={(event) => {\n if (event.detail !== 0 && suppressClick) {\n suppressClick = false;\n return;\n }\n dismiss();\n }}\n >\n <span aria-hidden=\"true\" />\n </button>\n </Show>\n {props.children}\n </PanelDialog>\n );\n}\n\n/** Bottom-edge presentation; dialogCore continues to own all modal state. */\nconst BottomSheet = Object.assign(BottomSheetRoot, {\n Header: PanelDialog.Header,\n Body: PanelDialog.Body,\n Footer: PanelDialog.Footer,\n Section: PanelDialog.Section,\n Tabs: PanelDialog.Tabs,\n});\nexport default BottomSheet;\n"],"mappings":"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","ignoreList":[]}
@@ -1,6 +1,6 @@
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_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_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";
4
4
  import { installScrollFades } from "./scroll-fade";
5
5
  const HANDLE_SELECTOR = "[data-app-workspace-resize]";
6
6
  const ROOT_SELECTOR = "[data-k2b-app-workspace]";
@@ -57,7 +57,16 @@ const controlledPanel = (root, handle) => {
57
57
  const panel = document.getElementById(controls);
58
58
  return panel?.closest(ROOT_SELECTOR) === root ? panel : null;
59
59
  };
60
- const controlsResizableRegion = (root, handle, kind) => (kind === "sidebar" ? sidebarElement(root) : controlledPanel(root, handle))?.dataset.workspaceResizable === "true";
60
+ const flexibleLevel = pane => {
61
+ 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;
64
+ };
65
+ const levelsMain = handle => handle.closest(".k2b-app-workspace__main")?.hasAttribute("data-workspace-levels") ?? false;
66
+ const controlsResizableRegion = (root, handle, kind) => {
67
+ const panel = kind === "sidebar" ? sidebarElement(root) : controlledPanel(root, handle);
68
+ return panel?.dataset.workspaceResizable === "true" && (!panel.hasAttribute("data-workspace-level") || isVisible(panel) && isVisible(handle) && !flexibleLevel(panel));
69
+ };
61
70
  const panelId = handle => safeAppWorkspacePanelId(handle.dataset.workspacePanelId ?? "primary") || "primary";
62
71
  const numberData = (handle, key, fallback) => {
63
72
  const value = Number(handle.dataset[key]);
@@ -66,7 +75,11 @@ const numberData = (handle, key, fallback) => {
66
75
  const sizeLimits = (root, handle, kind) => {
67
76
  const controlled = controlledPanel(root, handle);
68
77
  const main = kind === "pane" ? handle.closest(".k2b-app-workspace__main") : mainElement(root);
69
- const panes = main ? Array.from(main.querySelectorAll(".k2b-app-workspace__main-pane")).filter(pane => pane.closest(".k2b-app-workspace__main") === main && !pane.classList.contains("is-primary") && isVisible(pane)) : [];
78
+ const panes = main ? Array.from(main.querySelectorAll(".k2b-app-workspace__main-pane")).filter(pane => pane.closest(".k2b-app-workspace__main") === main && !pane.classList.contains("is-primary") && !flexibleLevel(pane) && isVisible(pane)) : [];
79
+ const separatorSize = kind === "pane" && main ? Array.from(main.querySelectorAll(`.k2b-app-workspace__resize${HANDLE_SELECTOR}`)).filter(separator => separator.closest(".k2b-app-workspace__main") === main && resizeKind(separator) === "pane" && isVisible(separator)).reduce((total, separator) => {
80
+ const style = getComputedStyle(separator);
81
+ return total + Math.max(0, separator.getBoundingClientRect().width + (Number.parseFloat(style.marginLeft) || 0) + (Number.parseFloat(style.marginRight) || 0));
82
+ }, 0) : 0;
70
83
  // Content ends before the detail track, so its width alone would shrink a
71
84
  // persisted panel again on every reconciliation. Measure from Content's
72
85
  // start to the workspace's inner end: this includes the detail column and
@@ -77,7 +90,7 @@ const sizeLimits = (root, handle, kind) => {
77
90
  return appWorkspaceResizeLimits({
78
91
  kind,
79
92
  workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width : content ? root.getBoundingClientRect().right - root.clientLeft - content.getBoundingClientRect().left : root.getBoundingClientRect().width,
80
- reservedSize: kind === "detail" ? 8 : kind === "pane" ? panes.reduce((total, pane) => total + (pane === controlled ? 0 : elementSize(pane, "pane")), 0) : 0,
93
+ reservedSize: kind === "detail" ? 8 : kind === "pane" ? panes.reduce((total, pane) => total + (pane === controlled ? 0 : elementSize(pane, "pane")), 0) + separatorSize : 0,
81
94
  min: numberData(handle, "workspaceMinSize", defaultMin),
82
95
  max: numberData(handle, "workspaceMaxSize", defaultMax),
83
96
  sidebarCollapsible: kind === "sidebar" && sidebarCollapsible(root)
@@ -140,6 +153,18 @@ const reconcilePanes = (root, layoutState, active = null) => {
140
153
  if (!main || !isVisible(main)) return;
141
154
  const handles = rootElements(root, HANDLE_SELECTOR).filter(handle => resizeKind(handle) === "pane" && controlsResizableRegion(root, handle, "pane"));
142
155
  if (!handles.length) return;
156
+ if (main.hasAttribute("data-workspace-levels")) {
157
+ // CSS owns the multi-level fit, including a flexible shown-last pane.
158
+ // Keep preferences intact when the window temporarily has less room.
159
+ for (const handle of handles) {
160
+ const min = numberData(handle, "workspaceMinSize", APP_WORKSPACE_PANE_MIN);
161
+ const max = Math.max(min, numberData(handle, "workspaceMaxSize", APP_WORKSPACE_PANE_MAX));
162
+ const preferred = active?.moved && active.handle === handle ? active.liveSize : layoutState.paneWidths?.[panelId(handle)];
163
+ if (preferred !== undefined) root.style.setProperty(appWorkspacePanelVariable("pane", panelId(handle)), `${clamp(preferred, min, max)}px`);
164
+ updateHandleValue(root, handle, "pane", currentSize(root, handle, "pane"));
165
+ }
166
+ return;
167
+ }
143
168
  const controlled = new Set(handles.map(handle => controlledPanel(root, handle)).filter(Boolean));
144
169
  const fixedWidth = Array.from(main.querySelectorAll(".k2b-app-workspace__main-pane")).filter(pane => pane.closest(".k2b-app-workspace__main") === main && !pane.classList.contains("is-primary") && isVisible(pane) && !controlled.has(pane)).reduce((total, pane) => total + elementSize(pane, "pane"), 0);
145
170
  const fits = handles.flatMap(handle => {
@@ -268,27 +293,43 @@ export const installAppWorkspaceController = (options = {}) => {
268
293
  if (control.matches("a[href]") || control.closest(ITEM_SELECTOR)) closeOverlay("kept");
269
294
  };
270
295
  const onOverlayKeyDown = event => {
271
- if (!overlay || event.defaultPrevented) return;
296
+ if (event.defaultPrevented) return;
272
297
  const target = eventElement(event);
273
298
  if (!target) return;
274
299
  // Escape also closes it when a click left focus on nothing, so on the body.
275
- if (event.key === "Escape" && (overlay.sidebar.contains(target) || target === ownerDocument.body)) {
300
+ if (overlay && event.key === "Escape" && (overlay.sidebar.contains(target) || target === ownerDocument.body)) {
276
301
  event.preventDefault();
277
302
  closeOverlay("button");
278
303
  return;
279
304
  }
280
- if (event.key !== "Tab" || !overlay.sidebar.contains(target)) return;
281
- const stops = tabStopsIn(overlay.sidebar);
305
+ const aside = target.closest(detailSelector);
306
+ const panel = overlay?.sidebar ?? (phone?.matches && aside && !aside.hidden && phonePage(aside) && eventRoot.contains(aside) ? aside : null);
307
+ if (event.key !== "Tab") return;
308
+ if (!panel) {
309
+ // Focus outside an open phone page, on the body after a load or on chrome the page covers, enters the page.
310
+ // Dialogs and popovers keep their own Tab order.
311
+ const page = phone?.matches ? allRoots().flatMap(openDetails).find(phonePage) : undefined;
312
+ if (!page || ownerDocument.querySelector("dialog[open], [aria-modal='true']") || target.closest("[popover]")) return;
313
+ const stops = tabStopsIn(page);
314
+ event.preventDefault();
315
+ (event.shiftKey ? stops.at(-1) : stops[0])?.focus();
316
+ return;
317
+ }
318
+ if (!panel.contains(target)) return;
319
+ const stops = tabStopsIn(panel);
282
320
  const first = stops[0];
283
321
  const last = stops.at(-1);
284
322
  if (!first || !last) {
285
323
  event.preventDefault();
286
324
  return;
287
325
  }
288
- if (event.shiftKey && (document.activeElement === first || document.activeElement === overlay.sidebar)) {
326
+ // Focus can also rest on an element that is no tab stop, such as a title focused as the panel opened.
327
+ const active = document.activeElement;
328
+ const before = (element, other) => Boolean(element && other && element.compareDocumentPosition(other) & Node.DOCUMENT_POSITION_FOLLOWING);
329
+ if (event.shiftKey && !stops.some(stop => before(stop, active))) {
289
330
  event.preventDefault();
290
331
  last.focus();
291
- } else if (!event.shiftKey && document.activeElement === last) {
332
+ } else if (!event.shiftKey && !stops.some(stop => before(active, stop))) {
292
333
  event.preventDefault();
293
334
  first.focus();
294
335
  }
@@ -445,7 +486,9 @@ export const installAppWorkspaceController = (options = {}) => {
445
486
  subtree: true
446
487
  });
447
488
  };
489
+ const phonePage = aside => aside.dataset.workspaceDetailPhone === "page";
448
490
  const setDetent = (aside, full) => {
491
+ if (phonePage(aside)) return;
449
492
  touchedDetails.add(aside);
450
493
  if (full) aside.dataset.workspaceDetailSheet = "full";else delete aside.dataset.workspaceDetailSheet;
451
494
  aside.querySelector(grabberSelector)?.setAttribute("aria-pressed", String(full));
@@ -489,6 +532,7 @@ export const installAppWorkspaceController = (options = {}) => {
489
532
  root.dataset.workspaceDetailSliding = "";
490
533
  };
491
534
  const revealItem = (root, aside, snapshot) => {
535
+ if (phone?.matches && phonePage(aside)) return;
492
536
  const pending = revealFrames.get(root);
493
537
  if (pending !== undefined) cancelAnimationFrame(pending);
494
538
  revealFrames.set(root, requestAnimationFrame(() => {
@@ -734,6 +778,7 @@ export const installAppWorkspaceController = (options = {}) => {
734
778
  const aside = target?.closest(detailSelector);
735
779
  const root = aside?.closest(ROOT_SELECTOR);
736
780
  if (!target || !aside || !root || aside.hidden || !observedRoots.has(root) || target.closest(ROOT_SELECTOR) !== root) return;
781
+ if (phonePage(aside)) return;
737
782
  const grabber = target.closest(grabberSelector);
738
783
  if (!grabber && (!target.closest(".k2b-detail-panel__header") || target.closest("a, button, input, select, textarea, [role='button'], [contenteditable]:not([contenteditable='false'])"))) return;
739
784
  claimedDetailGestures.add(event);
@@ -759,7 +804,7 @@ export const installAppWorkspaceController = (options = {}) => {
759
804
  const target = eventElement(event);
760
805
  const grabber = target?.closest(grabberSelector);
761
806
  const aside = grabber?.closest(detailSelector);
762
- if (!aside || aside.hidden || !phone?.matches || claimedToggleClicks.has(event)) return;
807
+ if (!aside || aside.hidden || phonePage(aside) || !phone?.matches || claimedToggleClicks.has(event)) return;
763
808
  claimedToggleClicks.add(event);
764
809
  if (suppressedClick === aside && event instanceof MouseEvent && event.detail !== 0) {
765
810
  event.preventDefault();
@@ -789,6 +834,7 @@ export const installAppWorkspaceController = (options = {}) => {
789
834
  const touch = event.touches[0];
790
835
  const root = aside?.closest(ROOT_SELECTOR);
791
836
  if (!target || !aside || !root || aside.hidden || !touch || !observedRoots.has(root) || target.closest(ROOT_SELECTOR) !== root) return;
837
+ if (phone?.matches && phonePage(aside)) return;
792
838
  if (phone?.matches && (verticalScroller(target, aside) ?? aside).scrollTop <= 0) {
793
839
  if (!ownsTouchGesture(target, aside, "sheet")) beginDetailDrag(aside, target, "sheet", touch.clientX, touch.clientY, touch.identifier);
794
840
  } else if (touchDrawer?.matches && closable(aside)) {
@@ -840,22 +886,27 @@ export const installAppWorkspaceController = (options = {}) => {
840
886
  const onDetailTouchEnd = event => {
841
887
  if (detailDrag && detailDrag.source !== "pointer") finishDetailDrag(event.type === "touchcancel");
842
888
  };
843
- const onDetailDoubleClick = event => {
889
+ const onPanelDoubleClick = event => {
844
890
  const handle = resizeHandle(event);
845
891
  const root = handle?.closest(ROOT_SELECTOR);
846
- if (!handle || !root || resizeKind(handle) !== "detail" || !workspaceResizable(root) || !controlsResizableRegion(root, handle, "detail")) return;
892
+ const kind = handle ? resizeKind(handle) : null;
893
+ if (!handle || !root || kind !== "detail" && kind !== "pane" || !workspaceResizable(root) || !controlsResizableRegion(root, handle, kind)) return;
894
+ const key = kind === "pane" ? "paneWidths" : "detailWidths";
847
895
  const widths = {
848
- ...layoutState.detailWidths
896
+ ...layoutState[key]
849
897
  };
850
898
  delete widths[panelId(handle)];
851
899
  layoutState = {
852
900
  ...layoutState,
853
- detailWidths: widths
901
+ [key]: widths
854
902
  };
855
- const variable = appWorkspacePanelVariable("detail", panelId(handle));
903
+ const variable = appWorkspacePanelVariable(kind, panelId(handle));
856
904
  root.style.removeProperty(variable);
857
- if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, `${numberData(handle, "workspaceDefaultSize", APP_WORKSPACE_DETAIL_DEFAULT)}px`);
858
- updateHandleValue(root, handle, "detail", currentSize(root, handle, "detail"));
905
+ // An SSR ancestor may still supply the old preference until the next request.
906
+ if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, `${numberData(handle, "workspaceDefaultSize", kind === "pane" ? APP_WORKSPACE_PANE_DEFAULT : APP_WORKSPACE_DETAIL_DEFAULT)}px`);
907
+ // The siblings fit again around the default width, also in a Main without levels.
908
+ if (kind === "pane") reconcilePanes(root, layoutState);
909
+ updateHandleValue(root, handle, kind, currentSize(root, handle, kind));
859
910
  options.writeState?.(layoutState);
860
911
  };
861
912
  const stopScrollFades = installScrollFades(eventRoot);
@@ -885,6 +936,40 @@ export const installAppWorkspaceController = (options = {}) => {
885
936
  }
886
937
  options.writeState?.(layoutState);
887
938
  };
939
+
940
+ // A level resize starts from what is on screen. When the stored widths do
941
+ // not fit beside the flexible level, CSS shrinks the resizable levels (a
942
+ // level without a separator does not shrink), so a step from a shrunk width
943
+ // would land on a preference that shrinks again and jump. The visible
944
+ // resizable levels therefore take their rendered widths as preferences when
945
+ // a step really moves; then they fit, and the step moves exactly by its
946
+ // size. All widths are measured before any is written, because each write
947
+ // reflows the others.
948
+ const settleLevels = (root, handle) => {
949
+ const main = handle.closest(".k2b-app-workspace__main");
950
+ if (!main?.hasAttribute("data-workspace-levels")) return;
951
+ const widths = Array.from(main.querySelectorAll(HANDLE_SELECTOR)).filter(other => other.closest(".k2b-app-workspace__main") === main && resizeKind(other) === "pane").flatMap(other => {
952
+ const panel = controlledPanel(root, other);
953
+ return isVisible(panel) && !flexibleLevel(panel) && panel.dataset.workspaceResizable === "true" ? [{
954
+ id: panelId(other),
955
+ width: Math.round(elementSize(panel, "pane"))
956
+ }] : [];
957
+ });
958
+ for (const {
959
+ id,
960
+ width
961
+ } of widths) {
962
+ if (layoutState.paneWidths?.[id] === width) continue;
963
+ root.style.setProperty(appWorkspacePanelVariable("pane", id), `${width}px`);
964
+ layoutState = {
965
+ ...layoutState,
966
+ paneWidths: {
967
+ ...layoutState.paneWidths,
968
+ [id]: width
969
+ }
970
+ };
971
+ }
972
+ };
888
973
  const flushPointerMove = () => {
889
974
  if (resizeFrame !== null) {
890
975
  cancelAnimationFrame(resizeFrame);
@@ -893,8 +978,18 @@ export const installAppWorkspaceController = (options = {}) => {
893
978
  if (!active || pendingClient === null) return;
894
979
  const client = pendingClient;
895
980
  pendingClient = null;
896
- active.moved ||= client !== active.startClient;
897
981
  const requested = active.startSize + (client - active.startClient) * active.direction;
982
+ // A press without movement, or a drag that the limits stop, changes no
983
+ // level preference, also not a sibling's.
984
+ if (!active.moved && client !== active.startClient && active.kind === "pane" && levelsMain(active.handle)) {
985
+ const {
986
+ min,
987
+ max
988
+ } = sizeLimits(active.root, active.handle, "pane");
989
+ if (clamp(requested, min, max) === Math.round(currentSize(active.root, active.handle, "pane"))) return;
990
+ settleLevels(active.root, active.handle);
991
+ }
992
+ active.moved ||= client !== active.startClient;
898
993
  // Below half the smallest visible width a hideable sidebar stays at its
899
994
  // minimum and previews hiding; dragging back out resumes the live width.
900
995
  const hiding = active.kind === "sidebar" && sidebarHideable(active.root) && requested < sizeLimits(active.root, active.handle, active.kind).min / 2;
@@ -1000,6 +1095,11 @@ export const installAppWorkspaceController = (options = {}) => {
1000
1095
  else if (kind !== "drawer" && event.key === "ArrowLeft") requested = collapsibleSidebar && current <= APP_WORKSPACE_SIDEBAR_MIN ? APP_WORKSPACE_SIDEBAR_COLLAPSED : current - step * direction;else if (kind !== "drawer" && event.key === "ArrowRight") requested = collapsibleSidebar && current <= APP_WORKSPACE_SIDEBAR_COLLAPSED ? layoutState.sidebarWidth ?? APP_WORKSPACE_SIDEBAR_MIN : current + step * direction;
1001
1096
  if (requested === null) return;
1002
1097
  event.preventDefault();
1098
+ if (kind === "pane" && levelsMain(handle)) {
1099
+ // A step that the limits stop changes no preference, also not a sibling's.
1100
+ if (clamp(requested, min, max) === Math.round(current)) return;
1101
+ settleLevels(root, handle);
1102
+ }
1003
1103
  persistSize(handle, kind, applySize(root, handle, kind, requested));
1004
1104
  if (kind === "sidebar" || kind === "detail") reconcilePanes(root, layoutState);
1005
1105
  };
@@ -1007,6 +1107,32 @@ export const installAppWorkspaceController = (options = {}) => {
1007
1107
  const nested = Array.from(eventRoot.querySelectorAll(ROOT_SELECTOR));
1008
1108
  return eventRoot instanceof HTMLElement && eventRoot.matches(ROOT_SELECTOR) ? [eventRoot, ...nested] : nested;
1009
1109
  };
1110
+ // Remember only attributes we own; application-set inert survives every transition.
1111
+ const phonePageInerted = new Set();
1112
+ const syncPhonePages = () => {
1113
+ const regions = new Set();
1114
+ if (phone?.matches) {
1115
+ for (const root of allRoots()) {
1116
+ if (!root.isConnected) continue;
1117
+ for (const aside of openDetails(root).filter(phonePage)) {
1118
+ for (let node = aside; node && node !== root; node = node.parentElement) {
1119
+ const parent = node.parentElement;
1120
+ if (parent) overlayRegions(parent, aside).forEach(region => regions.add(region));
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ phonePageInerted.forEach(region => {
1126
+ if (regions.has(region)) return;
1127
+ region.removeAttribute("inert");
1128
+ phonePageInerted.delete(region);
1129
+ });
1130
+ regions.forEach(region => {
1131
+ if (region.hasAttribute("inert")) return;
1132
+ region.setAttribute("inert", "");
1133
+ phonePageInerted.add(region);
1134
+ });
1135
+ };
1010
1136
  const roots = () => {
1011
1137
  // A hidden or zero-width workspace measures 0, which drives every limit
1012
1138
  // down to its minimum — reconciling one would overwrite its variables and
@@ -1125,12 +1251,13 @@ export const installAppWorkspaceController = (options = {}) => {
1125
1251
  };
1126
1252
  const layoutNode = node => node instanceof Element && (node.matches(LAYOUT_REGION_SELECTOR) || Boolean(node.querySelector(LAYOUT_REGION_SELECTOR)));
1127
1253
  const mutationObserver = typeof MutationObserver === "undefined" ? null : new MutationObserver(records => {
1254
+ syncPhonePages();
1128
1255
  if (pendingTitle && (pendingTitle.aside.hidden || !pendingTitle.aside.isConnected)) cancelTitle();
1129
1256
  // Hiding or removing the sidebar, or its button, ends the overlay.
1130
1257
  if (overlay && (overlay.sidebar.hidden || !overlay.sidebar.isConnected || !overlay.toggle.isConnected)) closeOverlay("kept");
1131
1258
  const details = new Set();
1132
1259
  for (const record of records) {
1133
- if (record.type === "attributes" && record.target instanceof HTMLElement && record.target.matches(detailSelector) && !details.has(record.target)) {
1260
+ if (record.type === "attributes" && record.attributeName === "hidden" && record.target instanceof HTMLElement && record.target.matches(detailSelector) && !details.has(record.target)) {
1134
1261
  details.add(record.target);
1135
1262
  detailTransition(record.target, detailHidden.get(record.target) ?? record.oldValue !== null);
1136
1263
  }
@@ -1152,7 +1279,7 @@ export const installAppWorkspaceController = (options = {}) => {
1152
1279
  eventRoot.addEventListener("focusin", onDetailFocusIn);
1153
1280
  eventRoot.addEventListener("pointerdown", onSheetPointerDown);
1154
1281
  eventRoot.addEventListener("click", onGrabberClick);
1155
- eventRoot.addEventListener("dblclick", onDetailDoubleClick);
1282
+ eventRoot.addEventListener("dblclick", onPanelDoubleClick);
1156
1283
  window.addEventListener("pointermove", onSheetPointerMove);
1157
1284
  window.addEventListener("pointerup", onSheetPointerEnd);
1158
1285
  window.addEventListener("pointercancel", onSheetPointerEnd);
@@ -1163,19 +1290,24 @@ export const installAppWorkspaceController = (options = {}) => {
1163
1290
  eventRoot.addEventListener("touchend", onDetailTouchEnd);
1164
1291
  eventRoot.addEventListener("touchcancel", onDetailTouchEnd);
1165
1292
  compact?.addEventListener("change", onCompactChange);
1293
+ phone?.addEventListener("change", syncPhonePages);
1166
1294
  eventRoot.addEventListener("keydown", onKeyDown);
1167
1295
  eventRoot.addEventListener("focusin", onFocusIn);
1168
1296
  window.addEventListener("resize", onResize);
1297
+ syncPhonePages();
1169
1298
  syncObservedRoots();
1170
1299
  mutationObserver?.observe(eventRoot, {
1171
1300
  attributes: true,
1172
1301
  attributeOldValue: true,
1173
- attributeFilter: ["hidden"],
1302
+ attributeFilter: ["hidden", "data-workspace-level-shown"],
1174
1303
  childList: true,
1175
1304
  subtree: true
1176
1305
  });
1177
1306
  scheduleReconcile();
1178
1307
  return () => {
1308
+ phonePageInerted.forEach(region => region.removeAttribute("inert"));
1309
+ phonePageInerted.clear();
1310
+ phone?.removeEventListener("change", syncPhonePages);
1179
1311
  cancelTitle();
1180
1312
  pendingAnchors.forEach(cancel => cancel());
1181
1313
  slides.forEach(end => end());
@@ -1200,7 +1332,7 @@ export const installAppWorkspaceController = (options = {}) => {
1200
1332
  eventRoot.removeEventListener("focusin", onDetailFocusIn);
1201
1333
  eventRoot.removeEventListener("pointerdown", onSheetPointerDown);
1202
1334
  eventRoot.removeEventListener("click", onGrabberClick);
1203
- eventRoot.removeEventListener("dblclick", onDetailDoubleClick);
1335
+ eventRoot.removeEventListener("dblclick", onPanelDoubleClick);
1204
1336
  window.removeEventListener("pointermove", onSheetPointerMove);
1205
1337
  window.removeEventListener("pointerup", onSheetPointerEnd);
1206
1338
  window.removeEventListener("pointercancel", onSheetPointerEnd);