@k2b/ui 0.29.0 → 0.31.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.
- package/README.md +2 -2
- package/dist/base.css +2 -2
- package/dist/browser/actions/Dropdown.js +6 -3
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/FilterChip.js +137 -7
- package/dist/browser/actions/FilterChip.js.map +1 -1
- package/dist/browser/actions/SegmentedControl.js +5 -9
- package/dist/browser/actions/SegmentedControl.js.map +1 -1
- package/dist/browser/actions/SpotlightSearch.js +6 -3
- package/dist/browser/actions/SpotlightSearch.js.map +1 -1
- package/dist/browser/actions/Tabs.js +5 -2
- package/dist/browser/actions/Tabs.js.map +1 -1
- package/dist/browser/actions/Toolbar.js +2 -0
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/actions/ViewTabs.js +294 -0
- package/dist/browser/actions/ViewTabs.js.map +1 -0
- package/dist/browser/actions/filter-messages.js +6 -2
- package/dist/browser/actions/filter-messages.js.map +1 -1
- package/dist/browser/actions/index.js +2 -1
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +1108 -604
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatDecision.js +146 -84
- package/dist/browser/chat/ChatDecision.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +292 -159
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/chat-messages.js +8 -0
- package/dist/browser/chat/chat-messages.js.map +1 -1
- package/dist/browser/chat/command-search.js +26 -15
- package/dist/browser/chat/command-search.js.map +1 -1
- package/dist/browser/chat/index.js +4 -2
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/content/Calendar.js +143 -116
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/CalendarWeekView.js +291 -266
- package/dist/browser/content/CalendarWeekView.js.map +1 -1
- package/dist/browser/content/DataTable.js +224 -155
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/MapAttribution.js +27 -0
- package/dist/browser/content/MapAttribution.js.map +1 -0
- package/dist/browser/content/MapView.js +553 -0
- package/dist/browser/content/MapView.js.map +1 -0
- package/dist/browser/content/MapViewFrame.js +395 -0
- package/dist/browser/content/MapViewFrame.js.map +1 -0
- package/dist/browser/content/data-table-messages.js +4 -2
- package/dist/browser/content/data-table-messages.js.map +1 -1
- package/dist/browser/content/index.js +5 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/map-palette.js +39 -0
- package/dist/browser/content/map-palette.js.map +1 -0
- package/dist/browser/content/map-style.js +276 -0
- package/dist/browser/content/map-style.js.map +1 -0
- package/dist/browser/content/map-view-geometry.js +269 -0
- package/dist/browser/content/map-view-geometry.js.map +1 -0
- package/dist/browser/content/map-view-live.js +28 -0
- package/dist/browser/content/map-view-live.js.map +1 -0
- package/dist/browser/content/map-view-messages.js +53 -0
- package/dist/browser/content/map-view-messages.js.map +1 -0
- package/dist/browser/content/map-view-types.js +2 -0
- package/dist/browser/content/map-view-types.js.map +1 -0
- package/dist/browser/content/map-view-worker.js +10 -0
- package/dist/browser/feedback/MovedHint.js +197 -0
- package/dist/browser/feedback/MovedHint.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +85 -9
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/feedback/index.js +1 -0
- package/dist/browser/feedback/index.js.map +1 -1
- package/dist/browser/feedback/prompts.js +6 -1
- package/dist/browser/feedback/prompts.js.map +1 -1
- package/dist/browser/inputs/DatePicker.js +337 -212
- package/dist/browser/inputs/DatePicker.js.map +1 -1
- package/dist/browser/inputs/date-picker.js +59 -5
- package/dist/browser/inputs/date-picker.js.map +1 -1
- package/dist/browser/intl/messages.js +12 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/intl/weekend.js +39 -0
- package/dist/browser/intl/weekend.js.map +1 -0
- package/dist/browser/layout/AppOverview.js +0 -1
- package/dist/browser/layout/AppOverview.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +701 -476
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/PageTitle.js +3 -2
- package/dist/browser/layout/PageTitle.js.map +1 -1
- package/dist/browser/layout/SettingsModal.js +2 -2
- package/dist/browser/layout/SettingsModal.js.map +1 -1
- package/dist/browser/layout/SidebarSwitcher.js +13 -12
- package/dist/browser/layout/SidebarSwitcher.js.map +1 -1
- package/dist/browser/layout/TabBar.js +1 -1
- package/dist/browser/layout/TabBar.js.map +1 -1
- package/dist/browser/layout/app-host-width.js +44 -0
- package/dist/browser/layout/app-host-width.js.map +1 -0
- package/dist/browser/layout/app-workspace-controller.js +331 -45
- package/dist/browser/layout/app-workspace-controller.js.map +1 -1
- package/dist/browser/layout/app-workspace-width.js +26 -0
- package/dist/browser/layout/app-workspace-width.js.map +1 -0
- package/dist/browser/layout/index.js +2 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/Placeholder.js +36 -13
- package/dist/browser/surfaces/Placeholder.js.map +1 -1
- package/dist/licenses/maplibre-gl.txt +116 -0
- package/dist/ssr/actions/Dropdown.js +5 -2
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/FilterChip.js +59 -7
- package/dist/ssr/actions/FilterChip.js.map +1 -1
- package/dist/ssr/actions/SegmentedControl.js +1 -2
- package/dist/ssr/actions/SegmentedControl.js.map +1 -1
- package/dist/ssr/actions/SpotlightSearch.js +2 -2
- package/dist/ssr/actions/SpotlightSearch.js.map +1 -1
- package/dist/ssr/actions/Tabs.js +2 -2
- package/dist/ssr/actions/Tabs.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +2 -0
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/actions/ViewTabs.js +211 -0
- package/dist/ssr/actions/ViewTabs.js.map +1 -0
- package/dist/ssr/actions/filter-messages.js +6 -2
- package/dist/ssr/actions/filter-messages.js.map +1 -1
- package/dist/ssr/actions/index.js +2 -1
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +546 -131
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatDecision.js +44 -19
- package/dist/ssr/chat/ChatDecision.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +95 -24
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/chat-messages.js +8 -0
- package/dist/ssr/chat/chat-messages.js.map +1 -1
- package/dist/ssr/chat/command-search.js +26 -15
- package/dist/ssr/chat/command-search.js.map +1 -1
- package/dist/ssr/chat/index.js +4 -2
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/content/Calendar.js +109 -100
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/CalendarWeekView.js +42 -28
- package/dist/ssr/content/CalendarWeekView.js.map +1 -1
- package/dist/ssr/content/DataTable.js +78 -31
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/MapAttribution.js +19 -0
- package/dist/ssr/content/MapAttribution.js.map +1 -0
- package/dist/ssr/content/MapView.js +474 -0
- package/dist/ssr/content/MapView.js.map +1 -0
- package/dist/ssr/content/MapViewFrame.js +193 -0
- package/dist/ssr/content/MapViewFrame.js.map +1 -0
- package/dist/ssr/content/data-table-messages.js +4 -2
- package/dist/ssr/content/data-table-messages.js.map +1 -1
- package/dist/ssr/content/index.js +5 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/map-palette.js +39 -0
- package/dist/ssr/content/map-palette.js.map +1 -0
- package/dist/ssr/content/map-style.js +276 -0
- package/dist/ssr/content/map-style.js.map +1 -0
- package/dist/ssr/content/map-view-geometry.js +269 -0
- package/dist/ssr/content/map-view-geometry.js.map +1 -0
- package/dist/ssr/content/map-view-live.js +1 -0
- package/dist/ssr/content/map-view-messages.js +53 -0
- package/dist/ssr/content/map-view-messages.js.map +1 -0
- package/dist/ssr/content/map-view-types.js +2 -0
- package/dist/ssr/content/map-view-types.js.map +1 -0
- package/dist/ssr/feedback/MovedHint.js +166 -0
- package/dist/ssr/feedback/MovedHint.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +85 -9
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/feedback/index.js +1 -0
- package/dist/ssr/feedback/index.js.map +1 -1
- package/dist/ssr/feedback/prompts.js +6 -1
- package/dist/ssr/feedback/prompts.js.map +1 -1
- package/dist/ssr/inputs/DatePicker.js +138 -69
- package/dist/ssr/inputs/DatePicker.js.map +1 -1
- package/dist/ssr/inputs/date-picker.js +59 -5
- package/dist/ssr/inputs/date-picker.js.map +1 -1
- package/dist/ssr/intl/messages.js +12 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/intl/weekend.js +39 -0
- package/dist/ssr/intl/weekend.js.map +1 -0
- package/dist/ssr/layout/AppOverview.js +0 -1
- package/dist/ssr/layout/AppOverview.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +216 -44
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/PageTitle.js +3 -2
- package/dist/ssr/layout/PageTitle.js.map +1 -1
- package/dist/ssr/layout/SettingsModal.js +2 -2
- package/dist/ssr/layout/SettingsModal.js.map +1 -1
- package/dist/ssr/layout/SidebarSwitcher.js +13 -12
- package/dist/ssr/layout/SidebarSwitcher.js.map +1 -1
- package/dist/ssr/layout/TabBar.js +1 -1
- package/dist/ssr/layout/TabBar.js.map +1 -1
- package/dist/ssr/layout/app-host-width.js +44 -0
- package/dist/ssr/layout/app-host-width.js.map +1 -0
- package/dist/ssr/layout/app-workspace-controller.js +331 -45
- package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
- package/dist/ssr/layout/app-workspace-width.js +26 -0
- package/dist/ssr/layout/app-workspace-width.js.map +1 -0
- package/dist/ssr/layout/index.js +2 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/Placeholder.js +24 -7
- package/dist/ssr/surfaces/Placeholder.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Dropdown.d.ts +4 -0
- package/dist/types/actions/FilterChip.d.ts +22 -0
- package/dist/types/actions/ViewTabs.d.ts +30 -0
- package/dist/types/actions/filter-messages.d.ts +2 -0
- package/dist/types/actions/index.d.ts +4 -2
- package/dist/types/chat/ChatComposer.d.ts +52 -0
- package/dist/types/chat/ChatDecision.d.ts +18 -2
- package/dist/types/chat/ChatPrimitives.d.ts +10 -0
- package/dist/types/chat/chat-messages.d.ts +4 -0
- package/dist/types/chat/command-search.d.ts +2 -0
- package/dist/types/chat/index.d.ts +9 -8
- package/dist/types/content/DataTable.d.ts +3 -0
- package/dist/types/content/MapAttribution.d.ts +6 -0
- package/dist/types/content/MapView.d.ts +5 -0
- package/dist/types/content/MapViewFrame.d.ts +31 -0
- package/dist/types/content/data-table-messages.d.ts +1 -0
- package/dist/types/content/index.d.ts +6 -0
- package/dist/types/content/map-palette.d.ts +58 -0
- package/dist/types/content/map-style.d.ts +29 -0
- package/dist/types/content/map-view-geometry.d.ts +55 -0
- package/dist/types/content/map-view-live.d.ts +2 -0
- package/dist/types/content/map-view-messages.d.ts +21 -0
- package/dist/types/content/map-view-types.d.ts +61 -0
- package/dist/types/feedback/MovedHint.d.ts +25 -0
- package/dist/types/feedback/dialog-core.d.ts +6 -2
- package/dist/types/feedback/index.d.ts +2 -0
- package/dist/types/feedback/prompts.d.ts +2 -0
- package/dist/types/inputs/DatePicker.d.ts +7 -0
- package/dist/types/inputs/date-picker.d.ts +25 -1
- package/dist/types/intl/messages.d.ts +6 -0
- package/dist/types/intl/weekend.d.ts +12 -0
- package/dist/types/layout/AppWorkspace.d.ts +48 -8
- package/dist/types/layout/app-host-width.d.ts +6 -0
- package/dist/types/layout/app-workspace-width.d.ts +13 -0
- package/dist/types/layout/index.d.ts +3 -2
- package/dist/types/surfaces/Placeholder.d.ts +2 -1
- package/map-glyphs/IBM Plex Sans Medium/0-255.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/1024-1279.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/256-511.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/512-767.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/768-1023.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/7680-7935.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8192-8447.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8448-8703.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8704-8959.pbf +0 -0
- package/map-glyphs/OFL.txt +93 -0
- package/package.json +11 -6
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { suppressTextSelection } from "../internal/text-selection";
|
|
2
2
|
import { prefersReducedMotion } from "../motion";
|
|
3
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
|
+
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]";
|
|
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
|
+
const LAYOUT_REGION_SELECTOR = [ROOT_SELECTOR, ".k2b-app-workspace__sidebar", ".k2b-app-workspace__main", ".k2b-app-workspace__main-pane", ".k2b-app-workspace__detail", ".k2b-app-workspace__drawer", HANDLE_SELECTOR].join(",");
|
|
8
9
|
const TOGGLE_SELECTOR = "[data-app-workspace-sidebar-toggle]";
|
|
9
|
-
/** Touch-tablet gestures remain viewport decisions. */
|
|
10
|
-
const TOUCH_DRAWER_QUERY = "(min-width: 48rem) and (max-width: 63.999rem) and (pointer: coarse)";
|
|
11
10
|
const DETAIL_TOGGLE_SELECTOR = "[data-app-workspace-detail-toggle]";
|
|
12
11
|
const DETAIL_DRAWER_SELECTOR = '.k2b-app-workspace__detail[data-workspace-detail-drawer="open"]';
|
|
13
12
|
const cssState = (element, name) => getComputedStyle(element).getPropertyValue(name).trim();
|
|
@@ -40,6 +39,12 @@ const FOCUSABLE_SELECTOR = ["a[href]", "button:not([disabled])", "input:not([dis
|
|
|
40
39
|
/** Text entry, menus, dialogs, and open popups keep their own keys. */
|
|
41
40
|
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']";
|
|
42
41
|
const ITEM_SELECTOR = ".k2b-app-workspace__sidebar-item";
|
|
42
|
+
/** Controls inside a selected row that act on their own instead of choosing the row. */
|
|
43
|
+
const NESTED_CONTROL_SELECTOR = "[aria-expanded], [aria-haspopup]:not([aria-haspopup='false']), [aria-pressed], [role='checkbox'], [role='switch'], [role='menuitem'], input, select, textarea, summary";
|
|
44
|
+
const DETAIL_REQUEST_CONTROL_SELECTOR = "[aria-pressed], [aria-expanded], [aria-controls]";
|
|
45
|
+
const SELECTION_MARK_SELECTOR = '[aria-selected="true"], [aria-current]:not([aria-current="false"]):not([aria-current="date"]):not([aria-current="time"]), [data-selected="true"]';
|
|
46
|
+
// Calendar day marks select a date range, not the Detail's subject.
|
|
47
|
+
const IGNORED_SELECTION_MARK_SELECTOR = "[data-calendar-day-key]";
|
|
43
48
|
const clamp = (value, min, max) => Math.round(Math.min(max, Math.max(min, value)));
|
|
44
49
|
const eventElement = event => event.target instanceof Element ? event.target : null;
|
|
45
50
|
const resizeHandle = event => eventElement(event)?.closest(HANDLE_SELECTOR) ?? null;
|
|
@@ -52,6 +57,7 @@ const workspaceRoot = handle => handle.closest(ROOT_SELECTOR);
|
|
|
52
57
|
const workspaceResizable = root => root.dataset.workspaceResizable !== "false";
|
|
53
58
|
const rootElements = (root, selector) => Array.from(root.querySelectorAll(selector)).filter(element => element.closest(ROOT_SELECTOR) === root);
|
|
54
59
|
const sidebarElement = root => rootElements(root, ".k2b-app-workspace__sidebar")[0] ?? null;
|
|
60
|
+
const sidebarForm = root => cssState(sidebarElement(root) ?? root, "--k2b-workspace-sidebar-form");
|
|
55
61
|
const mainElement = root => rootElements(root, ".k2b-app-workspace__main")[0] ?? null;
|
|
56
62
|
const sidebarCollapsible = root => sidebarElement(root)?.dataset.workspaceCollapsible === "true";
|
|
57
63
|
const sidebarHideable = root => sidebarElement(root)?.dataset.workspaceHideable === "true";
|
|
@@ -101,7 +107,7 @@ const numberData = (handle, key, fallback) => {
|
|
|
101
107
|
const value = Number(handle.dataset[key]);
|
|
102
108
|
return Number.isFinite(value) ? value : fallback;
|
|
103
109
|
};
|
|
104
|
-
const sizeLimits = (root, handle, kind) => {
|
|
110
|
+
const sizeLimits = (root, handle, kind, reserveMainMinimum = true) => {
|
|
105
111
|
const controlled = controlledPanel(root, handle);
|
|
106
112
|
const main = kind === "pane" ? handle.closest(".k2b-app-workspace__main") : mainElement(root);
|
|
107
113
|
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)) : [];
|
|
@@ -116,14 +122,24 @@ const sizeLimits = (root, handle, kind) => {
|
|
|
116
122
|
const content = kind === "detail" ? controlled?.closest(".k2b-app-workspace__content") : null;
|
|
117
123
|
const defaultMin = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MIN : kind === "pane" ? APP_WORKSPACE_PANE_MIN : kind === "detail" ? APP_WORKSPACE_DETAIL_MIN : APP_WORKSPACE_DRAWER_MIN;
|
|
118
124
|
const defaultMax = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MAX : kind === "pane" ? APP_WORKSPACE_PANE_MAX : kind === "detail" ? APP_WORKSPACE_DETAIL_MAX : APP_WORKSPACE_DRAWER_MAX;
|
|
119
|
-
|
|
125
|
+
const mainSpec = readAppWorkspaceWidth(mainElement(root)?.dataset.workspaceMainMin);
|
|
126
|
+
const mainMinValue = cssState(root, "--k2b-workspace-main-min");
|
|
127
|
+
const mainMin = typeof mainSpec === "object" ? resolveAppWorkspaceWidth(mainSpec, root.getBoundingClientRect().width) : mainMinValue.endsWith("px") ? Number.parseFloat(mainMinValue) : APP_WORKSPACE_MAIN_MIN;
|
|
128
|
+
const mainExtra = Math.max(0, (Number.isFinite(mainMin) ? mainMin : APP_WORKSPACE_MAIN_MIN) - APP_WORKSPACE_MAIN_MIN);
|
|
129
|
+
const limits = appWorkspaceResizeLimits({
|
|
120
130
|
kind,
|
|
121
|
-
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width : content ? root.getBoundingClientRect().right - root.clientLeft - content.getBoundingClientRect().left : root.getBoundingClientRect().width,
|
|
122
|
-
|
|
131
|
+
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width : content ? root.getBoundingClientRect().right - root.clientLeft - content.getBoundingClientRect().left : kind === "sidebar" ? root.getBoundingClientRect().width - 2 * root.clientLeft : root.getBoundingClientRect().width,
|
|
132
|
+
// Interactive sizing keeps Main's declared minimum beside the sidebar and the Detail.
|
|
133
|
+
reservedSize: kind === "sidebar" && reserveMainMinimum ? mainExtra : kind === "detail" ? 8 + (reserveMainMinimum ? mainExtra : 0) : kind === "pane" ? panes.reduce((total, pane) => total + (pane === controlled ? 0 : elementSize(pane, "pane")), 0) + separatorSize : 0,
|
|
123
134
|
min: numberData(handle, "workspaceMinSize", defaultMin),
|
|
124
135
|
max: numberData(handle, "workspaceMaxSize", defaultMax),
|
|
125
136
|
sidebarCollapsible: kind === "sidebar" && sidebarCollapsible(root)
|
|
126
137
|
});
|
|
138
|
+
// Widths are applied as whole pixels; rounding up must not cross the fit boundary.
|
|
139
|
+
return kind === "sidebar" ? {
|
|
140
|
+
min: limits.min,
|
|
141
|
+
max: Math.max(limits.min, Math.floor(limits.max))
|
|
142
|
+
} : limits;
|
|
127
143
|
};
|
|
128
144
|
const currentSize = (root, handle, kind) => kind === "sidebar" ? elementSize(sidebarElement(root), kind) : elementSize(controlledPanel(root, handle), kind);
|
|
129
145
|
const updateHandleValue = (root, handle, kind, size, limits = sizeLimits(root, handle, kind)) => {
|
|
@@ -135,7 +151,7 @@ const updateHandleValue = (root, handle, kind, size, limits = sizeLimits(root, h
|
|
|
135
151
|
handle.setAttribute("aria-valuemax", String(max));
|
|
136
152
|
handle.setAttribute("aria-valuenow", String(clamp(size, min, max)));
|
|
137
153
|
};
|
|
138
|
-
const
|
|
154
|
+
const applyRegionSize = (root, handle, kind, requestedSize,
|
|
139
155
|
/**
|
|
140
156
|
* `snapSidebar: false` tracks the pointer continuously and only *previews*
|
|
141
157
|
* the collapsed state. Snapping (the default, and what keyboard steps and
|
|
@@ -146,7 +162,7 @@ options = {}) => {
|
|
|
146
162
|
const {
|
|
147
163
|
min,
|
|
148
164
|
max
|
|
149
|
-
} = sizeLimits(root, handle, kind);
|
|
165
|
+
} = sizeLimits(root, handle, kind, options.reserveMainMinimum);
|
|
150
166
|
if (kind === "sidebar") {
|
|
151
167
|
const collapsible = sidebarCollapsible(root);
|
|
152
168
|
const sidebar = options.snapSidebar === false ? {
|
|
@@ -157,10 +173,8 @@ options = {}) => {
|
|
|
157
173
|
// Cloud removes the attribute rather than writing "false", so that a
|
|
158
174
|
// presence selector (`[data-sidebar-collapsed]`) means "collapsed".
|
|
159
175
|
if (sidebar.collapsed) root.dataset.sidebarCollapsed = "true";else delete root.dataset.sidebarCollapsed;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
max
|
|
163
|
-
});
|
|
176
|
+
// A restored width may require an overlay; the handle still advertises interactive limits.
|
|
177
|
+
updateHandleValue(root, handle, kind, sidebar.width);
|
|
164
178
|
return sidebar.width;
|
|
165
179
|
}
|
|
166
180
|
const size = clamp(requestedSize, min, max);
|
|
@@ -201,18 +215,39 @@ const reconcilePanes = (root, layoutState, active = null) => {
|
|
|
201
215
|
if (!isVisible(panel)) return [];
|
|
202
216
|
const min = numberData(handle, "workspaceMinSize", APP_WORKSPACE_PANE_MIN);
|
|
203
217
|
const max = Math.max(min, numberData(handle, "workspaceMaxSize", APP_WORKSPACE_PANE_MAX));
|
|
204
|
-
const
|
|
205
|
-
const
|
|
218
|
+
const spec = readAppWorkspaceWidth(handle.dataset.workspaceDefaultSize) ?? elementSize(panel, "pane");
|
|
219
|
+
const preferred = active?.moved && active.root === root && active.kind === "pane" && active.handle === handle ? active.liveSize : layoutState.paneWidths?.[panelId(handle)];
|
|
220
|
+
const cssDefault = preferred === undefined && typeof spec !== "number";
|
|
221
|
+
if (cssDefault && root.style.getPropertyValue(appWorkspacePanelVariable("pane", panelId(handle))))
|
|
222
|
+
// Drop a previous live width (or an inherited SSR preference after reset).
|
|
223
|
+
root.style.setProperty(appWorkspacePanelVariable("pane", panelId(handle)), "initial");
|
|
224
|
+
// CSS has already applied this default and the pane-fit clamp, including
|
|
225
|
+
// subpixels. Reserve exactly that box when fitting other preferences;
|
|
226
|
+
// measuring must not turn a responsive default into a stored px width.
|
|
227
|
+
const requested = preferred ?? resolveAppWorkspaceWidth(spec, root.getBoundingClientRect().width);
|
|
228
|
+
const desired = cssDefault ? elementSize(panel, "pane") : clamp(requested, min, max);
|
|
206
229
|
return [{
|
|
207
230
|
desired,
|
|
208
231
|
handle,
|
|
209
|
-
min
|
|
232
|
+
min: cssDefault ? desired : min,
|
|
233
|
+
cssDefault,
|
|
234
|
+
requested: clamp(requested, min, max),
|
|
235
|
+
configuredMin: min
|
|
210
236
|
}];
|
|
211
237
|
});
|
|
212
238
|
if (!fits.length) return;
|
|
213
|
-
const
|
|
239
|
+
const budget = main.getBoundingClientRect().width - fixedWidth - APP_WORKSPACE_MAIN_MIN;
|
|
240
|
+
// Each share default clamps only against Main, not its resizable or non-resizable siblings. When panes
|
|
241
|
+
// together leave Main's primary region less than its minimum, fit them like stored widths, allowing subpixels.
|
|
242
|
+
if (fits.reduce((total, fit) => total + fit.desired, 0) > budget + 0.5) for (const fit of fits) if (fit.cssDefault) Object.assign(fit, {
|
|
243
|
+
cssDefault: false,
|
|
244
|
+
desired: fit.requested,
|
|
245
|
+
min: fit.configuredMin
|
|
246
|
+
});
|
|
247
|
+
const sizes = fitAppWorkspacePaneSizes(fits, budget);
|
|
214
248
|
fits.forEach((fit, index) => {
|
|
215
|
-
|
|
249
|
+
// Defaults stay CSS-owned; fit only saved or live preferences around them.
|
|
250
|
+
if (!fit.cssDefault) root.style.setProperty(appWorkspacePanelVariable("pane", panelId(fit.handle)), `${sizes[index]}px`);
|
|
216
251
|
});
|
|
217
252
|
fits.forEach((fit, index) => updateHandleValue(root, fit.handle, "pane", sizes[index]));
|
|
218
253
|
};
|
|
@@ -229,7 +264,7 @@ const claimedToggleClicks = new WeakSet();
|
|
|
229
264
|
const claimedDetailGestures = new WeakSet();
|
|
230
265
|
|
|
231
266
|
/**
|
|
232
|
-
* The
|
|
267
|
+
* The overlay sidebar opens over the work area when its column does not fit
|
|
233
268
|
* from the button in the workspace's head. While it is open the rest of
|
|
234
269
|
* the workspace is inert, Tab stays in the sidebar, and Escape, the scrim,
|
|
235
270
|
* choosing an item, hiding the sidebar, or leaving the width range close it
|
|
@@ -304,13 +339,12 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
304
339
|
if (!aside || aside.hidden || !detailWaiting(aside) || claimedToggleClicks.has(event)) return;
|
|
305
340
|
claimedToggleClicks.add(event);
|
|
306
341
|
if (detailDrawer(aside)) parkDetailDrawer(aside);else {
|
|
307
|
-
aside
|
|
308
|
-
detailButton.setAttribute("aria-expanded", "true");
|
|
309
|
-
syncDetailDrawerSpace(aside);
|
|
342
|
+
openDetailDrawer(aside);
|
|
310
343
|
if (event.detail === 0) requestTitle(aside);else detailButton.focus({
|
|
311
344
|
preventScroll: true
|
|
312
345
|
});
|
|
313
346
|
}
|
|
347
|
+
syncDetailRequestControls();
|
|
314
348
|
return;
|
|
315
349
|
}
|
|
316
350
|
const toggle = target?.closest(TOGGLE_SELECTOR);
|
|
@@ -318,7 +352,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
318
352
|
const next = overlayTarget(toggle);
|
|
319
353
|
if (!next || claimedToggleClicks.has(event)) return;
|
|
320
354
|
claimedToggleClicks.add(event);
|
|
321
|
-
if (overlay?.sidebar === next.sidebar) closeOverlay("button");else if (
|
|
355
|
+
if (overlay?.sidebar === next.sidebar) closeOverlay("button");else if (sidebarForm(next.root) === "overlay") openOverlay(next);
|
|
322
356
|
return;
|
|
323
357
|
}
|
|
324
358
|
if (!overlay || !target) return;
|
|
@@ -386,8 +420,98 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
386
420
|
// Whether each open detail waited at the last check, so a column that starts
|
|
387
421
|
// waiting under focus hands that focus to its Details button.
|
|
388
422
|
const detailWasWaiting = new WeakMap();
|
|
423
|
+
const detailRequestExpanded = new Map();
|
|
424
|
+
const restoreDetailRequestExpanded = (control, state) => {
|
|
425
|
+
// A newer application render already owns the current value.
|
|
426
|
+
if (control.getAttribute("aria-expanded") !== state.written) return;
|
|
427
|
+
if (state.rendered === null) control.removeAttribute("aria-expanded");else control.setAttribute("aria-expanded", state.aside.hidden || !state.aside.isConnected ? "false" : state.rendered);
|
|
428
|
+
};
|
|
429
|
+
const requestedWaitingDetail = control => {
|
|
430
|
+
if (!wide?.matches || control.matches(DETAIL_TOGGLE_SELECTOR) || !eventRoot.contains(control)) return null;
|
|
431
|
+
const root = workspaceRoot(control);
|
|
432
|
+
const region = control.closest(".k2b-app-workspace__main, .k2b-app-workspace__detail");
|
|
433
|
+
if (!root || !region || region.closest(ROOT_SELECTOR) !== root) return null;
|
|
434
|
+
const ids = control.getAttribute("aria-controls")?.split(/\s+/);
|
|
435
|
+
return rootElements(root, detailSelector).find(aside => !aside.hidden && ids?.includes(aside.id) && detailWaiting(aside)) ?? null;
|
|
436
|
+
};
|
|
437
|
+
const syncDetailRequestControls = () => {
|
|
438
|
+
const waitingControls = new Map();
|
|
439
|
+
for (const root of allRoots()) {
|
|
440
|
+
const waitingDetails = wide?.matches ? rootElements(root, detailSelector).filter(aside => !aside.hidden && detailWaiting(aside)) : [];
|
|
441
|
+
if (!waitingDetails.length) continue;
|
|
442
|
+
// Only a control that names a Detail can request it.
|
|
443
|
+
for (const control of rootElements(root, "[aria-controls]")) {
|
|
444
|
+
if (control.matches(DETAIL_TOGGLE_SELECTOR) || !eventRoot.contains(control)) continue;
|
|
445
|
+
const region = control.closest(".k2b-app-workspace__main, .k2b-app-workspace__detail");
|
|
446
|
+
if (!region || region.closest(ROOT_SELECTOR) !== root) continue;
|
|
447
|
+
const ids = control.getAttribute("aria-controls")?.split(/\s+/);
|
|
448
|
+
const aside = waitingDetails.find(aside => ids?.includes(aside.id));
|
|
449
|
+
if (aside) waitingControls.set(control, aside);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
detailRequestExpanded.forEach((state, control) => {
|
|
453
|
+
if (waitingControls.has(control)) return;
|
|
454
|
+
restoreDetailRequestExpanded(control, state);
|
|
455
|
+
detailRequestExpanded.delete(control);
|
|
456
|
+
});
|
|
457
|
+
waitingControls.forEach((aside, control) => {
|
|
458
|
+
const current = control.getAttribute("aria-expanded");
|
|
459
|
+
const state = detailRequestExpanded.get(control);
|
|
460
|
+
const written = String(detailDrawer(aside));
|
|
461
|
+
// Keep the application's latest value to restore when waiting ends.
|
|
462
|
+
const rendered = !state || current !== state.written ? current : state.rendered;
|
|
463
|
+
detailRequestExpanded.set(control, {
|
|
464
|
+
aside,
|
|
465
|
+
rendered,
|
|
466
|
+
written
|
|
467
|
+
});
|
|
468
|
+
if (current !== written) control.setAttribute("aria-expanded", written);
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
const onDetailRequestClick = event => {
|
|
472
|
+
if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
|
|
473
|
+
const control = eventElement(event)?.closest(DETAIL_REQUEST_CONTROL_SELECTOR);
|
|
474
|
+
const aside = control ? requestedWaitingDetail(control) : null;
|
|
475
|
+
if (!control || !aside) return;
|
|
476
|
+
// The application keeps its Detail open; only its waiting drawer changes.
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
event.stopImmediatePropagation();
|
|
479
|
+
cancelPendingDetail();
|
|
480
|
+
cancelTitle();
|
|
481
|
+
clearActivation();
|
|
482
|
+
// A control inside the drawer leaves with it, so focus goes to Details.
|
|
483
|
+
if (detailDrawer(aside)) parkDetailDrawer(aside, aside.contains(ownerDocument.activeElement));else {
|
|
484
|
+
openDetailDrawer(aside);
|
|
485
|
+
if (event.detail === 0) requestTitle(aside);else control.focus({
|
|
486
|
+
preventScroll: true
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
syncDetailRequestControls();
|
|
490
|
+
};
|
|
491
|
+
const openDetailDrawer = aside => {
|
|
492
|
+
aside.dataset.workspaceDetailDrawer = "open";
|
|
493
|
+
detailToggle(aside)?.setAttribute("aria-expanded", "true");
|
|
494
|
+
syncDetailDrawerSpace(aside);
|
|
495
|
+
syncDetailRequestControls();
|
|
496
|
+
};
|
|
497
|
+
// A sidebar drag changes the room beside it without resizing the workspace,
|
|
498
|
+
// so it rechecks waiting once per frame.
|
|
499
|
+
let hostSyncFrame = null;
|
|
500
|
+
const scheduleHostSync = () => {
|
|
501
|
+
if (hostSyncFrame !== null) return;
|
|
502
|
+
hostSyncFrame = requestAnimationFrame(() => {
|
|
503
|
+
hostSyncFrame = null;
|
|
504
|
+
syncHostState();
|
|
505
|
+
});
|
|
506
|
+
};
|
|
507
|
+
const applySize = (...args) => {
|
|
508
|
+
const size = applyRegionSize(...args);
|
|
509
|
+
if (args[2] === "sidebar") scheduleHostSync();
|
|
510
|
+
return size;
|
|
511
|
+
};
|
|
389
512
|
const syncHostState = () => {
|
|
390
|
-
if (
|
|
513
|
+
if (pendingDetail && !canOpenPendingDetail(pendingDetail.aside)) cancelPendingDetail();
|
|
514
|
+
if (overlay && sidebarForm(overlay.root) !== "overlay") closeOverlay("none");
|
|
391
515
|
for (const root of allRoots()) {
|
|
392
516
|
// A drawer removed with its panel leaves no space behind in Content.
|
|
393
517
|
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");
|
|
@@ -410,10 +534,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
410
534
|
});
|
|
411
535
|
}
|
|
412
536
|
}
|
|
537
|
+
syncDetailRequestControls();
|
|
413
538
|
};
|
|
414
539
|
const phone = typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 47.999rem)") : null;
|
|
415
540
|
const wide = typeof window.matchMedia === "function" ? window.matchMedia("(min-width: 48rem)") : null;
|
|
416
|
-
const touchDrawer = typeof window.matchMedia === "function" ? window.matchMedia(TOUCH_DRAWER_QUERY) : null;
|
|
417
541
|
const detailSelector = ".k2b-app-workspace__detail";
|
|
418
542
|
const grabberSelector = ".k2b-app-workspace__detail-grabber";
|
|
419
543
|
const detailHidden = new WeakMap();
|
|
@@ -447,6 +571,36 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
447
571
|
snapshot.top = snapshot.item.getBoundingClientRect().top;
|
|
448
572
|
snapshot.scrollTop = snapshot.scroller?.scrollTop ?? 0;
|
|
449
573
|
};
|
|
574
|
+
// Native Enter/Space clicks and pointer clicks continue their initiating event.
|
|
575
|
+
let activationItem = null;
|
|
576
|
+
let activationFrame = null;
|
|
577
|
+
const clearActivation = () => {
|
|
578
|
+
if (activationFrame !== null) cancelAnimationFrame(activationFrame);
|
|
579
|
+
activationFrame = null;
|
|
580
|
+
activationItem = null;
|
|
581
|
+
};
|
|
582
|
+
const endActivation = () => {
|
|
583
|
+
if (!activationItem) return;
|
|
584
|
+
// Space's native click follows keyup before the next frame.
|
|
585
|
+
if (activationFrame !== null) cancelAnimationFrame(activationFrame);
|
|
586
|
+
activationFrame = requestAnimationFrame(clearActivation);
|
|
587
|
+
};
|
|
588
|
+
const cancelActivation = () => {
|
|
589
|
+
// A cancelled gesture must not open a Detail that appears later.
|
|
590
|
+
if (lastChosen?.interaction === "pointer" && lastChosen.item === activationItem) {
|
|
591
|
+
lastChosen.chosen = false;
|
|
592
|
+
lastChosen = null;
|
|
593
|
+
}
|
|
594
|
+
clearActivation();
|
|
595
|
+
};
|
|
596
|
+
const onChoiceClick = event => {
|
|
597
|
+
const preceded = event.target instanceof Node && Boolean(activationItem?.contains(event.target));
|
|
598
|
+
clearActivation();
|
|
599
|
+
if (event.detail === 0 && !preceded) {
|
|
600
|
+
cancelTitleOnInteraction(event);
|
|
601
|
+
rememberInteraction(event);
|
|
602
|
+
} else if (!(event.target instanceof Node && lastChosen?.item.contains(event.target))) cancelTitleOnInteraction(event);
|
|
603
|
+
};
|
|
450
604
|
// A table row can exceed its scroller's width, so keep the clicked cell for horizontal reveal.
|
|
451
605
|
const rememberInteraction = event => {
|
|
452
606
|
const target = eventElement(event);
|
|
@@ -455,24 +609,37 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
455
609
|
const previous = remembered.get(root);
|
|
456
610
|
if (previous && openDetails(root).length) refreshItem(previous);
|
|
457
611
|
const main = mainElement(root);
|
|
458
|
-
|
|
459
|
-
|
|
612
|
+
// Collection rows with a roving tab index are items too, though no tab stops.
|
|
613
|
+
const item = target.closest(`${FOCUSABLE_SELECTOR}, [tabindex]`);
|
|
614
|
+
if (!main || !item || item === main || !main.contains(item) || item.closest(ROOT_SELECTOR) !== root) return;
|
|
460
615
|
if (event instanceof KeyboardEvent && event.key !== "Enter" && event.key !== " ") return;
|
|
616
|
+
if (event.type === "pointerdown" || event.type === "keydown") activationItem = item;
|
|
617
|
+
const assistive = event instanceof MouseEvent && event.type === "click" && event.detail === 0 && event.button === 0;
|
|
461
618
|
const cell = target.closest("td, th");
|
|
462
619
|
const snapshot = {
|
|
463
620
|
item,
|
|
464
621
|
cell: cell && main.contains(cell) && cell.closest(ROOT_SELECTOR) === root ? cell : null,
|
|
465
|
-
interaction: event instanceof KeyboardEvent ? "keyboard" : "pointer",
|
|
622
|
+
interaction: event instanceof KeyboardEvent || assistive ? "keyboard" : "pointer",
|
|
466
623
|
scroller: verticalScroller(item, main),
|
|
467
624
|
scrollTop: 0,
|
|
468
625
|
top: 0,
|
|
469
|
-
fresh: true
|
|
626
|
+
fresh: true,
|
|
627
|
+
// Text entry and focus alone do not choose an item.
|
|
628
|
+
// A modified activation, such as Ctrl/Cmd-click opening a new tab, chooses nothing here.
|
|
629
|
+
chosen: (event instanceof PointerEvent && event.type === "pointerdown" && event.button === 0 || event instanceof KeyboardEvent || assistive) && !(event instanceof MouseEvent || event instanceof KeyboardEvent ? event.ctrlKey || event.metaKey || event.shiftKey || event.altKey : false) &&
|
|
630
|
+
// Space scrolls on native links; it does not activate them.
|
|
631
|
+
!(event instanceof KeyboardEvent && event.key === " " && item.matches("a[href], area[href]") && (!item.hasAttribute("role") || item.getAttribute("role") === "link")) && !target.closest(KEY_OWNER_SELECTOR) && !item.closest("[role='tab'], [role='tablist']")
|
|
470
632
|
};
|
|
471
633
|
refreshItem(snapshot);
|
|
472
634
|
remembered.set(root, snapshot);
|
|
635
|
+
if (snapshot.chosen) lastChosen = snapshot;
|
|
636
|
+
if (snapshot.chosen) {
|
|
637
|
+
const aside = openDetails(root).find(canOpenPendingDetail);
|
|
638
|
+
if (aside) checkPendingDetail(root, aside, snapshot, event instanceof KeyboardEvent && event.key === " ");
|
|
639
|
+
}
|
|
473
640
|
// Enter in an editor or a field types; only an item that chooses something moves on to its title.
|
|
474
|
-
if (
|
|
475
|
-
const aside = openDetails(root).find(isVisible);
|
|
641
|
+
if (snapshot.chosen && snapshot.interaction === "keyboard") {
|
|
642
|
+
const aside = openDetails(root).find(aside => isVisible(aside) && (!detailWaiting(aside) || detailDrawer(aside)));
|
|
476
643
|
if (aside) requestTitle(aside, true);
|
|
477
644
|
}
|
|
478
645
|
};
|
|
@@ -523,7 +690,18 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
523
690
|
// content, so focus falls to the body. The panel keeps owning the steps until
|
|
524
691
|
// focus or a pointer goes elsewhere, so a quick second J still ends on the title.
|
|
525
692
|
let panelStep = null;
|
|
693
|
+
// The last choice in Main, until another interaction replaces it: a Detail
|
|
694
|
+
// that opens later than that does not take the drawer or the focus.
|
|
695
|
+
let lastChosen = null;
|
|
526
696
|
const cancelTitleOnInteraction = event => {
|
|
697
|
+
if (event.type === "pointerdown" || event.type === "keydown") clearActivation();
|
|
698
|
+
if (lastChosen && !(event.target instanceof Node && lastChosen.item.contains(event.target))) {
|
|
699
|
+
lastChosen.chosen = false;
|
|
700
|
+
lastChosen = null;
|
|
701
|
+
}
|
|
702
|
+
if (pendingDetail &&
|
|
703
|
+
// Native pointer focus on the chosen item continues its activation.
|
|
704
|
+
!(event.type === "focusin" && event.target instanceof Node && pendingDetail.snapshot.item.contains(event.target))) cancelPendingDetail();
|
|
527
705
|
if (event.type !== "keydown" && !(event.target instanceof Node && panelStep?.contains(event.target))) panelStep = null;
|
|
528
706
|
if (event.type !== "focusin" || event.target !== focusingTitle) cancelTitle();
|
|
529
707
|
};
|
|
@@ -573,14 +751,13 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
573
751
|
delete aside.dataset.workspaceDetailDragging;
|
|
574
752
|
};
|
|
575
753
|
const anchorItem = (root, aside, snapshot) => {
|
|
576
|
-
if (!wide?.matches ||
|
|
754
|
+
if (!wide?.matches || detailDrawer(aside) || !snapshot?.item.isConnected) return;
|
|
577
755
|
if (!mainElement(root)?.contains(snapshot.item) || snapshot.item.closest(ROOT_SELECTOR) !== root) return;
|
|
578
756
|
const scroller = snapshot.scroller;
|
|
579
757
|
if (scroller?.isConnected && scroller.scrollTop === snapshot.scrollTop) scroller.scrollTop += snapshot.item.getBoundingClientRect().top - snapshot.top;
|
|
580
758
|
};
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
// edge, so the workspace clips while the panel moves (see the stylesheet).
|
|
759
|
+
// A moving Detail would cross a framed workspace's edge, so the workspace
|
|
760
|
+
// clips while it slides (see the stylesheet).
|
|
584
761
|
const slide = (root, aside) => {
|
|
585
762
|
slides.get(root)?.();
|
|
586
763
|
if (!root.hasAttribute("data-workspace-motion") || prefersReducedMotion() || !wide?.matches) return;
|
|
@@ -665,6 +842,82 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
665
842
|
}
|
|
666
843
|
}));
|
|
667
844
|
};
|
|
845
|
+
let pendingDetail = null;
|
|
846
|
+
const canOpenPendingDetail = aside => aside.isConnected && eventRoot.contains(aside) && !aside.hidden && detailWaiting(aside) && !detailDrawer(aside) && aside.dataset.workspaceDetailChoice !== "request";
|
|
847
|
+
const cancelPendingDetail = () => {
|
|
848
|
+
pendingDetail?.stop();
|
|
849
|
+
pendingDetail = null;
|
|
850
|
+
};
|
|
851
|
+
// Check a completed choice after two frames. If its selection mark arrives
|
|
852
|
+
// later, keep waiting until another interaction replaces the choice.
|
|
853
|
+
const checkPendingDetail = (root, aside, snapshot, space = false) => {
|
|
854
|
+
cancelPendingDetail();
|
|
855
|
+
let frame = null;
|
|
856
|
+
let observer = null;
|
|
857
|
+
const settle = () => {
|
|
858
|
+
ownerDocument.removeEventListener("click", onClick, true);
|
|
859
|
+
ownerDocument.removeEventListener("keyup", onKeyUp, true);
|
|
860
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
861
|
+
frame = requestAnimationFrame(() => {
|
|
862
|
+
frame = requestAnimationFrame(() => {
|
|
863
|
+
frame = null;
|
|
864
|
+
const main = mainElement(root);
|
|
865
|
+
if (!canOpenPendingDetail(aside) || !snapshot.chosen || !snapshot.item.isConnected || !main?.contains(snapshot.item) || snapshot.item.closest(ROOT_SELECTOR) !== root) {
|
|
866
|
+
cancelPendingDetail();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
// ARIA marks, and `data-selected` as DataTable and Calendar mark their selection.
|
|
870
|
+
let selected = snapshot.item.closest(SELECTION_MARK_SELECTOR);
|
|
871
|
+
while (selected?.matches(IGNORED_SELECTION_MARK_SELECTOR)) selected = selected.parentElement?.closest(SELECTION_MARK_SELECTOR) ?? null;
|
|
872
|
+
if (!selected || selected === main || !main.contains(selected)) {
|
|
873
|
+
if (!observer) {
|
|
874
|
+
observer = new MutationObserver(settle);
|
|
875
|
+
observer.observe(main, {
|
|
876
|
+
subtree: true,
|
|
877
|
+
attributes: true,
|
|
878
|
+
attributeFilter: ["aria-selected", "aria-current", "data-selected"]
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
cancelPendingDetail();
|
|
884
|
+
// A control inside a selected row that discloses, toggles, or opens
|
|
885
|
+
// a menu does its own job and selects nothing.
|
|
886
|
+
if (selected !== snapshot.item && snapshot.item.matches(NESTED_CONTROL_SELECTOR)) return;
|
|
887
|
+
snapshot.fresh = false;
|
|
888
|
+
openDetailDrawer(aside);
|
|
889
|
+
revealItem(root, aside, snapshot);
|
|
890
|
+
// The same item keeps its title; another one may still be loading,
|
|
891
|
+
// so focus follows the title that replaces the current one.
|
|
892
|
+
if (snapshot.interaction === "keyboard") {
|
|
893
|
+
focusTitle(aside);
|
|
894
|
+
requestTitle(aside, true);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
};
|
|
899
|
+
const onClick = event => {
|
|
900
|
+
if (event.button === 0 && event.target instanceof Node && snapshot.item.contains(event.target)) settle();else cancelPendingDetail();
|
|
901
|
+
};
|
|
902
|
+
// Space activates a button on release, so its check waits for keyup.
|
|
903
|
+
const onKeyUp = event => {
|
|
904
|
+
if (event.key === " ") settle();
|
|
905
|
+
};
|
|
906
|
+
const stop = () => {
|
|
907
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
908
|
+
observer?.disconnect();
|
|
909
|
+
ownerDocument.removeEventListener("click", onClick, true);
|
|
910
|
+
ownerDocument.removeEventListener("keyup", onKeyUp, true);
|
|
911
|
+
ownerDocument.removeEventListener("pointercancel", cancelPendingDetail, true);
|
|
912
|
+
};
|
|
913
|
+
pendingDetail = {
|
|
914
|
+
aside,
|
|
915
|
+
snapshot,
|
|
916
|
+
stop
|
|
917
|
+
};
|
|
918
|
+
ownerDocument.addEventListener("pointercancel", cancelPendingDetail, true);
|
|
919
|
+
if (space) ownerDocument.addEventListener("keyup", onKeyUp, true);else if (snapshot.interaction === "keyboard") settle();else ownerDocument.addEventListener("click", onClick, true);
|
|
920
|
+
};
|
|
668
921
|
const detailTransition = (aside, wasHidden) => {
|
|
669
922
|
const root = aside.closest(ROOT_SELECTOR);
|
|
670
923
|
detailHidden.set(aside, Boolean(aside.hidden));
|
|
@@ -679,6 +932,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
679
932
|
const followedInteraction = snapshot?.fresh ?? false;
|
|
680
933
|
if (snapshot) snapshot.fresh = false;
|
|
681
934
|
if (aside.hidden) {
|
|
935
|
+
if (pendingDetail?.aside === aside) cancelPendingDetail();
|
|
682
936
|
parkDetailDrawer(aside);
|
|
683
937
|
if (pendingTitle?.aside === aside) cancelTitle();
|
|
684
938
|
if (panelStep === aside) panelStep = null;
|
|
@@ -708,7 +962,11 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
708
962
|
if (detailDrag?.aside === aside) finishDetailDrag(true);
|
|
709
963
|
return;
|
|
710
964
|
}
|
|
711
|
-
if (detailWaiting(aside) && !detailDrawer(aside))
|
|
965
|
+
if (detailWaiting(aside) && !detailDrawer(aside)) {
|
|
966
|
+
if (!followedInteraction || !snapshot?.chosen) return;
|
|
967
|
+
if (aside.dataset.workspaceDetailChoice === "request" && !snapshot.item.matches(DETAIL_REQUEST_CONTROL_SELECTOR)) return;
|
|
968
|
+
openDetailDrawer(aside);
|
|
969
|
+
}
|
|
712
970
|
anchorItem(root, aside, snapshot);
|
|
713
971
|
if (phone?.matches) setDetent(aside, false);
|
|
714
972
|
if (followedInteraction && snapshot) {
|
|
@@ -722,7 +980,8 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
722
980
|
if (!target || target.closest(KEY_OWNER_SELECTOR)) return;
|
|
723
981
|
if (ownerDocument.querySelector("dialog[open], [aria-modal='true']")) return;
|
|
724
982
|
try {
|
|
725
|
-
|
|
983
|
+
// A passive hint does not own the keyboard.
|
|
984
|
+
if (ownerDocument.querySelector(":popover-open:not(.k2b-moved-hint)")) return;
|
|
726
985
|
} catch {
|
|
727
986
|
// Older engines have no popover selector; the other popup guards still apply.
|
|
728
987
|
}
|
|
@@ -738,6 +997,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
738
997
|
if (!aside || !owningRoot || !isVisible(aside)) return;
|
|
739
998
|
if (event.key === "Escape" && detailDrawer(aside)) {
|
|
740
999
|
parkDetailDrawer(aside, true);
|
|
1000
|
+
syncDetailRequestControls();
|
|
741
1001
|
event.preventDefault();
|
|
742
1002
|
return;
|
|
743
1003
|
}
|
|
@@ -764,7 +1024,8 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
764
1024
|
scroller: verticalScroller(item, main),
|
|
765
1025
|
scrollTop: 0,
|
|
766
1026
|
top: 0,
|
|
767
|
-
fresh: false
|
|
1027
|
+
fresh: false,
|
|
1028
|
+
chosen: false
|
|
768
1029
|
};
|
|
769
1030
|
refreshItem(snapshot);
|
|
770
1031
|
remembered.set(owningRoot, snapshot);
|
|
@@ -829,7 +1090,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
829
1090
|
clearDrag(drag.aside);
|
|
830
1091
|
if (cancelled || !drag.moved) return;
|
|
831
1092
|
if (drag.source === "drawer") {
|
|
832
|
-
if (drag.dx > 80 || drag.dx > 30 && drag.velocity > 0.5)
|
|
1093
|
+
if (drag.dx > 80 || drag.dx > 30 && drag.velocity > 0.5) parkDetailDrawer(drag.aside, drag.aside.contains(ownerDocument.activeElement));
|
|
833
1094
|
} else if (drag.dy > 60) {
|
|
834
1095
|
setDetent(drag.aside, false);
|
|
835
1096
|
if (!drag.full && closable(drag.aside)) requestClose(drag.aside);
|
|
@@ -918,7 +1179,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
918
1179
|
if (phone?.matches && phonePage(aside)) return;
|
|
919
1180
|
if (phone?.matches && (verticalScroller(target, aside) ?? aside).scrollTop <= 0) {
|
|
920
1181
|
if (!ownsTouchGesture(target, aside, "sheet")) beginDetailDrag(aside, target, "sheet", touch.clientX, touch.clientY, touch.identifier);
|
|
921
|
-
} else if (
|
|
1182
|
+
} else if (wide?.matches && detailDrawer(aside)) {
|
|
922
1183
|
if (!ownsTouchGesture(target, aside, "drawer")) beginDetailDrag(aside, target, "drawer", touch.clientX, touch.clientY, touch.identifier);
|
|
923
1184
|
}
|
|
924
1185
|
if (detailDrag) claimedDetailGestures.add(event);
|
|
@@ -984,7 +1245,12 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
984
1245
|
const variable = appWorkspacePanelVariable(kind, panelId(handle));
|
|
985
1246
|
root.style.removeProperty(variable);
|
|
986
1247
|
// An SSR ancestor may still supply the old preference until the next request.
|
|
987
|
-
if (getComputedStyle(root).getPropertyValue(variable).trim())
|
|
1248
|
+
if (getComputedStyle(root).getPropertyValue(variable).trim()) {
|
|
1249
|
+
const spec = readAppWorkspaceWidth(handle.dataset.workspaceDefaultSize) ?? (kind === "pane" ? APP_WORKSPACE_PANE_DEFAULT : APP_WORKSPACE_DETAIL_DEFAULT);
|
|
1250
|
+
// A share must resolve at the pane, where the workspace length exists.
|
|
1251
|
+
// `initial` clears an inherited SSR preference and restores its CSS fallback.
|
|
1252
|
+
root.style.setProperty(variable, typeof spec === "number" ? `${spec}px` : "initial");
|
|
1253
|
+
}
|
|
988
1254
|
// The siblings fit again around the default width, also in a Main without levels.
|
|
989
1255
|
if (kind === "pane") reconcilePanes(root, layoutState);
|
|
990
1256
|
updateHandleValue(root, handle, kind, currentSize(root, handle, kind));
|
|
@@ -1221,7 +1487,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1221
1487
|
return allRoots().filter(root => getComputedStyle(root).display !== "none" && root.getBoundingClientRect().width > 0);
|
|
1222
1488
|
};
|
|
1223
1489
|
const reconcile = () => {
|
|
1224
|
-
if (detailDrag && (detailDrag.source === "drawer" ? !
|
|
1490
|
+
if (detailDrag && (detailDrag.source === "drawer" ? !wide?.matches || !detailDrawer(detailDrag.aside) : !phone?.matches)) finishDetailDrag(true);
|
|
1225
1491
|
allRoots().forEach(root => {
|
|
1226
1492
|
if (!phone?.matches) {
|
|
1227
1493
|
for (const aside of rootElements(root, detailSelector)) {
|
|
@@ -1248,7 +1514,9 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1248
1514
|
return;
|
|
1249
1515
|
}
|
|
1250
1516
|
const persisted = kind === "sidebar" ? layoutState.sidebarCollapsed && sidebarCollapsible(root) ? APP_WORKSPACE_SIDEBAR_COLLAPSED : layoutState.sidebarWidth : kind === "detail" ? layoutState.detailWidths?.[panelId(handle)] : layoutState.drawerHeights?.[panelId(handle)];
|
|
1251
|
-
if (persisted !== undefined) applySize(root, handle, kind, persisted
|
|
1517
|
+
if (persisted !== undefined) applySize(root, handle, kind, persisted, {
|
|
1518
|
+
reserveMainMinimum: false
|
|
1519
|
+
});else updateHandleValue(root, handle, kind, currentSize(root, handle, kind));
|
|
1252
1520
|
});
|
|
1253
1521
|
reconcilePanes(root, layoutState, active?.root === root ? active : null);
|
|
1254
1522
|
});
|
|
@@ -1361,8 +1629,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1361
1629
|
};
|
|
1362
1630
|
const layoutNode = node => node instanceof Element && (node.matches(LAYOUT_REGION_SELECTOR) || Boolean(node.querySelector(LAYOUT_REGION_SELECTOR)));
|
|
1363
1631
|
const mutationObserver = typeof MutationObserver === "undefined" ? null : new MutationObserver(records => {
|
|
1632
|
+
if (records.some(record => record.type === "childList" || ["hidden", "aria-controls", "aria-expanded", "data-workspace-detail-drawer"].includes(record.attributeName ?? ""))) syncDetailRequestControls();
|
|
1364
1633
|
syncPhonePages();
|
|
1365
1634
|
if (pendingTitle && (pendingTitle.aside.hidden || !pendingTitle.aside.isConnected)) cancelTitle();
|
|
1635
|
+
if (pendingDetail && (!canOpenPendingDetail(pendingDetail.aside) || !pendingDetail.snapshot.chosen || !pendingDetail.snapshot.item.isConnected)) cancelPendingDetail();
|
|
1366
1636
|
// Hiding or removing the sidebar, or its button, ends the overlay.
|
|
1367
1637
|
if (overlay && (overlay.sidebar.hidden || !overlay.sidebar.isConnected || !overlay.toggle.isConnected)) closeOverlay("kept");
|
|
1368
1638
|
const details = new Set();
|
|
@@ -1376,7 +1646,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1376
1646
|
if (!changed) return;
|
|
1377
1647
|
syncObservedRoots();
|
|
1378
1648
|
// 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();
|
|
1649
|
+
if (records.some(record => record.type === "childList" || record.attributeName === "data-width" || record.attributeName === "data-workspace-main-min")) syncHostState();
|
|
1380
1650
|
scheduleReconcile();
|
|
1381
1651
|
});
|
|
1382
1652
|
eventRoot.addEventListener("pointerdown", onPointerDown);
|
|
@@ -1386,6 +1656,11 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1386
1656
|
ownerDocument.addEventListener("focusin", cancelTitleOnInteraction, true);
|
|
1387
1657
|
ownerDocument.addEventListener("pointerdown", cancelTitleOnInteraction, true);
|
|
1388
1658
|
ownerDocument.addEventListener("keydown", cancelTitleOnInteraction, true);
|
|
1659
|
+
ownerDocument.addEventListener("click", onDetailRequestClick, true);
|
|
1660
|
+
ownerDocument.addEventListener("click", onChoiceClick, true);
|
|
1661
|
+
ownerDocument.addEventListener("keyup", endActivation, true);
|
|
1662
|
+
ownerDocument.addEventListener("pointerup", endActivation, true);
|
|
1663
|
+
ownerDocument.addEventListener("pointercancel", cancelActivation, true);
|
|
1389
1664
|
eventRoot.addEventListener("pointerdown", rememberInteraction, true);
|
|
1390
1665
|
eventRoot.addEventListener("keydown", rememberInteraction, true);
|
|
1391
1666
|
eventRoot.addEventListener("focusin", onDetailFocusIn);
|
|
@@ -1408,10 +1683,11 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1408
1683
|
window.addEventListener("resize", onResize);
|
|
1409
1684
|
syncPhonePages();
|
|
1410
1685
|
syncObservedRoots();
|
|
1686
|
+
syncDetailRequestControls();
|
|
1411
1687
|
mutationObserver?.observe(eventRoot, {
|
|
1412
1688
|
attributes: true,
|
|
1413
1689
|
attributeOldValue: true,
|
|
1414
|
-
attributeFilter: ["hidden", "data-workspace-level-shown", "data-width"],
|
|
1690
|
+
attributeFilter: ["hidden", "aria-controls", "aria-expanded", "data-workspace-detail-drawer", "data-workspace-level-shown", "data-width", "data-workspace-main-min", "data-workspace-default-size"],
|
|
1415
1691
|
childList: true,
|
|
1416
1692
|
subtree: true
|
|
1417
1693
|
});
|
|
@@ -1421,6 +1697,7 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1421
1697
|
phonePageInerted.clear();
|
|
1422
1698
|
phone?.removeEventListener("change", syncPhonePages);
|
|
1423
1699
|
cancelTitle();
|
|
1700
|
+
cancelPendingDetail();
|
|
1424
1701
|
pendingAnchors.forEach(cancel => cancel());
|
|
1425
1702
|
slides.forEach(end => end());
|
|
1426
1703
|
stopScrollFades();
|
|
@@ -1439,6 +1716,11 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1439
1716
|
ownerDocument.removeEventListener("focusin", cancelTitleOnInteraction, true);
|
|
1440
1717
|
ownerDocument.removeEventListener("pointerdown", cancelTitleOnInteraction, true);
|
|
1441
1718
|
ownerDocument.removeEventListener("keydown", cancelTitleOnInteraction, true);
|
|
1719
|
+
clearActivation();
|
|
1720
|
+
ownerDocument.removeEventListener("click", onChoiceClick, true);
|
|
1721
|
+
ownerDocument.removeEventListener("keyup", endActivation, true);
|
|
1722
|
+
ownerDocument.removeEventListener("pointerup", endActivation, true);
|
|
1723
|
+
ownerDocument.removeEventListener("pointercancel", cancelActivation, true);
|
|
1442
1724
|
eventRoot.removeEventListener("pointerdown", rememberInteraction, true);
|
|
1443
1725
|
eventRoot.removeEventListener("keydown", rememberInteraction, true);
|
|
1444
1726
|
eventRoot.removeEventListener("focusin", onDetailFocusIn);
|
|
@@ -1457,7 +1739,11 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1457
1739
|
// torn down, so unmounting during a drag cannot lose the final size.
|
|
1458
1740
|
stopResize();
|
|
1459
1741
|
if (reconcileFrame !== null) cancelAnimationFrame(reconcileFrame);
|
|
1742
|
+
if (hostSyncFrame !== null) cancelAnimationFrame(hostSyncFrame);
|
|
1460
1743
|
mutationObserver?.disconnect();
|
|
1744
|
+
detailRequestExpanded.forEach((state, control) => restoreDetailRequestExpanded(control, state));
|
|
1745
|
+
detailRequestExpanded.clear();
|
|
1746
|
+
ownerDocument.removeEventListener("click", onDetailRequestClick, true);
|
|
1461
1747
|
resizeObserver?.disconnect();
|
|
1462
1748
|
observedRoots.clear();
|
|
1463
1749
|
observedHosts.clear();
|