@adecore/ui 0.18.4 → 0.20.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 +4 -0
- package/dist/ColumnResizeHandle.d.ts +3 -2
- package/dist/ColumnResizeHandle.js +2 -2
- package/dist/SlidingColumn.d.ts +2 -1
- package/dist/SlidingColumn.js +11 -4
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/locales/en.json +10 -0
- package/dist/locales/nl.json +10 -0
- package/dist/theme.css +181 -0
- package/dist/useColumnResize.js +12 -17
- package/dist/workspace/SplitView.d.ts +49 -0
- package/dist/workspace/SplitView.js +421 -0
- package/dist/workspace/TabStrip.d.ts +42 -0
- package/dist/workspace/TabStrip.js +168 -0
- package/dist/workspace/Workspace.d.ts +15 -0
- package/dist/workspace/Workspace.js +27 -0
- package/dist/workspace/context.d.ts +6 -0
- package/dist/workspace/context.js +5 -0
- package/dist/workspace/geometry.d.ts +39 -0
- package/dist/workspace/geometry.js +153 -0
- package/dist/workspace/model.d.ts +83 -0
- package/dist/workspace/model.js +360 -0
- package/dist/workspace/preview.d.ts +35 -0
- package/dist/workspace/preview.js +72 -0
- package/package.json +1 -1
- package/src/ColumnResizeHandle.tsx +4 -3
- package/src/SlidingColumn.tsx +23 -6
- package/src/index.ts +19 -0
- package/src/locales/en.json +10 -0
- package/src/locales/nl.json +10 -0
- package/src/theme.css +181 -0
- package/src/useColumnResize.ts +12 -18
- package/src/workspace/SplitView.tsx +656 -0
- package/src/workspace/TabStrip.tsx +296 -0
- package/src/workspace/Workspace.tsx +72 -0
- package/src/workspace/context.ts +8 -0
- package/src/workspace/geometry.ts +190 -0
- package/src/workspace/model.ts +440 -0
- package/src/workspace/preview.ts +116 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const TAB_TOP_INSET = 4;
|
|
2
|
+
export declare const TAB_RADIUS = 6;
|
|
3
|
+
export declare const BODY_RADIUS = 4;
|
|
4
|
+
export declare const DROP_STROKE = 2;
|
|
5
|
+
export interface DropPreviewShape {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
barHeight: number;
|
|
11
|
+
tabLeft: number;
|
|
12
|
+
tabWidth: number;
|
|
13
|
+
tabTop: number;
|
|
14
|
+
radius: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PreviewRect {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function rectPreview({ x, y, width, height }: PreviewRect): DropPreviewShape;
|
|
23
|
+
export declare function tabPreview(drop: TabDrop, origin: {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
}): DropPreviewShape;
|
|
27
|
+
export declare function dropPreviewPath({ x, y, width, height, barHeight, tabLeft, tabWidth, tabTop, radius }: DropPreviewShape, inset?: number): string;
|
|
28
|
+
export interface TabDrop {
|
|
29
|
+
gap: number;
|
|
30
|
+
left: number;
|
|
31
|
+
tabWidth: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
barHeight: number;
|
|
35
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* The gap above the indicator's tab, so it reads as a tab standing in the bar. */
|
|
2
|
+
export const TAB_TOP_INSET = 4;
|
|
3
|
+
/* The corner of the indicator's tab, and of its body, which is `rounded-sm` like the other drop indicator. */
|
|
4
|
+
export const TAB_RADIUS = 6;
|
|
5
|
+
export const BODY_RADIUS = 4;
|
|
6
|
+
/* The stroke is centered on the path, so the path runs half of it inside the box to keep the outer edge on it. */
|
|
7
|
+
export const DROP_STROKE = 2;
|
|
8
|
+
function coordinate(value) {
|
|
9
|
+
return String(Math.round(value * 100) / 100 + 0);
|
|
10
|
+
}
|
|
11
|
+
/*
|
|
12
|
+
* The preview for a split or a trade: the rectangle as a tab with no height, parked just inside its
|
|
13
|
+
* top left corner so the corner keeps its radius. It is where a tab target grows out of the rectangle.
|
|
14
|
+
*/
|
|
15
|
+
export function rectPreview({ x, y, width, height }) {
|
|
16
|
+
return { x, y, width, height, barHeight: 0, tabLeft: x + BODY_RADIUS, tabWidth: 0, tabTop: y, radius: TAB_RADIUS };
|
|
17
|
+
}
|
|
18
|
+
/* The preview for a tab: the tab and the body of the cell it lands in, `origin` being the cell's top left in the grid. */
|
|
19
|
+
export function tabPreview(drop, origin) {
|
|
20
|
+
return {
|
|
21
|
+
x: origin.x,
|
|
22
|
+
y: origin.y,
|
|
23
|
+
width: drop.width,
|
|
24
|
+
height: drop.height,
|
|
25
|
+
barHeight: drop.barHeight,
|
|
26
|
+
tabLeft: origin.x + drop.left,
|
|
27
|
+
tabWidth: drop.tabWidth,
|
|
28
|
+
tabTop: origin.y + TAB_TOP_INSET,
|
|
29
|
+
radius: TAB_RADIUS
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/*
|
|
33
|
+
* The outline of a tab grown into the body under it, as one closed path so no line runs between the
|
|
34
|
+
* two. The commands are the same whatever the numbers are (an arc of radius 0 is a straight line), so
|
|
35
|
+
* the browser can move one outline into another with a transition on `d`. `inset` is how far the
|
|
36
|
+
* path runs inside the box, which is half the stroke, so the outer edge of the line is on the box.
|
|
37
|
+
*/
|
|
38
|
+
export function dropPreviewPath({ x, y, width, height, barHeight, tabLeft, tabWidth, tabTop, radius }, inset = DROP_STROKE / 2) {
|
|
39
|
+
const left = inset;
|
|
40
|
+
const right = width - inset;
|
|
41
|
+
const bottom = height - inset;
|
|
42
|
+
const top = barHeight + inset;
|
|
43
|
+
const tabStart = Math.max(left, Math.min(tabLeft - x + inset, right));
|
|
44
|
+
const tabEnd = Math.max(tabStart, Math.min(tabLeft - x + tabWidth - inset, right));
|
|
45
|
+
const tabY = Math.min(tabTop - y + inset, top);
|
|
46
|
+
const tabCorner = Math.max(0, Math.min(radius - inset, (tabEnd - tabStart) / 2, top - tabY));
|
|
47
|
+
const bodyCorner = Math.max(0, BODY_RADIUS - inset);
|
|
48
|
+
// A tab flush with a side of the box continues that side, so the body has no corner there.
|
|
49
|
+
const cornerLeft = Math.min(bodyCorner, tabStart - left);
|
|
50
|
+
const cornerRight = Math.min(bodyCorner, right - tabEnd);
|
|
51
|
+
const cornerBottom = Math.max(0, Math.min(bodyCorner, (right - left) / 2, (bottom - top) / 2));
|
|
52
|
+
const at = (px, py) => `${coordinate(x + px)},${coordinate(y + py)}`;
|
|
53
|
+
const arc = (corner, px, py) => `A${coordinate(corner)},${coordinate(corner)} 0 0 1 ${at(px, py)}`;
|
|
54
|
+
return [
|
|
55
|
+
`M${at(left + cornerLeft, top)}`,
|
|
56
|
+
`H${coordinate(x + tabStart)}`,
|
|
57
|
+
`V${coordinate(y + tabY + tabCorner)}`,
|
|
58
|
+
arc(tabCorner, tabStart + tabCorner, tabY),
|
|
59
|
+
`H${coordinate(x + tabEnd - tabCorner)}`,
|
|
60
|
+
arc(tabCorner, tabEnd, tabY + tabCorner),
|
|
61
|
+
`V${coordinate(y + top)}`,
|
|
62
|
+
`H${coordinate(x + right - cornerRight)}`,
|
|
63
|
+
arc(cornerRight, right, top + cornerRight),
|
|
64
|
+
`V${coordinate(y + bottom - cornerBottom)}`,
|
|
65
|
+
arc(cornerBottom, right - cornerBottom, bottom),
|
|
66
|
+
`H${coordinate(x + left + cornerBottom)}`,
|
|
67
|
+
arc(cornerBottom, left, bottom - cornerBottom),
|
|
68
|
+
`V${coordinate(y + top + cornerLeft)}`,
|
|
69
|
+
arc(cornerLeft, left + cornerLeft, top),
|
|
70
|
+
'Z'
|
|
71
|
+
].join(' ');
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PointerEvent as ReactPointerEvent, Ref } from 'react';
|
|
1
|
+
import type { CSSProperties, PointerEvent as ReactPointerEvent, Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import type { ColumnEdge } from './useColumnResize.ts';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ export interface ColumnResizeHandleProps {
|
|
|
7
7
|
from: ColumnEdge;
|
|
8
8
|
onPointerDown(event: ReactPointerEvent<HTMLElement>): void;
|
|
9
9
|
className?: string;
|
|
10
|
+
style?: CSSProperties;
|
|
10
11
|
ref?: Ref<HTMLDivElement>;
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -18,6 +19,6 @@ const PLACEMENT: Record<ColumnEdge, string> = {
|
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
/* An invisible strip over the free edge of a column, so the column needs `relative` and draws its own border. */
|
|
21
|
-
export function ColumnResizeHandle({ from, onPointerDown, className, ref }: ColumnResizeHandleProps) {
|
|
22
|
-
return <div ref={ref} className={clsx('absolute z-10', PLACEMENT[from], className)} onPointerDown={onPointerDown} />;
|
|
22
|
+
export function ColumnResizeHandle({ from, onPointerDown, className, style, ref }: ColumnResizeHandleProps) {
|
|
23
|
+
return <div ref={ref} className={clsx('absolute z-10', PLACEMENT[from], className)} style={style} onPointerDown={onPointerDown} />;
|
|
23
24
|
}
|
package/src/SlidingColumn.tsx
CHANGED
|
@@ -11,6 +11,8 @@ export interface SlidingColumnProps {
|
|
|
11
11
|
open: boolean;
|
|
12
12
|
/* Already clamped by the caller, which knows what has to stay beside it (`clampColumnSize`). */
|
|
13
13
|
width: number;
|
|
14
|
+
/* Space before the surface, in pixels. Replaces the border and carries the resize handle. */
|
|
15
|
+
gap?: number;
|
|
14
16
|
bounds: { min: number; max(): number };
|
|
15
17
|
onWidthChange(width: number): void;
|
|
16
18
|
/*
|
|
@@ -38,7 +40,7 @@ export interface SlidingColumnProps {
|
|
|
38
40
|
* moves, and they stay mounted until the slide is over, so a close plays out. The left edge is the
|
|
39
41
|
* drag handle.
|
|
40
42
|
*/
|
|
41
|
-
export function SlidingColumn({ open, width, bounds, onWidthChange, instant = false, body, className, ref, children }: SlidingColumnProps) {
|
|
43
|
+
export function SlidingColumn({ open, width, gap = 0, bounds, onWidthChange, instant = false, body, className, ref, children }: SlidingColumnProps) {
|
|
42
44
|
/* Closed and done animating. Until then the contents stay mounted, so a close plays out. */
|
|
43
45
|
const [settled, setSettled] = useState(!open);
|
|
44
46
|
/* A width that lands without a transition fires no `transitionend`, so the motion it would have
|
|
@@ -48,7 +50,14 @@ export function SlidingColumn({ open, width, bounds, onWidthChange, instant = fa
|
|
|
48
50
|
}
|
|
49
51
|
const present = open || !settled;
|
|
50
52
|
const column = useRef<HTMLElement>(null);
|
|
51
|
-
const
|
|
53
|
+
const spacing = Math.max(0, Math.round(gap));
|
|
54
|
+
const { startResize } = useColumnResize(column, {
|
|
55
|
+
min: bounds.min + spacing,
|
|
56
|
+
max: () => bounds.max() + spacing,
|
|
57
|
+
size: width + spacing,
|
|
58
|
+
from: 'right',
|
|
59
|
+
onSize: (next) => onWidthChange(next - spacing)
|
|
60
|
+
});
|
|
52
61
|
|
|
53
62
|
useEffect(() => {
|
|
54
63
|
if (open || settled) {
|
|
@@ -66,8 +75,9 @@ export function SlidingColumn({ open, width, bounds, onWidthChange, instant = fa
|
|
|
66
75
|
ref={mergeRefs(column, ref)}
|
|
67
76
|
inert={!open}
|
|
68
77
|
data-instant={instant ? '' : undefined}
|
|
78
|
+
data-gap={spacing > 0 ? '' : undefined}
|
|
69
79
|
className={clsx('sliding-column flex h-full shrink-0 justify-end overflow-hidden', className)}
|
|
70
|
-
style={{ width: open ? width : 0 }}
|
|
80
|
+
style={{ width: open ? width + spacing : 0 }}
|
|
71
81
|
onTransitionEnd={(event) => {
|
|
72
82
|
if (event.propertyName === 'width' && event.target === event.currentTarget) {
|
|
73
83
|
setSettled(!open);
|
|
@@ -78,11 +88,18 @@ export function SlidingColumn({ open, width, bounds, onWidthChange, instant = fa
|
|
|
78
88
|
<div
|
|
79
89
|
ref={body?.ref}
|
|
80
90
|
tabIndex={body ? -1 : undefined}
|
|
81
|
-
className=
|
|
82
|
-
style={{ width }}
|
|
91
|
+
className={clsx('relative flex h-full shrink-0 flex-col bg-surface outline-none', spacing === 0 && 'border-l border-border')}
|
|
92
|
+
style={{ width, marginLeft: spacing }}
|
|
83
93
|
onKeyDown={body?.onKeyDown}
|
|
84
94
|
>
|
|
85
|
-
{open &&
|
|
95
|
+
{open && (
|
|
96
|
+
<ColumnResizeHandle
|
|
97
|
+
from="right"
|
|
98
|
+
className="sliding-column-resize"
|
|
99
|
+
style={spacing > 0 ? { left: -spacing, width: spacing } : undefined}
|
|
100
|
+
onPointerDown={startResize}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
86
103
|
{children}
|
|
87
104
|
</div>
|
|
88
105
|
)}
|
package/src/index.ts
CHANGED
|
@@ -122,3 +122,22 @@ export type {
|
|
|
122
122
|
TreeControlProps,
|
|
123
123
|
TreeCheckboxProps
|
|
124
124
|
} from './tree/parts.tsx';
|
|
125
|
+
|
|
126
|
+
export { Workspace, type WorkspaceProps } from './workspace/Workspace.tsx';
|
|
127
|
+
export type { WorkspaceLayout } from './workspace/context.ts';
|
|
128
|
+
export { DocumentTab, TabStrip, type DocumentTabProps, type TabStripProps, type TabStripItem } from './workspace/TabStrip.tsx';
|
|
129
|
+
export { SplitView, type SplitViewProps, type SplitViewInfo, type SplitViewBounds, type SplitDropTarget, type SplitDrag } from './workspace/SplitView.tsx';
|
|
130
|
+
export {
|
|
131
|
+
createSplitLayout,
|
|
132
|
+
normalizeSplitLayout,
|
|
133
|
+
updateSplitLayout,
|
|
134
|
+
splitPanes,
|
|
135
|
+
type SplitAxis,
|
|
136
|
+
type SplitSide,
|
|
137
|
+
type SplitPane,
|
|
138
|
+
type SplitBranch,
|
|
139
|
+
type SplitNode,
|
|
140
|
+
type SplitLayout,
|
|
141
|
+
type SplitCommand
|
|
142
|
+
} from './workspace/model.ts';
|
|
143
|
+
export type { SplitRect, SplitEdges, SplitMinimum } from './workspace/geometry.ts';
|
package/src/locales/en.json
CHANGED
|
@@ -64,5 +64,15 @@
|
|
|
64
64
|
"label": "Files",
|
|
65
65
|
"expand": "Expand",
|
|
66
66
|
"collapse": "Collapse"
|
|
67
|
+
},
|
|
68
|
+
"workspace": {
|
|
69
|
+
"tabs": "Open views",
|
|
70
|
+
"unsaved": "Unsaved changes",
|
|
71
|
+
"closeTab": "Close {{name}}",
|
|
72
|
+
"resize": "Resize panels",
|
|
73
|
+
"pane": "Panel",
|
|
74
|
+
"movePane": "Move panel",
|
|
75
|
+
"maximize": "Maximize panel",
|
|
76
|
+
"restore": "Restore panels"
|
|
67
77
|
}
|
|
68
78
|
}
|
package/src/locales/nl.json
CHANGED
|
@@ -64,5 +64,15 @@
|
|
|
64
64
|
"label": "Bestanden",
|
|
65
65
|
"expand": "Uitvouwen",
|
|
66
66
|
"collapse": "Invouwen"
|
|
67
|
+
},
|
|
68
|
+
"workspace": {
|
|
69
|
+
"tabs": "Open views",
|
|
70
|
+
"unsaved": "Niet-opgeslagen wijzigingen",
|
|
71
|
+
"closeTab": "Sluit {{name}}",
|
|
72
|
+
"resize": "Panelen vergroten of verkleinen",
|
|
73
|
+
"pane": "Paneel",
|
|
74
|
+
"movePane": "Paneel verplaatsen",
|
|
75
|
+
"maximize": "Paneel maximaliseren",
|
|
76
|
+
"restore": "Panelen herstellen"
|
|
67
77
|
}
|
|
68
78
|
}
|
package/src/theme.css
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
own clips its background to the padding box (see `@layer base`), or the border would blend
|
|
27
27
|
with that surface and never with the ground behind it. */
|
|
28
28
|
--border: rgb(0 0 0 / 0.11);
|
|
29
|
+
/* Opaque, so a gap has the same color on every surface. */
|
|
30
|
+
--separator: oklch(0.922 0.003 286.347);
|
|
29
31
|
--border-strong: rgb(0 0 0 / 0.2);
|
|
30
32
|
/* A hairline that stays out of the way: on a sunken field a full `--border` draws a box. */
|
|
31
33
|
--border-soft: rgb(0 0 0 / 0.06);
|
|
@@ -94,6 +96,7 @@
|
|
|
94
96
|
--surface-active: #28282e;
|
|
95
97
|
/* White at a low alpha, which is what gives a dark surface its lifted edge. */
|
|
96
98
|
--border: rgb(255 255 255 / 0.07);
|
|
99
|
+
--separator: oklch(0.236 0.006 285.956);
|
|
97
100
|
--border-strong: rgb(255 255 255 / 0.13);
|
|
98
101
|
--border-soft: rgb(255 255 255 / 0.03);
|
|
99
102
|
--text: #ececf1;
|
|
@@ -205,6 +208,7 @@
|
|
|
205
208
|
--color-surface-hover: var(--surface-hover);
|
|
206
209
|
--color-surface-active: var(--surface-active);
|
|
207
210
|
--color-border: var(--border);
|
|
211
|
+
--color-separator: var(--separator);
|
|
208
212
|
--color-border-strong: var(--border-strong);
|
|
209
213
|
--color-border-soft: var(--border-soft);
|
|
210
214
|
--color-text: var(--text);
|
|
@@ -567,6 +571,17 @@
|
|
|
567
571
|
|
|
568
572
|
/* A `SlidingColumn` opening and closing. A drag or a width restored on load lands at once. */
|
|
569
573
|
.sliding-column { transition: width 200ms cubic-bezier(0, 0, 0.2, 1); }
|
|
574
|
+
.sliding-column[data-gap] { background: var(--separator); }
|
|
575
|
+
.sliding-column-resize { touch-action: none; }
|
|
576
|
+
.sliding-column[data-gap] .sliding-column-resize::after {
|
|
577
|
+
content: '';
|
|
578
|
+
position: absolute;
|
|
579
|
+
inset-block: 0;
|
|
580
|
+
left: calc(50% - 1px);
|
|
581
|
+
width: 2px;
|
|
582
|
+
}
|
|
583
|
+
.sliding-column[data-gap] .sliding-column-resize:hover::after,
|
|
584
|
+
.sliding-column[data-gap][data-resizing] .sliding-column-resize::after { background: var(--accent); }
|
|
570
585
|
.sliding-column[data-resizing],
|
|
571
586
|
.sliding-column[data-instant] { transition: none; }
|
|
572
587
|
}
|
|
@@ -706,3 +721,169 @@
|
|
|
706
721
|
}
|
|
707
722
|
}
|
|
708
723
|
}
|
|
724
|
+
|
|
725
|
+
@layer components {
|
|
726
|
+
.ade-workspace {
|
|
727
|
+
display: flex;
|
|
728
|
+
width: 100%;
|
|
729
|
+
height: 100%;
|
|
730
|
+
min-width: 0;
|
|
731
|
+
min-height: 0;
|
|
732
|
+
overflow: hidden;
|
|
733
|
+
background: var(--separator);
|
|
734
|
+
--border: var(--separator);
|
|
735
|
+
}
|
|
736
|
+
.ade-workspace-center {
|
|
737
|
+
display: flex;
|
|
738
|
+
flex: 1;
|
|
739
|
+
min-width: 0;
|
|
740
|
+
min-height: 0;
|
|
741
|
+
flex-direction: column;
|
|
742
|
+
}
|
|
743
|
+
.ade-workspace-sidebar, .ade-workspace-side-panel {
|
|
744
|
+
flex: none;
|
|
745
|
+
min-width: 0;
|
|
746
|
+
min-height: 0;
|
|
747
|
+
overflow: hidden;
|
|
748
|
+
background: var(--surface);
|
|
749
|
+
}
|
|
750
|
+
.ade-workspace-sidebar { border-right: 1px solid var(--separator); }
|
|
751
|
+
.ade-workspace-side-panel { border-left: 1px solid var(--separator); }
|
|
752
|
+
.ade-workspace-toolbar {
|
|
753
|
+
flex: none;
|
|
754
|
+
overflow: hidden;
|
|
755
|
+
background: var(--surface);
|
|
756
|
+
border-bottom: 1px solid var(--separator);
|
|
757
|
+
}
|
|
758
|
+
.ade-workspace-content {
|
|
759
|
+
position: relative;
|
|
760
|
+
flex: 1;
|
|
761
|
+
min-width: 0;
|
|
762
|
+
min-height: 0;
|
|
763
|
+
overflow: hidden;
|
|
764
|
+
background: var(--surface);
|
|
765
|
+
}
|
|
766
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-center { gap: 8px; }
|
|
767
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-sidebar:not([data-auto]) { margin-right: 8px; }
|
|
768
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-side-panel:not([data-auto]) { margin-left: 8px; }
|
|
769
|
+
.ade-workspace-sidebar[data-auto], .ade-workspace-side-panel[data-auto] { display: contents; }
|
|
770
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-sidebar,
|
|
771
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-side-panel,
|
|
772
|
+
.ade-workspace[data-layout="roomy"] > .ade-workspace-center > .ade-workspace-toolbar { border: 0; }
|
|
773
|
+
.ade-workspace[data-layout="roomy"][data-sidebar] > .ade-workspace-center > .ade-workspace-toolbar { border-bottom-left-radius: 8px; }
|
|
774
|
+
.ade-workspace[data-layout="roomy"][data-side-panel] > .ade-workspace-center > .ade-workspace-toolbar { border-bottom-right-radius: 8px; }
|
|
775
|
+
.ade-split-view {
|
|
776
|
+
position: relative;
|
|
777
|
+
isolation: isolate;
|
|
778
|
+
width: 100%;
|
|
779
|
+
height: 100%;
|
|
780
|
+
min-width: 0;
|
|
781
|
+
min-height: 0;
|
|
782
|
+
overflow: hidden;
|
|
783
|
+
background: var(--separator);
|
|
784
|
+
--border: var(--separator);
|
|
785
|
+
}
|
|
786
|
+
.ade-split-pane, .ade-split-content {
|
|
787
|
+
position: absolute;
|
|
788
|
+
min-width: 0;
|
|
789
|
+
min-height: 0;
|
|
790
|
+
overflow: hidden;
|
|
791
|
+
background: var(--surface);
|
|
792
|
+
}
|
|
793
|
+
.ade-split-pane[hidden], .ade-split-content[hidden] { display: none; }
|
|
794
|
+
.ade-split-header {
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
min-width: 0;
|
|
798
|
+
border-bottom: 1px solid var(--separator);
|
|
799
|
+
}
|
|
800
|
+
.ade-split-content { outline-offset: -2px; }
|
|
801
|
+
.ade-split-view[data-dragging] > .ade-split-content { pointer-events: none; }
|
|
802
|
+
.ade-split-divider {
|
|
803
|
+
position: absolute;
|
|
804
|
+
z-index: 3;
|
|
805
|
+
touch-action: none;
|
|
806
|
+
outline-offset: 2px;
|
|
807
|
+
}
|
|
808
|
+
/* The 2px line also marks keyboard focus. */
|
|
809
|
+
.ade-split-divider:focus-visible { outline: none; }
|
|
810
|
+
.ade-split-divider[aria-orientation="vertical"] { cursor: col-resize; }
|
|
811
|
+
.ade-split-divider[aria-orientation="horizontal"] { cursor: row-resize; }
|
|
812
|
+
.ade-split-divider > span {
|
|
813
|
+
position: absolute;
|
|
814
|
+
inset: 0;
|
|
815
|
+
}
|
|
816
|
+
.ade-split-divider[aria-orientation="vertical"] > span { left: -3px; right: -3px; }
|
|
817
|
+
.ade-split-divider[aria-orientation="horizontal"] > span { top: -3px; bottom: -3px; }
|
|
818
|
+
.ade-split-view[data-layout="roomy"] > .ade-split-divider > span { inset: 0; }
|
|
819
|
+
.ade-split-divider::after { content: ""; position: absolute; pointer-events: none; }
|
|
820
|
+
.ade-split-divider[aria-orientation="vertical"]::after { inset-block: 0; left: -1px; width: 2px; }
|
|
821
|
+
.ade-split-divider[aria-orientation="horizontal"]::after { inset-inline: 0; top: -1px; height: 2px; }
|
|
822
|
+
.ade-split-view[data-layout="roomy"] > .ade-split-divider[aria-orientation="vertical"]::after { left: 3px; }
|
|
823
|
+
.ade-split-view[data-layout="roomy"] > .ade-split-divider[aria-orientation="horizontal"]::after { top: 3px; }
|
|
824
|
+
.ade-workspace[data-layout="roomy"][data-toolbar][data-sidebar] > .ade-workspace-center > .ade-workspace-content { border-top-left-radius: 8px; }
|
|
825
|
+
.ade-workspace[data-layout="roomy"][data-toolbar][data-side-panel] > .ade-workspace-center > .ade-workspace-content { border-top-right-radius: 8px; }
|
|
826
|
+
.ade-split-divider:is(:hover, :focus-visible, [data-resizing])::after { background: var(--accent); }
|
|
827
|
+
.ade-split-preview {
|
|
828
|
+
position: absolute;
|
|
829
|
+
inset: 0;
|
|
830
|
+
z-index: 4;
|
|
831
|
+
pointer-events: none;
|
|
832
|
+
overflow: visible;
|
|
833
|
+
}
|
|
834
|
+
.ade-split-preview path {
|
|
835
|
+
fill: color-mix(in srgb, var(--accent) 8%, transparent);
|
|
836
|
+
stroke: var(--accent);
|
|
837
|
+
stroke-width: 2px;
|
|
838
|
+
transition: d 150ms ease-out;
|
|
839
|
+
}
|
|
840
|
+
.ade-tab-strip {
|
|
841
|
+
position: relative;
|
|
842
|
+
display: flex;
|
|
843
|
+
flex: 1;
|
|
844
|
+
height: 100%;
|
|
845
|
+
min-width: 0;
|
|
846
|
+
align-items: stretch;
|
|
847
|
+
overflow-x: auto;
|
|
848
|
+
scrollbar-width: none;
|
|
849
|
+
}
|
|
850
|
+
.ade-tab-strip::-webkit-scrollbar { display: none; }
|
|
851
|
+
.ade-document-tab {
|
|
852
|
+
position: relative;
|
|
853
|
+
display: inline-flex;
|
|
854
|
+
flex: none;
|
|
855
|
+
align-items: center;
|
|
856
|
+
gap: 4px;
|
|
857
|
+
padding: 0 8px 0 12px;
|
|
858
|
+
color: var(--text-muted);
|
|
859
|
+
}
|
|
860
|
+
.ade-document-tab:hover { color: var(--text); background: var(--surface-raised); }
|
|
861
|
+
.ade-document-tab[data-active] { color: var(--text); }
|
|
862
|
+
.ade-document-tab[data-active]::after {
|
|
863
|
+
content: "";
|
|
864
|
+
position: absolute;
|
|
865
|
+
inset-inline: 0;
|
|
866
|
+
bottom: 0;
|
|
867
|
+
height: 2px;
|
|
868
|
+
background: var(--text-muted);
|
|
869
|
+
}
|
|
870
|
+
.ade-tab-strip[data-focused] .ade-document-tab[data-active]::after { background: var(--accent); }
|
|
871
|
+
.ade-document-tab-open {
|
|
872
|
+
display: inline-flex;
|
|
873
|
+
height: 100%;
|
|
874
|
+
min-width: 0;
|
|
875
|
+
max-width: 160px;
|
|
876
|
+
align-items: center;
|
|
877
|
+
gap: 6px;
|
|
878
|
+
font-size: var(--text-sm);
|
|
879
|
+
color: inherit;
|
|
880
|
+
outline-offset: -2px;
|
|
881
|
+
white-space: nowrap;
|
|
882
|
+
}
|
|
883
|
+
.ade-document-tab-close { opacity: 0; }
|
|
884
|
+
.ade-document-tab:is(:hover, :focus-within, [data-active]) .ade-document-tab-close { opacity: 1; }
|
|
885
|
+
.ade-document-tab[data-detail] .ade-document-tab-open { max-width: 200px; }
|
|
886
|
+
.ade-tab-unsaved { width: 6px; height: 6px; flex: none; border-radius: 999px; background: var(--text-muted); }
|
|
887
|
+
.ade-tab-insertion { position: absolute; top: 4px; bottom: 4px; width: 2px; pointer-events: none; background: var(--accent); }
|
|
888
|
+
@media (prefers-reduced-motion: reduce) { .ade-split-preview path { transition: none; } }
|
|
889
|
+
}
|
package/src/useColumnResize.ts
CHANGED
|
@@ -28,20 +28,6 @@ export const clampColumnSize = (options: Pick<ColumnResizeOptions, 'min' | 'max'
|
|
|
28
28
|
/* Where the pointer sits on the axis this edge belongs to. */
|
|
29
29
|
const along = (from: ColumnEdge, event: { clientX: number; clientY: number }): number => (from === 'left' || from === 'right' ? event.clientX : event.clientY);
|
|
30
30
|
|
|
31
|
-
/* The edge the column is pinned to, in page coordinates, or the window's own when there is no box yet. */
|
|
32
|
-
const anchorOf = (from: ColumnEdge, rect: DOMRect | undefined): number => {
|
|
33
|
-
switch (from) {
|
|
34
|
-
case 'left':
|
|
35
|
-
return rect?.left ?? 0;
|
|
36
|
-
case 'right':
|
|
37
|
-
return rect?.right ?? window.innerWidth;
|
|
38
|
-
case 'top':
|
|
39
|
-
return rect?.top ?? 0;
|
|
40
|
-
case 'bottom':
|
|
41
|
-
return rect?.bottom ?? window.innerHeight;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
31
|
/*
|
|
46
32
|
* One resizable column or row: a handle that drags the size its owner keeps. The handle takes the
|
|
47
33
|
* pointer capture, so the drag survives leaving the few pixels it is wide, and `[data-resizing]` on
|
|
@@ -52,23 +38,31 @@ export const useColumnResize = (ref: RefObject<HTMLElement | null>, options: Col
|
|
|
52
38
|
event.preventDefault();
|
|
53
39
|
const handle = event.currentTarget;
|
|
54
40
|
const column = ref.current;
|
|
55
|
-
const
|
|
41
|
+
const rect = column?.getBoundingClientRect();
|
|
42
|
+
const size = (options.from === 'left' || options.from === 'right' ? rect?.width : rect?.height) ?? options.size;
|
|
43
|
+
const start = along(options.from, event);
|
|
56
44
|
/* The far edge grows as the pointer comes towards it; the near edge grows as it goes away. */
|
|
57
45
|
const growsTowardsAnchor = options.from === 'right' || options.from === 'bottom';
|
|
58
46
|
column?.setAttribute('data-resizing', 'true');
|
|
59
47
|
const onMove = (move: PointerEvent): void => {
|
|
60
|
-
const
|
|
61
|
-
options.onSize(clampColumnSize(options, growsTowardsAnchor ?
|
|
48
|
+
const delta = along(options.from, move) - start;
|
|
49
|
+
options.onSize(clampColumnSize(options, size + (growsTowardsAnchor ? -delta : delta)));
|
|
62
50
|
};
|
|
63
51
|
const onUp = (): void => {
|
|
64
52
|
handle.removeEventListener('pointermove', onMove);
|
|
65
53
|
handle.removeEventListener('pointerup', onUp);
|
|
66
|
-
handle.
|
|
54
|
+
handle.removeEventListener('pointercancel', onUp);
|
|
55
|
+
handle.removeEventListener('lostpointercapture', onUp);
|
|
56
|
+
if (handle.hasPointerCapture(event.pointerId)) {
|
|
57
|
+
handle.releasePointerCapture(event.pointerId);
|
|
58
|
+
}
|
|
67
59
|
column?.removeAttribute('data-resizing');
|
|
68
60
|
};
|
|
69
61
|
handle.setPointerCapture(event.pointerId);
|
|
70
62
|
handle.addEventListener('pointermove', onMove);
|
|
71
63
|
handle.addEventListener('pointerup', onUp);
|
|
64
|
+
handle.addEventListener('pointercancel', onUp);
|
|
65
|
+
handle.addEventListener('lostpointercapture', onUp);
|
|
72
66
|
};
|
|
73
67
|
|
|
74
68
|
return { startResize };
|