@k2b/ui 0.22.0 → 0.24.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/dist/browser/content/Calendar.js +1122 -1065
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/CalendarDayView.js +1187 -0
- package/dist/browser/content/CalendarDayView.js.map +1 -0
- package/dist/browser/content/CalendarWeekView.js +1593 -0
- package/dist/browser/content/CalendarWeekView.js.map +1 -0
- package/dist/browser/content/ChartExplorer.js +174 -82
- package/dist/browser/content/ChartExplorer.js.map +1 -1
- package/dist/browser/content/ChartFilterControls.js +90 -33
- package/dist/browser/content/ChartFilterControls.js.map +1 -1
- package/dist/browser/content/DataTable.js +235 -197
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/calendar-day-scroll.js +111 -0
- package/dist/browser/content/calendar-day-scroll.js.map +1 -0
- package/dist/browser/content/calendar-days.js +408 -0
- package/dist/browser/content/calendar-days.js.map +1 -0
- package/dist/browser/content/calendar-dialogs.js +236 -0
- package/dist/browser/content/calendar-dialogs.js.map +1 -0
- package/dist/browser/content/calendar-messages.js +210 -2
- package/dist/browser/content/calendar-messages.js.map +1 -1
- package/dist/browser/content/calendar-pointer.js +15 -3
- package/dist/browser/content/calendar-pointer.js.map +1 -1
- package/dist/browser/content/calendar-shared.js +328 -0
- package/dist/browser/content/calendar-shared.js.map +1 -0
- package/dist/browser/content/calendar-time-view.js +1654 -0
- package/dist/browser/content/calendar-time-view.js.map +1 -0
- package/dist/browser/content/chart-messages.js +10 -0
- package/dist/browser/content/chart-messages.js.map +1 -1
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/inputs/Combobox.js +0 -1
- package/dist/browser/inputs/Combobox.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +125 -97
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/Navigation.js +18 -3
- package/dist/browser/layout/Navigation.js.map +1 -1
- package/dist/browser/layout/PageTitle.js +18 -1
- package/dist/browser/layout/PageTitle.js.map +1 -1
- package/dist/browser/layout/SidebarSwitcher.js +505 -0
- package/dist/browser/layout/SidebarSwitcher.js.map +1 -0
- package/dist/browser/layout/app-workspace-controller.js +174 -86
- package/dist/browser/layout/app-workspace-controller.js.map +1 -1
- package/dist/browser/layout/index.js +1 -0
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/layout/navigation-model.js +1 -0
- package/dist/browser/layout/navigation-model.js.map +1 -1
- package/dist/browser/surfaces/StatCell.js +18 -6
- package/dist/browser/surfaces/StatCell.js.map +1 -1
- package/dist/ssr/content/Calendar.js +553 -341
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/CalendarDayView.js +800 -0
- package/dist/ssr/content/CalendarDayView.js.map +1 -0
- package/dist/ssr/content/CalendarWeekView.js +1090 -0
- package/dist/ssr/content/CalendarWeekView.js.map +1 -0
- package/dist/ssr/content/ChartExplorer.js +93 -34
- package/dist/ssr/content/ChartExplorer.js.map +1 -1
- package/dist/ssr/content/ChartFilterControls.js +49 -7
- package/dist/ssr/content/ChartFilterControls.js.map +1 -1
- package/dist/ssr/content/DataTable.js +88 -66
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/calendar-day-scroll.js +111 -0
- package/dist/ssr/content/calendar-day-scroll.js.map +1 -0
- package/dist/ssr/content/calendar-days.js +408 -0
- package/dist/ssr/content/calendar-days.js.map +1 -0
- package/dist/ssr/content/calendar-dialogs.js +159 -0
- package/dist/ssr/content/calendar-dialogs.js.map +1 -0
- package/dist/ssr/content/calendar-messages.js +210 -2
- package/dist/ssr/content/calendar-messages.js.map +1 -1
- package/dist/ssr/content/calendar-pointer.js +15 -3
- package/dist/ssr/content/calendar-pointer.js.map +1 -1
- package/dist/ssr/content/calendar-shared.js +261 -0
- package/dist/ssr/content/calendar-shared.js.map +1 -0
- package/dist/ssr/content/calendar-time-view.js +1311 -0
- package/dist/ssr/content/calendar-time-view.js.map +1 -0
- package/dist/ssr/content/chart-messages.js +10 -0
- package/dist/ssr/content/chart-messages.js.map +1 -1
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/inputs/Combobox.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +40 -29
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/Navigation.js +12 -2
- package/dist/ssr/layout/Navigation.js.map +1 -1
- package/dist/ssr/layout/PageTitle.js +18 -1
- package/dist/ssr/layout/PageTitle.js.map +1 -1
- package/dist/ssr/layout/SidebarSwitcher.js +271 -0
- package/dist/ssr/layout/SidebarSwitcher.js.map +1 -0
- package/dist/ssr/layout/app-workspace-controller.js +174 -86
- package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
- package/dist/ssr/layout/index.js +1 -0
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/layout/navigation-model.js +1 -0
- package/dist/ssr/layout/navigation-model.js.map +1 -1
- package/dist/ssr/surfaces/StatCell.js +2 -2
- package/dist/ssr/surfaces/StatCell.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/content/Calendar.d.ts +95 -1
- package/dist/types/content/CalendarDayView.d.ts +7 -0
- package/dist/types/content/CalendarWeekView.d.ts +7 -0
- package/dist/types/content/ChartExplorer.d.ts +36 -1
- package/dist/types/content/ChartFilterControls.d.ts +6 -1
- package/dist/types/content/DataTable.d.ts +14 -0
- package/dist/types/content/calendar-day-scroll.d.ts +47 -0
- package/dist/types/content/calendar-days.d.ts +192 -0
- package/dist/types/content/calendar-dialogs.d.ts +22 -0
- package/dist/types/content/calendar-messages.d.ts +99 -0
- package/dist/types/content/calendar-pointer.d.ts +9 -0
- package/dist/types/content/calendar-shared.d.ts +69 -0
- package/dist/types/content/calendar-time-view.d.ts +252 -0
- package/dist/types/content/chart-messages.d.ts +5 -0
- package/dist/types/content/index.d.ts +2 -2
- package/dist/types/layout/AppWorkspace.d.ts +27 -3
- package/dist/types/layout/PageTitle.d.ts +2 -1
- package/dist/types/layout/SidebarSwitcher.d.ts +65 -0
- package/dist/types/layout/app-workspace-controller.d.ts +9 -1
- package/dist/types/layout/index.d.ts +1 -0
- package/dist/types/layout/navigation-model.d.ts +4 -0
- package/dist/types/surfaces/StatCell.d.ts +5 -0
- package/package.json +3 -3
|
@@ -35,7 +35,11 @@ const sidebarHideable = root => sidebarElement(root)?.dataset.workspaceHideable
|
|
|
35
35
|
export const APP_WORKSPACE_SIDEBAR_HIDE_EVENT = "k2b-app-workspace-sidebar-hide";
|
|
36
36
|
/** Asks the owning `AppWorkspace.Detail` to close; its `onClose` decides. */
|
|
37
37
|
export const APP_WORKSPACE_DETAIL_CLOSE_EVENT = "k2b-app-workspace-detail-close";
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Asks the owning Detail to step by `detail.direction` (`-1` or `1`). It writes
|
|
40
|
+
* the Main element showing the selection to `detail.item`, read synchronously
|
|
41
|
+
* by the controller after dispatch.
|
|
42
|
+
*/
|
|
39
43
|
export const APP_WORKSPACE_DETAIL_STEP_EVENT = "k2b-app-workspace-detail-step";
|
|
40
44
|
const previewSidebarHidden = (root, hidden) => {
|
|
41
45
|
const sidebar = sidebarElement(root);
|
|
@@ -64,16 +68,17 @@ const sizeLimits = (root, handle, kind) => {
|
|
|
64
68
|
const controlled = controlledPanel(root, handle);
|
|
65
69
|
const main = kind === "pane" ? handle.closest(".k2b-app-workspace__main") : mainElement(root);
|
|
66
70
|
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)) : [];
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
71
|
+
// Content ends before the detail track, so its width alone would shrink a
|
|
72
|
+
// persisted panel again on every reconciliation. Measure from Content's
|
|
73
|
+
// start to the workspace's inner end: this includes the detail column and
|
|
74
|
+
// leaves out a desktop sidebar, while an overlay sidebar takes no width.
|
|
70
75
|
const content = kind === "detail" ? controlled?.closest(".k2b-app-workspace__content") : null;
|
|
71
76
|
const defaultMin = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MIN : kind === "pane" ? APP_WORKSPACE_PANE_MIN : kind === "detail" ? APP_WORKSPACE_DETAIL_MIN : APP_WORKSPACE_DRAWER_MIN;
|
|
72
77
|
const defaultMax = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MAX : kind === "pane" ? APP_WORKSPACE_PANE_MAX : kind === "detail" ? APP_WORKSPACE_DETAIL_MAX : APP_WORKSPACE_DRAWER_MAX;
|
|
73
78
|
return appWorkspaceResizeLimits({
|
|
74
79
|
kind,
|
|
75
|
-
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width :
|
|
76
|
-
reservedSize: kind === "detail" ?
|
|
80
|
+
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width : content ? root.getBoundingClientRect().right - root.clientLeft - content.getBoundingClientRect().left : root.getBoundingClientRect().width,
|
|
81
|
+
reservedSize: kind === "detail" ? 8 : kind === "pane" ? panes.reduce((total, pane) => total + (pane === controlled ? 0 : elementSize(pane, "pane")), 0) : 0,
|
|
77
82
|
min: numberData(handle, "workspaceMinSize", defaultMin),
|
|
78
83
|
max: numberData(handle, "workspaceMaxSize", defaultMax),
|
|
79
84
|
sidebarCollapsible: kind === "sidebar" && sidebarCollapsible(root)
|
|
@@ -317,18 +322,17 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
317
322
|
const reducedMotion = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
318
323
|
const detailSelector = ".k2b-app-workspace__detail";
|
|
319
324
|
const grabberSelector = ".k2b-app-workspace__detail-grabber";
|
|
320
|
-
const trailSelector = ".k2b-detail-trail";
|
|
321
325
|
const detailHidden = new WeakMap();
|
|
322
326
|
const remembered = new WeakMap();
|
|
323
327
|
const lastFocus = new WeakMap();
|
|
324
|
-
const trailPadding = new WeakMap();
|
|
325
|
-
const heldTrails = new Set();
|
|
326
328
|
const touchedDetails = new Set();
|
|
327
329
|
// Only a panel blocks scrolling for its touch gestures; a document-wide
|
|
328
330
|
// non-passive listener would make every scroll on the page wait for script.
|
|
329
331
|
const touchMoveAsides = new Set();
|
|
330
332
|
const titleTabIndices = new Set();
|
|
331
333
|
const motionFrames = new Map();
|
|
334
|
+
const pendingAnchors = new Map();
|
|
335
|
+
const slides = new Map();
|
|
332
336
|
const motionRoots = new Set();
|
|
333
337
|
const revealFrames = new Map();
|
|
334
338
|
const openDetails = root => rootElements(root, detailSelector).filter(aside => !aside.hidden);
|
|
@@ -337,17 +341,19 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
337
341
|
const requestClose = aside => {
|
|
338
342
|
if (aside.hasAttribute("data-workspace-detail-closable")) aside.dispatchEvent(new CustomEvent(APP_WORKSPACE_DETAIL_CLOSE_EVENT));else closeControl(aside)?.click();
|
|
339
343
|
};
|
|
340
|
-
const
|
|
344
|
+
const scrollerOn = (axis, target, boundary) => {
|
|
341
345
|
for (let element = target; element && boundary.contains(element); element = element.parentElement) {
|
|
342
|
-
if (element instanceof HTMLElement && /^(auto|scroll)$/.test(getComputedStyle(element).overflowY) && element.scrollHeight > element.clientHeight) return element;
|
|
346
|
+
if (element instanceof HTMLElement && /^(auto|scroll)$/.test(axis === "y" ? getComputedStyle(element).overflowY : getComputedStyle(element).overflowX) && (axis === "y" ? element.scrollHeight > element.clientHeight : element.scrollWidth > element.clientWidth)) return element;
|
|
343
347
|
if (element === boundary) break;
|
|
344
348
|
}
|
|
345
349
|
return null;
|
|
346
350
|
};
|
|
351
|
+
const verticalScroller = (target, boundary) => scrollerOn("y", target, boundary);
|
|
347
352
|
const refreshItem = snapshot => {
|
|
348
353
|
snapshot.top = snapshot.item.getBoundingClientRect().top;
|
|
349
354
|
snapshot.scrollTop = snapshot.scroller?.scrollTop ?? 0;
|
|
350
355
|
};
|
|
356
|
+
// A table row can exceed its scroller's width, so keep the clicked cell for horizontal reveal.
|
|
351
357
|
const rememberInteraction = event => {
|
|
352
358
|
const target = eventElement(event);
|
|
353
359
|
const root = target?.closest(ROOT_SELECTOR);
|
|
@@ -358,8 +364,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
358
364
|
const item = target.closest(FOCUSABLE_SELECTOR);
|
|
359
365
|
if (!main || !item || !main.contains(item) || item.closest(ROOT_SELECTOR) !== root) return;
|
|
360
366
|
if (event instanceof KeyboardEvent && event.key !== "Enter" && event.key !== " ") return;
|
|
367
|
+
const cell = target.closest("td, th");
|
|
361
368
|
const snapshot = {
|
|
362
369
|
item,
|
|
370
|
+
cell: cell && main.contains(cell) && cell.closest(ROOT_SELECTOR) === root ? cell : null,
|
|
363
371
|
interaction: event instanceof KeyboardEvent ? "keyboard" : "pointer",
|
|
364
372
|
scroller: verticalScroller(item, main),
|
|
365
373
|
scrollTop: 0,
|
|
@@ -417,7 +425,12 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
417
425
|
pendingTitle?.observer.disconnect();
|
|
418
426
|
pendingTitle = null;
|
|
419
427
|
};
|
|
428
|
+
// A step from inside the panel can remove the focused element with the old
|
|
429
|
+
// content, so focus falls to the body. The panel keeps owning the steps until
|
|
430
|
+
// focus or a pointer goes elsewhere, so a quick second J still ends on the title.
|
|
431
|
+
let panelStep = null;
|
|
420
432
|
const cancelTitleOnInteraction = event => {
|
|
433
|
+
if (event.type !== "keydown" && !(event.target instanceof Node && panelStep?.contains(event.target))) panelStep = null;
|
|
421
434
|
if (event.type !== "focusin" || event.target !== focusingTitle) cancelTitle();
|
|
422
435
|
};
|
|
423
436
|
const requestTitle = (aside, waitForChange = false) => {
|
|
@@ -463,42 +476,104 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
463
476
|
aside.style.removeProperty("transform");
|
|
464
477
|
delete aside.dataset.workspaceDetailDragging;
|
|
465
478
|
};
|
|
466
|
-
const trailsIn = root => {
|
|
467
|
-
const main = mainElement(root);
|
|
468
|
-
return main ? rootElements(root, trailSelector).filter(trail => main.contains(trail)) : [];
|
|
469
|
-
};
|
|
470
|
-
const clearHolds = root => {
|
|
471
|
-
for (const trail of trailsIn(root)) {
|
|
472
|
-
trail.style.removeProperty("--k2b-detail-trail-hold");
|
|
473
|
-
heldTrails.delete(trail);
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
const rememberTrailPadding = root => {
|
|
477
|
-
if (!openDetails(root).some(isVisible)) return;
|
|
478
|
-
for (const trail of trailsIn(root)) trailPadding.set(trail, Number.parseFloat(getComputedStyle(trail).paddingInlineEnd) || 0);
|
|
479
|
-
};
|
|
480
|
-
const shrinkHold = trail => {
|
|
481
|
-
const hold = Number.parseFloat(trail.style.getPropertyValue("--k2b-detail-trail-hold")) || 0;
|
|
482
|
-
const visible = trail.scrollWidth <= trail.clientWidth || trail.scrollLeft < 1 ? 0 : Math.min(hold, Math.max(0, Math.ceil(trail.scrollLeft + trail.clientWidth - (trail.scrollWidth - hold))));
|
|
483
|
-
if (visible > 0) trail.style.setProperty("--k2b-detail-trail-hold", `${visible}px`);else {
|
|
484
|
-
trail.style.removeProperty("--k2b-detail-trail-hold");
|
|
485
|
-
heldTrails.delete(trail);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
const onTrailScroll = event => {
|
|
489
|
-
const target = eventElement(event);
|
|
490
|
-
if (target instanceof HTMLElement && heldTrails.has(target)) shrinkHold(target);
|
|
491
|
-
};
|
|
492
479
|
const anchorItem = (root, aside, snapshot) => {
|
|
493
|
-
if (!wide?.matches || touchDrawer?.matches && closable(aside) ||
|
|
480
|
+
if (!wide?.matches || touchDrawer?.matches && closable(aside) || !snapshot?.item.isConnected) return;
|
|
494
481
|
if (!mainElement(root)?.contains(snapshot.item) || snapshot.item.closest(ROOT_SELECTOR) !== root) return;
|
|
495
482
|
const scroller = snapshot.scroller;
|
|
496
483
|
if (scroller?.isConnected && scroller.scrollTop === snapshot.scrollTop) scroller.scrollTop += snapshot.item.getBoundingClientRect().top - snapshot.top;
|
|
497
484
|
};
|
|
485
|
+
// From 64 rem the workspace does not clip, so the panel's shadow shows on
|
|
486
|
+
// every edge. A panel sliding in or out would cross a framed workspace's
|
|
487
|
+
// edge, so the workspace clips while the panel moves (see the stylesheet).
|
|
488
|
+
const slide = (root, aside) => {
|
|
489
|
+
slides.get(root)?.();
|
|
490
|
+
if (!root.hasAttribute("data-workspace-motion") || reducedMotion?.matches || !wide?.matches) return;
|
|
491
|
+
const end = () => {
|
|
492
|
+
aside.removeEventListener("transitionend", onEnd);
|
|
493
|
+
aside.removeEventListener("transitioncancel", onEnd);
|
|
494
|
+
cancelAnimationFrame(frame);
|
|
495
|
+
slides.delete(root);
|
|
496
|
+
delete root.dataset.workspaceDetailSliding;
|
|
497
|
+
};
|
|
498
|
+
const onEnd = event => {
|
|
499
|
+
if (event.target === aside && event.propertyName === "transform") end();
|
|
500
|
+
};
|
|
501
|
+
aside.addEventListener("transitionend", onEnd);
|
|
502
|
+
aside.addEventListener("transitioncancel", onEnd);
|
|
503
|
+
// A slow first frame must not end the clip early, so only a panel that
|
|
504
|
+
// does not slide at all ends it in that frame.
|
|
505
|
+
const frame = requestAnimationFrame(() => {
|
|
506
|
+
const sliding = aside.getAnimations?.().some(animation => "transitionProperty" in animation && animation.transitionProperty === "transform");
|
|
507
|
+
if (!sliding) end();
|
|
508
|
+
});
|
|
509
|
+
slides.set(root, end);
|
|
510
|
+
root.dataset.workspaceDetailSliding = "";
|
|
511
|
+
};
|
|
512
|
+
const revealItem = (root, aside, snapshot) => {
|
|
513
|
+
const pending = revealFrames.get(root);
|
|
514
|
+
if (pending !== undefined) cancelAnimationFrame(pending);
|
|
515
|
+
revealFrames.set(root, requestAnimationFrame(() => {
|
|
516
|
+
revealFrames.delete(root);
|
|
517
|
+
if (!aside.hidden && aside.isConnected && snapshot.item.isConnected && mainElement(root)?.contains(snapshot.item) && snapshot.item.closest(ROOT_SELECTOR) === root) {
|
|
518
|
+
const behavior = reducedMotion?.matches ? "auto" : "smooth";
|
|
519
|
+
const main = mainElement(root);
|
|
520
|
+
const cell = snapshot.cell;
|
|
521
|
+
const target = cell?.isConnected && main?.contains(cell) ? cell : snapshot.item;
|
|
522
|
+
if (phone?.matches && main) {
|
|
523
|
+
// The sheet covers Main's lower part, so the item ends 8px above it;
|
|
524
|
+
// the other edges include the scroller's scroll padding, like scrollIntoView.
|
|
525
|
+
const page = ownerDocument.scrollingElement;
|
|
526
|
+
const vertical = verticalScroller(target, main) ?? page;
|
|
527
|
+
const horizontal = scrollerOn("x", target, main) ?? page;
|
|
528
|
+
const bounds = scroller => {
|
|
529
|
+
if (!scroller || scroller === page) return null;
|
|
530
|
+
const rect = scroller.getBoundingClientRect();
|
|
531
|
+
const style = ownerWindow.getComputedStyle(scroller);
|
|
532
|
+
const padding = value => Number.parseFloat(value) || 0;
|
|
533
|
+
return {
|
|
534
|
+
top: rect.top + padding(style.scrollPaddingTop),
|
|
535
|
+
left: rect.left + padding(style.scrollPaddingLeft),
|
|
536
|
+
right: rect.right - padding(style.scrollPaddingRight)
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
const box = target.getBoundingClientRect();
|
|
540
|
+
const top = Math.max(0, bounds(vertical)?.top ?? 0);
|
|
541
|
+
const left = Math.max(0, bounds(horizontal)?.left ?? 0);
|
|
542
|
+
const right = Math.min(ownerWindow.innerWidth, bounds(horizontal)?.right ?? Infinity);
|
|
543
|
+
const dy = box.top < top ? box.top - top - 8 : Math.max(0, box.bottom - (ownerWindow.innerHeight - aside.offsetHeight) + 8);
|
|
544
|
+
// As with scrollIntoView's "nearest", an item past both edges stays put
|
|
545
|
+
// and one wider than the view keeps its start in view.
|
|
546
|
+
const dx = box.left < left && box.right > right ? 0 : box.left < left ? box.left - left - 8 : box.right > right ? Math.min(box.right - right + 8, Math.max(0, box.left - left - 8)) : 0;
|
|
547
|
+
if (vertical && vertical === horizontal) {
|
|
548
|
+
if (dy || dx) vertical.scrollBy({
|
|
549
|
+
top: dy,
|
|
550
|
+
left: dx,
|
|
551
|
+
behavior
|
|
552
|
+
});
|
|
553
|
+
} else {
|
|
554
|
+
if (dy) vertical?.scrollBy({
|
|
555
|
+
top: dy,
|
|
556
|
+
behavior
|
|
557
|
+
});
|
|
558
|
+
if (dx) horizontal?.scrollBy({
|
|
559
|
+
left: dx,
|
|
560
|
+
behavior
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
} else target.scrollIntoView({
|
|
564
|
+
block: "nearest",
|
|
565
|
+
inline: "nearest",
|
|
566
|
+
behavior
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}));
|
|
570
|
+
};
|
|
498
571
|
const detailTransition = (aside, wasHidden) => {
|
|
499
572
|
const root = aside.closest(ROOT_SELECTOR);
|
|
500
573
|
detailHidden.set(aside, Boolean(aside.hidden));
|
|
501
574
|
if (!root || !observedRoots.has(root) || Boolean(aside.hidden) === wasHidden) return;
|
|
575
|
+
pendingAnchors.get(root)?.();
|
|
576
|
+
slide(root, aside);
|
|
502
577
|
touchedDetails.add(aside);
|
|
503
578
|
const pending = revealFrames.get(root);
|
|
504
579
|
if (pending !== undefined) cancelAnimationFrame(pending);
|
|
@@ -508,18 +583,25 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
508
583
|
if (snapshot) snapshot.fresh = false;
|
|
509
584
|
if (aside.hidden) {
|
|
510
585
|
if (pendingTitle?.aside === aside) cancelTitle();
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
586
|
+
if (panelStep === aside) panelStep = null;
|
|
587
|
+
if (root.hasAttribute("data-workspace-motion") && !reducedMotion?.matches && wide?.matches) {
|
|
588
|
+
// Closing holds the detail column until the slide ends; anchor after Main widens.
|
|
589
|
+
const cancel = () => {
|
|
590
|
+
root.removeEventListener("transitionend", onTransitionEnd);
|
|
591
|
+
clearTimeout(timer);
|
|
592
|
+
pendingAnchors.delete(root);
|
|
593
|
+
};
|
|
594
|
+
const finish = () => {
|
|
595
|
+
cancel();
|
|
596
|
+
if (aside.hidden && aside.isConnected) anchorItem(root, aside, snapshot);
|
|
597
|
+
};
|
|
598
|
+
const onTransitionEnd = event => {
|
|
599
|
+
if (event.target === root && event.propertyName === "--k2b-workspace-detail-track") finish();
|
|
600
|
+
};
|
|
601
|
+
const timer = setTimeout(finish, 300);
|
|
602
|
+
root.addEventListener("transitionend", onTransitionEnd);
|
|
603
|
+
pendingAnchors.set(root, cancel);
|
|
604
|
+
} else anchorItem(root, aside, snapshot);
|
|
523
605
|
const focused = ownerDocument.activeElement;
|
|
524
606
|
if (snapshot?.item.isConnected && (aside.contains(lastFocus.get(root) ?? null) || aside.contains(focused) || focused === ownerDocument.body)) snapshot.item.focus({
|
|
525
607
|
preventScroll: true
|
|
@@ -528,30 +610,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
528
610
|
if (detailDrag?.aside === aside) finishDetailDrag(true);
|
|
529
611
|
return;
|
|
530
612
|
}
|
|
531
|
-
clearHolds(root);
|
|
532
613
|
anchorItem(root, aside, snapshot);
|
|
533
614
|
if (phone?.matches) setDetent(aside, false);
|
|
534
|
-
rememberTrailPadding(root);
|
|
535
615
|
if (followedInteraction && snapshot) {
|
|
536
|
-
|
|
537
|
-
revealFrames.delete(root);
|
|
538
|
-
if (!aside.hidden && aside.isConnected && snapshot.item.isConnected && mainElement(root)?.contains(snapshot.item) && snapshot.item.closest(ROOT_SELECTOR) === root) {
|
|
539
|
-
const behavior = reducedMotion?.matches ? "auto" : "smooth";
|
|
540
|
-
if (phone?.matches) {
|
|
541
|
-
const sheetTop = ownerWindow.innerHeight - aside.offsetHeight;
|
|
542
|
-
const overlap = snapshot.item.getBoundingClientRect().bottom - sheetTop + 8;
|
|
543
|
-
const main = mainElement(root);
|
|
544
|
-
if (overlap > 0 && main) (verticalScroller(snapshot.item, main) ?? ownerDocument.scrollingElement)?.scrollBy({
|
|
545
|
-
top: overlap,
|
|
546
|
-
behavior
|
|
547
|
-
});
|
|
548
|
-
} else snapshot.item.scrollIntoView({
|
|
549
|
-
block: "nearest",
|
|
550
|
-
inline: "nearest",
|
|
551
|
-
behavior
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}));
|
|
616
|
+
revealItem(root, aside, snapshot);
|
|
555
617
|
if (snapshot.interaction === "keyboard" || phone?.matches) requestTitle(aside);
|
|
556
618
|
}
|
|
557
619
|
};
|
|
@@ -575,11 +637,45 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
575
637
|
}
|
|
576
638
|
const owningRoot = aside?.closest(ROOT_SELECTOR);
|
|
577
639
|
if (!aside || !owningRoot) return;
|
|
578
|
-
if (event.key === "Escape" && closable(aside)) requestClose(aside);else if ((event.key.toLowerCase() === "j" || event.key.toLowerCase() === "k") && aside.hasAttribute("data-workspace-detail-steppable"))
|
|
579
|
-
|
|
640
|
+
if (event.key === "Escape" && closable(aside)) requestClose(aside);else if ((event.key.toLowerCase() === "j" || event.key.toLowerCase() === "k") && aside.hasAttribute("data-workspace-detail-steppable")) {
|
|
641
|
+
const main = mainElement(owningRoot);
|
|
642
|
+
const inMain = remembered.get(owningRoot)?.item.contains(target) || main?.contains(target);
|
|
643
|
+
const inPanel = aside.contains(target) || target === ownerDocument.body && panelStep === aside;
|
|
644
|
+
panelStep = inPanel ? aside : null;
|
|
645
|
+
const previousTitle = inPanel ? titleIn(aside) : null;
|
|
646
|
+
const previousText = previousTitle?.textContent;
|
|
647
|
+
const detail = {
|
|
580
648
|
direction: event.key.toLowerCase() === "j" ? 1 : -1
|
|
649
|
+
};
|
|
650
|
+
aside.dispatchEvent(new CustomEvent(APP_WORKSPACE_DETAIL_STEP_EVENT, {
|
|
651
|
+
detail
|
|
652
|
+
}));
|
|
653
|
+
const shown = detail.item;
|
|
654
|
+
const item = shown instanceof HTMLElement && main?.contains(shown) && shown.closest(ROOT_SELECTOR) === owningRoot ? shown : null;
|
|
655
|
+
if (item && main) {
|
|
656
|
+
const snapshot = {
|
|
657
|
+
item,
|
|
658
|
+
cell: null,
|
|
659
|
+
interaction: "keyboard",
|
|
660
|
+
scroller: verticalScroller(item, main),
|
|
661
|
+
scrollTop: 0,
|
|
662
|
+
top: 0,
|
|
663
|
+
fresh: false
|
|
664
|
+
};
|
|
665
|
+
refreshItem(snapshot);
|
|
666
|
+
remembered.set(owningRoot, snapshot);
|
|
667
|
+
revealItem(owningRoot, aside, snapshot);
|
|
581
668
|
}
|
|
582
|
-
|
|
669
|
+
const title = titleIn(aside);
|
|
670
|
+
if (item && inMain) item.focus({
|
|
671
|
+
preventScroll: true
|
|
672
|
+
});
|
|
673
|
+
// A panel that swapped its title during the event has the new one now.
|
|
674
|
+
else if (item && inPanel && title && (title !== previousTitle || title.textContent !== previousText)) focusTitle(aside);
|
|
675
|
+
// This key's capture cancelled an earlier step's title request, so a
|
|
676
|
+
// step from the body, even past the last item, asks again.
|
|
677
|
+
else if (inPanel && (item || target === ownerDocument.body)) requestTitle(aside, Boolean(item));
|
|
678
|
+
} else if (event.key === "F6") {
|
|
583
679
|
if (aside.contains(target)) {
|
|
584
680
|
const item = remembered.get(owningRoot)?.item;
|
|
585
681
|
const main = mainElement(owningRoot);
|
|
@@ -781,7 +877,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
781
877
|
root.style.removeProperty(variable);
|
|
782
878
|
if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, `${numberData(handle, "workspaceDefaultSize", APP_WORKSPACE_DETAIL_DEFAULT)}px`);
|
|
783
879
|
updateHandleValue(root, handle, "detail", currentSize(root, handle, "detail"));
|
|
784
|
-
rememberTrailPadding(root);
|
|
785
880
|
options.writeState?.(layoutState);
|
|
786
881
|
};
|
|
787
882
|
const stopScrollFades = installScrollFades(eventRoot);
|
|
@@ -863,7 +958,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
863
958
|
const size = finished.kind === "pane" ? finished.liveSize : applySize(finished.root, finished.handle, finished.kind, finished.liveSize);
|
|
864
959
|
persistSize(finished.handle, finished.kind, size);
|
|
865
960
|
if (finished.kind !== "drawer") reconcilePanes(finished.root, layoutState);
|
|
866
|
-
if (finished.kind === "detail") rememberTrailPadding(finished.root);
|
|
867
961
|
}
|
|
868
962
|
if (finished.handle.hasPointerCapture?.(finished.pointerId)) finished.handle.releasePointerCapture(finished.pointerId);
|
|
869
963
|
finished.releaseTextSelection();
|
|
@@ -929,7 +1023,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
929
1023
|
event.preventDefault();
|
|
930
1024
|
persistSize(handle, kind, applySize(root, handle, kind, requested));
|
|
931
1025
|
if (kind === "sidebar" || kind === "detail") reconcilePanes(root, layoutState);
|
|
932
|
-
if (kind === "detail") rememberTrailPadding(root);
|
|
933
1026
|
};
|
|
934
1027
|
const allRoots = () => {
|
|
935
1028
|
const nested = Array.from(eventRoot.querySelectorAll(ROOT_SELECTOR));
|
|
@@ -949,7 +1042,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
949
1042
|
if (aside.dataset.workspaceDetailSheet === "full") setDetent(aside, false);
|
|
950
1043
|
}
|
|
951
1044
|
}
|
|
952
|
-
rememberTrailPadding(root);
|
|
953
1045
|
});
|
|
954
1046
|
roots().forEach(root => {
|
|
955
1047
|
// `data-workspace-resizable="false"` already refuses pointer and keyboard
|
|
@@ -974,7 +1066,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
974
1066
|
});
|
|
975
1067
|
reconcilePanes(root, layoutState, active?.root === root ? active : null);
|
|
976
1068
|
});
|
|
977
|
-
heldTrails.forEach(shrinkHold);
|
|
978
1069
|
};
|
|
979
1070
|
/**
|
|
980
1071
|
* A handle's limits move whenever anything around it does — opening a detail
|
|
@@ -1036,6 +1127,8 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1036
1127
|
const next = new Set(allRoots());
|
|
1037
1128
|
observedRoots.forEach(root => {
|
|
1038
1129
|
if (!next.has(root)) {
|
|
1130
|
+
pendingAnchors.get(root)?.();
|
|
1131
|
+
slides.get(root)?.();
|
|
1039
1132
|
resizeObserver?.unobserve(root);
|
|
1040
1133
|
const frame = motionFrames.get(root);
|
|
1041
1134
|
if (frame !== undefined) cancelAnimationFrame(frame);
|
|
@@ -1069,7 +1162,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1069
1162
|
}));
|
|
1070
1163
|
}));
|
|
1071
1164
|
}
|
|
1072
|
-
rememberTrailPadding(root);
|
|
1073
1165
|
});
|
|
1074
1166
|
};
|
|
1075
1167
|
const layoutNode = node => node instanceof Element && (node.matches(LAYOUT_REGION_SELECTOR) || Boolean(node.querySelector(LAYOUT_REGION_SELECTOR)));
|
|
@@ -1099,10 +1191,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1099
1191
|
eventRoot.addEventListener("pointerdown", rememberInteraction, true);
|
|
1100
1192
|
eventRoot.addEventListener("keydown", rememberInteraction, true);
|
|
1101
1193
|
eventRoot.addEventListener("focusin", onDetailFocusIn);
|
|
1102
|
-
eventRoot.addEventListener("scroll", onTrailScroll, {
|
|
1103
|
-
passive: true,
|
|
1104
|
-
capture: true
|
|
1105
|
-
});
|
|
1106
1194
|
eventRoot.addEventListener("pointerdown", onSheetPointerDown);
|
|
1107
1195
|
eventRoot.addEventListener("click", onGrabberClick);
|
|
1108
1196
|
eventRoot.addEventListener("dblclick", onDetailDoubleClick);
|
|
@@ -1131,13 +1219,14 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1131
1219
|
scheduleReconcile();
|
|
1132
1220
|
return () => {
|
|
1133
1221
|
cancelTitle();
|
|
1222
|
+
pendingAnchors.forEach(cancel => cancel());
|
|
1223
|
+
slides.forEach(end => end());
|
|
1134
1224
|
stopScrollFades();
|
|
1135
1225
|
finishDetailDrag(true);
|
|
1136
1226
|
if (suppressedClickTimer !== null) clearTimeout(suppressedClickTimer);
|
|
1137
1227
|
motionFrames.forEach(cancelAnimationFrame);
|
|
1138
1228
|
revealFrames.forEach(cancelAnimationFrame);
|
|
1139
1229
|
motionRoots.forEach(root => delete root.dataset.workspaceMotion);
|
|
1140
|
-
heldTrails.forEach(trail => trail.style.removeProperty("--k2b-detail-trail-hold"));
|
|
1141
1230
|
touchedDetails.forEach(aside => {
|
|
1142
1231
|
clearDrag(aside);
|
|
1143
1232
|
delete aside.dataset.workspaceDetailSheet;
|
|
@@ -1151,7 +1240,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1151
1240
|
eventRoot.removeEventListener("pointerdown", rememberInteraction, true);
|
|
1152
1241
|
eventRoot.removeEventListener("keydown", rememberInteraction, true);
|
|
1153
1242
|
eventRoot.removeEventListener("focusin", onDetailFocusIn);
|
|
1154
|
-
eventRoot.removeEventListener("scroll", onTrailScroll, true);
|
|
1155
1243
|
eventRoot.removeEventListener("pointerdown", onSheetPointerDown);
|
|
1156
1244
|
eventRoot.removeEventListener("click", onGrabberClick);
|
|
1157
1245
|
eventRoot.removeEventListener("dblclick", onDetailDoubleClick);
|