import React$1, { ReactNode, FC, HTMLElementType, ComponentType, CSSProperties, ReactElement, Ref, Dispatch } from 'react'; interface SwitchProps { value: T; /** * @description 可选的比对函数,默认() => a===b * @description_en optional compare function, default to () => a === b **/ compare?: (a: T, b: T) => boolean; children?: React$1.ReactNode; /** * @description 是否严格模式,默认 false.建议跟随开发环境变化,严格模式下,会循环所有节点来提供更多的错误提示 * @description_en strict mode, default to false. It is recommended to follow the development environment changes. In strict mode, all nodes will be looped to provide more error prompts * @extra * 1.严格模式下,无论什么情况都会循环所有节点,以确保所有的 case 和 default 都能被检查到 * 2.非严格模式下,如果有一个 case 匹配成功,就不会继续循环 * @extra_en * 1. In strict mode, all nodes will be looped regardless of the situation to ensure that all cases and defaults can be checked * 2. In non-strict mode, if a case matches successfully, it will not continue to loop */ strict?: boolean; } interface SwitchCaseProps { value: T; children?: React$1.ReactNode; } interface SwitchDefaultProps { children?: React$1.ReactNode; } /** * @description Switch 组件用于根据传入的 value 渲染不同的子组件 * @description_en The Switch component is used to render different child components based on the passed value */ declare const Switch: { (props: SwitchProps): React$1.ReactElement | null; displayName: string; Case: { (props: SwitchCaseProps): React$1.ReactElement; displayName: string; }; Default: { (props: SwitchDefaultProps): React$1.ReactElement; displayName: string; }; createTyped(): { Switch: (props: SwitchProps) => React$1.ReactElement | null; Case: (props: SwitchCaseProps) => React$1.ReactElement; Default: (props: SwitchDefaultProps) => React$1.ReactElement; }; }; interface IfProps { condition: boolean; children?: React$1.ReactNode; } interface ThenProps { children?: React$1.ReactNode; } interface ElseProps { children?: React$1.ReactNode; } interface ElseIfProps { condition: boolean; children?: React$1.ReactNode; } declare const If: { ({ condition, children, }: IfProps): React$1.ReactElement | null; displayName: string; Then: React$1.FC; ElseIf: React$1.FC; Else: React$1.FC; createTyped(): { If: (props: IfProps) => React$1.ReactElement | null; Then: (props: ThenProps) => React$1.ReactElement; ElseIf: (props: ElseIfProps) => React$1.ReactElement; Else: (props: ElseProps) => React$1.ReactElement; }; }; interface TrueProps { condition: boolean; children?: ReactNode; } declare const True: FC; interface FalseProps { condition: boolean; children?: ReactNode; } declare const False: FC; interface WhenProps { /** * @description_en An array of conditions where all must be true to render children. * @description_zh 一个条件数组,所有条件都为真时渲染子元素。 * @index 1 */ all?: boolean[]; /** * @description_en An array of conditions where at least one must be true to render children. * @description_zh 一个条件数组,至少有一个条件为真时渲染子元素。 * @index 2 */ any?: boolean[]; /** * @description_en An array of conditions where all must be false to render children. * @description_zh 一个条件数组,所有条件都为假时渲染子元素。 * @index 3 */ none?: boolean[]; /** * @description_en The content to render when conditions are satisfied. * @description_zh 满足条件时渲染的内容。 */ children?: ReactNode; /** * @description_en The content to render when conditions are not satisfied (alternative to `Else`). * @description_zh 不满足条件时渲染的内容(替代 `Else`)。 */ fallback?: ReactNode; } /** * @description_zh 一个声明式组件,用于基于多个条件进行条件渲染。比If更简洁。 * @description_en A declarative component for conditional rendering based on multiple conditions. More concise than If. * @component * @example * ```tsx * * * * ``` * * @example * ```tsx * }> * * * ``` * */ declare const When: FC; /** * Props for the `Pipe` component. * * @interface PipeProps * @property {any} data - The initial data to process. * @property {((input: any) => any)[]} transform - Array of functions to transform the data. * @property {(result: any) => ReactNode} render - Function to render the transformed data. * @property {ReactNode} [fallback] - Content to render if the result is null or undefined. */ interface PipeProps { /** * @description_en The initial data to process. * @description_zh 要处理的初始数据。 */ data: any; /** * @description_en Array of functions to transform the data. * @description_zh 转换数据的函数数组。 */ transform: ((input: any) => any)[]; /** * @description_en Function to render the transformed data. * @description_zh 渲染转换后数据的函数。 */ render: (result: any) => ReactNode; /** * @description_en Content to render if the result is null or undefined. * @description_zh 如果结果为 null 或 undefined 时渲染的内容。 * @optional * @default null */ fallback?: ReactNode; } /** * @description_zh 一个声明式组件,用于通过管道式处理数据并渲染,简化多步骤数据转换。 * @description_en A declarative component for processing data through a pipeline and rendering, simplifying multi-step data transformations. * @component * @example * ```tsx * data.filter(user => user.active), * (data) => data.map(user => user.name) * ]} * render={(names) =>
{names.join(", ")}
} * fallback={
No Data
} * /> * ``` */ declare const Pipe: FC; interface SizeBoxProps { size?: number | string; height?: number | string; width?: number | string; h?: number | string; w?: number | string; children?: ReactNode; className?: string; } /** * @description SizeBox 组件用于设置一个固定大小的盒子 */ declare const SizeBox: FC; /** * Props for the `Scope` component. * * @interface ScopeProps * @property {Record | ((props: any) => Record)} let - An object or function defining local variables for the scope. * @property {any} [props] - Optional props passed to the let function. * @property {(scope: Record) => ReactNode} [children] - Render function to access the scope variables. * @property {ReactNode} [fallback] - Content to render if children or scope is empty. */ interface ScopeProps { /** * @description_en An object or function defining local variables for the scope. * @description_zh 定义作用域局部变量的对象或函数。 */ let: Record | ((props: any) => Record); /** * @description_en Optional props passed to the let function. * @description_zh 传递给 let 函数的可选属性。 * @optional */ props?: any; /** * @description_en Render function to access the scope variables. * @description_zh 访问作用域变量的渲染函数。 * @optional */ children?: (scope: Record) => ReactNode; /** * @description_en Content to render if children or scope is empty. * @description_zh 如果 children 或 scope 为空时渲染的内容。 * @optional * @default null */ fallback?: ReactNode; } /** * @description_zh 一个声明式组件,为子节点提供局部作用域,简化临时状态或上下文管理。 * @description_en A declarative component that provides a local scope for children, simplifying temporary state or context management. * @component * @example * ```tsx * * {({ count, text }) =>
{text} {count}
} *
* ``` * * @example * ```tsx * ({ total: props.items.length })} props={{ items: [1, 2] }} fallback={
Empty
}> * {({ total }) =>
Total: {total}
} *
* ``` */ declare const Scope: FC; type CxInput = string | string[] | Record | undefined | null | false; declare function cx(...args: CxInput[]): string; interface StylesDescriptor { base?: CxInput; hover?: CxInput; active?: CxInput; focus?: CxInput; disabled?: CxInput; color?: CxInput; size?: CxInput; layer?: CxInput; wrapper?: CxInput; dark?: CxInput; light?: CxInput; sundry?: CxInput; [key: string]: CxInput; } type StylesType = StylesDescriptor | string; interface StylesProps { /** * @description_en either as a simple string or a categorized object with predefined or custom keys. * @description 可以是简单字符串或包含预定义或自定义键的分类对象。 * @example * ```tsx * // Simple string * * * * * // Categorized object * * * * ``` */ className?: StylesType; /** * @description 传入容器标签名.是否生成包含所有 `className` 的 `wrapper`, 默认 false, 传递 `true` 为 `div。` * @description_en Whether to generate a `wrapper` containing all `className`, default is false, and pass the container tag name, if `true` will be `div`. */ asWrapper?: boolean | HTMLElementType; children?: React$1.ReactNode; } /** * @description 分类编写样式和基本的string样式,内置类似 `clsx` 对类型描述对象的值进行组合,支持去除重复类名,支持嵌套。 * @description_en Categorized writing styles and basic string styles, built-in similar to `clsx` to combine the values of type description objects, support removing duplicate class names, and support nesting. * @component * @example * ```tsx * * * * ``` * * @example * ```tsx * * * * ``` */ declare const Styles: FC; interface ToggleProps { /** * @description_en The initial value to toggle. * @description_zh 初始切换值。 * @default 0 */ index?: number; /** * @description_en Array of values to toggle between. * @description_zh 可切换的值数组。 */ options: T[]; /** * @description_en Function to determine the next value index in the toggle sequence. * @description_zh 确定切换序列中下一个值索引的函数。 * @optional */ next?: (curIndex: number, options: T[]) => number; /** * @description_en Render function, receiving the toggled value and toggle function. * @description_zh 渲染函数,接收切换后的值和切换函数。 */ render: (value: T, toggle: () => void) => ReactNode; } /** * @description_zh 一个声明式组件,用于在预定义选项中切换值并通过 render 函数传递给子组件,支持自定义切换逻辑。 * @description_en A declarative component for toggling between predefined values and passing them to children via a render function, supporting custom toggle logic. * @component * @example * ```tsx * ( *
当前主题: {theme}
* )} * /> * ``` */ declare const Toggle: (props: ToggleProps) => React$1.ReactNode; interface ObserverProps { /** * @description_en Callback function when intersection occurs. * @description_zh 交叉时触发的回调函数。 */ onIntersect: (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void; /** * @description_en Threshold(s) at which to trigger the callback. Default is 0.1. * @description_zh 触发回调的阈值,默认为 0.1。 * @optional * @default 0.1 */ threshold?: number | number[]; /** * @description_en The root element for intersection. Default is viewport. * @description_zh 交叉的根元素,默认为视口。 * @optional * @default null */ root?: Element | Document | null; /** * @description_en Margin around the root. Default is "0px". * @description_zh 根元素周围的边距,默认为 "0px"。 * @optional * @default "0px" */ rootMargin?: string; /** * @description_en Whether to trigger only once. Default is false. * @description_zh 是否只触发一次,默认为 false。 * @optional * @default false */ triggerOnce?: boolean; /** * @description_en Whether to disable the observer. Default is false. * @description_zh 是否禁用观察者,默认为 false。 * @optional * @default false */ disabled?: boolean; /** * @description_en Child elements to observe. * @description_zh 要观察的子元素。 * @optional */ children?: ReactNode; /** * @description_en CSS class name. * @description_zh CSS 类名。 * @optional */ className?: string; /** * @description_en Inline styles. * @description_zh 内联样式。 * @optional */ style?: React$1.CSSProperties; } /** * @description_zh 交叉观察者组件,用于监听元素与视口的交叉状态,常用于懒加载和无限滚动场景。 * @description_en Intersection Observer component for monitoring element-viewport intersection, commonly used for lazy loading and infinite scrolling. * @component * @example * ```tsx * // 懒加载示例 * * Lazy loaded * * * // 无限滚动示例 * *
滚动到这里加载更多
*
* * // 自定义根元素和边距 * *
观察目标
*
* ``` */ declare const Observer: React$1.FC; interface RepeatProps { /** * @description_en Number of times to repeat. * @description_zh 重复次数。 */ times: number; /** * @description_en Render function receiving the current index (0-based). * @description_zh 渲染函数,接收当前索引(从 0 开始)。 */ children: (index: number) => ReactNode; } /** * @description_zh 声明式重复渲染组件,常用于骨架屏、占位符等场景。 * @description_en Declarative repeat-render component, commonly used for skeleton screens and placeholders. * @component * @example * ```tsx * * {(i) => } * * ``` */ declare function Repeat({ times, children }: RepeatProps): ReactNode; interface PortalProps { /** * @description_en Target DOM element to mount into. Defaults to document.body. * @description_zh 挂载目标 DOM 元素,默认为 document.body。 * @default document.body */ to?: Element | null; /** * @description_en Child elements to render into the portal. * @description_zh 要渲染到 portal 中的子元素。 */ children?: ReactNode; /** * @description_en Whether to disable the portal and render children inline. Default is false. * @description_zh 是否禁用 portal,直接内联渲染子元素。默认为 false。 * @default false */ disabled?: boolean; } /** * @description_zh 声明式 Portal 组件,将子元素渲染到指定 DOM 节点,常用于模态框、浮层等场景。 * @description_en Declarative Portal component that renders children into a specified DOM node, commonly used for modals and overlays. * @component * @example * ```tsx * * * * * * * * ``` */ declare function Portal({ to, children, disabled }: PortalProps): ReactNode; interface BoundaryProps { /** * @description_en Fallback UI to render when an error is caught. Receives the error and a reset function. * @description_zh 捕获到错误时渲染的降级 UI,接收错误对象和重置函数。 */ fallback: (error: Error, reset: () => void) => ReactNode; /** * @description_en Called when an error is caught, useful for logging. * @description_zh 捕获到错误时的回调,可用于上报日志。 * @optional */ onError?: (error: Error, info: React$1.ErrorInfo) => void; /** * @description_en Child elements to protect. * @description_zh 需要保护的子元素。 */ children?: ReactNode; } /** * @description_zh Error Boundary 的声明式封装,通过 render prop 提供降级 UI 和重置能力。 * @description_en Declarative Error Boundary wrapper with render-prop fallback and reset capability. * @component * @example * ```tsx * ( *
*

出错了: {error.message}

* *
* )}> * *
* ``` */ declare function Boundary(props: BoundaryProps): ReactNode; interface FocusableOptions { /** * @description_en Whether to include the container element in results. * @description_zh 是否将容器元素本身纳入结果。 * @default false */ includeContainer?: boolean; /** * @description_en Traverse open shadow roots. Pass a function for custom shadow resolution. * @description_zh 是否遍历 open shadow root;也可传入函数自定义 shadow 解析。 * @default true */ getShadowRoot?: boolean | GetShadowRootFn; /** * @description_en Strategy for visibility checks. * @description_zh 可见性检查策略。 * @default 'full' */ displayCheck?: 'full' | 'full-native' | 'legacy-full' | 'non-zero-area' | 'none'; } type GetShadowRootFn = (element: Element) => ShadowRoot | boolean | undefined; /** * @description_zh 获取元素的有效 tab 顺序值(含浏览器默认映射)。 * @description_en Returns the effective tab order for an element, including browser defaults. */ declare function getTabIndex(node: Element): number; /** * @description_zh 判断单个元素是否可被 programmatic focus(含 tabindex="-1")。 * @description_en Whether an element can receive programmatic focus (includes tabindex="-1"). */ declare function isFocusable(node: Element, options?: FocusableOptions): boolean; /** * @description_zh 判断单个元素是否可通过 Tab 键聚焦。 * @description_en Whether an element can be focused via the Tab key. */ declare function isTabbable(node: Element, options?: FocusableOptions): boolean; /** * @description_zh 获取容器内所有可聚焦元素(含 tabindex="-1")。 * @description_en Returns all focusable elements within a container (includes tabindex="-1"). */ declare function getFocusableElements(container: Element, options?: FocusableOptions): HTMLElement[]; /** * @description_zh 获取容器内所有可通过 Tab 键循环聚焦的元素,按 tab 顺序排列。 * @description_en Returns tabbable elements within a container, sorted by tab order. */ declare function getTabbableElements(container: Element, options?: FocusableOptions): HTMLElement[]; type FocusDirection = 'next' | 'prev' | 'first' | 'last'; interface FocusTrapProps { /** * @description_en The child elements to trap focus within. * @description_zh 需要劫持焦点的子元素。 */ children?: ReactNode; /** * @description_en Whether to disable the focus trap. * @description_zh 是否禁用焦点劫持。 * @default false */ disabled?: boolean; /** * @description_en Whether to auto-focus the first tabbable element on mount. * @description_zh 是否在挂载时自动聚焦到第一个可 Tab 聚焦的元素。 * @default false */ autoFocus?: boolean; /** * @description_en Whether to restore focus to the previously focused element on unmount. * @description_zh 是否在卸载时恢复焦点到之前聚焦的元素。 * @default false */ restoreFocus?: boolean; /** * @description_en Custom key-to-direction mapping to extend or override the default Tab-based navigation. * @description_zh 自定义按键到焦点方向的映射,用于扩展或覆盖默认的 Tab 导航。 * @default { Tab: 'next' } * @example * ```tsx * // Arrow up/down navigation * keyMap={{ ArrowDown: 'next', ArrowUp: 'prev' }} * // Arrow left/right navigation * keyMap={{ ArrowRight: 'next', ArrowLeft: 'prev' }} * ``` */ keyMap?: Partial>; /** * @description_en Custom focus resolution function. Return the element to focus, or null to use default cycle. * @description_zh 自定义焦点解析函数。返回要聚焦的元素,或返回 null 使用默认循环行为。 * @optional */ onNavigate?: (current: HTMLElement | null, elements: HTMLElement[], direction: FocusDirection) => HTMLElement | null; /** * @description_en Options passed to getTabbableElements. * @description_zh 传递给 getTabbableElements 的选项。 * @optional */ focusableOptions?: FocusableOptions; /** * @description_en CSS class name for the container. * @description_zh 容器元素的 CSS 类名。 * @optional */ className?: string; /** * @description_en Inline styles for the container. * @description_zh 容器元素的内联样式。 * @optional */ style?: React$1.CSSProperties; } /** * @description_zh 焦点陷阱组件,将键盘焦点循环限制在容器内的可聚焦元素中,支持自定义按键映射和导航逻辑。 * @description_en Focus trap component that constrains keyboard focus cycling to focusable elements within a container, with support for custom key mappings and navigation logic. * @component * @example * ```tsx * // Default Tab trapping * * * * * * // Arrow key navigation * * * * * * // With auto-focus and restore * * * * * * // Cross-list navigation: items from multiple lists are collected * // into a single focus order, seamlessly crossing between lists. * // ArrowDown from A-2 → B-1, ArrowUp from B-1 → A-2 * *
*

List A

* * *
*
*

List B

* * *
*
* ``` */ declare function FocusTrap({ children, disabled, autoFocus, restoreFocus, keyMap, onNavigate, focusableOptions, className, style, }: FocusTrapProps): ReactNode; declare namespace FocusTrap { var displayName: string; } interface ArrayRenderProps { items: T[]; renderItem: (item: T, index: number) => React$1.ReactNode; filter?: (item: T) => boolean; renderEmpty?: () => React$1.ReactNode; sort?: (a: T, b: T) => number; } declare function ArrayRender(props: ArrayRenderProps): ReactNode; interface DateRenderProps { /** * @description_en The input date to render (Date object, ISO string, or timestamp). * @description_zh 要渲染的输入日期(Date 对象、ISO 字符串或时间戳)。 */ source: Date | string | number; /** * @description_en Function to format the date. * @description_zh 格式化日期的函数。 * @optional * @default toLocaleString */ format?: (date: Date) => T; /** * @description_en Function to render the formatted date. * @description_zh 渲染格式化后日期的函数。 */ children: (formatted: T) => React$1.ReactNode; } /** * @description_zh 一个声明式组件,用于格式化并渲染日期,简单易用且支持自定义格式化。 * @description_en A declarative component for formatting and rendering dates, simple to use with support for custom formatting. * @component * @template T - The type of the formatted date value * @example * ```tsx * * {(formatted) =>
日期: {formatted}
} *
* ``` * * @example * ```tsx * * source={new Date()} * format={(date) => date.toLocaleDateString("zh-CN")} * > * {(formatted) =>
日期: {formatted}
} * * ``` */ declare function DateRender({ source, format, children, }: DateRenderProps): React$1.JSX.Element | null; /** * @zh `useAppStack()` 返回的导航 API。每个方法绑定到最近的 `AppStackRouter` 实例。 * @en Navigation API returned by `useAppStack()`. Each method is bound to the nearest `AppStackRouter`. */ interface AppStackApi { /** * @zh 压入一个新屏幕到栈顶。`params` 会作为 props 透传给该组件。 * @en Push a new screen onto the top. `params` are forwarded as props. */ push:

(Component: ComponentType

, params?: P) => void; /** * @zh 弹出栈顶屏幕。栈空时为空操作。 * @en Pop the top screen. No-op when empty. */ pop: () => void; /** * @zh 替换栈顶屏幕,不改变栈深度。空栈时退化为 push。 * @en Replace the top screen without changing depth. Degrades to push on an empty stack. */ replace:

(Component: ComponentType

, params?: P) => void; /** * @zh 清空整个堆栈,回到根屏幕(无过渡动画)。 * @en Clear the entire stack, returning to the root screen (no transition). */ reset: () => void; /** * @zh 当前栈是否可出栈(深度 > 0)。 * @en Whether the stack can be popped (depth > 0). */ canPop: () => boolean; /** * @zh 当前栈深度(不含根屏幕)。 * @en Current stack depth (excludes the root screen). */ size: number; } /** * @zh 在 `AppStackRouter` 内部任意子组件中获取导航 API。在 Router 外调用会抛错。 * * 注意:`size` 与 `canPop()` 会在栈深度变化时同步更新(内部仅订阅深度,不订阅完整栈, * 因此栈内容变化但深度不变时不会触发重渲染,比订阅完整栈更高效)。 * 如需在栈内容变化时重渲染(罕见),使用 `useStackSize` 或直接读取 `size`。 * @en Obtain the navigation API from any descendant of `AppStackRouter`. Throws when used outside. * * Note: `size` and `canPop()` stay in sync with the stack depth (subscribed to depth only, not the * full stack, so content changes that don't alter depth won't trigger a re-render -- more efficient * than subscribing to the full stack). */ declare function useAppStack(): AppStackApi; /** * @zh 响应式订阅栈深度(不含根屏幕)。仅深度变化时重渲染。 * @en Reactively subscribe to the stack depth (excludes the root screen). Re-renders only on depth change. */ declare function useStackSize(): number; /** * @zh 响应式订阅当前栈是否可出栈(深度 > 0)。仅 canPop 状态变化时重渲染。 * @en Reactively subscribe to whether the stack can be popped (depth > 0). Re-renders only when the * canPop state changes. */ declare function useCanPop(): boolean; interface AppStackRouterProps { /** * @description_zh 根屏幕,始终位于栈底并被渲染。刷新后回到这里。 * @description_en Root screen, always at the bottom and rendered. The app returns here on refresh. */ root: React$1.ReactElement; /** * @description_zh 最大栈深度,超过则丢弃最底层屏幕以释放内存。默认无限制。 * @description_en Max stack depth; the bottom screen is dropped when exceeded. Default unlimited. * @optional */ maxStackSize?: number; /** * @description_zh 是否启用边缘左滑返回手势。 * @description_en Whether the edge swipe-back gesture is enabled. * @default true */ swipeBack?: boolean; /** * @description_zh 触发拖拽的左边缘宽度(px)。 * @description_en Left-edge width (px) that starts a drag. * @default 40 */ swipeBackEdgeWidth?: number; /** * @description_zh 松手时若手指正朝"取消"方向(向左)运动,则强制回弹、不出栈, * 即使当前位移已超过阈值。设为 false 则仅按距离/速度判定。典型场景:向右划出后反悔、 * 向左划回,松手时位置仍在阈值之上但意图是取消返回。 * @description_en When the finger is moving towards the "cancel" direction (leftwards) on release, * force a snap-back instead of committing a pop, even if the offset already exceeds the threshold. * Set to false to judge only by distance/velocity. Typical scenario: drag out to the right, change * your mind and drag back left; on release the position is still above the threshold but the intent * is to cancel. * @default true */ swipeBackCancelOnReverseRelease?: boolean; /** * @description_zh 判定"朝取消方向运动"的最小瞬时速度(px / ms)。 * @description_en Minimum instantaneous velocity (px/ms) to count as a clear "cancel" motion. * @default 0.1 */ swipeBackCancelVelocity?: number; /** * @description_zh 是否启用安全区域(env(safe-area-inset-*))内边距。 * @description_en Whether to apply safe-area (env(safe-area-inset-*)) padding. * @default true */ safeArea?: boolean; /** * @description_zh 进出场过渡时长(ms)。设为 0 可禁用过渡。 * @description_en Enter/exit transition duration (ms). Set to 0 to disable. * @default 300 */ transitionDuration?: number; /** * @description_zh 是否以视口高度(100dvh)撑满整个屏幕。移动端单页应用建议保持开启; * 若需嵌入到已具备高度的父容器内,可设为 false(此时容器高度为 100%,依赖父级高度)。 * @description_en Whether to fill the whole screen via viewport height (100dvh). Recommended on for * mobile SPAs; set to false to embed inside a parent that already has a height (the container then * uses 100% height, depending on the parent). * @default true */ fullscreen?: boolean; /** * @description_zh 容器 className。 * @description_en Container className. * @optional */ className?: string; /** * @description_zh 容器内联样式。 * @description_en Container inline style. * @optional */ style?: CSSProperties; /** * @description_zh 渲染在堆栈之上的全局叠层(如 toast)。不受手势/过渡影响。 * @description_en Global overlay rendered above the stack (e.g. toast). Unaffected by gestures/transitions. * @optional */ children?: ReactNode; } /** * @description_zh 移动端 H5 单页应用风格的堆栈视图容器。屏幕切换后被压入堆栈并 keep-alive(不卸载), * 支持编程式导航、边缘左滑返回、拦截浏览器返回键、安全区域适配。不引入任何额外依赖。 * * 使用前建议在 HTML 中设置 `` * 以让 `env(safe-area-inset-*)` 生效。 * * @description_en A mobile-app-style stack view container for H5 SPAs. Screens are pushed onto a * stack and kept alive (not unmounted). Supports programmatic navigation, edge swipe-back, browser * back-button interception, and safe-area adaptation. Zero extra dependencies. * * For `env(safe-area-inset-*)` to take effect, set * `` in HTML. * @component * @example * ```tsx * function Home() { * const { push } = useAppStack() * return * } * * function Profile({ id }: { id: number }) { * const { pop } = useAppStack() * return * } * * } /> * ``` */ declare function AppStackRouter({ root, maxStackSize, swipeBack, swipeBackEdgeWidth, swipeBackCancelOnReverseRelease, swipeBackCancelVelocity, safeArea, transitionDuration, fullscreen, className, style, children, }: AppStackRouterProps): ReactNode; declare namespace AppStackRouter { var displayName: string; } /** * @zh 合帧投递:把 props 更新合并为每个帧率窗口至多一次提交。 * * 它不是"节流渲染",而是"合帧投递"。设间隔 `T = 1000 / fps`,对任意窗口 `[t, t+T)`: * * 1. 子组件收到的 props 至多更新一次,且携带窗口内最后一次更新的值(最新值胜出); * 2. 若新值与已提交值按 `compare` 判定相等,则不发生任何提交 —— 子组件继续收到上一次的 * 同一个元素引用,React 在 `memo` 边界直接 bail out; * 3. 未提交期间父组件传入的新 props 只进控制器,绝不进入子组件的 props。 * * 父组件可以以任意高的频率重渲染:本组件自身每次都会重新执行(一次 O(1)、零分配的捕获), * 但昂贵的子树只在帧边界上渲染一次。这正是"抽离状态层"的收益所在 —— 高频状态放在本组件 * 外层,昂贵的渲染留在内层。 * * 它由两个已有工具组合而成:`rafSchedule` 当泵、`createLatestValue` 当合并槽。 * * @en Framed delivery: coalesce prop updates into at most one commit per frame window. * * This is not "throttled rendering" but framed delivery. With `T = 1000 / fps`, for any window * `[t, t+T)`: * * 1. the child receives at most one props update, carrying the LAST value of the window * (latest wins); * 2. if the new value compares equal to the committed one (per `compare`), no commit happens at * all — the child keeps receiving the same element reference and React bails out at the * `memo` boundary; * 3. updates arriving before a commit reach the controller only, never the child's props. * * The parent may re-render arbitrarily often: this component re-runs every time (one O(1), * allocation-free capture), but the expensive subtree renders only at frame boundaries. That is * why extracting the state layer pays off — keep the high-frequency state OUTSIDE and the * expensive rendering INSIDE. * * It is assembled from two existing utilities: `rafSchedule` as the pump and `createLatestValue` * as the merge cell. */ /** * @zh 泵的传输层策略。 * - `'auto'`(默认):可见时用 `requestAnimationFrame`,隐藏时(仅在 `pauseWhenHidden` 为 * false 时才会走到)退化为定时器。 * - `'raf'`:始终用 `requestAnimationFrame`。与绘制对齐,但标签页隐藏时不会触发。 * - `'timer'`:始终用 `setTimeout`,节拍与绘制解耦。 * * @en Transport strategy for the pump. * - `'auto'` (default): `requestAnimationFrame` while visible, falling back to a timer while * hidden (only reachable when `pauseWhenHidden` is false). * - `'raf'`: always `requestAnimationFrame` — aligned with painting, but it does not fire while * the tab is hidden. * - `'timer'`: always `setTimeout`, decoupled from painting. */ type FrameStrategy = 'auto' | 'raf' | 'timer'; /** * @zh 可注入的帧调度器:接收回调,返回取消函数。默认是 `requestAnimationFrame`。注入它既能 * 替换传输层(例如改用 `requestIdleCallback`),也让单元测试可以确定性地推进帧而不依赖真实 * 计时。 * * @en Injectable frame scheduler: receives a callback and returns a cancel function. Defaults to * `requestAnimationFrame`. Injecting one both replaces the transport (e.g. to use * `requestIdleCallback`) and lets unit tests advance frames deterministically. */ type FrameScheduler = (callback: (time: number) => void) => () => void; /** * @zh 相等判定策略。返回 true 表示"相等",即跳过本次提交。 * - `'shallow'`(默认):逐键 `Object.is` 浅比较; * - `'reference'`:只比引用。最便宜,但父组件每次新建的字面量对象都会判定为不等,等于每个 * 窗口都提交; * - `'never'`:永不相等,即每次泵触发都提交; * - 自定义函数:自行决定,适合用一个廉价的 version 字段代替深比较。 * * @en Equality strategy. True means "equal", i.e. skip this commit. * - `'shallow'` (default): per-key `Object.is` shallow compare; * - `'reference'`: reference only. Cheapest, but a literal object rebuilt by the parent on every * render always compares unequal, so every window commits; * - `'never'`: never equal — commit on every pump tick; * - custom function: your call — handy for comparing a cheap version field instead of deep * comparing. */ type FrameCompare = 'shallow' | 'reference' | 'never' | ((prev: unknown, next: unknown) => boolean); /** * @zh `onDrop` 的丢弃原因。只报告"有意义的丢弃":被更新值覆盖(合帧的正常工况)不计入 —— * 一个窗口内 10 次更新有 9 次被覆盖,逐条上报只会是噪音,那个数字看统计里的 `coalesced`。 * * @en Why an update was dropped. Only meaningful drops are reported; being superseded by a newer * value is the normal operation of coalescing and is not reported — 9 of 10 updates in a window * are superseded routinely, and reporting each would be pure noise. Read `coalesced` instead. */ type FrameDropReason = 'vetoed' | 'cancelled' | 'unmounted'; /** * @zh 传给各生命周期钩子的上下文快照。 * @en Context snapshot handed to the lifecycle callbacks. */ interface FrameRenderContext { /** * @zh 本阶段的时间戳,与 rAF 回调同一时间线(`performance.now()` 口径)。 * @en Timestamp of this stage, on the same timeline as rAF callbacks (`performance.now()`). */ time: number; /** * @zh 泵触发过多少次(含最终没有提交的帧)。 * @en Pump ticks so far, including ticks that did not commit. */ frame: number; /** * @zh 已提交次数。 * @en Commits so far. */ commits: number; /** * @zh 被合帧抑制的父渲染次数。 * @en Parent renders suppressed by coalescing. */ coalesced: number; /** * @zh 当前目标帧率。 * @en Current target frame rate. */ fps: number; } /** * @zh `getStats()` 的返回结构,自挂载起累计。 * @en Shape returned by `getStats()`, accumulated since mount. */ interface FrameRenderStats { /** * @zh 泵触发次数。 * @en Pump ticks. */ frames: number; /** * @zh 捕获到的父渲染次数。自身提交引起的重渲染不计入 —— 那种渲染没有带来新东西。 * @en Captured parent renders. Re-renders caused by our own commits are not counted — they * brought nothing new. */ captures: number; /** * @zh 实际提交次数 —— 子组件渲染次数的上界。 * @en Actual commits — an upper bound on child renders. */ commits: number; /** * @zh `compare` 判定相等而跳过的次数,这部分完全不渲染。 * @en Skips where `compare` found the value equal — no render at all. */ skips: number; /** * @zh `shouldCommit` 否决的次数。 * @en Vetoes by `shouldCommit`. */ vetoes: number; /** * @zh 被合帧抑制的父渲染次数 —— 收益的直接度量。 * @en Parent renders suppressed by coalescing — the direct measure of the win. */ coalesced: number; /** * @zh 真正被丢弃的次数(否决 + 取消 + 卸载)。 * @en Truly dropped updates (veto + cancel + unmount). */ dropped: number; /** * @zh 最近若干次提交实测出的提交频率。 * @en Commit rate measured over the most recent commits. */ commitsPerSecond: number; } /** * @zh 命令式句柄,通过 `ref` 获取。 * @en Imperative handle, obtained through `ref`. */ interface FrameRenderHandle { /** * @zh 立即提交待处理的最新值,跳过时间门。用于"这一刻必须新鲜"的场景:用户点击刷新、 * 导出快照、提交表单前。返回是否真的发生了提交(无待处理值或判定相等时为 false)。 * @en Commit the latest pending value immediately, bypassing the time gate. For moments that * must be fresh: a refresh click, exporting a snapshot, right before submitting a form. * Returns whether a commit actually happened (false when nothing is pending or the value * compares equal). */ flush(): boolean; /** * @zh 丢弃待处理值并取消已预约的帧。返回是否丢弃了东西。 * @en Drop the pending value and cancel the booked frame. Returns whether anything was dropped. */ cancel(): boolean; /** * @zh 暂停提交,待处理值保留。 * @en Pause committing; the pending value is kept. */ pause(): void; /** * @zh 恢复提交;若有待处理值,下一帧提交最新值。 * @en Resume committing; with a pending value, the latest value commits on the next frame. */ resume(): void; /** * @zh 读取统计快照。用它验证收益,而不是凭感觉。 * @en Read a stats snapshot. Verify the win instead of guessing. */ getStats(): FrameRenderStats; } /** * @zh `FrameRender` 的 props。泛型 `P` 是子组件的 props 类型:element 形式下由元素推断, * 函数形式下由 `props` 与该函数的签名共同决定。 * @en `FrameRender` props. Generic `P` is the child's props type: inferred from the element in * element form, or from `props` together with the render function's signature. */ interface FrameRenderProps

> { /** * @zh 要合帧投递的子元素,或 `(props) => ReactNode` 渲染函数。必须是单个元素:Fragment、 * 数组、字符串无法提取 props,会报警告并旁路直通。 * * element 形式最顺手,但注意**窗口内子组件渲染的是上一次的 props** —— 这就是换取吞吐的 * 代价。函数形式适合需要派生的场景,且不存在"陈旧 children"的歧义。 * @en The child element to deliver frame by frame, or a `(props) => ReactNode` render function. * Must be a single element: a Fragment, array or string has no extractable props, so it warns * and passes through. * * The element form reads best, but note that **inside a window the child renders the previous * props** — that is the price of throughput. The function form suits cases needing derivation * and has no "stale children" ambiguity. */ children: ReactElement

| ((props: P) => ReactNode); /** * @zh 函数形式的 props 包(element 形式不需要)。省略时函数收到 `{}`。 * @en The props bag for the function form (not needed in element form). Defaults to `{}`. */ props?: P; /** * @zh 目标提交帧率。`T = 1000 / fps`,提交间隔不小于 `T`(含 4ms 相位容差,避免间隔与帧 * 周期相等时掉到一半帧率)。超过屏幕刷新率没有意义,有效上限就是刷新率。传 `<= 0` 表示旁路 * (每次父渲染都提交),用于和正常模式对比收益。 * @default 60 * @en Target commit frame rate. `T = 1000 / fps`, commits at least `T` apart (with a 4ms phase * tolerance so an interval equal to the frame period does not halve the rate). Exceeding the * display refresh rate achieves nothing — the refresh rate is the ceiling. `<= 0` bypasses * framing (commit on every parent render) for A/B-ing the win. */ fps?: number; /** * @zh 泵的传输层策略。 * @default 'auto' * @en Transport strategy for the pump. */ strategy?: FrameStrategy; /** * @zh 窗口内首个更新是否尽快提交(下一帧,不是同步)。置 false 则首个提交需要等满一个间隔。 * 与 throttle 的 leading 同义。 * @default true * @en Whether the first update of a window commits as soon as possible (next frame, not * synchronously). False makes the first commit wait a full interval. Same as throttle leading. */ leading?: boolean; /** * @zh 窗口内的更新是否算数。置 false 为采样语义:只有落到窗口边界上的那次更新会提交,窗口 * 内的更新直接忽略。注意 `leading` 与 `trailing` 同为 false 时几乎不会提交。 * @default true * @en Whether updates inside a window count. False means sampling: only the update landing on a * window boundary commits, and in-window updates are ignored. With both `leading` and `trailing` * false, commits become nearly impossible. */ trailing?: boolean; /** * @zh 暂停提交,待处理值保留;恢复时下一帧提交最新值。适合手势或弹窗打开期间冻结更新。 * @default false * @en Pause committing while keeping the pending value; on resume the latest value commits on the * next frame. Handy for freezing updates during a gesture or an open modal. */ paused?: boolean; /** * @zh 完全旁路:不调度、不比较、不统计,每次父渲染直接透传。用于 A/B 对比合帧收益,也用于 * 排查"是不是合帧导致的问题"。等价于 `fps <= 0`。 * @default false * @en Full bypass: no scheduling, no comparison, no stats — every parent render passes straight * through. For A/B-ing the win, or bisecting "is framing causing this?". Equivalent to * `fps <= 0`. */ disabled?: boolean; /** * @zh 标签页隐藏时不做提交。这是性能组件的正确默认值:不可见的提交是纯浪费,且 rAF 在隐藏时 * 本就不触发。注意它会把 `onCommit` 这类副作用的时序推迟到标签页重新可见。 * * 还有一条浏览器行为要知道:`strategy` 为 `'auto'` / `'raf'` 时泵走 rAF,而浏览器不只在 * `visibilityState === 'hidden'` 时暂停 rAF —— 窗口被其他窗口完全遮挡时同样会暂停。那种情况下 * 也不会发生提交,效果与暂停等同,窗口回到前台后立即恢复。若需要在被遮挡时仍保持提交节拍, * 用 `strategy: 'timer'`。 * @default true * @en Do not commit while the tab is hidden. The right default for a performance component: * invisible commits are pure waste, and rAF does not fire while hidden. Note that this defers * side effects such as `onCommit` until the tab is visible again. * * One browser behaviour is worth knowing too: with `strategy` set to `'auto'` / `'raf'` the pump * rides on rAF, and the browser suspends rAF not only when `visibilityState === 'hidden'` but also * when the window is fully covered by another window. In that state nothing commits either — the * effect equals a pause — and it resumes as soon as the window comes back. If you need the commit * cadence to hold while covered, use `strategy: 'timer'`. */ pauseWhenHidden?: boolean; /** * @zh 注入自定义帧调度器,替代 `strategy`。也用于让测试确定性地推进帧。 * @en Inject a custom frame scheduler, replacing `strategy`. Also used to advance frames * deterministically in tests. */ scheduler?: FrameScheduler; /** * @zh 只影响**比较**,不影响子组件收到的 props。返回参与比较的字段子集 —— 子组件仍然拿到完整 * 的 props,但只有选出的字段变化才会触发提交。这是消除"每次渲染都新建的回调 / 字面量"造成 * 无谓提交的关键。 * @en Affects the **comparison** only, never what the child receives. Return the subset of fields * that participate in comparison — the child still gets the full props, but only a change in the * selected fields triggers a commit. The key to eliminating pointless commits caused by * per-render callbacks and object literals. */ select?: (props: P) => unknown; /** * @zh 相等判定策略,返回 true 表示相等、跳过提交。 * @default 'shallow' * @en Equality strategy; true means equal, so the commit is skipped. */ compare?: FrameCompare; /** * @zh 提交前的策略否决。返回 false 则丢弃本次待处理值(计入 `vetoes`,并经 `onDrop` 以 * `'vetoed'` 上报)。例如:标签页隐藏时不做无用渲染、拖拽中冻结、或业务上判定"无实质变化"。 * * 参数是 `select` 之后的值,与 `compare` 看到的一致。 * @en Policy veto before committing. False drops the pending value (counted in `vetoes` and * reported through `onDrop` as `'vetoed'`) — e.g. skip invisible work while hidden, freeze during * a drag, or drop business-insignificant changes. * * Arguments are the post-`select` values, the same ones `compare` sees. */ shouldCommit?: (prev: unknown, next: unknown, ctx: FrameRenderContext) => boolean; /** * @zh 每次泵触发都调用(含最终没有提交的帧)。**不是**通用帧时钟:只有存在待处理更新时才会 * 泵,空闲时一帧都不跑 —— 这也正是它省电的原因。需要连续帧回调请直接用 `rafSchedule`。 * @en Called on every pump tick, including ticks that end up not committing. This is **not** a * general frame clock: the pump only runs while an update is pending, and runs zero frames when * idle — which is exactly why it is cheap. For a continuous frame callback use `rafSchedule`. */ onFrame?: (ctx: FrameRenderContext) => void; /** * @zh 提交之后在 effect 中调用(绝不在渲染期间)。用于埋点、同步外部系统、记录指标。 * @en Called after a commit, inside an effect (never during render). For instrumentation, syncing * external systems, recording metrics. */ onCommit?: (props: P, ctx: FrameRenderContext) => void; /** * @zh 发生"有意义的丢弃"时调用:被否决、被取消、卸载时仍有待处理值。被更新值覆盖(合帧的 * 正常工况)不上报。 * @en Called for meaningful drops: vetoed, cancelled, or still pending at unmount. Superseding * (the normal operation of coalescing) is not reported. */ onDrop?: (props: P, reason: FrameDropReason, ctx: FrameRenderContext) => void; /** * @zh 是否在开发环境打印一次"只适合无状态子组件"的提示。默认开启;置 false 关闭。 * @default true * @en Whether to print the "stateless children only" notice once in development. On by default; * pass false to silence it. */ warn?: boolean; } /** * @zh 合帧投递组件。详见模块头与 {@link FrameRenderProps}。 * * @example * ```tsx * // 状态在外层(高频无所谓),昂贵的子树每帧只渲染一次 * function Dashboard() { * const ticks = useHighFrequencyTicks() * return ( * * * * ) * } * * // 渲染函数形式:需要派生时用,props 包是被合帧投递的对象 * * {({data, theme}) => } * * ``` * * @en Framed delivery component. See the module header and {@link FrameRenderProps}. */ declare const FrameRender:

>(props: FrameRenderProps

& { ref?: Ref; }) => ReactElement | null; interface UseControlledOptions { /** * @description - 非受控模式下的默认值,会被受控模式下的值覆盖 * @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode */ defaultValue: T; /** * @description - 值变更前的回调函数,可用于拦截或修改新值 * @description_en - Callback function before the value changes, can be used to intercept or modify the new value */ onBeforeChange?: (newValue: T, currentValue: T) => boolean | void; /** * @description - 当值发生变化时触发的回调函数名 * @description_en - Callback function name triggered when the value changes * @default - onChange */ trigger?: string; /** * @description - 值的属性名 * @description_en - Property name of the value * @default - value */ valuePropName?: string; props: Record; } declare function useControlled(options: UseControlledOptions): [T, Dispatch>]; /** * @en Disposal primitives for the library's push-based APIs ({@link Emitter}, {@link Event}, * {@link DisposableStore}). * * The protocol is a plain `dispose()` method — the shape VS Code, RxJS and monaco use — and every * object built here *also* answers the real `Symbol.dispose` at runtime when the environment has it * (Chrome 125+, Safari 18.4+, Firefox 134+, Node 20+), so `using` declarations and * `Symbol.dispose` interop work without extra ceremony. * * The symbol is attached at runtime but deliberately kept out of the public *type*: this package * ships `src/` and its `.d.ts`, and naming the global `Disposable` / `Symbol.dispose` in a * signature makes `tsc` fail for consumers whose `lib` stops before `esnext.disposable`. It is the * same reason {@link safePromiseTry} reaches for `Promise.try` through a cast instead of calling * it: the library must not require a specific `lib` from everyone who imports it. Consumers who * do have the newer lib can write `using sub = event(handler)` and get the cleanup for free. * * **No module-level side effects.** The symbol is installed from each *constructor* * (`withDisposeSymbol(X.prototype)` is idempotent, so the first instance pays for it and the rest * just hit the `in` check), never from a top-level statement. A top-level call makes the whole * module undroppable for bundlers — a consumer importing `cx` would still ship the event system — * and it is also what makes `"sideEffects": false` in `package.json` truthful. See the build * section of AGENTS.md before moving one of these calls back out. * * @zh 推送式 API({@link Emitter}、{@link Event}、{@link DisposableStore})所需的可释放对象 * 基础设施。 * * 协议就是一个普通的 `dispose()` 方法——与 VS Code、RxJS、monaco 同形——同时本模块产出的每个 * 对象在运行时支持的情况下还会响应真正的 `Symbol.dispose`(Chrome 125+、Safari 18.4+、 * Firefox 134+、Node 20+ 具备),因此 `using` 声明与 `Symbol.dispose` 互操作无需额外处理。 * * 符号只在运行时挂上,刻意不写进公开*类型*:本库会发布 `src/` 与 `.d.ts`,在签名里引用全局 * `Disposable` / `Symbol.dispose` 会让 `lib` 停在 `esnext.disposable` 之前的使用者工程直接 * 编译报错——{@link safePromiseTry} 用类型断言而不是直接调用来取 `Promise.try`,是同一个理由。 * 而 lib 较新的使用者可以直接写 `using sub = event(handler)`,清理照常生效。 * * **模块顶层不做任何调用。** 符号安装放在各自*构造函数*里(`withDisposeSymbol(X.prototype)` * 是幂等的,第一个实例付这次开销,之后只走 `in` 判断),绝不写成顶层语句:顶层调用会让整个模块 * 无法被 bundler 丢弃——只 import `cx` 的使用者也会把事件系统打进产物——同时它也是 * `package.json` 里 `"sideEffects": false` 能成立的前提。要把这类调用挪回顶层之前,请先读 * AGENTS.md 的构建一节。 */ /** * @en A resource that is released by calling `dispose()`. Structurally compatible with TypeScript's * built-in `Disposable` at runtime (see the module header). * @zh 通过调用 `dispose()` 释放的资源。运行时与 TypeScript 内置的 `Disposable` 结构兼容 * (见模块头说明)。 */ interface CompatDisposable { dispose(): void; } /** * @en Teach an object — or a class prototype — to answer `Symbol.dispose` by calling its own * `dispose()`. No-op where the symbol does not exist. Returns the target so it can wrap a literal. * @zh 让一个对象(或类原型)以自身的 `dispose()` 响应 `Symbol.dispose`。环境没有该符号时什么也 * 不做。返回传入对象,方便直接包住一个字面量。 */ declare const withDisposeSymbol: (target: T) => T; declare const noopDisposable: CompatDisposable; /** * @en Check whether `thing` is {@link CompatDisposable}. The arity check mirrors VS Code: an object * with an unrelated `dispose(arg)` method should not be mistaken for a resource. * @zh 判断 `thing` 是否为 {@link CompatDisposable}。形参个数检查来自 VS Code:名字叫 `dispose` * 但带参数的无关方法不应该被当成可释放资源。 */ declare function isDisposable(thing: E): thing is E & CompatDisposable; /** * @en Dispose every item of an iterable, collecting failures instead of stopping at the first one: * one broken resource must not leave the rest un-released. * * A single failure is rethrown as-is; several are thrown as an `AggregateError` where the platform * has it, and as the first failure elsewhere (the rest are still reported through `console.error`, * so nothing is silently swallowed). * * @zh 释放一个可迭代对象里的每一项,并收集失败而不是在第一个错误处停下:一个坏掉的资源不该 * 让其余资源都留在那里。 * * 只有一个失败时原样抛出;多个失败在平台支持时抛 `AggregateError`,否则抛第一个失败(其余仍会 * 通过 `console.error` 报出来,不会静默吞掉)。 */ declare function disposeAll(disposables: Iterable): void; /** * @en Turn a cleanup function into a {@link CompatDisposable}. `fn` is guaranteed to run **once** * (pass an arrow function if it needs `this`). * @zh 把一个清理函数变成 {@link CompatDisposable}。`fn` 保证只执行**一次**(需要 `this` 时请传 * 箭头函数)。 */ declare function toDisposable(fn: () => void): CompatDisposable; /** * @en Combine several disposables into one that releases all of them. * @zh 把多个可释放对象合成一个,释放它即释放全部。 */ declare function combinedDisposable(...disposables: CompatDisposable[]): CompatDisposable; /** * @en Manages a collection of disposables. * * Preferred over a bare `CompatDisposable[]` because it handles the edge cases: the same value can * be added twice (the `Set` keeps one entry), and adding to an already-disposed store **warns and * drops** the newcomer instead of registering it. Note the newcomer is *not* disposed for you (VS * Code behaves the same way): the store cannot know whether the caller still holds it, so the choice * stays with the caller — see `DisposableStore.DISABLE_DISPOSED_WARNING` to silence the warning. * * @example * ```ts * const store = new DisposableStore() * store.add(emitter.event(handler)) * store.add(() => {}) // no — a function is not a disposable; wrap it in toDisposable() * store.dispose() // everything above is released * ``` * * @zh 管理一组可释放对象。 * * 比裸的 `CompatDisposable[]` 可靠,因为它处理了边界情况:同一个值可以重复添加(`Set` 只留一份), * 往已释放的 store 里添加会**告警并丢弃**新来的对象,而不是登记它。注意它**不会**替你释放新来的 * 对象(VS Code 也是如此):store 无法判断调用方是否还持有它,这个选择留给调用方——要静默这条告警 * 见 `DisposableStore.DISABLE_DISPOSED_WARNING`。 */ declare class DisposableStore implements CompatDisposable { #private; /** * @en Set to `true` to silence the warning for adding to an already-disposed store (useful in * tests that assert on the disposal path itself). * @zh 置为 `true` 可关闭「向已释放的 store 添加对象」的告警(在断言释放路径本身的测试里有 * 用)。 */ static DISABLE_DISPOSED_WARNING: boolean; constructor(); /** * @en Dispose of every registered disposable and mark this store as disposed. Later additions are * dropped with a warning (they are *not* disposed of — see the class docs). * @zh 释放所有已登记的对象并把本 store 标记为已释放。之后添加进来的对象会被告警丢弃(**不会** * 被释放,见类文档)。 */ dispose(): void; /** * @en Whether this store has been disposed of. * @zh 本 store 是否已被释放。 */ get isDisposed(): boolean; /** * @en Dispose of everything currently registered, but keep the store usable. * @zh 释放当前登记的全部对象,但 store 本身仍可继续使用。 */ clear(): void; /** * @en Register a disposable, returning it for chaining. * @zh 登记一个可释放对象,并把它返回出来以便链式书写。 */ add(o: T): T; /** * @en Remove a disposable from the store **and** dispose of it. Never throws when it was not part * of the store. * @zh 从 store 中移除一个对象**并**释放它。对象本就不在 store 里时也不抛错。 */ delete(o: T): void; /** * @en Remove a disposable from the store **without** disposing of it, handing ownership back to * the caller. * @zh 把对象从 store 中移除但**不**释放,所有权交还给调用方。 */ deleteAndLeak(o: T): void; /** * @en Report through the console when this store has already been disposed of, meant for * assertions in code that must not run after teardown. * @zh 本 store 已被释放时在控制台报告,用于「释放后不该再执行」的断言。 */ assertNotDisposed(): void; } /** * @en A map that owns the disposables it stores: overwriting or deleting a key releases the value * that was there. * * @example * ```ts * const listeners = new DisposableMap() * things.forEach(thing => listeners.set(thing, thing.onData(handle))) * listeners.deleteAndDispose(goneThing) // that one subscription only * ``` * * @zh 一个「拥有」其值的 Map:覆盖或删除某个 key 会释放原来挂在那里的对象。 */ declare class DisposableMap implements CompatDisposable { #private; constructor(store?: Map); /** * @en Dispose every stored value and mark the map as disposed. * @zh 释放所有值并把本 map 标记为已释放。 */ dispose(): void; /** * @en Dispose every stored value and empty the map, but keep the map usable. * @zh 释放所有值并清空 map,但 map 本身仍可继续使用。 */ clearAndDisposeAll(): void; /** * @en Whether a value is stored under `key`. * @zh `key` 下是否存有值。 */ has(key: K): boolean; /** * @en Number of stored values. * @zh 已存值的个数。 */ get size(): number; /** * @en The value stored under `key`, if any. * @zh `key` 下存的值(如果有)。 */ get(key: K): V | undefined; /** * @en Store `value` under `key`, disposing of whatever was there before. Pass * `skipDisposeOnOverwrite` when the previous value is still referenced elsewhere. * @zh 把 `value` 存到 `key` 下,并释放原先挂在那里的值。原来的值还被别处引用时,传 * `skipDisposeOnOverwrite`。 */ set(key: K, value: V, skipDisposeOnOverwrite?: boolean): void; /** * @en Remove the value stored under `key` from this map and dispose of it. * @zh 把 `key` 下的值移出本 map 并释放。 */ deleteAndDispose(key: K): void; /** * @en Remove the value stored under `key` and **return** it — the caller now owns the disposal. * @zh 把 `key` 下的值移出并**返回**——释放责任归调用方。 */ deleteAndLeak(key: K): V | undefined; /** * @en The stored keys. * @zh 已存的 key。 */ keys(): IterableIterator; /** * @en The stored values. * @zh 已存的值。 */ values(): IterableIterator; /** * @en Iterate the `[key, value]` pairs. Iterating does not transfer ownership. * @zh 遍历 `[key, value]` 对。遍历不会转移所有权。 */ [Symbol.iterator](): IterableIterator<[K, V]>; } /** * @en A push-based event system: an `Emitter` fires, any number of `Event` subscribers receive. * * The pair is deliberately not reactive state. An `Event` carries a signal, not a value you can * read at any time; a late subscriber has missed everything that already happened. When you need * "the current value plus changes" use `createExternalState`, `ValueWithChangeEvent` or * `useSyncExternalStore` instead. In React the usual shape is * * ```tsx * useEffect(() => { * const sub = emitter.event(handler) * return () => sub.dispose() * }, [emitter]) * ``` * * ## What is different from the original * * The event semantics are ported in full — the single-listener fast path, the sparse listener * array and its compaction, the delivery queue that makes re-entrant `fire()` behave, leak * detection, refuse-to-add, profiler and every combinator. What changed is only what the original * borrowed from the rest of `vs/base`: * * 1. **Disposal** comes from {@link ./disposable} instead of `lifecycle.ts`/`IDisposable`: the * protocol is `dispose()`, plus the real `Symbol.dispose` at runtime. `Disposable.None` is * {@link noopDisposable}. * 2. **No base-layer imports.** `LinkedList`, `createSingleCallFunction` and `diffSets` are * implemented privately below, `StopWatch` is a two-line `performance.now()` measurement inside * {@link EventProfiling}, and the default listener-error handler is a local * {@link onUnexpectedError} that reports through `console.error` instead of rethrowing on a * later turn of the loop — a library has no business turning a listener's exception into an * uncaught global error. * 3. **`env.VSCODE_DEV`** became {@link isDevelopment} (`process.env.NODE_ENV`), which bundlers * replace statically so the buffer-leak warnings disappear from production builds. * 4. **`fromObservable` / `fromObservableLight`** take a minimal structural observable — `get`, * `reportChanges`, `addObserver`, `removeObserver`. VS Code's `IObservable` carries a whole * operator set (`read`, `map`, `keepObserved`, `flatten`, …) that belongs to its observable * implementation, which this library does not ship; anything exposing the four members above * (including a VS Code observable) still satisfies the interface. * * @zh 推送式事件系统:`Emitter` 触发,任意多个 `Event` 订阅者接收。 * * 它与响应式状态是两回事,刻意不混。`Event` 传递的是信号而非「随时可读的值」;晚到的订阅者 * 已经错过了此前发生的一切。需要「当前值 + 变更通知」时请用 `createExternalState`、 * `ValueWithChangeEvent` 或 `useSyncExternalStore`。React 里的典型写法见上方代码块。 * * ## 与原版的差异 * * 事件语义是完整迁移的——单监听器快路径、稀疏监听器数组及其压缩、让重入 `fire()` 行为正确的 * 投递队列、泄漏检测、拒绝新增监听器、性能剖析,以及每一个组合子都在。变的只是原版从 `vs/base` * 其它模块借来的东西,共四处,逐条列在上面。 * * 注意第 1 条:本模块的可释放对象协议是 `dispose()`(运行时附带 `Symbol.dispose`),不是 * `IDisposable` 这个类型名本身,理由见 {@link ./disposable} 的模块头。 */ declare class LinkedList { #private; get size(): number; isEmpty(): boolean; clear(): void; push(element: E): () => void; shift(): E | undefined; [Symbol.iterator](): Iterator; } /** * @en Passed as the `delay` of {@link Event.debounce} / {@link Event.throttle} to flush on the next * microtask instead of on a timer. Cheaper and ordered with the rest of the microtask queue, at the * cost of not coalescing anything that arrives in a later task. * @zh 作为 {@link Event.debounce} / {@link Event.throttle} 的 `delay` 传入,表示在下一个微任务 * 而不是定时器上冲刷。更便宜,且与其余微任务保持顺序;代价是跨任务到达的事件不会被合并。 */ declare const MicrotaskDelay: unique symbol; /** * @en A promise that can be given up on. `cancel()` detaches the listener but — matching the * original — does not reject the promise: a cancelled wait simply never settles. * @zh 一个可以放弃等待的 promise。`cancel()` 会摘掉监听器,但与原版一致,**不会**让 promise * 变成 rejected:被取消的等待只是永远不结算。 */ interface CancelablePromise extends Promise { cancel(): void; } /** * @en A flag that only ever turns one way, plus an event for the turn. Only the read-only face is * declared here: {@link AsyncEmitter} consumes tokens, it never creates them. Any object with these * two members works, including VS Code's or `AbortSignal`-based adapters. * @zh 一个只会单向翻转的标志,外加翻转时的事件。这里只声明只读的一面:{@link AsyncEmitter} 消费 * token,不产生 token。任何具备这两个成员的对象都可以(包括 VS Code 的,或基于 `AbortSignal` * 的适配器)。 */ interface CancellationToken { /** * @en Whether cancellation has already been requested. * @zh 是否已经请求了取消。 */ readonly isCancellationRequested: boolean; /** * @en Fires when cancellation is requested. Late subscribers are still called, and only once. * @zh 取消被请求时触发。晚到的订阅者同样会被调用,且只调用一次。 */ readonly onCancellationRequested: Event; } /** * @en An observable value, as much of VS Code's interface as {@link Event.fromObservable} needs. * @zh 可观察值,取 VS Code 接口中 {@link Event.fromObservable} 需要的那部分。 */ interface IObservable extends IObservableWithChange { } /** * @en An observable value whose changes carry a payload. This library does not ship an observable * implementation; the interface is here so any compatible one can be adapted to an `Event`. * @zh 变更带载荷的可观察值。本库不提供 observable 实现,声明这个接口只是为了让任何兼容实现都能 * 被适配成 `Event`。 */ interface IObservableWithChange { /** * @en The current value. * @zh 当前值。 */ get(): T; /** * @en Force a check for changes and report them to observers. Must not be called from * {@link IObserver.handleChange}. * @zh 强制检查变更并上报给观察者。不可在 {@link IObserver.handleChange} 中调用。 */ reportChanges(): void; /** * @en Subscribe an observer (idempotent). * @zh 订阅一个观察者(幂等)。 */ addObserver(observer: IObserver): void; /** * @en Unsubscribe an observer (idempotent). * @zh 退订一个观察者(幂等)。 */ removeObserver(observer: IObserver): void; } /** * @en The receiving half of {@link IObservable}. * @zh {@link IObservable} 的接收端。 */ interface IObserver { /** * @en A transaction that may have modified `observable` started. Every call is paired with an * {@link IObserver.endUpdate}. * @zh 一次可能改动了 `observable` 的事务开始。每次调用都会配对一个 * {@link IObserver.endUpdate}。 */ beginUpdate(observable: IObservable): void; /** * @en That transaction ended — the place to react to it. * @zh 事务结束——反应变更的地方。 */ endUpdate(observable: IObservable): void; /** * @en `observable` might have changed. Handle lazily or in {@link IObserver.endUpdate}. * @zh `observable` 可能变了。请惰性处理,或留到 {@link IObserver.endUpdate}。 */ handlePossibleChange(observable: IObservable): void; /** * @en `observable` changed, with the change payload. * @zh `observable` 发生了变化,并附带变更载荷。 */ handleChange(observable: IObservableWithChange, change: TChange): void; } /** * @en An event with zero or one parameter that can be subscribed to. The event *is* a function: * call it with a listener to subscribe, and keep the returned {@link CompatDisposable} to * unsubscribe. Events are free to be hot (fire before you subscribe), and calling one is cheap — * the emitter does nothing at all until the first listener arrives. * * @example * ```ts * class Document { * private readonly _onDidChange = new Emitter() * readonly onDidChange: Event = this._onDidChange.event * * private edit(text: string) { * this._onDidChange.fire(text) * } * } * * const subscription = doc.onDidChange(text => console.log(text)) * subscription.dispose() * ``` * * @zh 零参或单参、可被订阅的事件。事件本身就是函数:传入监听器即订阅,保留返回的 * {@link CompatDisposable} 即可退订。事件可以是热的(订阅前就触发过),调用本身很便宜——第一个 * 监听器到来之前 emitter 什么都不做。 */ type Event = (listener: (e: T) => unknown, thisArgs?: any, disposables?: CompatDisposable[] | DisposableStore) => CompatDisposable; declare namespace Event { /** * @en An event that never fires. Safe to hand out as a default. * @zh 永不触发的事件。适合作为默认值分发出去。 */ const None: Event; /** * @en Given an event, returns another event which debounces calls and defers the listeners to a * later task via a shared `setTimeout`. The event is converted into a signal (`Event`) to * avoid additional object creation as a result of merging events and to try prevent race * conditions that could arise when using related deferred and non-deferred events. * * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not * block critical work (eg. latency of keypress to text rendered). * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties", e.g the event is a public * property. Otherwise a leaked listener on the returned event causes this utility to leak a * listener on the original event. * * @param event The event source for the new event. * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. * @param disposable A disposable store to add the new EventEmitter to. * * @zh 把一个事件变成「合并后延迟到后续任务再通知」的信号事件(统一转成 `Event`,避免合并 * 事件带来的额外对象分配,也尽量避免延迟事件与非延迟事件混用时的竞态)。适合把非关键工作(例如 * 常规 UI 更新)让开,别挡住关键路径(例如按键到出字的延迟)。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到(例如作为公开属性),就必须配一个 * {@link DisposableStore} 使用;否则返回事件上泄漏的监听器会连带在原事件上泄漏一个。 */ function defer(event: Event, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event; /** * @en Given an event, returns another event which only fires once. * * @zh 只触发一次的事件。 */ function once(event: Event): Event; /** * @en Fires once, and only when `condition` holds. * @zh 仅当 `condition` 成立时触发一次。 */ function onceIf(event: Event, condition: (e: T) => boolean): Event; /** * @en Maps an event of one type into an event of another type, like `Array.prototype.map`. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 像 `Array.prototype.map` 一样把一种类型的事件映射成另一种。*注意*:返回的 `Event` 只要 * 会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function map(event: Event, map: (i: I) => O, disposable?: DisposableStore): Event; /** * @en Runs `each` on every event object before the listener sees it — the place for a side effect * that must not change the value. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 在监听器收到事件对象之前先跑一遍 `each`——用于不改变值的副作用。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function forEach(event: Event, each: (i: I) => void, disposable?: DisposableStore): Event; /** * @en Wraps an event in another event that fires only when some condition is met. The type-guard * overload narrows the event type. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 只在条件成立时触发的事件;带类型守卫的重载会同时收窄事件类型。*注意*:返回的 `Event` * 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function filter(event: Event, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event; function filter(event: Event, filter: (e: T) => boolean, disposable?: DisposableStore): Event; function filter(event: Event, filter: (e: T | R) => e is R, disposable?: DisposableStore): Event; /** * @en Given an event, returns the same event but typed as `Event`. * @zh 同一个事件,但类型上是 `Event`——只关心「发生了」,不关心载荷。 */ function signal(event: Event): Event; /** * @en Fires whenever any of the given events fires, carrying that event's payload. * @zh 任一给定事件触发时都触发,并带上该事件的载荷。 */ function any(...events: Event[]): Event; function any(...events: Event[]): Event; /** * @en Folds every event object into an accumulator, firing the accumulator after each one. With * `initial` the first fire sees `merge(initial, first)`; without it the first event object is * passed through as the initial value. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 把每个事件对象折进累加值,每次折叠后触发一次。给了 `initial` 时首次触发即 * `merge(initial, first)`;不给则把第一个事件对象直接当作初始值透出。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function reduce(event: Event, merge: (last: O | undefined, event: I) => O, initial?: O, disposable?: DisposableStore): Event; /** * @en Debounces an event and merges everything that arrives inside the window. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @param event The original event to debounce. * @param merge A function that reduces all events into a single event. * @param delay The number of milliseconds to debounce, or `MicrotaskDelay`. * @param leading Whether to fire a leading event without debouncing. * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. * Without it, some events could go missing if the last listener leaves inside the window. * @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}. * @param disposable A disposable store to register the debounce emitter to. * * @zh 防抖:把窗口期内到达的事件合并成一次触发。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 * * `flushOnListenerRemove` 为假时,如果最后一个监听器在窗口期内离开,待触发的那批事件会丢失。 */ function debounce(event: Event, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; function debounce(event: Event, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; /** * @en Debounces an event, firing after some delay (default 0) with an array of everything that * arrived in the window. Flushes on listener removal by default, so nothing goes missing. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 防抖并把窗口期内的所有事件对象收集成数组后触发(默认延迟 0)。默认在监听器移除时冲刷, * 因此不会丢事件。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function accumulate(event: Event, delay?: number | typeof MicrotaskDelay, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event; /** * @en Throttles an event, ensuring it fires at most once per delay period. Unlike {@link debounce} * it can fire on both edges: immediately (`leading`) and after the delay with the merged value of * everything that arrived meanwhile (`trailing`). * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @zh 节流:每个延迟窗口最多触发一次。与 {@link debounce} 不同,它可以在两端都触发——立即 * (`leading`)以及延迟结束后带窗口期内的合并值(`trailing`)。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function throttle(event: Event, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; function throttle(event: Event, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; /** * @en Filters an event such that the same value is not emitted twice in a row — the way to * collapse "the same window got focus" from two sources into one notification. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @example * ```ts * // Fire only one time when a single window is opened or focused * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow)) * ``` * * @zh 连续重复的值只透出一次——把两个来源的「同一个窗口被聚焦」合并成一次通知。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 */ function latch(event: Event, equals?: (a: T, b: T) => boolean, disposable?: DisposableStore): Event; /** * @en Splits an event whose parameter is a union into one event per member. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @example * ```ts * const event = new Emitter().event * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined) * ``` * * @zh 把联合类型的事件拆成每个成员一个事件。*注意*:返回的 `Event` 只要会被「第三方」拿到, * 就必须配 {@link DisposableStore} 使用。 */ function split(event: Event, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event, Event]; /** * @en Buffers an event until it has a listener attached, then replays what was buffered. * * *NOTE* that this function returns an `Event` and it MUST be called with a {@link DisposableStore} * whenever the returned event is accessible to "third parties". * * @param event The event source for the new event. * @param debugName A name for this buffer, used in leak detection warnings. * @param flushAfterTimeout Whether to flush the buffer through a `setTimeout` when the first * listener is added, so several listeners attached in the same turn all receive it. * @param _buffer Internal: a source event array used for tests. * * @example * ```ts * // Start accumulating events; when the first listener is attached, flush * // after a timeout such that multiple listeners attached before the * // timeout would receive the event * this.onInstallExtension = Event.buffer(service.onInstallExtension, 'onInstallExtension', true) * ``` * * @zh 在有监听器之前先缓冲事件,等第一个监听器到来后再回放。 * * *注意*:返回的 `Event` 只要会被「第三方」拿到,就必须配 {@link DisposableStore} 使用。 * * `flushAfterTimeout` 让回放走一次 `setTimeout`,这样同一轮里挂上来的多个监听器都能收到。 */ function buffer(event: Event, debugName: string, flushAfterTimeout?: boolean, _buffer?: T[], disposable?: DisposableStore): Event; /** * @en Wraps the event in an {@link IChainableSythensis}, allowing a more functional programming * style. The synthesis object is built once per subscription, and the steps run in the order they * were written; a step that returns `HaltChainable` (what `filter` and `latch` do when they do * not pass) stops the chain for that event object. * * @example * ```ts * // Normal * const onEnterPressNormal = Event.filter( * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)), * e => e.keyCode === KeyCode.Enter * ) * * // Using chain * const onEnterPressChain = Event.chain(onKeyPress.event, $ => $ * .map(e => new StandardKeyboardEvent(e)) * .filter(e => e.keyCode === KeyCode.Enter) * ) * ``` * * @zh 用链式写法组合事件(见上方示例)。每次订阅构建一个合成器,各步按书写顺序执行;某一步返回 * `HaltChainable`(`filter`、`latch` 不通过时就是它)即中断该事件对象的后续处理。 */ function chain(event: Event, sythensize: ($: IChainableSythensis) => IChainableSythensis): Event; /** * @en The chainable face of an event, as produced by {@link Event.chain}. Methods return the same * synthesis object, so the calls can be chained; the generic parameter tracks the value flowing * through, which is why `map` changes it and `filter` only narrows it. * @zh {@link Event.chain} 提供的链式接口。各方法返回同一个合成器对象,因此可以连写;泛型参数 * 记录流经的值,所以 `map` 会改变它而 `filter` 只做收窄。 */ interface IChainableSythensis { map(fn: (i: T) => O): IChainableSythensis; forEach(fn: (i: T) => void): IChainableSythensis; filter(fn: (e: T) => e is R): IChainableSythensis; filter(fn: (e: T) => boolean): IChainableSythensis; reduce(merge: (last: R, event: T) => R, initial: R): IChainableSythensis; reduce(merge: (last: R | undefined, event: T) => R): IChainableSythensis; latch(equals?: (a: T, b: T) => boolean): IChainableSythensis; } /** * @en The subset of a Node.js `EventEmitter` that {@link Event.fromNodeEventEmitter} needs. * @zh {@link Event.fromNodeEventEmitter} 需要的 Node.js `EventEmitter` 子集。 */ interface NodeEventEmitter { on(event: string | symbol, listener: Function): unknown; removeListener(event: string | symbol, listener: Function): unknown; } /** * @en Creates an {@link Event} from a node event emitter. The source is only touched while * somebody is listening, so an event nobody subscribes to does not keep the emitter warm. * @zh 从 node 事件发射器创建 {@link Event}。只在有人监听期间才碰源,因此没人订阅时不会白白 * 占着发射器。 */ function fromNodeEventEmitter(emitter: NodeEventEmitter, eventName: string, map?: (...args: any[]) => T): Event; /** * @en The subset of a DOM `EventTarget` that {@link Event.fromDOMEventEmitter} needs. * @zh {@link Event.fromDOMEventEmitter} 需要的 DOM `EventTarget` 子集。 */ interface DOMEventEmitter { addEventListener(event: string | symbol, listener: any, options?: any): void; removeEventListener(event: string | symbol, listener: any, options?: any): void; } /** * @en Creates an {@link Event} from a DOM event emitter (an `EventTarget`, e.g. `window` or an * element). Same laziness as {@link fromNodeEventEmitter}. * @zh 从 DOM 事件发射器(`EventTarget`,例如 `window` 或某个元素)创建 {@link Event}。惰性 * 行为与 {@link fromNodeEventEmitter} 相同。 */ function fromDOMEventEmitter(emitter: DOMEventEmitter, eventName: string, map?: (...args: any[]) => T): Event; /** * @en Creates a promise out of an event, using {@link Event.once}. * * `cancel()` detaches the listener. Matching the original, it does *not* reject the promise — the * promise simply never settles, so `await`ing a cancelled wait hangs forever. Race it against * something else if that matters. * * @zh 用 {@link Event.once} 把事件变成 promise。 * * `cancel()` 会摘掉监听器,但与原版一致,**不会**让 promise 变成 rejected——它只是永远不结算, * 因此 `await` 一个被取消的等待会一直挂着。在意这点的话,请用别的东西和它 race。 */ function toPromise(event: Event, disposables?: CompatDisposable[] | DisposableStore): CancelablePromise; /** * @en A convenience function for forwarding an event to another emitter which improves * readability. This is similar to {@link Relay} but allows instantiating and forwarding on a * single line, and also allows for multiple source events. * * @example * ```ts * Event.forward(event, emitter) * // equivalent to * event(e => emitter.fire(e)) * // equivalent to * event(emitter.fire, emitter) * ``` * * @zh 把事件转发到另一个 emitter 的便捷写法,可读性比 `event(e => emitter.fire(e))` 好。 * 与 {@link Relay} 类似,但可以一行内完成创建与转发,也支持多个源事件。 */ function forward(from: Event, to: Emitter): CompatDisposable; /** * @en Adds a listener to an event and calls the listener immediately with `initial` as the event * object — the shape "render the current state, then keep it updated". * * @example * ```ts * // Initialize the UI and update it when dataChangeEvent fires * runAndSubscribe(dataChangeEvent, () => this._updateUI()) * ``` * * @zh 订阅事件并立刻用 `initial` 调一次监听器——「先渲染当前状态,再跟着更新」的形态。 */ function runAndSubscribe(event: Event, handler: (e: T) => unknown, initial: T): CompatDisposable; function runAndSubscribe(event: Event, handler: (e: T | undefined) => unknown): CompatDisposable; /** * @en Creates an event that fires when the observable changes, reading the new value for each * notification. The observable is only observed while somebody listens to the event. * @zh 观察值变化时触发的事件,每次通知都去读一次新值。只有当有人监听该事件期间才会挂上观察者。 */ function fromObservable(obs: IObservable, store?: DisposableStore): Event; /** * @en Same as {@link fromObservable}, but every listener is attached to the observable directly * (there is no shared emitter in between) and only the fact that something changed is passed on. * Cheaper when the value itself is not needed, more expensive with many listeners. * @zh 与 {@link fromObservable} 类似,但每个监听器直接挂到 observable 上(中间没有共享的 * emitter),并且只传递「变了」这个事实。不需要值时更省,监听器很多时更贵。 */ function fromObservableLight(observable: IObservable): Event; } /** * @en The hooks an {@link Emitter} exposes around listener bookkeeping. All of them are optional, * and every one is only called on the transition it names — `onWillAddFirstListener` fires for the * first subscriber, not for the second. * * Two of them are load-bearing for the combinators in {@link Event}: `onWillAddFirstListener` is * how a derived event starts listening to its source, and `onDidRemoveLastListener` is how it stops, * which is what keeps an unsubscribed derived event from holding the source alive. * * @zh {@link Emitter} 在监听器增删前后暴露的钩子。全部可选,且各自只在对应的一次转变时调用—— * `onWillAddFirstListener` 只在第一个订阅者到来时触发,第二个不会。 * * 其中两个对 {@link Event} 里的组合子至关重要:`onWillAddFirstListener` 是派生事件挂上源的时机, * `onDidRemoveLastListener` 是它摘下来的时机,后者正是「没人订阅的派生事件不会拖住源」的原因。 */ interface EmitterOptions { /** * @en Called *before* the very first listener is added. * @zh 第一个监听器加入*之前*调用。 */ onWillAddFirstListener?: Function; /** * @en Called *after* the very first listener is added. * @zh 第一个监听器加入*之后*调用。 */ onDidAddFirstListener?: Function; /** * @en Called after a listener is added. * @zh 每次有监听器加入后调用。 */ onDidAddListener?: Function; /** * @en Called *before* a listener is removed. * @zh 监听器移除*之前*调用。 */ onWillRemoveListener?: Function; /** * @en Called *after* the very last listener is removed. * @zh 最后一个监听器移除*之后*调用。 */ onDidRemoveLastListener?: Function; /** * @en Called when a listener throws. Defaults to a `console.error` report (the original in VS Code * rethrows instead); the remaining listeners still run either way. * @zh 监听器抛错时调用。默认写 `console.error`(VS Code 原版是重新抛出);两种情况其余监听器 * 都会照常执行。 */ onListenerError?: (e: any) => void; /** * @en Number of listeners allowed before a leak is assumed. Defaults to the globally configured * value — see {@link setGlobalLeakWarningThreshold}. * @zh 允许的监听器数量上限,超过即认为泄漏。默认取全局配置值,见 * {@link setGlobalLeakWarningThreshold}。 */ leakWarningThreshold?: number; /** * @en Human-readable name for the emitter, included in leak warning messages so a leak can be * traced back to its owner. * @zh emitter 的可读名字,会写进泄漏告警,便于定位是谁在泄漏。 */ leakWarningName?: string; /** * @en Pass in a delivery queue, which is useful for ensuring in-order event delivery across * multiple emitters. * @zh 传入一个投递队列,用于保证跨多个 emitter 的事件投递顺序。 */ deliveryQueue?: EventDeliveryQueue; /** * @en ONLY enable this during development. Names the emitter in {@link EventProfiling.all}. * @zh 仅开发期使用。取的名字会出现在 {@link EventProfiling.all} 里。 */ _profName?: string; } /** * @en Per-emitter timing, enabled by passing `_profName`. Every instance registers itself in * {@link EventProfiling.all} (a `Set` that holds strong references — clear it when done) so a * profiling overlay can walk all emitters in the process. * * @zh 每个 emitter 的耗时统计,通过 `_profName` 开启。每个实例都会把自己登记到 * {@link EventProfiling.all}(一个持有强引用的 `Set`——用完记得清)里,方便性能面板遍历进程内 * 全部 emitter。 */ declare class EventProfiling { #private; static readonly all: Set; private static _idPool; readonly name: string; listenerCount: number; invocationCount: number; elapsedOverall: number; durations: number[]; constructor(name: string); start(listenerCount: number): void; stop(): void; } /** * @en Sets the process-wide leak warning threshold, returning a disposable that restores the * previous value — handy in tests that want the warnings on for a single case. * * @zh 设置进程级的泄漏告警阈值,返回一个可释放对象用于还原旧值——在只想给单个用例打开告警的 * 测试里很方便。 */ declare function setGlobalLeakWarningThreshold(n: number): CompatDisposable; /** * @en A captured call stack, kept as a string until somebody asks to print it (capturing is cheap, * formatting is not). * @zh 一次调用栈快照,在被要求打印之前只以字符串形式保留(抓栈便宜,格式化不便宜)。 */ declare class Stacktrace { readonly value: string; static create(): Stacktrace; private constructor(); print(): void; } /** * @en The error logged when an emitter goes over its configured listener threshold. `kind` is * `dominated` when a single call site accounts for most of the listeners (a real leak) and * `popular` when they come from everywhere. * * @zh emitter 超过监听器阈值时记录的错误。`kind` 为 `dominated` 表示绝大多数监听器来自同一个 * 调用点(真泄漏),`popular` 表示各处都有。 */ declare class ListenerLeakError extends Error { readonly kind: string; readonly listenerCount: number; /** * @en The detailed message including listener count and most frequent stack. Available locally * for debugging but intentionally not used as the error `message`. * @zh 含监听器数量与最高频调用栈的详细消息。本地调试可用,但刻意不作为 error 的 `message`。 */ readonly details: string; constructor(kind: 'dominated' | 'popular', details: string, stack: string, listenerCount: number, emitterName?: string); static is(err: unknown): err is ListenerLeakError; } /** * @en The severe variant, logged when an emitter has gone so far past its threshold that it refuses * to accept new listeners at all (see {@link Emitter.event}). * @zh 更严重的一种:emitter 远超阈值时连新监听器都拒绝接受(见 {@link Emitter.event})。 */ declare class ListenerRefusalError extends ListenerLeakError { constructor(kind: 'dominated' | 'popular', details: string, stack: string, listenerCount: number, emitterName?: string); } /** * @en Wraps a listener so it can be identified inside the listener list. A single listener is the * most common case for an emitter, so `Emitter` keeps it unwrapped in an array; the container is * what makes "remove *this* one" possible without comparing function identity. * @zh 包住监听器,使其在监听器列表里可被识别。emitter 只有一个监听器是最常见的情形,因此 * `Emitter` 会把它单独存放而不用数组;容器的作用是让「移除这一个」不必比较函数身份。 */ declare class UniqueContainer { readonly value: T; stack?: Stacktrace; id: number; constructor(value: T); } type ListenerContainer = UniqueContainer<(data: T) => void>; type ListenerOrListeners = (ListenerContainer | undefined)[] | ListenerContainer; /** * @en The Emitter can be used to expose an Event to the public to fire it from the insides. * * @example * ```ts * class Document { * private readonly _onDidChange = new Emitter() * readonly onDidChange: Event = this._onDidChange.event * * private doIt(value: string) { * this._onDidChange.fire(value) * } * } * ``` * * Three implementation notes worth knowing before changing anything here: * * 1. **A single listener is stored bare, not in an array.** Most emitters have one subscriber, so * the array (and the allocation that comes with it) is avoided until a second one arrives. A * list never downgrades back to a single container even after removals — swapping between the * two shapes would cost more than the memory it saves. * 2. **The listener array can be sparse.** Removal writes `undefined` in place and only compacts * when more than half the slots are holes, so removing listeners is not quadratic. * 3. **`fire()` goes through a delivery queue** as soon as there is more than one listener, which * is what makes re-entrant and nested `fire()` calls deliver in order instead of interleaving, * and what makes removing a listener during delivery safe. * * @zh 对外暴露 `Event`、对内触发的事件发射器。 * * 改动这里之前值得知道的三点实现取舍: * * 1. **单个监听器直接存放,不进数组。** 大多数 emitter 只有一个订阅者,所以数组(以及随之而来的 * 分配)等到第二个订阅者出现才产生。列表一旦形成就不会因为移除而退回单个容器——两种形态来回切换 * 的开销大于省下的内存。 * 2. **监听器数组可以是稀疏的。** 移除时原位写 `undefined`,只有当空洞超过一半时才压缩,因此移除 * 监听器不是平方级开销。 * 3. **一旦监听器多于一个,`fire()` 就走投递队列**,这正是重入与嵌套 `fire()` 能按顺序投递而不是 * 交错的原因,也是投递过程中移除监听器安全的原因。 */ declare class Emitter { private readonly _options?; private readonly _leakWarningThreshold?; private readonly _leakWarningName?; private readonly _leakWarningErrorHandler?; private _leakageMon?; private readonly _perfMon?; private _disposed?; private _event?; /** * A listener, or list of listeners. * * `_listeners` and `_size` use TS `protected` rather than `#private` on purpose: subclasses * ({@link AsyncEmitter}, {@link PauseableEmitter}, {@link MicrotaskEmitter}) need them, and `#` * fields are invisible to subclasses. */ protected _listeners?: ListenerOrListeners; /** * Always to be defined if `_listeners` is an array. It's no longer a true queue, but holds the * dispatching 'state'. If `fire()` is called on an emitter, any work left in the `_deliveryQueue` * is finished first. */ private _deliveryQueue?; protected _size: number; constructor(options?: EmitterOptions); private _getLeakageMonitor; /** * @en Detach every listener and make the emitter reuse-proof: subscribing to a disposed emitter * returns {@link noopDisposable} instead of registering anything. Disposal is idempotent. * * Remaining listeners are *not* blamed right away — the popular * * ```ts * store.add(model) // (1) create and register the model * store.add(model.onChange(…)) // (2) subscribe and register the subscription * store.dispose() // disposes (1) then (2) * ``` * * pattern would otherwise warn on every teardown. * * @zh 摘掉所有监听器,并让 emitter 之后不再可用:向已释放的 emitter 订阅会返回 * {@link noopDisposable} 而不是登记监听器。释放是幂等的。 * * 剩余监听器不会被立刻清算——上方那种「先注册模型、再注册订阅」的常见写法会在每次拆卸时误报。 */ dispose(): void; /** * @en For the public to allow to subscribe to events from this Emitter. * * The returned function is cached, so `emitter.event === emitter.event` — worth knowing when an * effect dependency list has `emitter.event` in it. * * @zh 供外部订阅本 emitter 的事件。 * * 返回的函数是缓存的,因此 `emitter.event === emitter.event`——effect 依赖数组里写 * `emitter.event` 时这一点很重要。 */ get event(): Event; private _removeListener; private _deliver; /** Delivers items in the queue. Assumes the queue is ready to go. */ private _deliverQueue; /** * @en To be kept private to fire an event to subscribers. * * Fire order is definition order, and it is stable across re-entrant calls: if a listener fires * the same emitter again, the nested delivery finishes before the outer loop continues. * * @zh 触发事件(对外不开放)。投递顺序即注册顺序,并且在重入时保持稳定:如果某个监听器再次触发 * 同一个 emitter,嵌套的那一轮会先投递完,外层循环才继续。 */ fire(event: T): void; /** * @en Whether anybody is listening right now. Useful to skip building an expensive event object * nobody will receive. * @zh 当前是否有人监听。可以在没人接收时省掉构造昂贵的事件对象。 */ hasListeners(): boolean; } /** * @en A marker interface for an event delivery queue. Pass an instance to * {@link EmitterOptions.deliveryQueue} to make several emitters share one dispatch order — without * it, each emitter keeps its own and interleaved fires can be observed out of order. * @zh 事件投递队列的标记接口。传给 {@link EmitterOptions.deliveryQueue} 可让多个 emitter 共享同一 * 份投递顺序——不共享时各 emitter 各持一份,交错触发在观察者看来会乱序。 */ interface EventDeliveryQueue { _isEventDeliveryQueue: true; } /** * @en Creates a delivery queue to share between emitters. * @zh 创建一个可在多个 emitter 之间共享的投递队列。 */ declare const createEventDeliveryQueue: () => EventDeliveryQueue; /** * @en The contract {@link AsyncEmitter} requires of the event object it hands to listeners: a * cancellation token, plus a way to keep the delivery waiting for asynchronous work. * @zh {@link AsyncEmitter} 交给监听器的事件对象所需满足的约定:一个取消令牌,以及一种让投递等待 * 异步工作的方式。 */ interface IWaitUntil { token: CancellationToken; waitUntil(thenable: Promise): void; } /** * @en The data half of an {@link IWaitUntil} event object — what the caller passes to * {@link AsyncEmitter.fireAsync}, with `token` and `waitUntil` added per listener. * @zh {@link IWaitUntil} 事件对象的数据部分——调用方传给 {@link AsyncEmitter.fireAsync} 的内容, * `token` 与 `waitUntil` 由框架按监听器补上。 */ type IWaitUntilData = Omit, 'token'>; /** * @en An emitter whose `fireAsync` awaits each listener in turn, in registration order, and lets a * listener extend its own turn with `waitUntil(promise)`. * * This is the "listener can veto / must finish before the next one runs" shape used for things like * save-participants: every participant gets a chance to contribute, and delivery only moves on once * they are done. Failures are reported through `onUnexpectedError` and do not stop the others. * * @zh 一个「依次等待每个监听器」的 emitter:按注册顺序逐个 await,监听器可以用 * `waitUntil(promise)` 延长自己这一轮。 * * 这是「监听器可以拦一下 / 必须处理完才轮到下一个」的形态,例如保存参与者:每个参与者都有机会 * 参与,全部处理完才继续往下。失败通过 `onUnexpectedError` 上报,不影响其余监听器。 */ declare class AsyncEmitter extends Emitter { private _asyncDeliveryQueue?; /** * @en Deliver `data` to every current listener in order, awaiting each one and any promise it * registered through `waitUntil` before moving on. Stops early once `token` is cancelled; the * listeners already reached have run. * * @param data The event object, without `token`/`waitUntil` (added per listener). * @param token Cancellation for the whole delivery. * @param promiseJoin Lets the caller wrap each awaited promise, e.g. to add a timeout or to * attribute it to the listener for progress reporting. * * @zh 按顺序把 `data` 投递给当前每个监听器,逐个等待,并且等它通过 `waitUntil` 注册的 promise * 都结算后才继续。`token` 被取消时提前结束;已经轮到的监听器已经执行过。 */ fireAsync(data: IWaitUntilData, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise; } /** * @en An emitter whose events can be held back and replayed later. Useful when changes arrive * during a batch of work and the subscribers should see them only once the batch is done. * * Nesting is counted: every `pause()` needs its own `resume()`, and only the last one flushes. * While paused nothing is delivered, so a subscriber attached *during* the pause sees nothing until * the flush — which is the point. * * @zh 可以先把事件按住、之后再回放的 emitter。适合「一批工作期间到达的变更,希望订阅者等这批做完 * 才看到」的场景。 * * 暂停是计数的:每次 `pause()` 都要配一次 `resume()`,只有最后一次会冲刷。暂停期间不投递任何 * 事件,因此在暂停**期间**挂上的订阅者在冲刷前什么都看不到——这正是它的用途。 */ declare class PauseableEmitter extends Emitter { private _isPaused; /** @zh 暂停期间积压的事件。`@en` Events accumulated while paused. */ protected _eventQueue: LinkedList; private _mergeFn?; /** * @en Whether the emitter is currently holding events back. * @zh 当前是否处于「按住事件」的状态。 */ get isPaused(): boolean; constructor(options?: EmitterOptions & { merge?: (input: T[]) => T; }); /** * @en Hold back further events. Counted, so nested pauses need matching resumes. * @zh 按住后续事件。计数式,因此嵌套暂停需要一一对应地恢复。 */ pause(): void; /** * @en Release the most recent pause; when the count reaches zero, deliver what accumulated — as * one merged event if `merge` was given, otherwise one by one (and a listener that pauses again * mid-flush stops the loop, leaving the rest queued). * @zh 解除最近一次暂停;计数归零时投递积压的事件——给了 `merge` 就合并成一个,否则逐个投递 * (某个监听器在冲刷过程中再次暂停会中止循环,剩余事件留在队列里)。 */ resume(): void; fire(event: T): void; } /** * @en Like {@link PauseableEmitter}, but the pause starts by itself on the first `fire()` and lifts * after `delay` — so a burst collapses into one merged event. * @zh 与 {@link PauseableEmitter} 类似,但暂停由第一次 `fire()` 自动开始、`delay` 后自动解除—— * 一串密集触发因此塌缩成一次合并事件。 */ declare class DebounceEmitter extends PauseableEmitter { private readonly _delay; private _handle; constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number; }); fire(event: T): void; } /** * @en An emitter which queues all events and processes them at the end of the current task. * * Note the difference from {@link PauseableEmitter}: nobody has to resume this one, and a fire with * no listeners is dropped (there is no point queueing for nobody). * * @zh 把事件排队、在本轮任务末尾统一处理的 emitter。 * * 与 {@link PauseableEmitter} 的区别:这里不需要谁来恢复;而且没有监听器时直接丢弃(没人为之排队)。 */ declare class MicrotaskEmitter extends Emitter { private _queuedEvents; private _mergeFn?; constructor(options?: EmitterOptions & { merge?: (input: T[]) => T; }); fire(event: T): void; } /** * @en An event emitter that multiplexes many events into a single event. * * @example * ```ts * // Listen to the `onData` event of all `Thing`s, dynamically adding and removing `Thing`s * // to the multiplexer as needed. * const anythingDataMultiplexer = new EventMultiplexer<{ data: string }>() * const thingListeners = new DisposableMap() * * thingService.onDidAddThing(thing => { * thingListeners.set(thing, anythingDataMultiplexer.add(thing.onData)) * }) * thingService.onDidRemoveThing(thing => { * thingListeners.deleteAndDispose(thing) * }) * * anythingDataMultiplexer.event(e => { * console.log('Something fired data ' + e.data) * }) * ``` * * @zh 把多个事件汇聚成一个事件。 * * 关键语义:源事件只在**有人订阅聚合事件**期间才被挂上(`add()` 在无人订阅时只是登记,不挂钩), * 最后一个订阅者离开时全部摘下。 */ declare class EventMultiplexer implements CompatDisposable { private readonly emitter; private hasListeners; private events; constructor(); /** * @en The aggregated event. * @zh 聚合后的事件。 */ get event(): Event; /** * @en Add a source event, returning a handle that removes it again. Disposing the handle twice is * safe; removing a source while nobody listens just unregisters it. * @zh 加入一个源事件,返回用于移除它的句柄。重复释放该句柄是安全的;无人订阅时移除源只是取消 * 登记。 */ add(event: Event): CompatDisposable; private onFirstListenerAdd; private onLastListenerRemove; private hook; private unhook; /** * @en Unhook every source and dispose the aggregated emitter. The multiplexer cannot be reused * afterwards. * @zh 摘掉所有源并释放聚合 emitter。之后不能再用。 */ dispose(): void; } /** * @en The public face of {@link DynamicListEventMultiplexer}: an aggregated event plus disposal. * @zh {@link DynamicListEventMultiplexer} 的公开形态:聚合事件加释放能力。 */ interface IDynamicListEventMultiplexer extends CompatDisposable { readonly event: Event; } /** * @en Aggregates one event per item of a list that changes over time: items already present are * hooked immediately, and the `onAddItem` / `onRemoveItem` events keep it in sync as the list * changes. Each item's subscription is owned by a {@link DisposableMap}, so removing an item * unhooks exactly that item. * * @zh 汇聚「随时间变化的列表」中每项的一个事件:已有项立刻挂钩,之后由 `onAddItem` / * `onRemoveItem` 保持同步。每项的订阅由 {@link DisposableMap} 持有,因此移除某项只摘掉那一项。 */ declare class DynamicListEventMultiplexer implements IDynamicListEventMultiplexer { private readonly _store; readonly event: Event; constructor(items: TItem[], onAddItem: Event, onRemoveItem: Event, getEvent: (item: TItem) => Event); /** * @en Unhook everything: the item listeners, the two list events and the multiplexer. * @zh 摘掉所有东西:各项的监听、两个列表事件,以及 multiplexer 本身。 */ dispose(): void; } /** * @en The EventBufferer is useful in situations in which you want to delay firing your events * during some code. You can wrap that code and be sure that the event will not be fired during that * wrap. * * ``` * const emitter: Emitter * const delayer = new EventBufferer() * const delayedEvent = delayer.wrapEvent(emitter.event) * * delayedEvent(console.log) * * delayer.bufferEvents(() => { * emitter.fire() // event will not be fired yet * }) * * // event will only be fired at this point * ``` * * @zh 用于「某段代码期间先别触发事件」的场景:把那段代码包起来,事件在这段包裹期间不会触发, * 结束后统一投递(见上方示例)。 */ declare class EventBufferer { private data; /** * @en Wrap an event so it is buffered while a {@link bufferEvents} call is active. * * With `reduce`, all events buffered during the wrap collapse into one call carrying the merged * value. Without it, every event is replayed one by one afterwards. * * **Known upstream limitation of the `reduce` form:** it is only correct with a single listener on * the returned event. With two or more, each listener pushes the same event into the shared * accumulator (so `fire(1)` + `fire(2)` with two listeners reduces `1+1+2+2`), and only the first * listener to arrive gets notified at flush time. This is the original implementation's behaviour, * kept as-is rather than silently diverging; use the non-reduce form (or `Event.accumulate`) when * the derived event has several subscribers. * * @zh 包一个事件,使其在 {@link bufferEvents} 生效期间被缓冲。 * * 给了 `reduce` 时,包裹期间缓冲的全部事件会合并成一次投递;不给则结束后逐个回放。 * * **`reduce` 形式的上游已知限制:** 只有返回的事件「恰好一个监听器」时才是正确的。有两个及以上 * 监听器时,每个监听器都会把同一个事件推进共享累加器(于是两个监听器下 `fire(1)`+`fire(2)` 会 * 累加成 `1+1+2+2`),而且冲刷时只有最先到的那个监听器收到通知。这是原版实现的行为,这里原样 * 保留而不是悄悄改掉;派生事件有多个订阅者时请用非 reduce 形式(或 `Event.accumulate`)。 */ wrapEvent(event: Event): Event; wrapEvent(event: Event, reduce: (last: T | undefined, event: T) => T): Event; wrapEvent(event: Event, reduce: (last: O | undefined, event: T) => O, initial: O): Event; /** * @en Run `fn` with buffering on, then flush. * * Two behaviours worth knowing, both inherited from the original: * * - Nested calls each own a layer, and **every layer flushes its own buffers when it ends** — so * an event fired inside the inner call is delivered before an event fired before it in the outer * call. Nesting therefore reverses the order of what each layer buffered; if that matters, do * not nest. * - A throw inside `fn` still flushes what was buffered so far (the layer is popped and flushed on * the way out) and then propagates. * * @zh 在开启缓冲的情况下执行 `fn`,随后冲刷。 * * 两个由原版继承来的行为值得知道: * * - 嵌套时每一层各管一层缓冲,**且每一层在结束时都会冲刷自己缓冲的事件**——于是「内层调用里 * 触发的事件」会排在「外层调用里更早触发的事件」前面。嵌套会反转各层缓冲内容的顺序;在意顺序 * 就不要嵌套。 * - `fn` 抛错时已缓冲的内容仍会被冲刷(该层弹栈并冲刷),然后继续向上抛出。 */ bufferEvents(fn: () => R): R; } /** * @en A Relay is an event forwarder which functions as a replugable event pipe. Once created, you * can connect an input event to it and it will simply forward events from that input event through * its own `event` property. The `input` can be changed at any point in time. * * @zh 可换源的转发管道。创建后把输入事件接到它上面,它就把该输入事件通过自己的 `event` 转发出去; * `input` 随时可以更换。 */ declare class Relay implements CompatDisposable { private listening; private inputEvent; private inputEventListener; private readonly emitter; constructor(); /** @zh 转发出去的事件。`@en` The forwarded event. */ readonly event: Event; /** * @en Point the relay at a new source. While somebody is listening the old subscription is * replaced immediately; otherwise the new source is only used the next time the first listener * arrives. * @zh 把中继指向新的源。有人监听时立刻换掉旧的订阅;否则新源要等下次第一个监听器到来才被使用。 */ set input(event: Event); /** * @en Detach the input and dispose the internal emitter. * @zh 摘掉输入并释放内部 emitter。 */ dispose(): void; } /** * @en A value plus a notification that it changed — the read-anytime counterpart to {@link Event}. * The event carries no payload; read `value` when you need it. * @zh 「值 + 变更通知」——{@link Event} 的「随时可读」对应物。事件不带载荷,需要时去读 `value`。 */ interface IValueWithChangeEvent { readonly onDidChange: Event; get value(): T; } /** * @en The mutable, self-contained implementation of {@link IValueWithChangeEvent}: writing `value` * fires `onDidChange`, but only when the new value differs (`!==`) from the current one. * * @example * ```ts * const selection = new ValueWithChangeEvent(undefined) * const sub = selection.onDidChange(() => console.log(selection.value)) * selection.value = 'a' // logs 'a' * selection.value = 'a' // nothing — same reference * ``` * * @zh {@link IValueWithChangeEvent} 的可变实现:写 `value` 会触发 `onDidChange`,但只在 `!==` * 意义上确实变了时触发。 */ declare class ValueWithChangeEvent implements IValueWithChangeEvent { private _value; /** * @en A value that never changes, exposing {@link Event.None} as its event — cheaper than a real * instance when the value is fixed. * @zh 不变的值,事件就是 {@link Event.None}——值固定时比真造一个实例更省。 */ static const(value: T): IValueWithChangeEvent; private readonly _onDidChange; readonly onDidChange: Event; constructor(_value: T); /** @zh 当前值。`@en` The current value. */ get value(): T; set value(value: T); } /** * @en Keep a {@link DisposableMap} in sync with a set that lives elsewhere: `handleItem` is called * for each item in the set (only the first time it is seen), and the disposable it returns is * disposed once the item leaves the set. * * Returns a single disposable that ends the tracking — disposing it does *not* dispose the handles * of items still in the set (they are owned by the returned store the caller may keep). * * @param getData Reads the current set. * @param onDidChangeData Tells when to re-read it. * @param handleItem Is called for each item in the set (but only the first time the item is seen in * the set). The returned disposable is disposed if the item is no longer in the set. * * @zh 让一个 {@link DisposableMap} 跟随别处维护的集合:集合里每出现一项就调用一次 `handleItem` * (只在第一次见到该项时调用),该项离开集合时释放它返回的句柄。 * * 返回一个用于结束追踪的可释放对象——释放它**不会**连带上集合里仍在的那些句柄(它们归调用方持有的 * 那个 store 管)。 */ declare function trackSetChanges(getData: () => ReadonlySet, onDidChangeData: Event, handleItem: (d: T) => CompatDisposable): CompatDisposable; /** * @zh 状态回调函数。对于异步函数,会在状态更新后执行,不会阻塞状态更新,尽可能在外部使用 useEffect 处理异步副作用。 * @en State callback function. Async callbacks run after the state update without blocking it; prefer useEffect for async side effects. * @template T The type of the state / 状态的类型 * @param newState The new state value / 新的状态值 * @param prevState The previous state value / 之前的状态值 */ type ExternalStateCallback = (newState: T, prevState: T) => any | Promise; /** * @zh 相等性判断函数。默认是 `Object.is`;需要「内容相同即相等」时传入 `shallowEqual`。 * @en Equality predicate. Defaults to `Object.is`; pass `shallowEqual` when equal-by-content * should count as equal. * @template S The type of the compared value / 被比较的值的类型 */ type EqualityFn = (a: S, b: S) => boolean; /** * @zh 选择性订阅的回调,第一个参数是当前切片,第二个是上一次的切片(`fireImmediately` 时两者相同)。 * @en Listener for selector subscriptions: the current slice, then the previous slice (both the * same when `fireImmediately`). * @template S The type of the selected slice / 被选中的切片的类型 */ type SelectorListener = (nextSlice: S, prevSlice: S) => void; /** * @en Options for `subscribeWithSelector` * @zh `subscribeWithSelector` 的选项 * @template S The type of the selected slice / 被选中的切片的类型 */ interface SubscribeSelectorOptions { /** * @zh 切片相等性判断,默认 `Object.is`。 * @en Slice equality, `Object.is` by default. */ isEqual?: EqualityFn; /** * @zh 订阅时立即用当前切片触发一次回调(`nextSlice` 与 `prevSlice` 相同)。 * @en Fire once immediately with the current slice (`nextSlice` and `prevSlice` are the same). */ fireImmediately?: boolean; } /** * @en Options for creating external state * @zh 创建外部状态的选项 * @template T The type of the state / 状态的类型 */ interface ExternalStateOptions { /** * @en Callback invoked on every `set` call, even when the value is unchanged * @zh 每次调用 `set` 后触发,即使值未发生变化 */ onSet?: ExternalStateCallback; /** * @en Callback invoked only when the stored value actually changes * @zh 仅在内部存储值发生变化时触发 */ onChange?: ExternalStateCallback; /** * @zh 通知订阅者的时机。 * * `'sync'`(默认):`set` 返回前就通知完毕,写完立刻读的代码(含测试里的同步断言)都成立。 * `'microtask'`:同一轮任务内多次 `set` 只通知一次,通知在微任务里执行。适合「订阅者多 + 写很频繁」 * 的场景——省下的是每次 `set` 的一遍遍历与通知,React 那侧本来就会合并渲染,所以净语义不变;区别是 * `set` 返回时订阅者还没收到通知,且中间态被跳过(订阅者只看到本轮最后的值)。 * `onSet` / `onChange` 不受影响,仍然逐次同步执行。 * @en When subscribers are notified. * * `'sync'` (default): notification completes before `set` returns, so code that reads right after * writing (including synchronous assertions in tests) holds. `'microtask'`: several `set` calls in * one task notify once, from a microtask. Worth it when there are many subscribers and writes are * frequent — what it saves is the per-`set` walk and notification, and since React coalesces * renders anyway the net semantics are the same; the difference is that subscribers have not been * notified when `set` returns and intermediate states are skipped (they see the last value of the * batch). `onSet` / `onChange` are unaffected and still run synchronously for every `set`. */ notify?: 'sync' | 'microtask'; } /** * @en External state management interface * @zh 外部状态管理接口 * @template T The type of the state / 状态的类型 */ interface ExternalState { /** * @en Get the current state value * @zh 获取当前状态值 * @returns The current state value / 当前状态值 */ get: () => T; /** * @zh 设置新的状态值。传入 updater 时必须返回**新引用**:原地修改 * (`set((prev) => {prev.list.push(x); return prev})`)与旧值 `Object.is` 相等,会被判定为 * 「没有变化」,订阅者不会收到通知。 * @en Set a new state value. An updater must return a **new reference**: mutating in place * (`set((prev) => {prev.list.push(x); return prev})`) compares `Object.is`-equal to the previous * value, counts as "unchanged", and notifies nobody. * @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数 */ set: (newState: T | ((prevState: T) => T)) => void; /** * @en React Hook for using external state in components. * @zh 在组件中使用外部状态的 React Hook。 * @returns Array containing current state and update function, similar to React useState / 包含当前状态和更新函数的数组,类似于 React useState */ useState: () => [T, (newState: T | ((prevState: T) => T)) => void]; /** * @zh useState 的变体,只获取 value. * @en A variant of useState that only gets the value. */ useGetter: () => T; /** * @zh 选择性订阅:只有 `selector` 选出的切片发生变化时才重渲染。 * * 整份 state 变化时,`set` 依然会通知所有订阅者,但每个消费者的快照是「按 selector 计算 + * 相等性比较后的缓存切片」;切片相等时 `useSyncExternalStore` 判定快照未变,不调度重渲染。 * 于是改一个字段不会让只读其它字段的组件重渲染。 * * selector 返回新对象/新数组时(`s => ({a: s.a})`、`s => s.list.filter(...)`)每次都是新引用, * 默认的 `Object.is` 永远认为「变了」,此时应显式传入 `isEqual`(如 `shallowEqual`)。 * @en Fine-grained subscription: re-render only when the slice picked by `selector` changes. * * A `set` still notifies every subscriber, but each consumer's snapshot is the slice computed * by `selector` and cached with an equality check. When the slice compares equal, * `useSyncExternalStore` sees an unchanged snapshot and skips the re-render — so writing one * field no longer re-renders components that read other fields. * * A selector that builds a fresh object/array (`s => ({a: s.a})`, `s => s.list.filter(...)`) * returns a new reference every call, so the default `Object.is` always reports a change; pass * an explicit `isEqual` (e.g. `shallowEqual`) in that case. * @param selector Derives the slice from the whole state / 从整份 state 派生切片 * @param isEqual Slice equality, `Object.is` by default / 切片相等性判断,默认 `Object.is` * @returns The selected slice / 选中的切片 * @example * ```tsx * const appState = createExternalState({name: 'wwog', age: 1, theme: 'light'}) * * // 改 age / theme 都不会让这个组件重渲染 * const name = appState.useSelector((s) => s.name) * * // 合成对象必须给相等函数,否则每次都是新引用 * const head = appState.useSelector((s) => ({name: s.name, age: s.age}), shallowEqual) * ``` */ useSelector: (selector: (state: T) => S, isEqual?: EqualityFn) => S; /** * @zh 在组件外订阅「任意变化」,返回退订函数。组件内请用 `useState` / `useSelector`。 * @en Subscribe to any change outside components; returns an unsubscribe function. Inside * components use `useState` / `useSelector` instead. * @param listener Called on every `set` / 每次 `set` 后触发 * @returns Unsubscribe / 退订函数 */ subscribe: (listener: () => void) => () => void; /** * @zh 在组件外按切片订阅:只有 `selector` 选出的切片变化才调用 `listener`,不相关的写入会被跳过。 * 适合「模块级逻辑只关心某几个字段」的场景,也用于替换手写的监听器集合。 * @en Subscribe to a slice outside components: `listener` runs only when the slice changes and * unrelated writes are skipped. Useful for module-level logic that cares about a few fields, and * for replacing hand-rolled listener sets. * @param selector Derives the slice from the whole state / 从整份 state 派生切片 * @param listener Receives (nextSlice, prevSlice); on the first change `prevSlice` is the slice * as it was when subscribing / 接收 (nextSlice, prevSlice);首次变化时 `prevSlice` 是订阅时的切片 * @param options `isEqual` / `fireImmediately` / `isEqual` 与 `fireImmediately` * @returns Unsubscribe / 退订函数 * @example * ```ts * const stop = appState.subscribeWithSelector( * (s) => s.age, * (age, prevAge) => console.log(`age: ${prevAge} → ${age}`), * ) * appState.set((prev) => ({...prev, theme: 'dark'})) // 切片没变,不触发 * stop() * ``` */ subscribeWithSelector: (selector: (state: T) => S, listener: SelectorListener, options?: SubscribeSelectorOptions) => () => void; } interface ExternalWithKernel extends ExternalState { __listeners: (() => void)[]; } /** * * @example * ```tsx * // Create an app-level theme state with options * const themeState = createExternalState('light', { * onChange: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`), * }); * * // Get or modify state outside components * console.log(themeState.get()); // 'light' * themeState.set((prev) => prev === 'light' ? 'dark' : 'light'); // Toggle theme * * // Use state in components * function ThemeConsumer() { * const [theme, setTheme] = themeState.useState(); * * return ( *

* *
* ); * } * ``` */ declare function createExternalState(initialState: T | (() => T), options?: ExternalStateOptions): ExternalState; interface StorageStateOptions { onSet?: ExternalStateCallback; onChange?: ExternalStateCallback; /** * @zh 使用 localStorage(默认)或 sessionStorage。 * @en Use localStorage (default) or sessionStorage. */ storageType?: 'local' | 'session'; /** * @zh 是否跟随其它标签页的写入:监听 `storage` 事件,把别的标签页写入的值同步进来。同步走 `set`, * 因此 `onSet` / `onChange` 照常触发,`useSelector` 那套切片订阅也照常工作。 * * 默认关闭:不跨标签页同步是既有行为,而且这个事件只在多个标签页共享同一份存储时才有意义 * (`sessionStorage` 是每标签页独立的,开了也收不到事件)。对方删除该键或调用 `clear()` 时状态回到 * `initialState`,并且不会把初值写回存储——否则每个还开着的标签页都会把对方清掉的内容重新写上去。 * @en Whether to follow writes from other tabs: listen for `storage` events and apply values * written elsewhere. The value goes through `set`, so `onSet` / `onChange` fire as usual and the * `useSelector` slice subscriptions keep working. * * Off by default: not syncing is the established behavior, and the event only exists when several * tabs share one storage area (`sessionStorage` is per-tab, so enabling it there has no effect). * When another tab removes the key or calls `clear()`, the state returns to `initialState` and the * initial value is **not** written back — otherwise every remaining tab would resurrect what the * other tab just cleared. */ syncAcrossTabs?: boolean; } declare function createStorageState(key: string, initialState: T, options?: StorageStateOptions): ExternalState; /** * @zh 浅比较:先比引用,再对「数组 / 普通对象」的每个自有可枚举键做一次 `Object.is`。 * * 主要用于 `useSelector` 的相等性判断:当 selector 需要合成一个新对象 * (`s => ({name: s.name, age: s.age})`)时,每次调用都会得到新引用,默认的 `Object.is` * 永远判定为「变了」,于是任何无关字段变化都会重渲染。传入 `shallowEqual` 才能让 * 「内容相同」被判为相等,从而跳过重渲染。 * * 注意:只有普通对象与数组参与逐键比较。`Date` / `Map` / `Set` / `RegExp` / 类实例的自有 * 可枚举键都是空的,逐键比较会把内容不同的两个对象误判为相等(`new Date(0)` 与 * `new Date(1)` 会「相等」),订阅者因此漏掉更新。这些类型一律退化为引用比较——宁可多渲染 * 一次,不可少渲染一次。 * @en Shallow equality: reference equality first, then one `Object.is` pass over the own * enumerable keys of arrays and plain objects. * * This exists mainly for `useSelector`: a selector that composes a new object * (`s => ({name: s.name, age: s.age})`) returns a fresh reference every call, so the default * `Object.is` always reports "changed" and every unrelated field update re-renders. Passing * `shallowEqual` lets equal-by-content selections count as equal and skip the re-render. * * Only arrays and plain objects are compared key-wise. `Date` / `Map` / `Set` / `RegExp` and * class instances have no own enumerable keys, so a key-wise pass would call two objects with * different contents equal (`new Date(0)` and `new Date(1)` would "match") and subscribers * would miss updates. Those types fall back to reference equality: an extra render is * recoverable, a missed one is not. * * @example * ```tsx * // 合成对象必须给相等函数,否则无关字段变化也会重渲染 * const head = appState.useSelector((s) => ({name: s.name, age: s.age}), shallowEqual) * ``` */ declare function shallowEqual(a: unknown, b: unknown): boolean; /** * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环 * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop */ declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void; /** * @param schema * @example * YY | 18 | Two-digit year * YYYY | 2018 | Four-digit year * M | 1-12 | The month, beginning at 1 * MM | 01-12 | The month, 2-digits * MMM | Jan-Dec | The abbreviated month name * MMMM | January-December | The full month name * D | 1-31 | The day of the month * DD | 01-31 | The day of the month, 2-digits * d | 0-6 | The day of the week, with Sunday as 0 * dd | Su-Sa | The min name of the day of the week * ddd | Sun-Sat | The short name of the day of the week * dddd | Sunday-Saturday | The name of the day of the week * H | 0-23 | The hour * HH | 00-23 | The hour, 2-digits * h | 1-12 | The hour, 12-hour clock * hh | 01-12 | The hour, 12-hour clock, 2-digits * m | 0-59 | The minute * mm | 00-59 | The minute, 2-digits * s | 0-59 | The second * ss | 00-59 | The second, 2-digits * SSS | 000-999 | The millisecond, 3-digits * Z | +05:00 | The offset from UTC, ±HH:mm * ZZ | +0500 | The offset from UTC, ±HHmm * A | AM | PM * a | am | pm */ declare function formatDate(schema: string, date?: Date): string; declare class Counter { count: number; /** * @description 获取下一个计数值,不考虑越界。 * @description_en Get the next count value, without considering overflow. */ next(): number; } /** * Takes a callback of any kind (returns or throws, synchronously or asynchronously) and wraps its result * in a Promise. * * @param callbackFn A function that is called synchronously. It can do anything: either return * a value, throw an error, or return a promise. * @param args Additional arguments, that will be passed to the callback. * * @returns A Promise that is: * - Already fulfilled, if the callback synchronously returns a value. * - Already rejected, if the callback synchronously throws an error. * - Asynchronously fulfilled or rejected, if the callback returns a promise. */ declare function promiseTry(callbackFn: (...args: U) => T | PromiseLike, ...args: U): Promise>; declare const safePromiseTry: typeof promiseTry; declare const safePromiseWithResolvers: () => { promise: Promise; resolve: (value: T | PromiseLike) => void; reject: (reason?: unknown) => void; }; declare const breakpoints: readonly ["base", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"]; declare const DefBreakpointDesc: BreakpointDesc; type BreakpointName = (typeof breakpoints)[number]; type BreakpointDesc = Partial>; type Responsive = T | Partial>; /** * @en Splitting: yield the main thread between pieces of long-running work. * * The browser's main thread is single-threaded: while a task runs, the rendering * pipeline (style / layout / paint) and user input cannot be processed. A task * longer than ~50ms is a "long task" and shows up as jank — the well-known * symptom is a streaming chat flood freezing the input field and animations. * * The fix is to split long work into pieces and yield the main thread between * them, so backlogged input and frame production get their turn in the gaps. * Yielding does NOT make the work faster (total time is unchanged, or slightly * longer due to scheduling overhead) — it makes the page *feel* responsive. * * @zh 拆分(Splitting):在长任务的各片段之间让出主线程。 * * 浏览器主线程是单线程的:一个任务运行期间,渲染管线(样式 / 布局 / 绘制)与用户输入 * 都无法处理。超过 ~50ms 的任务即“长任务”,表现为卡顿——典型症状是直播聊天洪流 * 把输入框和动画冻住。 * * 解法是把长工作切成小片,片段之间让出主线程,让积压的输入和帧生产在空隙中得以处理。 * 让出并不会让工作变快(总耗时不变,甚至因调度开销略增)——它让页面“感觉”流畅。 */ declare function yieldToMain(signal?: AbortSignal): Promise; /** * @en Options for {@link forEachChunked}. * @zh {@link forEachChunked} 的选项。 */ interface ForEachChunkedOptions { /** * @en How many items to process before each yield. Default `20`. * Must be a positive integer; `0`, negative or fractional values throw a * `RangeError` instead of silently disabling the yields. * Splitting too finely backfires: the yield/resume overhead can exceed the * work itself. Splitting too coarsely (hundreds+) recreates the long task. * @zh 每处理多少条让出一次主线程,默认 `20`。 * 必须为正整数;传 `0`、负数或小数会抛 `RangeError`,而不是静默取消让出。 * 切得太碎会适得其反:让出/恢复的开销可能超过工作本身;切得太粗(数百条以上) * 则又变回长任务。 */ chunkSize?: number; /** * @en AbortSignal for early termination. When aborted, iteration stops and * the returned promise rejects with the abort reason. * @zh 用于提前终止的 AbortSignal。中断后迭代停止,返回的 Promise 以中断原因 reject。 */ signal?: AbortSignal; } /** * @en Run `fn` over every item, yielding the main thread after every * `chunkSize` items — the streaming-chat-flood fix. * * Solves: a burst of hundreds of messages (or any bulk DOM/compute work) * processed in one task blocks input and paint for its whole duration. With a * yield every N items, the input field keeps responding while the flood is * still being drawn. Total wall-clock time is unchanged or slightly longer — * the win is responsiveness, not throughput. * * @param items Items to process, in order. * @param fn Called per item. May be async; each item completes before the next. * @param options See {@link ForEachChunkedOptions}. * @returns Promise that resolves when all items are processed. * @throws RangeError If `chunkSize` is not a positive integer. * * @example * ```ts * // A batch of chat messages arrives at once * socket.on('messages', (chats: Chat[]) => { * // draw the flood, yielding the main thread after every 20 messages * await forEachChunked(chats, (chat) => appendChatNode(chat)) * }) * ``` */ declare function forEachChunked(items: Iterable, fn: (item: T, index: number) => void | Promise, options?: ForEachChunkedOptions): Promise; /** * @en Options for {@link forEachInFrames}. * @zh {@link forEachInFrames} 的选项。 */ interface ForEachInFramesOptions { /** * @en Main-thread budget per frame, in milliseconds. Default `5`. * Must be a positive number; a non-positive or non-finite value throws a * `RangeError` rather than silently spinning without progress. * The practical frame budget is ~10ms (of the 16.6ms a 60Hz frame allows, * minus browser overhead); handing roughly half to background work leaves * the rest for animation callbacks, style, layout and paint. Shrink it if * the animations running alongside are heavy. * @zh 每帧占用的主线程预算(毫秒),默认 `5`。 * 必须为正数;传 0、负数或非有限值会抛 `RangeError`,而不是静默空转不推进。 * 实际帧预算约 10ms(60Hz 的 16.6ms 减去浏览器自身开销),分给后台工作约一半, * 其余留给动画回调、样式、布局与绘制。若同时运行的动画较重,应调小。 */ budgetMs?: number; /** * @en How many items to process between two `performance.now()` reads. * Default `1` (check the budget after every item — exact, and the right * choice when items are heavy). Reading the clock costs ~69ns, which is * negligible against a heavy item but dominant against a trivial one; if * items are cheap, raise this (e.g. `16`) to trade budget precision for * throughput — the budget can then overshoot by up to * `clockSampleEvery × per-item cost`. Must be a positive integer. * @zh 每处理多少条才读一次 `performance.now()`。默认 `1`(每条都检查预算 —— * 精确,且条目较重时的正确选择)。读一次时钟约 69ns,相对重条目可忽略, * 但相对极轻的条目就是主要开销;条目很轻时可调大(如 `16`),用预算精度换吞吐—— * 此时预算最多超出 `clockSampleEvery × 单条耗时`。必须为正整数。 */ clockSampleEvery?: number; /** * @en AbortSignal for early termination. * @zh 用于提前终止的 AbortSignal。 */ signal?: AbortSignal; } /** * @en Run `fn` over every item, but only for `budgetMs` per animation frame — * heavy work that coexists with running animations. * * Solves: work like N-body particle steering (~16M distance checks per pass) * blows the frame budget on its own, dropping fps to single digits. Anchoring * to the frame's start timestamp (passed to rAF callbacks) instead of "now" * makes the code cooperate naturally when several callbacks share one frame: * "use 5ms" becomes "use until 5ms after the frame started", so our share * shrinks by whatever earlier callbacks already used. * * Resumption lands via `requestAnimationFrame`, i.e. just before the next * frame is drawn — in rhythm with the rendering cycle, unlike * {@link yieldToMain}, which resumes without regard to it. * * At least one item is processed per frame, so the loop always makes progress * even when other callbacks already consumed the frame's budget. When `fn` is * async, the loop resumes on the next frame after each item (at most one * async item per frame); use a synchronous `fn` for full per-frame throughput. * * @param items Items to process, in order. * @param fn Called per item. May be async; each item completes before the next. * @param options See {@link ForEachInFramesOptions}. * @returns Promise that resolves when all items are processed. * @throws RangeError If `budgetMs` is not a positive finite number, or * `clockSampleEvery` is not a positive integer. * * @example * ```ts * // Recompute 4,000 particles without killing the 60fps animation * await forEachInFrames(particles, (p) => p.applyForces(), {budgetMs: 5}) * * // Very cheap items: read the clock every 16 items instead of every item * await forEachInFrames(rows, (row) => row.markDirty(), { * budgetMs: 5, * clockSampleEvery: 16, * }) * ``` */ declare function forEachInFrames(items: Iterable, fn: (item: T, index: number) => void | Promise, options?: ForEachInFramesOptions): Promise; /** * @en Batching: collapse work that fires too often into fewer, appropriately * sized tasks — so the rendering pipeline's fixed cost is paid once per batch * instead of once per item. * * Splitting deals with tasks so long that rendering can't squeeze in; batching * deals with tasks so *frequent* that the pipeline's fixed cost is paid over * and over. Events are the best targets: scroll, resize and input can fire * dozens or hundreds of times in a short span. * * Two collapse strategies exist: "run once after things quiet down" (debounce) * and "run at most once per interval" (throttle). For visual updates, the * screen only gets drawn once per frame anyway, so coalescing into one draw * per rAF (rafSchedule) loses no data while dropping the cost. * * DOM writes batch the same way: appending a hundred nodes via one * DocumentFragment instead of one at a time turns many layout/paint * invalidations into one. And when reads (offsetWidth, getBoundingClientRect) * are interleaved with writes, the browser is forced to recompute layout on * the spot each iteration — layout thrashing. Grouping all reads before all * writes avoids it. * * @zh 批量(Batching):把触发过于频繁的工作合并为大小合适的任务——让渲染管线的 * 固定成本每批只付一次,而不是每条付一次。 * * 拆分处理的是“长到渲染插不进”的任务;批量处理的是“频繁到固定成本反复支付”的任务。 * 最佳目标是事件:scroll、resize、input 在短时间内可能触发几十上百次。 * * 合并策略有两种:“等安静下来再跑一次”(debounce)和“每个区间最多跑一次”(throttle)。 * 对视觉更新而言,屏幕每帧本来就只画一次,把更新合并为每帧一次(rafSchedule)不丢数据, * 又省下成本。 * * DOM 写入同理:用 DocumentFragment 一次挂载一百个节点,把多次布局/绘制失效合并为一次。 * 而当读(offsetWidth、getBoundingClientRect)与写交错时,浏览器被迫当场重算布局—— * 即 layout thrashing(布局抖动)。先收集所有读、再统一写即可避免。 */ /** * @en Debounced function type: same parameters as the original, returns * `undefined` (execution is postponed). * @zh debounce 后的函数类型:参数与原函数相同,返回 `undefined`(执行被推迟)。 */ interface DebouncedFunction any> { (...args: Parameters): void; /** * @en Discard any pending invocation. The debounced function will not run. * @zh 丢弃未执行的调用,debounce 后的函数将不再运行。 */ cancel(): void; /** * @en If a call is pending, run it immediately. * @zh 若有等待中的调用,立即执行。 */ flush(): void; } /** * @en Return a debounced version of `fn` that postpones execution until * `wait` ms have elapsed since the last call. * * Solves: a heavy handler that runs on every keystroke — e.g. rebuilding a * ~2,000-line markdown preview per character — makes typing fall behind. * Debounced, the render happens just once, after typing stops. * * Each call during the wait window resets the timer; only the last call's * arguments survive. This is "run once after things quiet down". * * @param fn The function to debounce. * @param wait Milliseconds of quiet required before `fn` runs. Default `200`. * @returns The debounced function, with `cancel()` and `flush()`. * * @example * ```ts * // Rebuild the preview once, after the user stops typing * const renderPreview = debounce(() => renderMarkdown(editor.value), 300) * editor.addEventListener('input', renderPreview) * ``` */ declare function debounce any>(fn: F, wait?: number): DebouncedFunction; /** * @en Throttled function type: same parameters as the original, returns * `undefined`. * @zh throttle 后的函数类型:参数与原函数相同,返回 `undefined`。 */ interface ThrottledFunction any> { (...args: Parameters): void; /** * @en Stop future executions immediately. * @zh 立即停止后续执行。 */ cancel(): void; } /** * @en Return a throttled version of `fn` that runs at most once per `wait` ms. * * Solves: scroll / resize / pointermove handlers that fire dozens of times a * second and leave nothing of the main thread. Unlike debounce (which waits * for quiet), throttle guarantees steady cadence — the first call runs * immediately and following calls are dropped until the interval passes. * * Only the last call's arguments within an interval are kept (leading edge * fires immediately, trailing edge fires after the interval if calls occurred). * * @param fn The function to throttle. * @param wait Minimum interval between executions, in ms. Default `200`. * @returns The throttled function, with `cancel()`. * * @example * ```ts * // At most one highlight computation per 100ms, however fast the user scrolls * const onScroll = throttle(() => updateReadingPosition(), 100) * window.addEventListener('scroll', onScroll, {passive: true}) * ``` */ declare function throttle any>(fn: F, wait?: number): ThrottledFunction; /** * @en A frame-scheduled function: same parameters as the original, executed at * most once per animation frame. * @zh 按帧调度的函数类型:参数与原函数相同,每帧最多执行一次。 */ interface RafScheduledFunction any> { (...args: Parameters): void; /** * @en Drop the pending call and cancel the booked animation frame, so the * wrapped function does not run for that frame. * @zh 丢弃待执行调用并取消已预约的动画帧,该帧不会再执行被包装的函数。 */ cancel(): void; } /** * @en Return a version of `fn` that is scheduled at most once per animation * frame; later calls overwrite the arguments of the still-pending one. * * Solves: pushing 1,000+ messages per second into a 60-ticker board, where * redrawing on every message ("render every tick") drops fps to single digits. * The screen is drawn once per frame anyway, so coalescing all updates that * arrived during a frame into one call loses nothing — every arriving data * point is still reflected, but the fixed render cost is paid once per frame. * * Callers typically stash the latest value themselves and let `fn` read it, * or rely on the last-call-wins argument forwarding. * * There is deliberately no `flush()`: `requestAnimationFrame` cannot be forced * synchronously, so a synchronous flush would break the once-per-frame * guarantee it exists to provide. * * @param fn The function to coalesce; called at most once per frame, with the * most recent call's arguments. * @returns The scheduled function, with `cancel()` to drop a pending call. * * @example * ```ts * // 1,000 ticks/sec, one board render per frame — every point still lands * const renderBoard = rafSchedule(() => board.draw()) * socket.on('tick', (tick) => { * board.push(tick) // keep every data point — nothing is thrown away * renderBoard() // this frame's draw is already booked * }) * ``` */ declare function rafSchedule any>(fn: F): RafScheduledFunction; /** * @en Append many children to a parent in one operation, via DocumentFragment. * * Solves: appending nodes one at a time interleaves layout invalidations with * style recalculation for every node; a burst of hundreds (chat backlog, * table rows) multiplies the pipeline's fixed cost. Collecting the nodes in * an inert fragment first and attaching it once turns many insertions into * one — the same essence as assembling an HTML string and assigning * innerHTML in one shot, without the sanitization concerns. * * @param parent The element to append to. * @param children Nodes (or markup strings) to append, in order. * @returns The parent element, for chaining. * * @example * ```ts * // One reflow for a hundred rows instead of a hundred reflows * appendBatch(tbody, rows.map((row) => renderRow(row))) * ``` */ declare function appendBatch(parent: Element, children: (Node | string)[]): Element; /** * @en Run layout reads and writes as two separated phases to avoid layout * thrashing. * * Solves: interleaving reads (offsetWidth, getBoundingClientRect...) with * style writes forces the browser to recompute layout on the spot — inside a * loop, layout runs dozens of times in a single frame. Finishing all reads * first, then applying all writes together, makes layout run once. * * `read` returns the measurement values it gathered; that result is passed to * `write`, which applies the changes. * * @param read Gather all layout-dependent values. Called first. * @param write Apply all style/DOM changes. Receives `read`'s return value. * @returns Whatever `write` returns. * * @example * ```ts * // 🟢 All reads finish, then all writes — one layout pass * runLayoutBatch( * () => elements.map((el) => el.offsetWidth), // gather reads * (widths) => elements.forEach((el, i) => (el.style.width = `${widths[i]! + 10}px`)), * ) * * // 🔴 Reads and writes interleaved — forces a layout recalculation every iteration * for (const el of elements) { * const width = el.offsetWidth // read (needs layout) * el.style.width = width + 10 + 'px' // write (invalidates layout) * } * ``` */ declare function runLayoutBatch(read: () => T, write: (measured: T) => R): R; /** * @en Prioritizing: a cooperative task queue that controls the ORDER work runs * in — because on a main thread nothing can interrupt, order is the * responsiveness the user feels. * * The queue drains one job per task via MessageChannel (a macrotask with no * minimum delay — the same transport React's scheduler uses). Jobs are * processed FIFO, but urgent jobs cut to the front, and a job already waiting * in line can be promoted later — the "idle-until-urgent" pattern: get ahead * on work while idle, then rush the one the user actually wants. * * Classic scenario: the user attaches a few dozen photos. Previews are * generated unhurried, in order. But the moment the user clicks a photo that * isn't ready yet, that preview becomes the most urgent job there is — it * skips the queue and fills in right away. Total work is unchanged; only the * order changed, yet the experience is completely different. * * @zh 优先级(Prioritizing):一个协作式任务队列,控制工作执行的“顺序”—— * 在无法被中断的主线程上,顺序就是用户感受到的响应性。 * * 队列通过 MessageChannel 排干(无最小延迟的宏任务——与 React scheduler 同一传输层), * 每个任务处理一个 job。FIFO 处理,但紧急 job 插队;已排队的 job 之后也能被提升—— * 即“idle-until-urgent”模式:空闲时提前干活,用户要用哪个就立刻优先哪个。 * * 底层是 {@link Queue}:取出队首是 O(1),因此队列很深时"排干"本身不会变成平方开销; * 紧急插队同样 O(1)。只有按 tag 提升({@link PriorityQueue.promote})需要扫描队列。 * * 典型场景:用户一次附上几十张照片,预览图本可从容按序生成;但用户点开某张 * 还没就绪的照片时,它就成了最紧急的任务——跳过队列立刻填充。总工作量不变, * 只是顺序变了,体验却完全不同。 */ /** * @en A tagged job for the priority queue: an arbitrary function plus metadata * to find it later for promotion. * @zh 优先级队列中的带标签任务:任意函数 + 便于之后提升的元数据。 */ interface PriorityJob { /** * @en The work to run. * @zh 要执行的工作。 */ run: () => void | Promise; /** * @en Tag used by `promote` to locate this job in the queue. * @zh `promote` 用于在队列中定位该任务的标签。 */ tag?: TTag; } /** * @en The priority queue handle. * @zh 优先级队列句柄。 */ interface PriorityQueue { /** * @en Queue a job. Urgent jobs are placed at the front (LIFO among * themselves); regular jobs at the back. * @zh 入队一个任务。紧急任务插到队首(彼此之间后进先出);普通任务排到队尾。 * @param job The job to queue. * @param urgent Whether this job should run before queued ones. Default `false`. */ post(job: PriorityJob, urgent?: boolean): void; /** * @en Move an already-queued job (matched by `tag`) to the front — a * priority bump. Returns whether a matching job was found and moved. * @zh 将已入队的任务(按 `tag` 匹配)提到队首——优先级提升。 * 返回是否找到并移动了匹配的任务。 * @param tag Tag previously attached via `PriorityJob.tag`. */ promote(tag: TTag): boolean; /** * @en Remove all queued jobs. A job currently executing is not affected. * @zh 移除所有排队中的任务。正在执行的任务不受影响。 */ clear(): void; /** * @en Number of jobs currently waiting (excludes the one executing). * @zh 当前等待中的任务数(不含正在执行的那个)。 */ readonly size: number; } /** * @en Options for {@link createPriorityQueue}. * @zh {@link createPriorityQueue} 的选项。 */ interface PriorityQueueOptions { /** * @en Called when a job throws or rejects, so the error is surfaced instead * of silently swallowed. When omitted, the error is logged via * `console.error`. A failing job never blocks the jobs queued behind it. * @zh 任务抛错或 reject 时调用,用于把错误暴露出来而非静默吞掉。 * 省略时通过 `console.error` 记录。失败的任​务不会阻塞其后的排队任务。 */ onError?: (error: unknown, job: PriorityJob) => void; } /** * @en Create a main-thread task queue that drains one job per task and lets * urgent work jump the line. * * Each message = one task: one job runs, then the next is booked only if work * remains — so the queue always leaves gaps for input and rendering between * jobs (the draining itself is splitting-friendly), while `post(job, true)` * and `promote(tag)` provide the priority control. * * @param options See {@link PriorityQueueOptions}. * @returns The queue handle. See {@link PriorityQueue}. * * @example * ```ts * const queue = createPriorityQueue() * * // Build previews for the attached photos, in order * files.forEach((file, i) => { * queue.post({tag: i, run: () => createPreview(file, i)}) // tag it for later * }) * * // Clicking a photo that isn't ready pulls its job to the front → priority bump * onClickPhoto((i) => { * queue.promote(i) * }) * ``` */ declare function createPriorityQueue(options?: PriorityQueueOptions): PriorityQueue; /** * @en Draining: a FIFO queue that pops in constant time, so a long line of * waiting work does not turn the act of draining it into the quadratic cost. * * The obvious queue is an array with `push`/`shift`. `push` is O(1) — but * `Array.prototype.shift` is not: V8 moves every remaining element down one * slot, so draining n items costs O(n²). At 1,000 items that is too small to * notice (a fraction of a millisecond); at 50,000 it is ~135ms; at 200,000 it * is ~2.3s of pure main-thread bookkeeping. That is the shape of the bug this * queue exists to avoid: fine on the demo, quadratic on the real batch. * * The fix is to stop moving elements. A cursor marks where the live range * begins; `pop` reads at the cursor and advances it, leaving a dead prefix * behind. Once that prefix is worth reclaiming the live tail is copied down in * one step — a move that happens once per O(n) pops, so the cost amortizes to * O(1) per item. * * `pushFront` exists for the one legitimate reason to cut the line: a job that * has just become urgent. It goes into its own small stack at the front rather * than shifting the whole array, so it also costs O(1) — which is what makes * {@link createPriorityQueue}'s "urgent wins" cheap even on a long queue. * * @zh 排干(Draining):一个 pop 为常数时间的 FIFO 队列,让"排干一条很长的队伍"这件事 * 本身不会退化成平方级开销。 * * 最直觉的队列是数组 + `push`/`shift`。`push` 是 O(1),但 `Array.prototype.shift` * 不是:V8 会把剩余元素整体前移一格,于是排干 n 个元素要 O(n²)。1000 个元素时小到 * 察觉不到(不到一毫秒),50000 个就要约 135ms,200000 个则是约 2.3 秒纯主线程搬运。 * 这正是本队列要消除的问题形态:demo 上没事,真实批量上平方爆炸。 * * 做法是不再搬元素。一个游标标记活跃区间的起点;`pop` 在游标处取值并前移,身后留下 * 一段死前缀。等这段前缀值得回收时,一次性把活跃区尾部整体下移——每 O(n) 次 pop 才发生 * 一次,因此摊还到每个元素是 O(1)。 * * `pushFront` 只为一个正当理由存在:刚刚变紧急的任务。它进入队首自己的小栈,而不是 * 整体平移数组,因此同样是 O(1)——这也是 {@link createPriorityQueue} 的"紧急插队"在 * 长队列上依然便宜的原因。 */ /** * @en A FIFO queue with amortized O(1) `push`, `pushFront` and `pop`. * * Semantics: `push` appends at the back, `pushFront` inserts at the front (so * the most recently pushed front item is popped first — a LIFO stack at the * front), and `pop` removes from the front, which is the back region until the * front region is exhausted. * * @example * ```ts * const queue = new Queue() * queue.push('a') * queue.push('b') * queue.pushFront('urgent') * queue.pop() // 'urgent' * queue.pop() // 'a' * ``` * * @zh 一个 `push`、`pushFront`、`pop` 均为摊还 O(1) 的 FIFO 队列。 * * 语义:`push` 追加到队尾;`pushFront` 插入队首(因此最后 pushFront 的项会最先被 * pop——队首是一个后进先出的栈);`pop` 从队首取出,队首栈耗尽后转向队尾区。 */ declare class Queue { #private; /** * @en Number of items waiting (front region + live back region). * @zh 等待中的元素数(队首区 + 队尾活跃区)。 */ get length(): number; /** * @en Append `item` at the back. * @zh 把 `item` 追加到队尾。 */ push(item: T): void; /** * @en Insert `item` at the front, ahead of everything already queued. * @zh 把 `item` 插到队首,排在所有已排队元素之前。 */ pushFront(item: T): void; /** * @en Remove and return the frontmost item, or `undefined` when empty. * @zh 取出并返回最前面的元素;队列为空时返回 `undefined`。 */ pop(): T | undefined; /** * @en Remove the frontmost item matching `predicate` and return it, or * `undefined` when nothing matches. Scanning is O(n) — it exists for * occasional operations like promoting a queued job by tag, not for the * per-item path. * @zh 移除并返回最前面的、满足 `predicate` 的元素;没有匹配则返回 `undefined`。 * 扫描是 O(n)——它是为"按 tag 提升某个排队任务"这类偶发操作准备的,不在逐元素路径上。 */ remove(predicate: (item: T) => boolean): T | undefined; /** * @en Remove every item. * @zh 清空队列。 */ clear(): void; /** * @en Snapshot of the waiting items, frontmost first. O(n). * @zh 等待中元素的快照,最前面的在前。O(n)。 */ toArray(): T[]; } /** * @en Moving work to the compositor: FLIP animations. * * Animating layout properties (top/left/width/height) recomputes layout every * frame — main-thread work that stutters under load. Animating transform and * opacity instead moves an already-painted layer, which the compositor thread * handles directly; even a busy main thread can't stop it. * * But what about animations where the layout genuinely has to change — e.g. * deleting a list item makes the items below slide up into place? The FLIP * technique (First, Last, Invert, Play) resolves the dilemma: cause exactly * ONE layout change and leave the entire movement to transform. * * 1. First: measure the position before the move. * 2. Last: actually change the layout and measure the new position. Layout * happens exactly once, here. * 3. Invert: apply a transform to the element in its new position so it * appears to still be in the old one. * 4. Play: animate that transform away. This part belongs to the compositor. * * To the user's eye the element glides from its old spot to its new one, but * in reality it has already arrived — the transform briefly drags it back * before releasing it into place. Vue's TransitionGroup and Framer Motion's * layout animations are FLIP under the hood. * * @zh 把工作移交给合成器:FLIP 动画。 * * 用布局属性(top/left/width/height)做动画会让每帧都重算布局——主线程工作,负载一高 * 就卡。改用 transform/opacity 动画移动的是“已绘制的图层”,由合成器线程直接处理, * 主线程再忙也不受影响。 * * 可布局确实要变的动画怎么办?——比如删除列表项、下方项平滑上移。FLIP 技术 * (First, Last, Invert, Play)解决这个两难:只触发恰好一次布局变更,整个移动交给 transform。 * * 1. First:测量移动前的位置。 * 2. Last:真正改变布局并测量新位置。布局只在这里发生一次。 * 3. Invert:在新位置上施加 transform,让元素看起来还在旧位置。 * 4. Play:把该 transform 动画到无。这部分由合成器接管。 * * 用户眼中元素从旧位置滑向新位置,实际上它早已到位——transform 短暂把它拽回, * 再松手放回原处。Vue 的 TransitionGroup、Framer Motion 的布局动画底层都是 FLIP。 */ /** * @en Options for {@link flipAnimate}. * @zh {@link flipAnimate} 的选项。 */ interface FlipAnimateOptions { /** * @en Animation duration in milliseconds. Default `300`. * @zh 动画时长(毫秒),默认 `300`。 */ duration?: number; /** * @en Easing for the play phase. Default `'ease-in-out'`. * @zh 播放阶段的缓动曲线,默认 `'ease-in-out'`。 */ easing?: string; /** * @en If true, animate size changes with `transform: scale` as well * (scaling the visual rather than the layout). Default `false`. * @zh 若为 true,尺寸变化也用 `transform: scale` 动画(视觉缩放而非布局缩放), * 默认 `false`。 */ scale?: boolean; } /** * @en Animate a layout change with FLIP: measure, mutate once, then play the * inverse transform on the compositor. * * Solves: reorder/prepend/remove animations that would otherwise animate * `top`/`left` and trigger layout on every frame, stuttering whenever the * main thread gets busy. With FLIP the layout change happens exactly once * (inside the mutation callback) and the entire visible movement is a * transform interpolation — smooth even under load. * * @param element The element that will visually move. * @param layoutChange Callback that performs the real DOM mutation (prepend, * reorder, remove-with-collapse, ...). Runs between the First and Last * measurements. * @param options See {@link FlipAnimateOptions}. * @returns The Animation produced by the play phase. * * @example * ```ts * // Moving an item to the top of the list, animated * flipAnimate(el, () => { * list.prepend(el) // the one and only layout change * }) * * // With options * flipAnimate(el, () => list.prepend(el), {duration: 200, easing: 'linear'}) * ``` */ declare function flipAnimate(element: Element, layoutChange: () => void, options?: FlipAnimateOptions): Animation; /** * @en Sending work to a Worker: run heavy pure computation off the main * thread. * * A worker runs JavaScript on a separate thread, fully separated from the main * one. Hand heavy computation to a worker — parsing a large payload, image * processing (seam carving: hundreds of millions of pixel operations), complex * simulation — and the main thread can concentrate solely on keeping the UI * responsive. Run on the main thread, the same computation freezes the whole * page for its entire duration, with no task boundaries for paint to slip * into (you couldn't show intermediate steps even if you wanted to). In a * worker, the screen stays responsive and progress can stream in. * * It isn't free, though. Workers cannot touch the DOM, and the two threads * communicate only through postMessage, which COPIES (serializes) the data. * For large data the copy cost is considerable — pass Transferable objects * (ArrayBuffer and friends) to move them by reference instead: the sender * loses access, and the cost drops to near zero regardless of size * (see {@link postTransferable}). The same is true of what comes BACK: a job * that returns a multi-megabyte pixel buffer copies it home unless you name it * in `resultTransfer`. * * So workers aren't a cure-all: they shine when the computation is heavy * enough to outweigh the communication cost and has nothing to do with the * DOM. Every time, ask whether this work really needs to run on the main * thread. * * @zh 把工作送进 Worker:将重的纯计算移出主线程。 * * Worker 在独立线程上运行 JavaScript,与主线程完全隔离。把重计算交给 worker—— * 解析大 payload、图像处理(seam carving:数亿次像素操作)、复杂仿真——主线程就能 * 专注于保持 UI 响应。同样的计算放在主线程上跑,整个过程整页冻结,任务之间没有 * 让绘制插入的边界(想显示中间步骤都做不到);放在 worker 里,屏幕保持响应, * 中间进度还能实时流回。 * * 但它不是免费的。Worker 无法访问 DOM,两个线程只能通过 postMessage 通信,而 * postMessage 会“拷贝”(序列化)数据。数据一大,通信成本就不可忽视——改传 * Transferable 对象(ArrayBuffer 等)即可按引用转移:发送方失去使用权,成本与 * 体积无关、接近零(见 {@link postTransferable})。回程同理:任务若返回数兆字节的 * 像素缓冲,除非在 `resultTransfer` 里点名,否则仍要整份拷回来。 * * 所以 worker 不是万能药:计算足够重、能压过通信成本、且与 DOM 无关时才划算。 * 每次都先问:这份工作真的需要在主线程上跑吗? */ /** * @en A function that runs inside a worker. It must be self-contained (no * closures over outer variables — the source is serialized), and may only use * APIs available in worker scope (no DOM). * @zh 在 worker 内部执行的函数。必须自包含(源码会被序列化,不能捕获外部变量), * 且只能用 worker 作用域可用的 API(无 DOM)。 */ type WorkerFn = (arg: Arg) => Result | Promise; /** * @en Error thrown when the worker itself fails to start or the script errors * out, carrying the raw ErrorEvent message. A function that merely throws * inside the worker is reported through this too, so callers only ever handle * one error type. * @zh worker 启动失败或脚本内部报错时抛出,携带原始 ErrorEvent 消息。函数在 worker * 内抛错同样以它上报,调用方只需处理一种错误类型。 */ declare class WorkerError extends Error { /** * @en The raw error message from the worker's ErrorEvent. * @zh 来自 worker ErrorEvent 的原始错误消息。 */ readonly raw: string; constructor(message: string, raw?: string); } /** * @en Options for a worker job: what to move in, what to move back out. * @zh worker 任务的选项:哪些数据移入 worker、哪些移回主线程。 */ interface WorkerRunOptions { /** * @en Buffers to move (zero-copy) into the worker. The main thread loses * access to them as soon as the job is sent. * @zh 按零拷贝移入 worker 的缓冲区。任务一发出,主线程即失去使用权。 */ transfer?: Transferable[]; /** * @en Paths inside the RESULT whose values should be moved (zero-copy) back * out of the worker. A path is a dot-separated property chain — `'buf'` for * `result.buf`, `'meta.bytes'` for a nested field, `'.'` for the result * itself. ArrayBuffer views (TypedArray, DataView) are transferred as their * underlying `.buffer`. * * Solves the return leg of a pixel pipeline: without this, a job returning a * 10MB buffer copies it back on the main thread inside the message handler — * a synchronous stall proportional to the size. With it, the buffer moves and * the byte cost disappears. * * A path that does not resolve to something transferable is skipped, so a * slightly wrong path degrades to a copy instead of failing the job. The * worker's job then loses access to whatever it handed over. * * @zh 结果中需要按零拷贝移出 worker 的值的路径。路径是点分隔的属性链—— * `'buf'` 对应 `result.buf`,`'meta.bytes'` 对应嵌套字段,`'.'` 表示结果本身。 * ArrayBuffer 视图(TypedArray、DataView)按其底层 `.buffer` 转移。 * * 它解决像素流水线的回程问题:没有它,任务返回 10MB 缓冲时会在主线程的消息处理 * 里同步拷贝回来,停顿与体积成正比;有了它,缓冲按引用转移,字节成本消失。 * * 解析不到可转移值的路径会被跳过,因此路径写错只是退化为拷贝,不会让任务失败。 * 交出缓冲后,worker 内的任务即失去对该缓冲的使用权。 */ resultTransfer?: string[]; } /** * @en The two message keys that keep a successful reply apart from an error * reply. Generating them per pool keeps a reply from colliding with a job's own * result object, which may legitimately carry similar-looking keys. * @zh 用于区分成功回包与错误回包的两个消息键。按池生成可避免回包与任务自身的 * 结果对象冲突——结果对象完全可能带有形似的键。 */ interface WorkerReplyChannel { /** * @en Key under which a successful result arrives. * @zh 成功结果所在的键。 */ readonly okKey: string; /** * @en Key under which a thrown error message arrives. * @zh 抛出的错误消息所在的键。 */ readonly errKey: string; } /** * @en A decoded worker reply: either the job's value, or the message of the * error it threw. * @zh 解码后的 worker 回包:任务结果,或任务所抛错误的消息。 */ type WorkerReply = { ok: true; value: unknown; } | { ok: false; error: string; }; /** * @en Build the bootstrap script a worker runs. Used by {@link WorkerPool} to * start each of its workers, so both ends of the wire protocol — including * result transfer and the per-worker compile cache — live in one place. * * The script expects each message to be `{source, arg, paths}`: the job * function's source text, its argument, and the result-transfer paths. It * compiles `source` once per worker (a small bounded cache, so a batch of jobs * sharing one function does not recompile it every time), runs it, and replies * in a tagged envelope. * * This is low-level plumbing; usually you want {@link WorkerPool}. It stays * exported so a hand-written worker can speak the same protocol. * * @param channel The keys replies are tagged with. See {@link WorkerReplyChannel}. * @returns Script source, ready for `new Worker(URL.createObjectURL(...))`. * * @zh 生成 worker 运行的引导脚本。{@link WorkerPool} 用它启动每个 worker, * 使这条通信协议的两端——包括结果转移与 worker 内的编译缓存——只存在一处。 * * 脚本期望每条消息形如 `{source, arg, paths}`:任务函数源码、参数、结果转移路径。 * 它在每个 worker 内把 `source` 编译一次(一个很小的有界缓存,避免一批任务重复编译 * 同一个函数),执行后以带标记的信封回包。 * * 这属于底层管线;一般直接用 {@link WorkerPool}。 */ declare function createWorkerScript(channel: WorkerReplyChannel): string; /** * @en Decode a reply posted by the script from {@link createWorkerScript}. * @zh 解码由 {@link createWorkerScript} 生成的脚本所回传的消息。 * @param reply The raw `MessageEvent.data`. * @param channel The same channel the script was built with. * @returns The job's value, or the message of the error it threw. */ declare function readWorkerReply(reply: unknown, channel: WorkerReplyChannel): WorkerReply; /** * @en Send a message to a worker, transferring ownership of the listed * buffers instead of copying them, and get the worker's reply as a Promise. * * Solves: postMessage copies (serializes) its data, so a multi-megabyte pixel * buffer sent per intermediate frame would make the copy cost add up to more * than the computation itself. Transferable objects like ArrayBuffer move by * reference only — the cost is close to zero regardless of size. The side * that hands the buffer over can no longer use it; in exchange, the copy * cost disappears. * * @param worker The worker to talk to. * @param data The message to send (structured-cloneable, or containing the * buffers to transfer). * @param transfer Objects whose ownership moves to the worker. After the * transfer, this side can no longer use them. * @returns A promise resolving with the worker's next reply. Reuses a * one-message-per-call protocol: each call attaches a fresh listener and * removes it once the reply arrives. * * @example * ```ts * // Hand the pixel buffer to the worker without copying it * const worker = new Worker('imageProcessor.js') * const result = await postTransferable( * worker, * {buf: pixels.buffer, width, height}, // the message * [pixels.buffer], // ...and the ownership transfer * ) * // After the transfer, `pixels` on this side is detached (zero-length) * ``` */ declare function postTransferable(worker: Worker, data: Arg, transfer?: Transferable[]): Promise; /** * @en Pooling: a small, fixed set of workers shared by every job — worker * startup is paid once instead of once per call, and an idle worker steals * work that has been assigned but has not started yet. * * The pool holds at most `maxWorkers` workers (default 2) and never grows past * that. Workers are created on demand as jobs arrive, up to the cap, and are * never terminated while the pool lives. A worker is kept and reused for the * next job, so worker startup is paid once for the pool instead of per job. * * The workers are generic: a job carries its function's source and rebuilds it * inside the worker, so any job can run on any worker — which is what makes * stealing possible at all. Workers cannot see each other's queues, so the main * thread runs the whole show: * * 1. Assign — a submitted job goes to the least-loaded worker (fewest jobs in * flight plus queued). An idle worker starts it immediately; otherwise the * job waits in that worker's own queue. * 2. Steal — when a worker falls idle it drains its own queue first; if that is * empty, it looks for the busiest still-busy worker and takes the job that * has waited longest. A job that has not started carries no side effects, so * moving it is free. A job already running cannot be stolen. * * That is a work-stealing scheduler minus the threads: the queues are plain * arrays on the main thread, and the workers are the only real parallelism. It * pays off when many jobs of uneven duration are submitted at once — the worker * that finishes early takes over the backlog of the one still grinding, instead * of sitting idle. The queues themselves pop in O(1) (see {@link Queue}), so a * deep backlog costs no more per job than a shallow one. * * Two things follow from a shared, generic pool. Jobs are no longer serialized * per function: two calls can run in parallel on different workers, so `await` * when order matters. And the function source is rebuilt inside the worker with * `new Function`, so the worker script needs a CSP that allows `unsafe-eval` on * top of the usual `worker-src blob:`. If `unsafe-eval` is off the table, drive * a pre-built worker script yourself through {@link postTransferable}. * * @zh 池化(Pooling):一小组固定的 worker 被所有任务共享——启动成本只付一次而非 * 每次调用一次,空闲的 worker 还会窃取“已分配但尚未开始”的任务。 * * 池内最多 `maxWorkers` 个 worker(默认 2),永不越界。worker 随任务到达按需创建 * (不超过上限),池存活期间不会被回收:启动成本整池只付一次,而不是每个任务付一次。 * * worker 是通用的:任务自带函数源码,在 worker 内重建——因此任何任务都能跑在任何 * worker 上,这正是窃取得以成立的前提。worker 之间看不到彼此的队列,所以一切都由 * 主线程调度: * * 1. 分配——提交的任务交给“负载最轻”的 worker(在途 + 排队最少)。空闲 worker * 立即开跑;否则任务在该 worker 的本地队列里等待。 * 2. 窃取——worker 空闲时先清自己的队列;若已空,就找“尚未开始任务”堆积最多的 * 忙碌 worker,取走其中等待最久的一个。尚未开始的任务没有副作用,搬走是免费的; * 已在执行的任务不可被窃取。 * * 这就是去掉线程的工作窃取调度器:队列是主线程上的普通数组,worker 是唯一的真实并行。 * 任务多、耗时不均时收益最明显——先做完的 worker 会接管还在苦干的 worker 的积压, * 而不是闲着。队列取出是 O(1)(见 {@link Queue}),因此积压很深时每个任务的成本也与 * 积压很浅时相同。 * * 共享的通用池带来两个后果。任务不再按函数串行:两个调用可能在不同 worker 上并行, * 需要顺序时请 `await`。并且函数源码在 worker 内由 `new Function` 重建,因此 worker * 脚本在常规 `worker-src blob:` 之外还需要允许 `unsafe-eval`。若 CSP 不允许 * `unsafe-eval`,请自行驱动一个预先构建好的 worker 脚本,用 * {@link postTransferable} 与它通信。 */ /** * @en Options for {@link WorkerPool}. * @zh {@link WorkerPool} 的选项。 */ interface WorkerPoolOptions { /** * @en Hard upper bound on live workers. The pool starts workers on demand, * up to this many, and never grows past it; idle workers are kept rather than * recycled. Default `2`. * @zh 存活 worker 的硬上限。池按需启动 worker,最多到此数量,绝不越界;空闲 * worker 会被保留而非回收。默认 `2`。 */ maxWorkers?: number; } /** * @en A fixed-size pool of generic workers with least-loaded assignment and * work stealing. See the module header for the full picture. * * @example * ```ts * const pool = new WorkerPool({maxWorkers: 2}) * // Any function can run on any worker; the pool keeps two of them busy. * const totals = await Promise.all(chunks.map((c) => pool.run(sum, c))) * pool.dispose() * ``` * * @zh 固定大小的通用 worker 池,按最轻负载分配并支持工作窃取。完整说明见模块头。 */ declare class WorkerPool { #private; constructor(options?: WorkerPoolOptions); /** * @en Number of live workers currently held. * @zh 当前持有的存活 worker 数量。 */ get size(): number; /** * @en The configured upper bound on workers. * @zh 配置的 worker 上限。 */ get maxWorkers(): number; /** * @en Jobs assigned but not yet started, across every worker. * @zh 已分配但尚未开始的任务总数(跨所有 worker)。 */ get pending(): number; /** * @en Number of workers currently executing a job. * @zh 当前正在执行任务的 worker 数量。 */ get busy(): number; /** * @en How many jobs have been taken from another worker's queue so far — * the count of steals, useful for seeing whether the balance is working. * @zh 至今从其他 worker 队列中窃取的任务数——窃取次数,可用于观察负载是否均衡。 */ get stolen(): number; /** * @en Whether the pool has been disposed. * @zh 池是否已销毁。 */ get disposed(): boolean; /** * @en Submit `fn` to run on the pool. The job goes to the least-loaded worker * and starts there, or waits in that worker's queue to be stolen by whoever * frees up next. * * Same rules as {@link runInWorkerWithPool}: `fn` is serialized with * `toString()`, so it must not capture outer variables, and it receives and * returns structured-cloneable data. * * @param fn Self-contained function to run in a worker. * @param arg Argument passed to `fn`. * @param options See {@link WorkerRunOptions} — `transfer` for buffers going * in, `resultTransfer` for buffers coming back. * @returns Promise resolving with `fn`'s result. * @zh 把 `fn` 提交到池上执行。任务交给负载最轻的 worker 并就地启动,或在该 worker * 的队列中等待被下一个空闲者窃取。 * * 规则同 {@link runInWorkerWithPool}:`fn` 用 `toString()` 序列化,不能捕获外部变量, * 收发数据需可结构化克隆。 */ run(fn: WorkerFn, arg: Arg, options?: WorkerRunOptions): Promise; /** * @en Terminate every worker, release the script URL and reject all in-flight * and queued jobs. * @zh 终止所有 worker、释放脚本 URL,并 reject 全部在途与排队任务。 */ dispose(): void; } /** * @en Global registry key for the library-wide {@link WorkerPool} singleton. * `Symbol.for` keeps the key stable across duplicate copies of this library, so * any module — or any other bundle — resolves the same pool. * @zh 全局共享的 {@link WorkerPool} 单例注册键。使用 `Symbol.for` 让该键在库的多份 * 副本间保持稳定,任何模块乃至其他 bundle 都能解析到同一个池。 */ declare const workerPoolSymbol: symbol; /** * @en Get the shared {@link WorkerPool}, creating it on first access — the Rust * `LazyCell`/`OnceCell` pattern. No instance exists before the first call, and * no worker is spawned until the first job, so merely importing this library * never starts one. * @zh 获取共享的 {@link WorkerPool},首次访问时创建——对应 Rust 的 * `LazyCell`/`OnceCell` 模式。首次调用前不存在实例,首个任务前不会创建 worker, * 因此仅导入本库不会启动任何 worker。 * @returns The process-wide pool instance. */ declare function getWorkerPool(): WorkerPool; /** * @en Tear down the shared pool: terminate its workers, release the script URL * and drop the global instance so the next {@link getWorkerPool} creates a * fresh one. Useful in tests and on app teardown. * @zh 销毁共享池:终止其 worker、释放脚本 URL,并移除全局实例,使下次 * {@link getWorkerPool} 创建全新实例。适用于测试与应用卸载。 */ declare function disposeWorkerPool(): void; /** * @en Run `fn` on the shared, process-wide {@link WorkerPool} — the shortest * path to off-thread work, and the one most code should use. * * Solves: a batch of independent jobs — generating 60 thumbnails, parsing 30 * chunks — without setting up a pool by hand. Workers are created on demand * (default 2) and reused across calls, and a batch of uneven jobs is spread * across them by least-loaded assignment and stealing: the worker that finishes * early takes over the backlog of the one still grinding. * * Same self-containment and structured-clone rules as {@link WorkerPool.run}. * Call {@link disposeWorkerPool} on app teardown; until then the pool's workers * stay alive, which is what keeps later calls free of startup cost. * * @param fn Self-contained function to run in a worker. * @param arg Argument passed to `fn`. * @param options See {@link WorkerRunOptions} — `transfer` for buffers going * in, `resultTransfer` for buffers coming back. * @returns Promise resolving with `fn`'s result. * * @example * ```ts * // 60 thumbnails: two workers share the batch, and the faster one steals * // from the other's queue instead of idling. * const thumbs = await Promise.all( * images.map((img) => runInWorkerWithPool(makeThumbnail, img, {transfer: [img.buffer]})), * ) * ``` * * @zh 在进程内共享的 {@link WorkerPool} 上运行 `fn`——把工作移出主线程的最短路径, * 也是大多数代码该用的那一个。 * * 解决的是:一批互相独立的任务——生成 60 张缩略图、解析 30 个分片——不必手工建池。 * worker 按需创建(默认 2 个)并跨调用复用;一批耗时不均的任务会借“最轻负载分配 + * 窃取”铺满整个池:先做完的 worker 会接管还在苦干的 worker 的积压。 * * 自包含与可结构化克隆的要求同 {@link WorkerPool.run}。应用卸载时调用 * {@link disposeWorkerPool};在那之前池内 worker 一直存活,这正是后续调用无需启动 * 成本的原因。 */ declare function runInWorkerWithPool(fn: WorkerFn, arg: Arg, options?: WorkerRunOptions): Promise; /** * @en Eliminating work: memoization — skipping repeated computation. * * The best thing for performance is not doing the work in the first place. * Among the three ways to eliminate work (dropping, merging, skipping), * skipping targets repeated work rather than incoming work: if a computation * gives the same result for the same input, there is no reason to do it a * second time. Remembering results and reusing them is memoization. * * This idea hides everywhere in optimization: debounce skips executions during * typing, visibility-based rendering skips off-screen posts. Half of main-thread * optimization is really about removing work. * * @zh 消除工作:memoization —— 跳过重复计算。 * * 对性能最好的事是根本不做这件事。消除工作的三种方式(丢弃、合并、跳过)里, * 跳过针对的是“重复工作”而非“流入工作”:同一输入必得同一结果的计算,没有理由 * 算第二遍。记住结果并复用,即 memoization(记忆化)。 * * 这个思想藏在各种优化里:debounce 跳过打字期间的执行,可见性渲染跳过屏幕外的 * 帖子。主线程优化的一大半,本质上都是在“移除工作”。 */ /** * @en Cache statistics exposed by {@link memoize}. * @zh {@link memoize} 暴露的缓存统计。 */ interface MemoizeStats { /** * @en How many calls were served from the cache. * @zh 有多少次调用命中了缓存。 */ hits: number; /** * @en How many calls actually invoked the original function. * @zh 有多少次调用真正执行了原函数。 */ misses: number; } /** * @en A memoized function with cache introspection and clearing. * @zh 记忆化后的函数,附缓存查询与清空能力。 */ interface MemoizedFunction any> { (...args: Parameters): ReturnType; /** * @en Drop all remembered results. * @zh 丢弃所有已记住的结果。 */ clear(): void; /** * @en Read-only cache statistics. * @zh 只读缓存统计。 */ readonly stats: MemoizeStats; } declare function memoize any>(fn: F, keyFn?: (...args: Parameters) => unknown): MemoizedFunction; /** * @en Eliminating work: backpressure handling — dropping and merging. * * Batch as well as you like; once the inflow exceeds the maximum throughput, * the backlog grows without limit (backpressure). The browser has no good way * to tell the server to slow down, so at some point you have to give up on * doing everything you are given. There are generally two ways to eliminate * incoming work: * * - **Dropping**: for data that just flows past — live logs, chat — once * processing starts falling behind, quietly discard the oldest entries; * users won't notice. Keeping up with the present matters more than showing * everything. * - **Merging**: for data where only the latest value means anything — * rankings, tickers, form drafts — merge the backlogged updates and apply * only the final value. The amount of work is pinned to what the consumer * can digest, no matter how fast the inflow gets. * * (The third elimination, skipping repeated computation, is memoization — * see `memoize.ts`.) * * @zh 消除工作:背压处理 —— 丢弃与合并。 * * 批量做得再好,一旦流入速度超过最大吞吐,积压就会无限增长(背压)。浏览器没有 * 好办法让服务端放慢,所以到了某个程度就得放弃“给多少做多少”。消除流入中的工作 * 通常有两种方式: * * - **丢弃(drop)**:对只是流过的数据——直播日志、聊天——处理一旦落后,悄悄丢弃 * 最旧的条目,用户不会察觉。跟上“现在”比展示“全部”更重要。 * - **合并(merge)**:对只有最新值才有意义的数据——排行榜、行情、表单草稿—— * 把积压的更新合并,只应用最终值。无论流入多快,工作量都钉死在消费端 * 能消化的水平。 * * (第三种消除——跳过重复计算——即 memoization,见 `memoize.ts`。) */ /** * @en A bounded queue that drops the OLDEST items when full, for flow-past * data where keeping up with the present matters more than completeness. * @zh 一个满时丢弃“最旧”条目的有界队列,适用于只是流过的数据——跟上现在比 * 展示全部更重要。 */ interface DroppingQueue { /** * @en Push an item. If the queue is full, the OLDEST item is silently * discarded to make room. Returns whether this item itself was kept. * @zh 推入一个条目。若队列已满,最旧的条目被静默丢弃以腾出位置。 * 返回本次推入的条目是否被保留。 */ push(item: T): boolean; /** * @en Take the oldest item, or `undefined` if empty. * @zh 取出最旧的条目;队列为空时返回 `undefined`。 */ shift(): T | undefined; /** * @en All currently held items, oldest first. The returned array is a copy. * @zh 当前持有的所有条目,最旧在前。返回的是副本。 */ items(): T[]; /** * @en Take all held items at once, oldest first, emptying the queue. The * returned array is handed over (not a copy), so draining costs O(1) rather * than an O(n) copy — mutating it does not affect the queue. * @zh 一次性取走所有条目,最旧在前,取后队列清空。返回的数组是直接移交 * (非副本),因此 drain 是 O(1) 而非 O(n) 复制——修改它不会影响队列。 */ drainAll(): T[]; /** * @en Number of held items. * @zh 持有的条目数。 */ readonly size: number; } /** * @en Create a bounded FIFO queue that drops the oldest items when full — * the "dropping" backpressure strategy. * * Solves: a live log / streaming chat where arrivals outpace processing; the * backlog grows without limit and the messages reaching the screen get older * and older. A dropping queue caps the backlog at `capacity`: when the * consumer falls behind, the oldest undisplayed entries are quietly * discarded — keeping up with the present matters more than showing * everything. * * @param capacity Maximum number of items held. Default `100`. * @returns The queue handle. See {@link DroppingQueue}. * * @example * ```ts * // Live log tail: newest 200 lines, oldest silently dropped under pressure * const logs = createDroppingQueue(200) * socket.on('log', (line) => logs.push(line)) * setInterval(() => { * for (const line of logs.drainAll()) appendLogLine(line) // never backlogs * }, 500) * ``` */ declare function createDroppingQueue(capacity?: number): DroppingQueue; /** * @en A "latest value wins" cell for data where only the newest value means * anything — rankings, tickers, form drafts. * @zh 一个“最新值胜出”的格子,适用于只有最新值才有意义的数据——排行榜、行情、 * 表单草稿。 */ interface LatestValue { /** * @en Deposit a value. Any previously deposited value is merged away * (overwritten) — it will never be seen by a consumer. * @zh 存入一个值。之前存入的值被合并(覆盖)——消费端永远不会看到它。 */ set(value: T): void; /** * @en Take the current latest value, if any has arrived since the last * take. Returns `undefined` when nothing new is waiting. * @zh 取走当前最新值(若有新值到达)。没有新值等待时返回 `undefined`。 */ take(): T | undefined; /** * @en Peek at the current latest value without consuming it. * @zh 窥视当前最新值而不消费它。 */ peek(): T | undefined; /** * @en Whether a new value is waiting to be taken. * @zh 是否有新值等待被取走。 */ readonly pending: boolean; } /** * @en Create a single-slot cell where only the latest deposited value is ever * consumed — the "merging" backpressure strategy. * * Solves: data whose intermediate values are meaningless (a ranking reordered * 50 times a second, a ticker flashing). No matter how fast the inflow gets, * the amount of work is pinned to one application per consume cycle: every * backlogged update is merged into the final value, and nothing in between * is ever processed. * * @returns The cell handle. See {@link LatestValue}. * * @example * ```ts * // A leaderboard rebuilt at most once per frame, however often it changes * const board = createLatestValue() * socket.on('ranking', (r) => board.set(r)) // 50 updates/sec all merge * * const tick = () => { * const latest = board.take() * if (latest) renderBoard(latest) // one render, final value only * requestAnimationFrame(tick) * } * requestAnimationFrame(tick) * ``` */ declare function createLatestValue(): LatestValue; declare function getCurrentBreakpoint(breakpointDesc: BreakpointDesc, width: number): BreakpointName; declare function useScreen(breakpointDesc?: BreakpointDesc): "base" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"; /** * @en React bindings for the {@link Emitter} / {@link Event} system: subscribe for as long as a * component is alive, turn "the last thing that happened" into something renderable, and hand out * callbacks that are stable *and* see the latest props. * * Three non-obvious decisions are shared by everything here: * * 1. **Subscriptions live in an effect, never during render.** The effect body subscribes and the * cleanup unsubscribes, which is also what makes `StrictMode` (mount → unmount → mount) safe. A * subscription created during render would double up on every commit, and one disposed in a * cleanup that lives outside the effect (a `useMemo`/`useRef` store) would already be dead on the * second mount — the subscription silently stops working, which is exactly the failure this * shape avoids. * 2. **The listener is stabilised, the *event* is not.** A re-render must not resubscribe, so the * callback is forwarded through a ref ({@link useEventCallback}) and the subscription depends on * the event identity alone. That puts the burden on the event: `emitter.event` is cached and * stable, but `Event.map(ev, fn)` returns a fresh event on every call, so a derived event has to * be created once (`useMemo`, or a `DisposableStore` on the source) instead of inline in JSX. The * ref itself is written **during render** rather than from an effect, so an event fired between * render and commit already sees the new closure; last write wins, which is why neither * StrictMode's double render nor a discarded concurrent render leaves a wrong closure behind. * 3. **Missing an event is the caller's model, not a bug to paper over.** Events are hot: anything * fired between render and the effect is gone. `Event.buffer` is the opt-in fix, and * {@link useEventValue} covers "I need the current value *and* to follow changes" — neither is * forced on everyone. * * What is deliberately *not* here: no `useSyncExternalStore` (there is no shared snapshot to stay * consistent with — {@link useEventValue} caches the last payload per component, so there is nothing * to tear), and no subscription handle returned from the hooks (it would be a new object every * render). Subscribing outside a component's lifetime is `DisposableStore`'s job. * * @zh {@link Emitter} / {@link Event} 的 React 绑定:跟着组件生死订阅、把「最近发生的事」变成可渲染 * 的东西、给出既稳定又能读到最新 props 的回调。 * * 这里所有 hook 共享三个不显然的决定: * * 1. **订阅建在 effect 里,绝不在渲染期订阅。** effect 体订阅、清理函数退订,这也是 `StrictMode` * (挂载 → 卸载 → 再挂载)下依然正确的原因。渲染期建立的订阅会随每次提交翻倍;而把 store 放在 * effect 外(`useMemo`/`useRef`)、在清理函数里 dispose,第二次挂载拿到的就已经是死的—— * 订阅会静默失效,正是这个形状要规避的失败。 * 2. **稳定的是监听器,不是事件。** 重渲染不该重订阅,所以回调经由 ref 转发 * ({@link useEventCallback}),订阅只依赖事件身份。这反过来对事件提出了要求: * `emitter.event` 是缓存过的稳定引用,而 `Event.map(ev, fn)` 每次调用都返回新事件——派生事件必须 * 只建一次(放进 `useMemo`,或绑到源上的 `DisposableStore`),不能写在 JSX 里。ref 本身在**渲染期** * 写入而不是放进 effect:这样才能让「渲染到提交之间」触发的事件也拿到新闭包;写入是「最后一次为准」, * 所以 StrictMode 双跑与并发渲染丢弃分支都不会留下错误的闭包(被丢弃那一轮的闭包可能短暂留在 ref 里, * 直到下一次提交覆盖它,但两者的取值语义相同)。 * 3. **漏掉事件是使用者的模型问题,不用统一兜住。** 事件是热的:渲染到 effect 之间触发的事件不会 * 补发。想缓冲用 `Event.buffer`,「既要当前值又要跟变更」用 {@link useEventValue}——两者都不强加 * 给所有人。 * * 刻意没做的:没有用 `useSyncExternalStore`(这里没有需要保持一致性的共享快照,{@link useEventValue} * 只在组件本地缓存最后一次载荷,不存在 tearing),hook 也不返回订阅句柄(那会是每次渲染都新建的对象)。 * 组件生命周期之外的订阅交给 `DisposableStore`。 */ type AnyFunction = (...args: any[]) => any; /** * @description - 返回一个身份稳定、但总能读到最新一次渲染闭包的函数。 * - 身份稳定:组件整个生命周期内引用不变,可以安全地作为依赖项或传给子组件,不必为了「别变」而 * 维护 `useCallback` 的一长串依赖。 * - 最新闭包:调用时读到的是**当前**那次渲染的 props / state,因此适合放进事件监听、定时器、异步 * 回调里——这些地方的旧闭包问题是同一类 bug 的常见来源。 * - 与 React 19 的 `useEffectEvent` 的差别:那个只能在 effect 内调用、也不建议往下传;这个函数在 * 任何地方都能调用(事件处理器、effect、Promise 回调),代价是少了那层限制带来的保护。 * * @description_en - Returns a function whose identity is stable while always seeing the latest * render's closure. * - Stable identity: the same reference for the component's whole lifetime, safe in a dependency * array or passed to a child, with no `useCallback` dependency list to maintain. * - Latest closure: calling it reads the **current** render's props/state, which is what event * listeners, timers and async callbacks need. * - Versus React 19's `useEffectEvent`: that one may only be called inside an effect and should not * be passed down; this one can be called anywhere (event handlers, effects, promise callbacks), * at the cost of losing the guardrail that restriction provides. * * @example * ```tsx * function Search({ query }: { query: string }) { * // 每敲一次键都重新计时,但监听器只挂一次 * const onInput = useEventCallback(() => search(query)) * useEffect(() => input.onInput(onInput), [input, onInput]) * } * ``` */ declare function useEventCallback(fn: F): F; /** * @description - 在组件存活期间订阅一个事件:挂载时订阅,卸载时退订,回调始终看到最新一次渲染的 * props / state,而重渲染不会重订阅。 * * 要注意的三点: * - **事件必须是稳定引用**。`emitter.event` 是缓存过的,可以直接传;`Event.map(ev, fn)` 这类派生 * 每次调用都返回新事件,必须只建一次(`useMemo` 或绑到 `DisposableStore`),否则每轮渲染都会换源。 * - **渲染到 effect 之间触发的事件会丢**(事件是热的)。需要缓冲用 `Event.buffer`;需要「先有值再跟 * 更新」用 {@link useEventValue}。 * - **条件订阅不需要额外参数**:传 `enabled ? event : Event.None` 即可,`Event.None` 是稳定单例, * 订阅它零成本。 * * 不返回订阅句柄:每一次渲染都会得到新对象,退订由「卸载」与「换源」这两个时机负责。需要在渲染期之外 * 手动管理订阅时用 `DisposableStore`(并且建在 effect 内部)。 * * @description_en - Subscribe to an event for as long as the component is alive: subscribe on * mount, unsubscribe on unmount, always call the latest render's closure, and never resubscribe just * because props changed. * * Three things to keep in mind: * - **The event must be a stable reference.** `emitter.event` is cached and safe to pass inline; * derived events such as `Event.map(ev, fn)` return a fresh event per call and must be created * once (`useMemo`, or a `DisposableStore` on the source), or every render swaps the source. * - **Fires between render and the effect are lost** (events are hot). Use `Event.buffer` to buffer, * or {@link useEventValue} when you need a current value as well. * - **Conditional subscriptions need no extra parameter**: pass `enabled ? event : Event.None`. * `Event.None` is a stable singleton and subscribing to it costs nothing. * * No subscription handle is returned: it would be a new object every render, and unsubscribing is * owned by the unmount / source-change moments. Manage subscriptions outside the render phase with a * `DisposableStore` (created inside an effect). * * @example * ```tsx * function Chat() { * const [messages, setMessages] = useState([]) * useEvent(socket.onMessage, (message) => setMessages((all) => [...all, message])) * return
    {messages.map((m) =>
  • {m}
  • )}
