@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/dist/wip.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as MessageQuotedPreviewAuthorDelegatedBy, Hi as DialogTitleProps, Ki as ButtonProps, Si as DialogClose, Ti as DialogContentProps, Ui as DialogTrigger, Xi as StyledProps, Yi as DataAttributes, a as MessageListJumpPillDismissProps, c as MessageListJumpPillProps, cn as MessageItemDelegatedAvatar, dn as MessageItemDelegatedByButton, fn as MessageItemDelegatedByButtonProps, hn as MessageItemDelegatedByProps, i as MessageListJumpPillDismiss, ln as MessageItemDelegatedAvatarProps, mn as MessageItemDelegatedByNameProps, n as MessageListJumpControlsProps, o as MessageListJumpPillGroup, pn as MessageItemDelegatedByName, r as MessageListJumpPill, s as MessageListJumpPillGroupProps, t as MessageListJumpControls, un as MessageItemDelegatedBy, w as MessageQuotedPreviewAuthorDelegatedByProps, yi as Dialog } from "./index-DsVVBZ5T.mjs";
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
- 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 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 };
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 { Bt as useOverflowTitle, Hn as DialogTitle, Nn as DialogClose, Pn as DialogContent, Qn as useTheme, Un as DialogTrigger, Wn as Button$1, Zn as useThemeScopeProps, a as MessageListJumpPillDismiss, dt as MessageItemDelegatedBy, er as tv, ft as MessageItemDelegatedByButton, h as MessageQuotedPreviewAuthorDelegatedBy, i as MessageListJumpPill, jn as Dialog, o as MessageListJumpPillGroup, pt as MessageItemDelegatedByName, qn as defaultNativeButton, r as MessageListJumpControls, t as useHoldThenMoveTouchSensor, ut as MessageItemDelegatedAvatar } from "./touch-drag-CbWqiMlZ.mjs";
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-row-hover active:bg-fill-pressed",
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:press-overlay active:press-overlay",
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
- 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, MessageItemDelegatedAvatar, MessageItemDelegatedBy, MessageItemDelegatedByButton, MessageItemDelegatedByName, MessageListJumpControls, MessageListJumpPill, MessageListJumpPillDismiss, MessageListJumpPillGroup, MessageQuotedPreviewAuthorDelegatedBy, resolveCommentMarginLayout, setCommentHighlights, useCommentMarginFits, useHoldThenMoveTouchSensor };
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.19.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",