Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion design-system/packages/ui/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useDismissibleLayer } from "../../overlay/useDismissibleLayer";
import { useFocusScope } from "../../overlay/useFocusScope";
import { usePresence } from "../../overlay/usePresence";
import { IconButton, type IconButtonProps } from "../IconButton";
import { inertProps } from "../../internal/inert";
import styles from "./Dialog.module.css";

const EXIT_DURATION_MS = 180;
Expand Down Expand Up @@ -183,7 +184,7 @@ const OverlaySurface = forwardRef<HTMLDivElement, OverlaySurfaceProps>(function
<DialogContext.Provider value={context}>
<div
{...surfaceProps}
{...(!open ? { inert: "" } : {})}
{...inertProps(!open)}
aria-describedby={ariaDescribedBy ?? (hasDescription ? descriptionId : undefined)}
aria-hidden={exiting || undefined}
aria-label={ariaLabel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { classNames } from "../../internal/classNames";
import { OverflowText } from "../../primitives/OverflowText";
import { Icon } from "../Icon";
import { inertProps, type InertProps } from "../../internal/inert";
import styles from "./Disclosure.module.css";

type DisclosureTriggerProps = Pick<ButtonHTMLAttributes<HTMLButtonElement>,
Expand Down Expand Up @@ -46,7 +47,7 @@ interface NativeDisclosureProps

export type DisclosureProps = CustomDisclosureProps | NativeDisclosureProps;

type InertContentAttributes = HTMLAttributes<HTMLDivElement> & { inert?: "" };
type InertContentAttributes = HTMLAttributes<HTMLDivElement> & InertProps;

const CustomDisclosure = forwardRef<HTMLElement, CustomDisclosureProps>(
function Disclosure({
Expand All @@ -73,7 +74,7 @@ const CustomDisclosure = forwardRef<HTMLElement, CustomDisclosureProps>(
const contentId = `openbitfun-disclosure-${generatedId}-content`;
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
const resolvedOpen = open ?? uncontrolledOpen;
const inertContentAttributes: InertContentAttributes = resolvedOpen ? {} : { inert: "" };
const inertContentAttributes: InertContentAttributes = inertProps(!resolvedOpen);

const [retained, setRetained] = useState(resolvedOpen);
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Portal } from "../../overlay/Portal";
import { useDismissibleLayer } from "../../overlay/useDismissibleLayer";
import { useFocusScope } from "../../overlay/useFocusScope";
import { usePresence } from "../../overlay/usePresence";
import { inertProps } from "../../internal/inert";
import styles from "./MenuPopover.module.css";

export interface MenuEntry {
Expand Down Expand Up @@ -204,7 +205,7 @@ function MenuLevel({ items, open, phase, treeId, onClose, onBack, anchorRef, pos
return <>
<MenuSurface {...props} ref={node => { (menuRef as { current: HTMLDivElement | null }).current = node; }} className={classNames(styles.popup, className)} inlineSize={inlineSize} autoFocusFirstItem={open && autoFocusFirstItem && Boolean(layout)} tabIndex={-1}
style={{ ...layout?.style, ...style, visibility: layout ? undefined : "hidden" }} data-openbitfun-native-webview-occlusion data-openbitfun-menu-tree={treeId} data-placement={layout?.placement ?? placement} data-state={phase}
aria-hidden={!open || undefined} {...(!open ? { inert: "" } : {})} onContextMenu={event => event.preventDefault()}>
aria-hidden={!open || undefined} {...inertProps(!open)} onContextMenu={event => event.preventDefault()}>
{items.map(item => item.separator ? <Separator key={item.id} /> : <Item key={item.id} data-menu-id={item.id} leading={item.icon ? <Leading className={styles.icon} data-openbitfun-icon-slot="true">{item.icon}</Leading> : undefined} shortcut={item.shortcut ? <Shortcut>{item.shortcut}</Shortcut> : undefined} tone={item.tone} role={item.role} checked={item.checked}
disabled={item.disabled} aria-disabled={item.disabled || undefined} aria-haspopup={item.submenu?.length ? "menu" : undefined} aria-expanded={item.submenu?.length ? activeEntry?.id === item.id : undefined}
aria-controls={activeEntry?.id === item.id ? submenuId : undefined} metadata={item.submenu?.length ? <Arrow className={styles.submenuArrow}><Icon name="chevron-right" size="sm" /></Arrow> : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type CSSProperties, type HTMLAttributes, type PointerEvent, type ReactNode,
} from 'react';
import { classNames } from '../../internal/classNames';
import { inertProps } from '../../internal/inert';
import styles from './SplitView.module.css';

export type SplitViewMode = 'split' | 'primary' | 'secondary';
Expand All @@ -16,7 +17,7 @@ export function SplitViewSurface({ pane, active = true, className, children, ...
return <div {...props} tabIndex={-1}
className={classNames(styles.pane, styles[pane], styles.surface, className)}
hidden={hidden} aria-hidden={hidden || !active}
{...(hidden || !active ? { inert: '' } : {})}
{...inertProps(Boolean(hidden || !active))}
data-split-surface={pane} data-split-active={!hidden && active}>
{children}
</div>;
Expand Down
12 changes: 11 additions & 1 deletion design-system/packages/ui/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useId,
useRef,
useState,
version as reactVersion,
type FocusEvent as ReactFocusEvent,
type MouseEvent as ReactMouseEvent,
type ReactElement,
Expand Down Expand Up @@ -73,6 +74,15 @@ function assignRef<T>(ref: Ref<T> | undefined, value: T | null): void {
(ref as { current: T | null }).current = value;
}

// React 19 passes `ref` as a regular prop and warns when `element.ref` is read.
const REF_IS_PROP = Number.parseInt(reactVersion, 10) >= 19;

function readElementRef(element: ReactElement | undefined): Ref<HTMLElement> | undefined {
if (!element) return undefined;
if (REF_IS_PROP) return (element.props as { ref?: Ref<HTMLElement> }).ref;
return (element as ReactElement & { ref?: Ref<HTMLElement> }).ref;
}

const OPPOSITE_PLACEMENT: Record<TooltipPlacement, TooltipPlacement> = {
top: "bottom",
bottom: "top",
Expand Down Expand Up @@ -475,7 +485,7 @@ export function Tooltip({
}, [stopWatchingScroll, triggerRef, visible]);

const childProps = (children?.props ?? {}) as Record<string, unknown>;
const childRef = (children as (ReactElement & { ref?: Ref<HTMLElement> }) | undefined)?.ref;
const childRef = readElementRef(children as ReactElement | undefined);

const handleTriggerRef = useCallback((node: HTMLElement | null) => {
internalTriggerRef.current = node;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useLayoutEffect, useRef, useState, type HTMLAttributes, type ReactNode } from 'react';
import { inertProps } from '../../internal/inert';
import './ConversationBlocks.css';
export const FLOWCHAT_COLLAPSE_DURATION_MS = 300;
export const FLOWCHAT_COLLAPSE_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
Expand Down Expand Up @@ -129,7 +130,7 @@ export const FlowChatCollapse = ({
transitionTimingFunction: `${FLOWCHAT_COLLAPSE_EASING}, ${FLOWCHAT_COLLAPSE_EASING}, ${FLOWCHAT_COLLAPSE_EASING}`,
}}
aria-hidden={!isOpen}
{...{ inert: !isOpen ? '' : undefined }}
{...inertProps(!isOpen)}
>
{shouldRender && (
<div ref={innerRef} className={`smooth-height-collapse__inner ${innerClassName}`.trim()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useThinkingAnnotation } from './useThinkingAnnotation';
import type { ThinkingContinuationResolver } from './useThinkingAnnotation';
import { useThinkingHandoff } from './useThinkingHandoff';
import { useThinkingSuccessor } from './useThinkingSuccessor';
import { inertProps } from '../../internal/inert';
import './ConversationBlocks.css';

const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
Expand Down Expand Up @@ -145,15 +146,15 @@ export const ThinkingBlock = forwardRef<HTMLDivElement, ThinkingBlockProps>(func
</label>
</div>
</div>
<div id={contentId} aria-hidden={!expanded} {...{ inert: !expanded ? '' : undefined }}
<div id={contentId} aria-hidden={!expanded} {...inertProps(!expanded)}
ref={expandContainerRef}
className={`thinking-expand-container${expanded ? ' thinking-expand-container--open' : ''}`}
data-openbitfun-component="model-thinking-display" data-openbitfun-part="expandContainer">
{mountContent && <div className={`thinking-content-wrapper ${scrollState.hasScroll ? 'has-scroll' : ''} ${scrollState.atTop ? 'at-top' : ''} ${scrollState.atBottom ? 'at-bottom' : ''}`}
data-openbitfun-component="model-thinking-display" data-openbitfun-part="contentWrapper">
<div {...contentProps} ref={contentRef} data-openbitfun-component="model-thinking-display"
data-openbitfun-part="content" data-testid="chat-thinking-content" data-status={status}
aria-hidden={compactViewport || undefined} {...{ inert: compactViewport ? '' : undefined }}
aria-hidden={compactViewport || undefined} {...inertProps(Boolean(compactViewport))}
data-streaming={streaming ? 'true' : 'false'} className={`thinking-content expanded ${contentProps?.className ?? ''}`.trim()}>
<div className="thinking-content-body">{children}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ToolCapsulePresentationProvider, useToolCapsulePresentation } from './T
import { TOOL_CAPSULE_COLLAPSE_DURATION_MS, useToolCapsuleMotion } from '../motion/capsuleMotion';
import { classNames } from "../../internal/classNames";
import { OverflowText } from "../../primitives/OverflowText";
import { inertProps } from "../../internal/inert";
import styles from "./FlowChatToolCard.module.css";

export type FlowChatToolStatus =
Expand Down Expand Up @@ -223,7 +224,7 @@ function CollapsibleRegion({
ref={regionRef}
id={id}
aria-hidden={!open}
{...{ inert: !open ? '' : undefined }}
{...inertProps(!open)}
className={styles.collapse}
data-animate={disableAnimation ? "false" : "true"}
data-openbitfun-component="flow-chat-tool-card"
Expand Down
12 changes: 12 additions & 0 deletions design-system/packages/ui/src/internal/inert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { version } from "react";

// React 19 treats `inert` as a boolean prop and ignores an empty string;
// React 18 only forwards the attribute when it is given a string.
const INERT_IS_BOOLEAN = Number.parseInt(version, 10) >= 19;

export type InertProps = { inert?: "" };

export function inertProps(isInert: boolean): InertProps {
if (!isInert) return {};
return { inert: (INERT_IS_BOOLEAN ? true : "") as "" };
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useDismissibleLayer } from "../../overlay/useDismissibleLayer";
import { useFocusScope } from "../../overlay/useFocusScope";
import { usePresence } from "../../overlay/usePresence";
import type { OverlayDismissReason } from "../../overlay/types";
import { inertProps } from "../../internal/inert";
import styles from "./MobileSheet.module.css";

const EXIT_DURATION_MS = 180;
Expand Down Expand Up @@ -109,7 +110,7 @@ export const MobileSheet = forwardRef<HTMLDivElement, MobileSheetProps>(
>
<div
{...surfaceProps}
{...(!open ? { inert: "" } : {})}
{...inertProps(!open)}
aria-describedby={ariaDescribedBy ?? (description ? descriptionId : undefined)}
aria-hidden={exiting || undefined}
aria-label={ariaLabel}
Expand Down
3 changes: 2 additions & 1 deletion design-system/packages/ui/src/overlay/Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useDismissibleLayer } from "./useDismissibleLayer";
import { useFocusScope } from "./useFocusScope";
import { useScrollLock } from "./useScrollLock";
import type { OverlayDismissReason, OverlayPortalContainer, OverlayPortalTarget } from "./types";
import { inertProps } from "../internal/inert";
import styles from "./Portal.module.css";

const ParentLayerContext = createContext<{ id: symbol; stack: OverlayLayerStack } | null>(null);
Expand Down Expand Up @@ -118,7 +119,7 @@ function LayerMount({
<ParentLayerContext.Provider value={{ id: ticket.id, stack }}>
<FieldSurfaceContext.Provider value="default">
<div ref={slotRef} className={inline ? styles.inlineLayer : styles.layer} data-openbitfun-overlay-layer=""
data-openbitfun-overlay-modal={modal || undefined} {...(!open ? { inert: "" } : {})}>
data-openbitfun-overlay-modal={modal || undefined} {...inertProps(!open)}>
{children}
</div>
</FieldSurfaceContext.Provider>
Expand Down
10 changes: 5 additions & 5 deletions docs/interactive-capabilities/capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"title": "OpenBitFun Playbook",
"origin": "https://playbook.openbitfun.com",
"source": "src/shared/interactive-capabilities/catalog.json",
"digest": "c7c38fecfad3e83e4a9f6f451a37edbf7d42ef399409238253f9a0fd124f879b",
"digest": "da8a1c7e721910916da62280a534391c89f6457098aadf9200e85e797145a12a",
"ownerDigest": "103fc8646df7b35e9fac1c52149a35b86e9c6115b462b0aa36240a4600903ce4",
"searchAcceptance": [
{
Expand Down Expand Up @@ -25469,8 +25469,8 @@
"items": [
{
"id": "agent-cards",
"titleZh": "按需求或在任务过程中创建倒计时、清单、便签、进度、指标、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据",
"titleEn": "Create countdown, checklist, note, progress, metrics, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command",
"titleZh": "按需求或在任务过程中创建倒计时、清单、便签、进度、指标、图表、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据",
"titleEn": "Create countdown, checklist, note, progress, metrics, chart, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command",
"control": {
"kind": "delegate",
"tools": [
Expand Down Expand Up @@ -25602,8 +25602,8 @@
"Agents create and update cards when useful",
"View, show, hide, or delete cards from Home",
"Drag cards; positions are restored after restart",
"按需求或在任务过程中创建倒计时、清单、便签、进度、指标、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据",
"Create countdown, checklist, note, progress, metrics, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command",
"按需求或在任务过程中创建倒计时、清单、便签、进度、指标、图表、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据",
"Create countdown, checklist, note, progress, metrics, chart, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command",
"在首页“小卡片”区查看全部卡片,并显示、隐藏或删除单张卡片",
"See every card in the Home Desktop cards section, and show, hide, or delete individual cards",
"在卡片上刷新数据、勾选清单项、切换置顶、打开链接或回到来源会话",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Let agents place draggable cards outside the main window for glanceable informat

## 完整功能清单 / Everything included

- **由专用智能体工具控制 / Delegated Agent tool** · `DesktopCard` · 按需求或在任务过程中创建倒计时、清单、便签、进度、指标、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据
- Create countdown, checklist, note, progress, metrics, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command
- **由专用智能体工具控制 / Delegated Agent tool** · `DesktopCard` · 按需求或在任务过程中创建倒计时、清单、便签、进度、指标、图表、定时任务和自定义卡片,可定时从 HTTP 接口或本机命令刷新数据
- Create countdown, checklist, note, progress, metrics, chart, scheduled-job, and custom cards on request or during a task, optionally refreshed from an HTTP endpoint or a local command
- **由专用智能体工具控制 / Delegated Agent tool** · `DesktopCard` · 在首页“小卡片”区查看全部卡片,并显示、隐藏或删除单张卡片
- See every card in the Home Desktop cards section, and show, hide, or delete individual cards
- **由专用智能体工具控制 / Delegated Agent tool** · `DesktopCard` · 在卡片上刷新数据、勾选清单项、切换置顶、打开链接或回到来源会话
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"generatedFrom": "src/shared/interactive-capabilities/catalog.json",
"catalogDigest": "c7c38fecfad3e83e4a9f6f451a37edbf7d42ef399409238253f9a0fd124f879b",
"catalogDigest": "da8a1c7e721910916da62280a534391c89f6457098aadf9200e85e797145a12a",
"count": 216,
"reasonCounts": {
"externalAuth": 4,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 2,
"generatedFrom": "src/shared/interactive-capabilities/catalog.json",
"catalogDigest": "c7c38fecfad3e83e4a9f6f451a37edbf7d42ef399409238253f9a0fd124f879b",
"catalogDigest": "da8a1c7e721910916da62280a534391c89f6457098aadf9200e85e797145a12a",
"commandCount": 701,
"coverage": {
"commandCount": 701,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Use these tables to select tools for the user's tasks, then put their exact name
| --- | --- |
| `TodoWrite` | Create and update the session's todo list. |
| `Cron` | Manage scheduled jobs. |
| `DesktopCard` | Show glanceable cards (countdowns, checklists, metrics, status) on the user's desktop outside the main window; desktop host only. |
| `DesktopCard` | Show glanceable cards (countdowns, checklists, metrics, charts, status) on the user's desktop outside the main window; desktop host only. |
| `Worktree` | List worktrees and manage isolated sessions, branches, and worktree removal. |
| `GetFileDiff` | Show a file's diff against its baseline snapshot or Git HEAD. |
| `ReviewPlatform` | Inspect and operate on hosted pull requests or merge requests. |
Expand Down
Loading
Loading