@botiverse/raft-ui 0.19.0 → 0.21.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 +191 -3
- package/codemods/button-sizes.mjs +340 -0
- package/codemods/token-names.mjs +16 -0
- package/dist/cn.mjs +23 -2
- package/dist/components/combobox-select/combobox-select.css +58 -40
- package/dist/components/float-button/float-button.css +21 -12
- package/dist/foundation.css +66 -39
- package/dist/{index-DsVVBZ5T.d.mts → index-CP05_pvu.d.mts} +44 -2
- package/dist/index.d.mts +16 -25
- package/dist/index.mjs +213 -166
- package/dist/style.css +73 -58
- package/dist/styles.css +119 -24
- package/dist/tokens.json +90 -55
- package/dist/{touch-drag-CbWqiMlZ.mjs → touch-drag-B7uLTWXe.mjs} +265 -62
- package/dist/wip.d.mts +100 -3
- package/dist/wip.mjs +349 -5
- package/package.json +8 -1
package/dist/wip.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CSSProperties, ComponentPropsWithRef, PointerEvent, ReactNode } from "react";
|
|
1
|
+
import { Ai as DialogContentProps, C as MessageQuotedPreviewAuthorDelegatedBy, Di as DialogClose, Ji as DialogTrigger, Si as TextLinkProps, Zi as ButtonProps, _n as MessageItemDelegatedByProps, a as MessageListJumpPillDismissProps, c as MessageListJumpPillProps, dn as MessageItemDelegatedAvatarProps, fn as MessageItemDelegatedBy, gn as MessageItemDelegatedByNameProps, hn as MessageItemDelegatedByName, i as MessageListJumpPillDismiss, mn as MessageItemDelegatedByButtonProps, n as MessageListJumpControlsProps, na as DataAttributes, o as MessageListJumpPillGroup, pn as MessageItemDelegatedByButton, qi as DialogTitleProps, r as MessageListJumpPill, ra as StyledProps, s as MessageListJumpPillGroupProps, t as MessageListJumpControls, un as MessageItemDelegatedAvatar, w as MessageQuotedPreviewAuthorDelegatedByProps, wi as Dialog } from "./index-CP05_pvu.mjs";
|
|
2
|
+
import { CSSProperties, ComponentProps, ComponentPropsWithRef, PointerEvent, ReactNode, RefObject } from "react";
|
|
3
3
|
import { Button } from "@base-ui/react/button";
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
import { Combobox } from "@base-ui/react/combobox";
|
|
@@ -282,4 +282,101 @@ type HoldThenMoveOptions = {
|
|
|
282
282
|
*/
|
|
283
283
|
export declare function useHoldThenMoveTouchSensor({ holdMs, distance, tolerance }?: HoldThenMoveOptions): import("@dnd-kit/core").SensorDescriptor<TouchSensorOptions>;
|
|
284
284
|
//#endregion
|
|
285
|
-
|
|
285
|
+
//#region src/components/ellipsis/ellipsis.d.ts
|
|
286
|
+
/** What a function passed as `after` receives. */
|
|
287
|
+
interface EllipsisState {
|
|
288
|
+
/** The text is cut off right now. `false` while it is expanded. */
|
|
289
|
+
truncated: boolean;
|
|
290
|
+
expanded: boolean;
|
|
291
|
+
/** Expands the text, or collapses it again. */
|
|
292
|
+
toggle: () => void;
|
|
293
|
+
}
|
|
294
|
+
interface EllipsisProps extends Omit<ComponentProps<"span">, "children"> {
|
|
295
|
+
/**
|
|
296
|
+
* The text. Inline content only (text, links, code, mentions): a block
|
|
297
|
+
* child such as `<p>` or `<div>` breaks the clamp.
|
|
298
|
+
*/
|
|
299
|
+
children: ReactNode;
|
|
300
|
+
/** How many lines the text may take before it is cut. */
|
|
301
|
+
lines?: number;
|
|
302
|
+
/**
|
|
303
|
+
* The tooltip shown on hover, and on keyboard focus when the text is a
|
|
304
|
+
* button, while the text is cut: `true` shows the children, a node shows
|
|
305
|
+
* that node, `false` shows none. It never opens on a text that fits, or on
|
|
306
|
+
* a touch screen.
|
|
307
|
+
*/
|
|
308
|
+
tooltip?: ReactNode;
|
|
309
|
+
/**
|
|
310
|
+
* Shown after the text, at the end of the last visible line, only while the
|
|
311
|
+
* text is cut or expanded; it takes no room while the text fits. A function
|
|
312
|
+
* receives the state, for an expand control (`EllipsisToggle`).
|
|
313
|
+
*/
|
|
314
|
+
after?: ReactNode | ((state: EllipsisState) => ReactNode);
|
|
315
|
+
/** Whether the whole text shows (controlled). */
|
|
316
|
+
expanded?: boolean;
|
|
317
|
+
/** Whether the whole text shows at first (uncontrolled). */
|
|
318
|
+
defaultExpanded?: boolean;
|
|
319
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
320
|
+
/**
|
|
321
|
+
* Makes the cut text itself the button that expands it and collapses it
|
|
322
|
+
* again, for a keyboard and a touch screen as well as a pointer. Only plain
|
|
323
|
+
* text with no `after` takes it: a text with a link or another control
|
|
324
|
+
* inside stays text, and so does one with an `after`, whose control is the
|
|
325
|
+
* button instead (a button can't hold another).
|
|
326
|
+
*/
|
|
327
|
+
expandOnClick?: boolean;
|
|
328
|
+
}
|
|
329
|
+
export declare function Ellipsis({ children, lines, tooltip, after, expanded: expandedProp, defaultExpanded, onExpandedChange, expandOnClick, className, style, ...props }: EllipsisProps): import("react").JSX.Element;
|
|
330
|
+
type EllipsisToggleProps = Omit<TextLinkProps, "aria-expanded" | "aria-controls">;
|
|
331
|
+
/**
|
|
332
|
+
* The control that expands an Ellipsis and collapses it again, for its
|
|
333
|
+
* `after`. A real button, with `aria-expanded` and `aria-controls` pointing
|
|
334
|
+
* at the text. The label is the caller's: "Show More" / "Show Less".
|
|
335
|
+
*/
|
|
336
|
+
export declare function EllipsisToggle({ onClick, ...props }: EllipsisToggleProps): import("react").JSX.Element;
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/lib/truncation.d.ts
|
|
339
|
+
/**
|
|
340
|
+
* Whether a text is cut off, and when to look again: the one way raft-ui
|
|
341
|
+
* measures truncation (Ellipsis, `useTruncated`, and the `title` that
|
|
342
|
+
* fields give a cut value).
|
|
343
|
+
*
|
|
344
|
+
* The truncation itself is CSS (`text-overflow: ellipsis`, `line-clamp`);
|
|
345
|
+
* this module only answers "did it cut?".
|
|
346
|
+
*
|
|
347
|
+
* - It compares fractional geometry with a 0.5px tolerance. `scrollWidth` and
|
|
348
|
+
* `clientWidth` are whole pixels, so at some widths (and page zooms) a text
|
|
349
|
+
* that shows an ellipsis reports that it fits.
|
|
350
|
+
* - Every measured element on a page shares one ResizeObserver, one
|
|
351
|
+
* MutationObserver and one font-loading listener. A web font that finishes
|
|
352
|
+
* loading widens the text without resizing its box, which no
|
|
353
|
+
* ResizeObserver would notice.
|
|
354
|
+
* - Requests are batched into one animation frame, before paint: every
|
|
355
|
+
* target is isolated (an Ellipsis hides its `after` so the text is measured
|
|
356
|
+
* alone), then every target is read, then restored, then every result is
|
|
357
|
+
* committed in one synchronous React update. Reads never interleave with
|
|
358
|
+
* writes, and a result shows in the same frame it was measured in. A
|
|
359
|
+
* request made while a frame is handled is queued for the next one.
|
|
360
|
+
*/
|
|
361
|
+
/** `undefined`: not known yet, or the element has no box (hidden, not laid out). */
|
|
362
|
+
type Truncation = boolean | undefined;
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/lib/use-truncated.d.ts
|
|
365
|
+
/**
|
|
366
|
+
* Whether the text in the element `ref` points at is cut off by its CSS
|
|
367
|
+
* (`truncate`, `line-clamp`, a fixed size with `overflow: hidden`): `true`,
|
|
368
|
+
* `false`, or `undefined` until it is measured or while it has no box.
|
|
369
|
+
*
|
|
370
|
+
* It measures fractional sizes with a 0.5px tolerance, past either inline
|
|
371
|
+
* edge and below the bottom edge, so it needs no line count. It measures again
|
|
372
|
+
* when the element changes size, when its text changes and when a web font
|
|
373
|
+
* finishes loading, in one batched frame for every measured element on the
|
|
374
|
+
* page (lib/truncation.ts), before that frame paints.
|
|
375
|
+
*
|
|
376
|
+
* The element is read when the component mounts: keep `ref` on one element
|
|
377
|
+
* for the component's life, or give the component a `key` that changes with
|
|
378
|
+
* the element.
|
|
379
|
+
*/
|
|
380
|
+
export declare function useTruncated(ref: RefObject<Element | null>): Truncation;
|
|
381
|
+
//#endregion
|
|
382
|
+
export { Dialog as AnnouncementDialog, DialogClose as AnnouncementDialogClose, DialogTrigger as AnnouncementDialogTrigger, type ComboboxSelectGroup, type ComboboxSelectMessages, type ComboboxSelectOption, type ComboboxSelectProps, type ComboboxSelectSelection, type ComboboxSelectValue, type CommentCardMetaProps, type CommentCardProps, type CommentCardQuoteProps, type CommentCardTriggerProps, type CommentEntryAuthorProps, type CommentEntryBodyProps, type CommentEntryHeaderProps, type CommentEntryProps, type CommentEntryTimeProps, type CommentMarginItemProps, type CommentMarginLayout, type CommentMarginLayoutItem, type CommentMarginLayoutOptions, type CommentMarginProps, type CommentMarkerProps, type EllipsisProps, type EllipsisState, type EllipsisToggleProps, type HoldThenMoveOptions, MessageItemDelegatedAvatar, type MessageItemDelegatedAvatarProps, MessageItemDelegatedBy, MessageItemDelegatedByButton, type MessageItemDelegatedByButtonProps, MessageItemDelegatedByName, type MessageItemDelegatedByNameProps, type MessageItemDelegatedByProps, MessageListJumpControls, type MessageListJumpControlsProps, MessageListJumpPill, MessageListJumpPillDismiss, type MessageListJumpPillDismissProps, MessageListJumpPillGroup, type MessageListJumpPillGroupProps, type MessageListJumpPillProps, MessageQuotedPreviewAuthorDelegatedBy, type MessageQuotedPreviewAuthorDelegatedByProps, type UseCommentMarginFitsOptions };
|
package/dist/wip.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { An as Tooltip, Bn as DialogClose, Fn as TextLink, Gt as readNow, Ht as measureContent, Pn as TooltipTrigger, Rn as Dialog, Ut as measureElement, Vn as DialogContent, Vt as useOverflowTitle, Wt as observeTruncation, Xn as DialogTrigger, Yn as DialogTitle, Zn as Button$1, a as MessageListJumpPillDismiss, ar as useTheme, dt as MessageItemDelegatedAvatar, er as defaultNativeButton, ft as MessageItemDelegatedBy, h as MessageQuotedPreviewAuthorDelegatedBy, i as MessageListJumpPill, ir as useThemeScopeProps, jn as TooltipContent, mt as MessageItemDelegatedByName, o as MessageListJumpPillGroup, pt as MessageItemDelegatedByButton, r as MessageListJumpControls, sr as tv, t as useHoldThenMoveTouchSensor } from "./touch-drag-B7uLTWXe.mjs";
|
|
2
2
|
import { Check, ChevronDown, LoaderCircle, Plus, X } from "lucide-react";
|
|
3
|
-
import { createContext, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { createContext, use, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Button } from "@base-ui/react/button";
|
|
6
6
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -651,7 +651,7 @@ const comment = tv({
|
|
|
651
651
|
cardTrigger: [
|
|
652
652
|
"absolute -inset-px z-10 cursor-pointer",
|
|
653
653
|
"rounded-container border-0 bg-transparent p-0",
|
|
654
|
-
"hover:bg-fill-
|
|
654
|
+
"hover:bg-fill-subtle active:bg-fill-medium",
|
|
655
655
|
"outline-none focus-visible:shadow-focus"
|
|
656
656
|
],
|
|
657
657
|
cardQuote: ["sr-only"],
|
|
@@ -674,7 +674,7 @@ const comment = tv({
|
|
|
674
674
|
"rounded-chip border border-line-hairline py-0 ps-[3px] pe-1.5",
|
|
675
675
|
"font-mono text-mono-meta font-bold tabular-nums text-foreground",
|
|
676
676
|
"bg-layer-card [--avatar-group-surface:var(--layer-card)]",
|
|
677
|
-
"cursor-pointer hover:
|
|
677
|
+
"cursor-pointer hover:overlay-medium active:overlay-medium",
|
|
678
678
|
"data-active:border-[1.5px] data-active:border-line-strong data-active:bg-brand-soft data-active:ps-[2.5px] data-active:pe-[5.5px]",
|
|
679
679
|
"data-popup-open:border-[1.5px] data-popup-open:border-line-strong data-popup-open:bg-brand-soft data-popup-open:ps-[2.5px] data-popup-open:pe-[5.5px]",
|
|
680
680
|
"data-active:[--avatar-group-surface:var(--brand-soft)] data-popup-open:[--avatar-group-surface:var(--brand-soft)]",
|
|
@@ -1123,4 +1123,348 @@ function useCommentMarginFits(element, { minWidth = 784 } = {}) {
|
|
|
1123
1123
|
return desktop && element !== null && width >= minWidth;
|
|
1124
1124
|
}
|
|
1125
1125
|
//#endregion
|
|
1126
|
-
|
|
1126
|
+
//#region src/components/ellipsis/ellipsis.tsx
|
|
1127
|
+
const ellipsis = tv({
|
|
1128
|
+
slots: {
|
|
1129
|
+
root: ["group/ellipsis min-w-0 max-w-full"],
|
|
1130
|
+
text: ["min-w-0"],
|
|
1131
|
+
content: [],
|
|
1132
|
+
spacer: ["float-end h-[calc(100%-1lh)] w-0", "data-hidden:hidden group-data-measuring/ellipsis:hidden"],
|
|
1133
|
+
after: ["data-hidden:hidden group-data-measuring/ellipsis:hidden"]
|
|
1134
|
+
},
|
|
1135
|
+
variants: {
|
|
1136
|
+
multiline: {
|
|
1137
|
+
true: {
|
|
1138
|
+
root: ["flex"],
|
|
1139
|
+
text: ["flex-1", "wrap-break-word"],
|
|
1140
|
+
after: ["float-end clear-end ps-1"]
|
|
1141
|
+
},
|
|
1142
|
+
false: {
|
|
1143
|
+
root: ["inline-flex items-baseline gap-x-1 align-bottom"],
|
|
1144
|
+
after: ["shrink-0"]
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
expanded: {
|
|
1148
|
+
true: {},
|
|
1149
|
+
false: {}
|
|
1150
|
+
},
|
|
1151
|
+
clickable: {
|
|
1152
|
+
true: { text: ["cursor-pointer"] },
|
|
1153
|
+
false: {}
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
compoundVariants: [
|
|
1157
|
+
{
|
|
1158
|
+
multiline: false,
|
|
1159
|
+
expanded: false,
|
|
1160
|
+
class: { text: ["truncate", "print:overflow-visible print:whitespace-normal"] }
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
multiline: false,
|
|
1164
|
+
expanded: true,
|
|
1165
|
+
class: { text: ["wrap-anywhere whitespace-normal"] }
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
multiline: true,
|
|
1169
|
+
expanded: false,
|
|
1170
|
+
class: { text: ["line-clamp-(--ellipsis-lines)", "print:line-clamp-none"] }
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
multiline: true,
|
|
1174
|
+
expanded: true,
|
|
1175
|
+
class: { text: ["line-clamp-none"] }
|
|
1176
|
+
}
|
|
1177
|
+
]
|
|
1178
|
+
});
|
|
1179
|
+
const EllipsisContext = createContext(null);
|
|
1180
|
+
const INTERACTIVE = "a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable='false']), [role='button'], [role='link']";
|
|
1181
|
+
const TOUCH_MEDIA_QUERY = "(hover: none), (pointer: coarse)";
|
|
1182
|
+
function isTouchScreen() {
|
|
1183
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(TOUCH_MEDIA_QUERY).matches;
|
|
1184
|
+
}
|
|
1185
|
+
function contains(ref, target) {
|
|
1186
|
+
return target instanceof Node && ref.current?.contains(target) === true;
|
|
1187
|
+
}
|
|
1188
|
+
function hasSelection(within) {
|
|
1189
|
+
const selection = within.ownerDocument.defaultView?.getSelection();
|
|
1190
|
+
return !!selection && !selection.isCollapsed && selection.anchorNode != null && within.contains(selection.anchorNode);
|
|
1191
|
+
}
|
|
1192
|
+
function useExpanded(expandedProp, defaultExpanded, onExpandedChange) {
|
|
1193
|
+
const [expandedState, setExpandedState] = useState(defaultExpanded);
|
|
1194
|
+
const expanded = expandedProp ?? expandedState;
|
|
1195
|
+
return [expanded, useCallback(() => {
|
|
1196
|
+
const next = !expanded;
|
|
1197
|
+
if (expandedProp === void 0) setExpandedState(next);
|
|
1198
|
+
onExpandedChange?.(next);
|
|
1199
|
+
}, [
|
|
1200
|
+
expanded,
|
|
1201
|
+
expandedProp,
|
|
1202
|
+
onExpandedChange
|
|
1203
|
+
])];
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Registers the text with the shared measurer (lib/truncation.ts) and keeps
|
|
1207
|
+
* its answer: whether the text, alone, is cut, and whether it has a control
|
|
1208
|
+
* inside. While it is expanded the last collapsed answer is kept, so the
|
|
1209
|
+
* text and its control stay what they were until it collapses and is
|
|
1210
|
+
* measured again. `layout` names what remounts or reclamps the text.
|
|
1211
|
+
*/
|
|
1212
|
+
function useTruncation(expanded, layout, onNotTruncated) {
|
|
1213
|
+
const rootRef = useRef(null);
|
|
1214
|
+
const textRef = useRef(null);
|
|
1215
|
+
const contentRef = useRef(null);
|
|
1216
|
+
const afterRef = useRef(null);
|
|
1217
|
+
const setTextRef = useCallback((node) => {
|
|
1218
|
+
textRef.current = node;
|
|
1219
|
+
}, []);
|
|
1220
|
+
const [truncation, setTruncation] = useState(void 0);
|
|
1221
|
+
const [interactive, setInteractive] = useState(false);
|
|
1222
|
+
const expandedRef = useRef(expanded);
|
|
1223
|
+
const targetRef = useRef(null);
|
|
1224
|
+
const commit = useCallback((next) => {
|
|
1225
|
+
setInteractive(contentRef.current?.querySelector(INTERACTIVE) != null);
|
|
1226
|
+
if (expandedRef.current) return;
|
|
1227
|
+
setTruncation(next);
|
|
1228
|
+
if (next !== true) onNotTruncated();
|
|
1229
|
+
}, [onNotTruncated]);
|
|
1230
|
+
useLayoutEffect(() => {
|
|
1231
|
+
expandedRef.current = expanded;
|
|
1232
|
+
const root = rootRef.current;
|
|
1233
|
+
const text = textRef.current;
|
|
1234
|
+
const content = contentRef.current;
|
|
1235
|
+
if (!root || !text || !content) return void 0;
|
|
1236
|
+
const target = {
|
|
1237
|
+
observe: () => [text, afterRef.current],
|
|
1238
|
+
watch: () => content,
|
|
1239
|
+
isolate: () => {
|
|
1240
|
+
root.setAttribute("data-measuring", "");
|
|
1241
|
+
return () => root.removeAttribute("data-measuring");
|
|
1242
|
+
},
|
|
1243
|
+
read: () => measureContent(text, content),
|
|
1244
|
+
commit
|
|
1245
|
+
};
|
|
1246
|
+
targetRef.current = target;
|
|
1247
|
+
return observeTruncation(target);
|
|
1248
|
+
}, [
|
|
1249
|
+
commit,
|
|
1250
|
+
expanded,
|
|
1251
|
+
layout
|
|
1252
|
+
]);
|
|
1253
|
+
return {
|
|
1254
|
+
rootRef,
|
|
1255
|
+
textRef,
|
|
1256
|
+
setTextRef,
|
|
1257
|
+
contentRef,
|
|
1258
|
+
afterRef,
|
|
1259
|
+
truncation,
|
|
1260
|
+
interactive,
|
|
1261
|
+
measureNow: useCallback(() => {
|
|
1262
|
+
const target = targetRef.current;
|
|
1263
|
+
if (!target) return void 0;
|
|
1264
|
+
const next = readNow(target);
|
|
1265
|
+
commit(next);
|
|
1266
|
+
return next;
|
|
1267
|
+
}, [commit])
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/** The tooltip's `onOpenChange`: it opens only on a text measured as cut, now. */
|
|
1271
|
+
function useOpenWhenTruncated(setOpen, expanded, afterRef, measureNow) {
|
|
1272
|
+
return useCallback((next, details) => {
|
|
1273
|
+
if (!next) {
|
|
1274
|
+
setOpen(false);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (isTouchScreen() || expanded) return;
|
|
1278
|
+
if (contains(afterRef, details.event?.target)) return;
|
|
1279
|
+
if (measureNow() === true) setOpen(true);
|
|
1280
|
+
}, [
|
|
1281
|
+
afterRef,
|
|
1282
|
+
expanded,
|
|
1283
|
+
measureNow,
|
|
1284
|
+
setOpen
|
|
1285
|
+
]);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Makes the text the button that expands and collapses it, while
|
|
1289
|
+
* `clickable`. It stays one while expanded, `aria-expanded="true"`, so focus
|
|
1290
|
+
* stays on it and it collapses again the same way.
|
|
1291
|
+
*/
|
|
1292
|
+
function useExpandOnClick(clickable, expanded, toggle, textRef) {
|
|
1293
|
+
const onClick = useCallback(() => {
|
|
1294
|
+
if (!clickable) return;
|
|
1295
|
+
if (textRef.current && hasSelection(textRef.current)) return;
|
|
1296
|
+
toggle();
|
|
1297
|
+
}, [
|
|
1298
|
+
clickable,
|
|
1299
|
+
textRef,
|
|
1300
|
+
toggle
|
|
1301
|
+
]);
|
|
1302
|
+
const onKeyDown = useCallback((event) => {
|
|
1303
|
+
if (!clickable || event.target !== event.currentTarget) return;
|
|
1304
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1305
|
+
event.preventDefault();
|
|
1306
|
+
toggle();
|
|
1307
|
+
}
|
|
1308
|
+
}, [clickable, toggle]);
|
|
1309
|
+
if (!clickable) return {};
|
|
1310
|
+
return {
|
|
1311
|
+
role: "button",
|
|
1312
|
+
tabIndex: 0,
|
|
1313
|
+
"aria-expanded": expanded,
|
|
1314
|
+
onClick,
|
|
1315
|
+
onKeyDown
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function useRootStyle(style, lines) {
|
|
1319
|
+
return useMemo(() => lines > 1 ? {
|
|
1320
|
+
...style,
|
|
1321
|
+
["--ellipsis-lines"]: String(lines)
|
|
1322
|
+
} : style, [style, lines]);
|
|
1323
|
+
}
|
|
1324
|
+
function Ellipsis({ children, lines = 1, tooltip = true, after, expanded: expandedProp, defaultExpanded = false, onExpandedChange, expandOnClick = false, className, style, ...props }) {
|
|
1325
|
+
const textId = useId();
|
|
1326
|
+
const [open, setOpen] = useState(false);
|
|
1327
|
+
const closeTooltip = useCallback(() => setOpen(false), []);
|
|
1328
|
+
const [expanded, toggle] = useExpanded(expandedProp, defaultExpanded, onExpandedChange);
|
|
1329
|
+
const multiline = lines > 1;
|
|
1330
|
+
const hasTooltip = tooltip !== false && tooltip != null;
|
|
1331
|
+
const hasAfter = after != null;
|
|
1332
|
+
const measured = useTruncation(expanded, `${lines} ${hasAfter} ${hasTooltip}`, closeTooltip);
|
|
1333
|
+
const { rootRef, textRef, setTextRef, contentRef, afterRef, truncation } = measured;
|
|
1334
|
+
const handleOpenChange = useOpenWhenTruncated(setOpen, expanded, afterRef, measured.measureNow);
|
|
1335
|
+
const truncated = truncation === true && !expanded;
|
|
1336
|
+
const clickable = expandOnClick && !hasAfter && !measured.interactive && (truncation === true || expanded);
|
|
1337
|
+
const afterNode = typeof after === "function" ? after({
|
|
1338
|
+
truncated,
|
|
1339
|
+
expanded,
|
|
1340
|
+
toggle
|
|
1341
|
+
}) : after;
|
|
1342
|
+
const afterHidden = !truncated && !expanded;
|
|
1343
|
+
const slots = ellipsis({
|
|
1344
|
+
multiline,
|
|
1345
|
+
expanded,
|
|
1346
|
+
clickable
|
|
1347
|
+
});
|
|
1348
|
+
const context = useMemo(() => ({
|
|
1349
|
+
expanded,
|
|
1350
|
+
toggle,
|
|
1351
|
+
textId
|
|
1352
|
+
}), [
|
|
1353
|
+
expanded,
|
|
1354
|
+
toggle,
|
|
1355
|
+
textId
|
|
1356
|
+
]);
|
|
1357
|
+
const rootStyle = useRootStyle(style, lines);
|
|
1358
|
+
const clickProps = useExpandOnClick(clickable, expanded, toggle, textRef);
|
|
1359
|
+
const afterElement = hasAfter ? /* @__PURE__ */ jsx("span", {
|
|
1360
|
+
ref: afterRef,
|
|
1361
|
+
"data-slot": "ellipsis-after",
|
|
1362
|
+
"data-hidden": afterHidden ? "" : void 0,
|
|
1363
|
+
inert: afterHidden,
|
|
1364
|
+
className: slots.after(),
|
|
1365
|
+
onPointerEnter: closeTooltip,
|
|
1366
|
+
children: afterNode
|
|
1367
|
+
}) : null;
|
|
1368
|
+
const floatingAfter = multiline && hasAfter;
|
|
1369
|
+
const textChildren = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1370
|
+
floatingAfter && /* @__PURE__ */ jsx("span", {
|
|
1371
|
+
"aria-hidden": true,
|
|
1372
|
+
"data-slot": "ellipsis-spacer",
|
|
1373
|
+
"data-hidden": afterHidden ? "" : void 0,
|
|
1374
|
+
className: slots.spacer()
|
|
1375
|
+
}),
|
|
1376
|
+
floatingAfter && afterElement,
|
|
1377
|
+
/* @__PURE__ */ jsx("span", {
|
|
1378
|
+
ref: contentRef,
|
|
1379
|
+
"data-slot": "ellipsis-content",
|
|
1380
|
+
className: slots.content(),
|
|
1381
|
+
children
|
|
1382
|
+
})
|
|
1383
|
+
] });
|
|
1384
|
+
const textProps = {
|
|
1385
|
+
id: textId,
|
|
1386
|
+
"data-slot": "ellipsis-text",
|
|
1387
|
+
"data-truncated": truncated ? "" : void 0,
|
|
1388
|
+
className: slots.text(),
|
|
1389
|
+
...clickProps
|
|
1390
|
+
};
|
|
1391
|
+
const text = hasTooltip ? /* @__PURE__ */ jsxs(Tooltip, {
|
|
1392
|
+
open: open && truncated,
|
|
1393
|
+
onOpenChange: handleOpenChange,
|
|
1394
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1395
|
+
ref: setTextRef,
|
|
1396
|
+
render: /* @__PURE__ */ jsx("span", {}),
|
|
1397
|
+
...textProps,
|
|
1398
|
+
className: slots.text({ class: "select-auto" }),
|
|
1399
|
+
children: textChildren
|
|
1400
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children: tooltip === true ? children : tooltip })]
|
|
1401
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
1402
|
+
ref: setTextRef,
|
|
1403
|
+
...textProps,
|
|
1404
|
+
children: textChildren
|
|
1405
|
+
});
|
|
1406
|
+
return /* @__PURE__ */ jsx(EllipsisContext.Provider, {
|
|
1407
|
+
value: context,
|
|
1408
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1409
|
+
ref: rootRef,
|
|
1410
|
+
"data-slot": "ellipsis",
|
|
1411
|
+
className: slots.root({ className }),
|
|
1412
|
+
style: rootStyle,
|
|
1413
|
+
...props,
|
|
1414
|
+
children: [text, !multiline ? afterElement : null]
|
|
1415
|
+
})
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* The control that expands an Ellipsis and collapses it again, for its
|
|
1420
|
+
* `after`. A real button, with `aria-expanded` and `aria-controls` pointing
|
|
1421
|
+
* at the text. The label is the caller's: "Show More" / "Show Less".
|
|
1422
|
+
*/
|
|
1423
|
+
function EllipsisToggle({ onClick, ...props }) {
|
|
1424
|
+
const context = useContext(EllipsisContext);
|
|
1425
|
+
if (!context) throw new Error("EllipsisToggle must be inside an Ellipsis `after`.");
|
|
1426
|
+
const { expanded, toggle, textId } = context;
|
|
1427
|
+
const handleClick = useCallback((event) => {
|
|
1428
|
+
onClick?.(event);
|
|
1429
|
+
if (!event.defaultPrevented) toggle();
|
|
1430
|
+
}, [onClick, toggle]);
|
|
1431
|
+
return /* @__PURE__ */ jsx(TextLink, {
|
|
1432
|
+
"aria-expanded": expanded,
|
|
1433
|
+
"aria-controls": textId,
|
|
1434
|
+
onClick: handleClick,
|
|
1435
|
+
...props
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/lib/use-truncated.ts
|
|
1440
|
+
/**
|
|
1441
|
+
* Whether the text in the element `ref` points at is cut off by its CSS
|
|
1442
|
+
* (`truncate`, `line-clamp`, a fixed size with `overflow: hidden`): `true`,
|
|
1443
|
+
* `false`, or `undefined` until it is measured or while it has no box.
|
|
1444
|
+
*
|
|
1445
|
+
* It measures fractional sizes with a 0.5px tolerance, past either inline
|
|
1446
|
+
* edge and below the bottom edge, so it needs no line count. It measures again
|
|
1447
|
+
* when the element changes size, when its text changes and when a web font
|
|
1448
|
+
* finishes loading, in one batched frame for every measured element on the
|
|
1449
|
+
* page (lib/truncation.ts), before that frame paints.
|
|
1450
|
+
*
|
|
1451
|
+
* The element is read when the component mounts: keep `ref` on one element
|
|
1452
|
+
* for the component's life, or give the component a `key` that changes with
|
|
1453
|
+
* the element.
|
|
1454
|
+
*/
|
|
1455
|
+
function useTruncated(ref) {
|
|
1456
|
+
const [truncated, setTruncated] = useState(void 0);
|
|
1457
|
+
useLayoutEffect(() => {
|
|
1458
|
+
const element = ref.current;
|
|
1459
|
+
if (!element) return void 0;
|
|
1460
|
+
return observeTruncation({
|
|
1461
|
+
observe: () => [element],
|
|
1462
|
+
watch: () => element,
|
|
1463
|
+
read: () => measureElement(element),
|
|
1464
|
+
commit: setTruncated
|
|
1465
|
+
});
|
|
1466
|
+
}, [ref]);
|
|
1467
|
+
return truncated;
|
|
1468
|
+
}
|
|
1469
|
+
//#endregion
|
|
1470
|
+
export { Dialog as AnnouncementDialog, AnnouncementDialogAction, AnnouncementDialogBody, DialogClose as AnnouncementDialogClose, AnnouncementDialogContent, AnnouncementDialogFooter, AnnouncementDialogHeader, AnnouncementDialogLabel, AnnouncementDialogPicture, AnnouncementDialogSecondaryAction, AnnouncementDialogText, AnnouncementDialogTitle, DialogTrigger as AnnouncementDialogTrigger, COMMENT_HIGHLIGHT, COMMENT_HIGHLIGHT_ACTIVE, ComboboxSelect, CommentCard, CommentCardMeta, CommentCardQuote, CommentCardTrigger, CommentEntry, CommentEntryAuthor, CommentEntryBody, CommentEntryHeader, CommentEntryTime, CommentMargin, CommentMarginItem, CommentMarker, Ellipsis, EllipsisToggle, MessageItemDelegatedAvatar, MessageItemDelegatedBy, MessageItemDelegatedByButton, MessageItemDelegatedByName, MessageListJumpControls, MessageListJumpPill, MessageListJumpPillDismiss, MessageListJumpPillGroup, MessageQuotedPreviewAuthorDelegatedBy, resolveCommentMarginLayout, setCommentHighlights, useCommentMarginFits, useHoldThenMoveTouchSensor, useTruncated };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botiverse/raft-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "React components and CSS foundations for the Raft Design System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"tailwind-variants": "^3.3.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@babel/parser": "^7.29.9",
|
|
65
66
|
"@tailwindcss/vite": "^4.3.3",
|
|
66
67
|
"@testing-library/dom": "^10.4.2",
|
|
67
68
|
"@testing-library/react": "^16.3.3",
|
|
@@ -82,9 +83,15 @@
|
|
|
82
83
|
"vitest": "5.0.1"
|
|
83
84
|
},
|
|
84
85
|
"peerDependencies": {
|
|
86
|
+
"@babel/parser": "^7.29.9",
|
|
85
87
|
"react": "^19",
|
|
86
88
|
"react-dom": "^19"
|
|
87
89
|
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@babel/parser": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
88
95
|
"scripts": {
|
|
89
96
|
"bench:virtual-list": "node bench/virtual-list/run.mjs",
|
|
90
97
|
"build:tokens": "node scripts/build-tokens.mjs",
|