* } * ``` */ declare function useEvent(event: Event, handler: (e: T) => void): void; /** * @description - 把事件变成可渲染的值:保存最近一次触发收到的载荷,并在每次触发时重渲染。 * * 语义上是「组件本地的最后一次载荷」,不是状态存储: * - `initial` 只在挂载时使用,之后由事件驱动。 * - React 按 `Object.is` 比较新旧值,所以**同一个引用连续触发两次只重渲染一次**。要「每次触发都算数」 * 就在事件上自增(`Event.map(ev, () => n++)`);要「值真的变了才处理」用 `Event.latch`。 * - 想反映**全局**状态请用 `createExternalState` + `useSelector`(那边靠 `useSyncExternalStore` 保证 * 一次提交内读到一致的值);这里没有权威存储,因此不存在 tearing,也就没必要引入它。 * - 载荷与 `initial` 都可以是函数:两者都通过函数形式传给 React,不会被误当成状态更新器或惰性初始化函数。 * * @description_en - Turn an event into something renderable: keep the payload of the most recent * fire and re-render on each one. * * Semantically this is "the last payload, local to this component", not a state store: * - `initial` is only used on mount; the event drives it afterwards. * - React compares with `Object.is`, so **firing twice with the same reference re-renders once**. * If every fire must count, count in the event (`Event.map(ev, () => n++)`); if only real changes * matter, use `Event.latch`. * - To mirror **global** state use `createExternalState` + `useSelector` (which relies on * `useSyncExternalStore` to keep a single commit consistent). There is no authoritative store * here, so there is nothing to tear and no reason to introduce it. * - Both the payload and `initial` may be functions: each goes through the function form so React does * not mistake one for a state updater or a lazy initializer. * * @example * ```tsx * function Upload() { * const percent = useEventValue(uploader.onProgress, 0) * return * } * ``` */ declare function useEventValue(event: Event, initial: T): T; /** * 计算给定日期的星期几(基于 Michael Keith & Tom Craver 的优化算法)仅公历 * @param y - 年份(4位数,如 2023) * @param m - 月份(1-12) * @param d - 日期(1-31) * @returns 星期几(0=周日, 1=周一, ..., 6=周六) */ declare function weekday(y: number, m: number, d: number): number; /** * 计算儒略历日期的星期几 * @param y - 年份(4位数,如 1582) * @param m - 月份(1-12) * @param d - 日期(1-31) * @returns 星期几(0=周六, 1=周日, ..., 6=周五) */ declare function weekdayJulian(y: number, m: number, d: number): number; export { AppStackRouter, ArrayRender, AsyncEmitter, Boundary, Counter, DateRender, DebounceEmitter, DefBreakpointDesc, DisposableMap, DisposableStore, DynamicListEventMultiplexer, Emitter, Event, EventBufferer, EventMultiplexer, EventProfiling, False, FocusTrap, FrameRender, If, ListenerLeakError, ListenerRefusalError, MicrotaskDelay, MicrotaskEmitter, Observer, PauseableEmitter, Pipe, Portal, Queue, Relay, Repeat, Scope, SizeBox, Styles, Switch, Toggle, True, ValueWithChangeEvent, When, WorkerError, WorkerPool, appendBatch, breakpoints, childrenLoop, combinedDisposable, createDroppingQueue, createEventDeliveryQueue, createExternalState, createLatestValue, createPriorityQueue, createStorageState, createWorkerScript, cx, debounce, disposeAll, disposeWorkerPool, flipAnimate, forEachChunked, forEachInFrames, formatDate, getCurrentBreakpoint, getFocusableElements, getTabIndex, getTabbableElements, getWorkerPool, isDisposable, isFocusable, isTabbable, memoize, noopDisposable, postTransferable, rafSchedule, readWorkerReply, runInWorkerWithPool, runLayoutBatch, safePromiseTry, safePromiseWithResolvers, setGlobalLeakWarningThreshold, shallowEqual, throttle, toDisposable, trackSetChanges, useAppStack, useCanPop, useControlled, useEvent, useEventCallback, useEventValue, useScreen, useStackSize, weekday, weekdayJulian, withDisposeSymbol, workerPoolSymbol, yieldToMain }; export type { AppStackApi, AppStackRouterProps, ArrayRenderProps, BoundaryProps, BreakpointDesc, BreakpointName, CancelablePromise, CancellationToken, CompatDisposable, CxInput, DateRenderProps, DebouncedFunction, DroppingQueue, ElseIfProps, ElseProps, EmitterOptions, EqualityFn, EventDeliveryQueue, ExternalState, ExternalStateCallback, ExternalStateOptions, ExternalWithKernel, FalseProps, FlipAnimateOptions, FocusDirection, FocusTrapProps, FocusableOptions, ForEachChunkedOptions, ForEachInFramesOptions, FrameCompare, FrameDropReason, FrameRenderContext, FrameRenderHandle, FrameRenderProps, FrameRenderStats, FrameScheduler, FrameStrategy, IDynamicListEventMultiplexer, IObservable, IObservableWithChange, IObserver, IValueWithChangeEvent, IWaitUntil, IWaitUntilData, IfProps, LatestValue, MemoizeStats, MemoizedFunction, ObserverProps, PipeProps, PortalProps, PriorityJob, PriorityQueue, PriorityQueueOptions, RafScheduledFunction, RepeatProps, Responsive, ScopeProps, SelectorListener, StorageStateOptions, StylesDescriptor, StylesProps, StylesType, SubscribeSelectorOptions, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ThrottledFunction, ToggleProps, TrueProps, UseControlledOptions, WhenProps, WorkerFn, WorkerPoolOptions, WorkerReply, WorkerReplyChannel, WorkerRunOptions };