@botiverse/raft-ui 0.80.0 → 0.81.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/CHANGELOG.md +21 -0
- package/dist/components/choice-list/choice-list.recipe.mjs +16 -3
- package/dist/components/composer/sortable-composer-attachments.mjs +2 -3
- package/dist/components/drag/drag-lift.d.mts +10 -6
- package/dist/components/drag/drag-lift.mjs +11 -9
- package/dist/components/scroll-area/scroll-area.css +15 -2
- package/dist/components/tabs/tabs.mjs +2 -3
- package/dist/components/task-dnd/task-dnd.d.mts +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [0.81.0](https://github.com/botiverse/raft-design/compare/v0.80.0...v0.81.0) (2026-11-09)
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **ScrollArea's scrollbars paint over everything inside the area**: the gutters layer is at the highest `z-index`, and the area's root is a stacking context of its own (`isolation: isolate`), so a sticky heading or any other positioned content inside the area, whatever its `z-index`, no longer covers the thumb, with sticky and absolute gutters alike, and the scrollbars still never paint over anything outside the area.
|
|
6
|
+
- Why: in the Raft app's activity list each day's sticky heading (an opaque background at z-15) painted over the thumb as it slid by, cutting it into pieces, while the gutters were at z-1 (Artea, #proj-raft-ui:21bae128). Starting the scrollbar below the stuck heading (`railInset`) fixed only the heading at the top, not the ones scrolling past.
|
|
7
|
+
- Affects: content inside a ScrollArea can no longer cover anything outside it, whatever its `z-index`: a popup, a menu or a dragged copy rendered in place inside an area stacks with the area. Render those through a portal (raft-ui's overlays do, and now `DragLift` by default, below). The scrollbars' strip at the edge now takes the pointer over positioned content there too, as it already did over the rest. How the scrollbars look is unchanged.
|
|
8
|
+
- Docs: https://raft-design.botiverse.build/components/scroll-area/
|
|
9
|
+
- **`DragLift` renders the copy in `document.body` by default**: the lifted copy goes through a portal to the body unless `container` names another element, or `null` keeps it in place; the portal keeps the theme scope it was rendered in. `TaskDragOverlay` follows, as `SortableTabsList` and `SortableComposerAttachments` already did.
|
|
10
|
+
- Why: in place inside a ScrollArea, now a stacking context, the copy couldn't rise above anything outside the area. On the Raft app's Tasks board a dragged card was cut off by the left rail and covered by the top bar (`TaskDragOverlay` with no `container`).
|
|
11
|
+
- Affects: a `DragLift` or `TaskDragOverlay` without `container` now renders its copy in the body: CSS that styles the copy through an ancestor of the item (`.board [data-slot=drag-lift]`) no longer reaches it, and neither do CSS variables set on that ancestor; style the copy through its own children. The writing direction (`dir`) is carried over, below. Pass `container={null}` for the old in-place copy. How the copy looks is unchanged.
|
|
12
|
+
- Docs: https://raft-design.botiverse.build/components/drag/#anatomy
|
|
13
|
+
- **In a right-to-left layout the dragged copy keeps its direction**: `DragLift`'s portal carries the item's computed writing direction (`dir`) to the body, as it carries the theme scope.
|
|
14
|
+
- Why: in the body, the copy took its direction from `<html>`, so an item dragged out of a right-to-left container came out left-to-right (Jasper's review of #1094).
|
|
15
|
+
- Affects: a copy of an item in a `dir="rtl"` container now lays out right-to-left. Nothing else around the item reaches the copy (font, colour, a container's custom properties); the docs now say so.
|
|
16
|
+
- Docs: https://raft-design.botiverse.build/components/drag/#anatomy
|
|
17
|
+
- **ChoiceList's row actions keep clear of the scrollbar**: in a list that scrolls (search on, or `fill`), a row's action sits 12px from the end instead of 4px, and rows with an action keep their text 52px from the end instead of 44px.
|
|
18
|
+
- Why: the scrollbar's 16px strip at the end of a ScrollArea now paints over and takes the pointer from everything in the area; the last 8px of each 32px action lay under it, so a click there paged the list instead.
|
|
19
|
+
- Affects: in a scrolling ChoiceList, each action moves 8px toward the start, and a long name or description cuts off 8px sooner. A list that doesn't scroll is unchanged.
|
|
20
|
+
- Docs: https://raft-design.botiverse.build/components/choice-list/#behavior
|
|
21
|
+
|
|
1
22
|
## [0.80.0](https://github.com/botiverse/raft-design/compare/v0.79.0...v0.80.0) (2026-11-09)
|
|
2
23
|
|
|
3
24
|
### Changed
|
|
@@ -31,14 +31,27 @@ const choiceList = tv({
|
|
|
31
31
|
footer: ["shrink-0 border-t border-line-seam p-1"]
|
|
32
32
|
},
|
|
33
33
|
variants: {
|
|
34
|
-
scroll: { true: {
|
|
34
|
+
scroll: { true: {
|
|
35
|
+
scroller: ["max-h-72"],
|
|
36
|
+
action: ["pr-3"]
|
|
37
|
+
} },
|
|
35
38
|
fill: { true: {
|
|
36
39
|
root: ["flex min-h-0 flex-1 flex-col"],
|
|
37
40
|
frame: ["min-h-0 flex-1"],
|
|
38
|
-
scroller: ["flex-1"]
|
|
41
|
+
scroller: ["flex-1"],
|
|
42
|
+
action: ["pr-3"]
|
|
39
43
|
} },
|
|
40
44
|
hasAction: { true: { item: ["pr-11"] } }
|
|
41
|
-
}
|
|
45
|
+
},
|
|
46
|
+
compoundVariants: [{
|
|
47
|
+
scroll: true,
|
|
48
|
+
hasAction: true,
|
|
49
|
+
class: { item: ["pr-13"] }
|
|
50
|
+
}, {
|
|
51
|
+
fill: true,
|
|
52
|
+
hasAction: true,
|
|
53
|
+
class: { item: ["pr-13"] }
|
|
54
|
+
}]
|
|
42
55
|
});
|
|
43
56
|
//#endregion
|
|
44
57
|
export { choiceList };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComposerAttachmentsScrollArea } from "./composer-attachments-scroll-area.mjs";
|
|
2
2
|
import { createDragAnnouncements, dragInstructions } from "../drag/drag-announcements.mjs";
|
|
3
|
-
import { DragLift, useDragSiblingTransition
|
|
3
|
+
import { DragLift, useDragSiblingTransition } from "../drag/drag-lift.mjs";
|
|
4
4
|
import { useDragSensors } from "../drag/drag-sensors.mjs";
|
|
5
5
|
import { DropPlaceholder } from "../drag/drop-placeholder.mjs";
|
|
6
6
|
import { Children, createContext, isValidElement, use, useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
@@ -87,7 +87,6 @@ function SortableComposerAttachmentsRoot({ items, onReorder, children, messages,
|
|
|
87
87
|
const draftIdsRef = useRef([]);
|
|
88
88
|
const lastReorderPointerRef = useRef(null);
|
|
89
89
|
const sensors = useDragSensors();
|
|
90
|
-
const liftContainer = useLiftContainer(container);
|
|
91
90
|
const attachments = useMemo(() => attachmentsById(children), [children]);
|
|
92
91
|
const itemIds = useMemo(() => items.map(String), [items]);
|
|
93
92
|
const [announced] = useState(() => ({
|
|
@@ -186,7 +185,7 @@ function SortableComposerAttachmentsRoot({ items, onReorder, children, messages,
|
|
|
186
185
|
})
|
|
187
186
|
}), /* @__PURE__ */ jsx(DragLift, {
|
|
188
187
|
className: "rounded-control",
|
|
189
|
-
container
|
|
188
|
+
container,
|
|
190
189
|
children: activeId && overlay ? overlay : null
|
|
191
190
|
})]
|
|
192
191
|
});
|
|
@@ -50,8 +50,8 @@ export declare function keepGrabInside(proxyWidth: number, inset?: number): Modi
|
|
|
50
50
|
export declare function cancelDrag(win?: Window): void;
|
|
51
51
|
/**
|
|
52
52
|
* Above every raft-ui layer (popovers 50, a stacked dialog 60, toasts and the
|
|
53
|
-
* lightbox 70) and an app's own top layers
|
|
54
|
-
*
|
|
53
|
+
* lightbox 70) and an app's own top layers, among the layers rendered in
|
|
54
|
+
* `document.body`, where the copy goes by default. dnd-kit's own default.
|
|
55
55
|
*/
|
|
56
56
|
export declare const DRAG_LIFT_Z_INDEX = 999;
|
|
57
57
|
export type DragLiftProps = {
|
|
@@ -74,10 +74,14 @@ export type DragLiftProps = {
|
|
|
74
74
|
*/
|
|
75
75
|
autoScroll?: boolean;
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
77
|
+
* Where the copy renders: `document.body` by default, so no ancestor holds
|
|
78
|
+
* it in, neither a stacking context (a ScrollArea, an `isolate`) it couldn't
|
|
79
|
+
* rise out of nor a transform (a drawer, a dialog) that would offset a
|
|
80
|
+
* `position: fixed` copy. Another element renders it there, and `null` in
|
|
81
|
+
* place. It keeps the theme scope it was rendered in (`light` / `dark`), as
|
|
82
|
+
* raft-ui's portals do, and the item's writing direction (`dir`). Nothing
|
|
83
|
+
* else is inherited from where it was picked up (font, colour, custom
|
|
84
|
+
* properties a container sets): give the copy its own styles.
|
|
81
85
|
*/
|
|
82
86
|
container?: Element | DocumentFragment | null;
|
|
83
87
|
/** @default DRAG_LIFT_Z_INDEX (999) */
|
|
@@ -110,17 +110,16 @@ function cancelDrag(win = window) {
|
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Above every raft-ui layer (popovers 50, a stacked dialog 60, toasts and the
|
|
113
|
-
* lightbox 70) and an app's own top layers
|
|
114
|
-
*
|
|
113
|
+
* lightbox 70) and an app's own top layers, among the layers rendered in
|
|
114
|
+
* `document.body`, where the copy goes by default. dnd-kit's own default.
|
|
115
115
|
*/
|
|
116
116
|
const DRAG_LIFT_Z_INDEX = 999;
|
|
117
117
|
const noSubscribe = () => () => {};
|
|
118
118
|
const clientBody = () => document.body;
|
|
119
119
|
const serverBody = () => null;
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* matches), `null` is in place, and an element is that element.
|
|
121
|
+
* Where the copy goes: `undefined` is the body (once on the client, so
|
|
122
|
+
* hydration matches), `null` is in place, and an element is that element.
|
|
124
123
|
*/
|
|
125
124
|
function useLiftContainer(container) {
|
|
126
125
|
const body = useSyncExternalStore(noSubscribe, clientBody, serverBody);
|
|
@@ -141,8 +140,10 @@ function useLiftContainer(container) {
|
|
|
141
140
|
function DragLift({ children, className, style, modifiers, autoScroll = true, container, zIndex = 999 }) {
|
|
142
141
|
const { lift } = drag();
|
|
143
142
|
const { resolvedMode } = useTheme();
|
|
143
|
+
const target = useLiftContainer(container);
|
|
144
144
|
const reduced = useSyncExternalStore(subscribeReducedMotion, reducedMotion, () => false);
|
|
145
|
-
const { activatorEvent } = useDndContext();
|
|
145
|
+
const { activatorEvent, activeNode } = useDndContext();
|
|
146
|
+
const dir = useMemo(() => activeNode ? getComputedStyle(activeNode).direction : void 0, [activeNode]);
|
|
146
147
|
const liftStyle = useMemo(() => isKeyboardEvent(activatorEvent) ? {
|
|
147
148
|
translate: KEYBOARD_OFFSET,
|
|
148
149
|
...style
|
|
@@ -162,11 +163,12 @@ function DragLift({ children, className, style, modifiers, autoScroll = true, co
|
|
|
162
163
|
})
|
|
163
164
|
})
|
|
164
165
|
});
|
|
165
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DragSession, { autoScroll }),
|
|
166
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DragSession, { autoScroll }), target ? createPortal(/* @__PURE__ */ jsx("div", {
|
|
166
167
|
"data-slot": "drag-lift-portal",
|
|
167
168
|
className: cn("contents", resolvedMode),
|
|
169
|
+
dir,
|
|
168
170
|
children: overlay
|
|
169
|
-
}),
|
|
171
|
+
}), target) : overlay] });
|
|
170
172
|
}
|
|
171
173
|
/** Where the pointer is during a drag: auto-scroll and the refusal hint read it. */
|
|
172
174
|
const pointer = {
|
|
@@ -273,4 +275,4 @@ function autoScrollAt(x, y) {
|
|
|
273
275
|
if (dy) window.scrollBy(0, dy);
|
|
274
276
|
}
|
|
275
277
|
//#endregion
|
|
276
|
-
export { DRAG_AUTO_SCROLL_ZONE_PX, DRAG_KEYBOARD_OFFSET_PX, DRAG_LIFT_Z_INDEX, DRAG_SETTLE, DRAG_SIBLING_TRANSITION, DROP_GLIDE, DragLift, LIFT_CLASS, ORIGIN_OPACITY, autoScrollAt, cancelDrag, dragPointer, keepGrabInside, useDragSiblingTransition
|
|
278
|
+
export { DRAG_AUTO_SCROLL_ZONE_PX, DRAG_KEYBOARD_OFFSET_PX, DRAG_LIFT_Z_INDEX, DRAG_SETTLE, DRAG_SIBLING_TRANSITION, DROP_GLIDE, DragLift, LIFT_CLASS, ORIGIN_OPACITY, autoScrollAt, cancelDrag, dragPointer, keepGrabInside, useDragSiblingTransition };
|
|
@@ -452,11 +452,24 @@
|
|
|
452
452
|
resize: vertical;
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
/*
|
|
455
|
+
/*
|
|
456
|
+
* The scrollbars paint over everything in the area, as native ones do: a
|
|
457
|
+
* sticky day heading with its own background and `z-index` (z-15 in an
|
|
458
|
+
* activity list) covered the thumb as it slid by, cutting it into pieces.
|
|
459
|
+
* The root is a stacking context of its own (`isolation: isolate`), so the
|
|
460
|
+
* layer's z-index ranks it only among the area's own content and never
|
|
461
|
+
* lifts it over anything outside the area: a fixed bar, a popup. What
|
|
462
|
+
* must cover things outside the area portals out of it, as raft-ui's
|
|
463
|
+
* overlays do.
|
|
464
|
+
*/
|
|
465
|
+
.r-scroll-area {
|
|
466
|
+
isolation: isolate;
|
|
467
|
+
}
|
|
468
|
+
/* The gutters layer: out of the way of the content, but for each gutter itself; above all of it. */
|
|
456
469
|
.r-scroll-area > .r-scroll-area__gutters {
|
|
457
470
|
pointer-events: none;
|
|
458
471
|
user-select: none;
|
|
459
|
-
z-index:
|
|
472
|
+
z-index: 2147483647;
|
|
460
473
|
}
|
|
461
474
|
/*
|
|
462
475
|
* Sticky: a zero-height layer at the top of the viewport, the container's
|
|
@@ -2,7 +2,7 @@ import { tv } from "../../lib/tv.mjs";
|
|
|
2
2
|
import { useMergedRef } from "../../lib/use-merged-ref.mjs";
|
|
3
3
|
import { useTapActivation } from "../../lib/tap-activation.mjs";
|
|
4
4
|
import { createDragAnnouncements, dragInstructions } from "../drag/drag-announcements.mjs";
|
|
5
|
-
import { DragLift, useDragSiblingTransition
|
|
5
|
+
import { DragLift, useDragSiblingTransition } from "../drag/drag-lift.mjs";
|
|
6
6
|
import { useDragSensors } from "../drag/drag-sensors.mjs";
|
|
7
7
|
import { DropPlaceholder } from "../drag/drop-placeholder.mjs";
|
|
8
8
|
import { createContext, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
@@ -275,7 +275,6 @@ function SortableTabsList({ value, onReorder, onDragStart, messages, container,
|
|
|
275
275
|
const sensors = useDragSensors();
|
|
276
276
|
const [copies] = useState(() => /* @__PURE__ */ new Map());
|
|
277
277
|
const [nodes] = useState(() => /* @__PURE__ */ new Map());
|
|
278
|
-
const liftContainer = useLiftContainer(container);
|
|
279
278
|
const selectedValue = use(TabsValueContext);
|
|
280
279
|
const { tab: tabClassName, content: contentClassName } = tabs({
|
|
281
280
|
variant,
|
|
@@ -353,7 +352,7 @@ function SortableTabsList({ value, onReorder, onDragStart, messages, container,
|
|
|
353
352
|
})
|
|
354
353
|
}), /* @__PURE__ */ jsx(DragLift, {
|
|
355
354
|
className: "rounded-control",
|
|
356
|
-
container
|
|
355
|
+
container,
|
|
357
356
|
children: activeId === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
358
357
|
"data-slot": "tabs-tab",
|
|
359
358
|
"data-active": selectedValue === activeId ? "" : void 0,
|
|
@@ -62,7 +62,11 @@ export declare function TaskDropPlaceholder({ id, placement, className, render,
|
|
|
62
62
|
export type TaskDragOverlayProps = {
|
|
63
63
|
children?: ReactNode;
|
|
64
64
|
className?: string;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Where the copy renders: `document.body` by default, so it rises above
|
|
67
|
+
* everything outside a ScrollArea the board is in. `null` renders it in
|
|
68
|
+
* place (`DragLift`'s `container`).
|
|
69
|
+
*/
|
|
66
70
|
container?: Element | DocumentFragment | null;
|
|
67
71
|
};
|
|
68
72
|
/**
|