@zerodep-dev/vue-tsx-pro-ui 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ export interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged
37
37
  horizontal?: boolean;
38
38
  /** 没有数据时显示的内容 */
39
39
  empty?: VNodeChild;
40
- /** 加载中:没有数据时显示骨架行,已有数据时顶部显示进度条 */
40
+ /** 加载中:没有数据时铺几行骨架,已有数据(加载更多)时骨架接在末尾 */
41
41
  loading?: boolean;
42
42
  /** 加载中占位行的部件属性 */
43
43
  propsSkeleton?: SkeletonShapeProps;
@@ -62,7 +62,6 @@ export declare const virtualListStyles: Readonly<{
62
62
  itemHorizontal: css.CompiledStyles;
63
63
  itemVertical: css.CompiledStyles;
64
64
  list: css.CompiledStyles;
65
- progress: css.CompiledStyles;
66
65
  root: css.CompiledStyles;
67
66
  skeleton: css.CompiledStyles;
68
67
  skeletonRow: css.CompiledStyles;
@@ -1,11 +1,9 @@
1
- import { useLang } from "../config/context.js";
2
1
  import { elementProps, mergeAttrs, mergePartProps, mergeProps } from "../utils/props.js";
3
2
  import { focusRingStyles } from "../utils/style/styles.js";
4
3
  import { useDelayedLoading } from "../utils/motion/delayed-loading.js";
5
4
  import { ScrollArea } from "../layout/ScrollArea.js";
6
5
  import "../utils/style/shimmer.js";
7
6
  import { Skeleton } from "../feedback/Skeleton.js";
8
- import { Progress } from "../feedback/Progress.js";
9
7
  import { useVirtualizer } from "../utils/virtual/use-virtualizer.js";
10
8
  import { Fragment, createVNode, isVNode, shallowRef, watch } from "vue";
11
9
  import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
@@ -19,8 +17,9 @@ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
19
17
  function _isSlot(s) {
20
18
  return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
21
19
  }
22
- /** 加载中显示几行骨架 */
20
+ /** 加载中显示几行骨架:没有数据时铺几行,加载更多时接在末尾几行 */
23
21
  var SKELETON_ROWS = 5;
22
+ var MORE_SKELETON_ROWS = 2;
24
23
  /** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */
25
24
  var virtualListStyles = css.create({
26
25
  root: { overflowAnchor: "none" },
@@ -37,12 +36,6 @@ var virtualListStyles = css.create({
37
36
  },
38
37
  itemVertical: { width: "_full" },
39
38
  itemHorizontal: { height: "_full" },
40
- progress: {
41
- position: "sticky",
42
- top: 0,
43
- zIndex: 1,
44
- height: 0
45
- },
46
39
  skeletonRow: { paddingBlock: "_xs" },
47
40
  skeleton: {
48
41
  display: "block",
@@ -77,10 +70,16 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
77
70
  const propsList = readonlyProp(_props, "propsList");
78
71
  const propsItem = readonlyProp(_props, "propsItem");
79
72
  const rest = restProps(_props, _ctx, false);
80
- const lang = useLang();
81
73
  const indicator = useDelayedLoading(() => loading.value);
82
74
  let root = shallowRef(null);
83
75
  let list = shallowRef(null);
76
+ const skeletonRows = (count) => Array.from({ length: count }, (_, index) => createVNode("div", mergeJsxProps({
77
+ "key": `skeleton-${index}`,
78
+ "aria-hidden": "true"
79
+ }, css.attrs(virtualListStyles.skeletonRow)), [createVNode(Skeleton, mergePartProps({
80
+ sx: [virtualListStyles.skeleton, virtualListSizeStyles.skeleton(typeof estimateSize.value === "number" ? estimateSize.value : 40)],
81
+ delay: index * 120
82
+ }, propsSkeleton.value), null)]));
84
83
  const virtual = useVirtualizer(() => {
85
84
  const data = items.value;
86
85
  const estimate = estimateSize.value;
@@ -112,10 +111,7 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
112
111
  "aria-busy": loading.value || void 0,
113
112
  tabindex: isContainer ? 0 : void 0
114
113
  });
115
- const content = createVNode(Fragment, null, [indicator.value && hasItems && createVNode("div", mergeJsxProps({ "aria-hidden": "true" }, css.attrs(virtualListStyles.progress)), [createVNode(Progress, {
116
- "size": "xs",
117
- "aria-label": lang.loading
118
- }, null)]), hasItems ? createVNode("div", mergeAttrs({ sx: virtualListStyles.list }, propsList.value, {
114
+ const content = createVNode(Fragment, null, [hasItems ? createVNode("div", mergeAttrs({ sx: virtualListStyles.list }, propsList.value, {
119
115
  sx: horizontal.value ? css.bind(virtualListSizeStyles.width, () => [virtual.total], true) : css.bind(virtualListSizeStyles.height, () => [virtual.total], true),
120
116
  ref: list,
121
117
  role: "presentation"
@@ -129,13 +125,7 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
129
125
  "aria-setsize": items.value.length,
130
126
  "aria-posinset": item.index + 1
131
127
  }), { "data-index": item.index }), [children.value(data, item.index)]);
132
- })]) : loading.value ? indicator.value && Array.from({ length: SKELETON_ROWS }, (_, index) => createVNode("div", mergeJsxProps({
133
- "key": index,
134
- "aria-hidden": "true"
135
- }, css.attrs(virtualListStyles.skeletonRow)), [createVNode(Skeleton, mergePartProps({
136
- sx: [virtualListStyles.skeleton, css.bind(virtualListSizeStyles.skeleton, () => [typeof estimateSize.value === "number" ? estimateSize.value : 40], true)],
137
- delay: index * 120
138
- }, propsSkeleton.value), null)])) : empty.value]);
128
+ })]) : loading.value ? indicator.value && skeletonRows(SKELETON_ROWS) : empty.value, hasItems && indicator.value && skeletonRows(MORE_SKELETON_ROWS)]);
139
129
  return isContainer ? createVNode(ScrollArea, {
140
130
  "axis": horizontal.value ? "x" : "y",
141
131
  "focusable": false,
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualList.js","names":["_isVNode","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/display/VirtualList.tsx"],"sourcesContent":["/**\n * 虚拟列表:只渲染可见范围内的项,上万条数据也流畅。行高可以不固定,渲染后自动测量(COMPONENTS.md §10.7)。\n *\n * <VirtualList items={users} getKey={(user) => user.id} estimateSize={48} sx={styles.list}>\n * {(user) => <UserRow user={user} />}\n * </VirtualList>\n *\n * 默认组件自己是滚动容器,高度由使用方给(sx、class 或 style);scroll=\"window\" 时随页面滚动。\n * 读屏:role=\"list\",每项带 aria-setsize / aria-posinset,未渲染的项也算在总数里;没有数据时是 role=\"group\"(空状态、骨架不是列表项)。\n * loading:没有数据时显示骨架行(按 estimateSize 的高度),已有数据(刷新、加载更多)时顶部显示进度条;带 aria-busy。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { useLang } from '../config/context';\nimport { Progress } from '../feedback/Progress';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport {\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport type { VirtualScrollOptions, VisibleRange } from '../utils/virtual/virtualizer';\n\n/** 跟着谁滚动 */\nexport interface VirtualListScrolls {\n /** 组件自己是滚动容器(默认):要给它高度(横向时给宽度) */\n container: unknown;\n /** 随页面(window)滚动:列表按总高度撑开,放在页面的普通流里 */\n window: unknown;\n}\n\n/** handle 拿到的方法 */\nexport interface VirtualListHandle {\n /** 滚动到第 index 项;align 默认 auto(已经完整可见就不动) */\n scrollToIndex: (index: number, options?: VirtualScrollOptions) => void;\n /** 滚动到相对列表开头的位置(px) */\n scrollToOffset: (offset: number, options?: VirtualScrollOptions) => void;\n}\n\n/** 受管属性:角色与可聚焦由组件决定 */\nexport type VirtualListManaged = 'role' | 'tabindex';\n\n/** 未声明的属性落到外层元素(滚动容器)上;aria-label 写在这里就是列表的名称 */\nexport interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged> {\n /** 数据 */\n items: readonly T[];\n /** 渲染一项:`{(item, index) => …}` */\n children: (item: T, index: number) => VNodeChild;\n /** 每项的 key,默认用序号;数据会重排、增删时一定要给,测量结果与组件状态才对得上 */\n getKey?: (item: T, index: number) => PropertyKey;\n /** 每项的估计大小(px),数字或按项计算;渲染后以实际大小为准,默认 40 */\n estimateSize?: number | ((item: T, index: number) => number);\n /** 可见范围前后各多渲染几项,默认 4 */\n overscan?: number;\n /** 跟着谁滚动,默认 container */\n scroll?: keyof VirtualListScrolls;\n /** 横向排列与滚动 */\n horizontal?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 加载中:没有数据时显示骨架行,已有数据时顶部显示进度条 */\n loading?: boolean;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n /** 拿到滚动方法:`handle={(list) => (listHandle = list)}` */\n handle?: (handle: VirtualListHandle) => void;\n /** 可见范围变化(不含 overscan):接近末尾时加载更多 */\n onRangeChange?: (range: VisibleRange) => void;\n /** 列表层(撑开总高度、role=\"list\" 的项都在它里面)的部件属性 */\n propsList?: PartProps<'div', 'role'>;\n /**\n * 每一项外层(role=\"listitem\")的部件属性;可以按项计算:`(item, index) => ({ class: … })`。\n * 位置、序号与无障碍属性由组件管理\n */\n propsItem?: ItemProps | ((item: T, index: number) => ItemProps);\n}\n\n/** 项外层可以定制的属性 */\ntype ItemProps = PartProps<'div', 'role' | 'aria-setsize' | 'aria-posinset'>;\n\n/** 加载中显示几行骨架 */\nconst SKELETON_ROWS = 5;\n\n/** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */\nexport const virtualListStyles = css.create({\n // 自己调整滚动位置(见 virtualizer.ts),不要浏览器再按锚点调整一次\n root: { overflowAnchor: 'none' },\n // 容器模式:列表元素是 ScrollArea 的视口(浮层滚动条)\n container: { overscrollBehavior: 'contain' },\n list: { position: 'relative', overflowAnchor: 'none' },\n item: { position: 'absolute', top: 0, left: 0, overflowAnchor: 'none' },\n itemVertical: { width: '_full' },\n itemHorizontal: { height: '_full' },\n // 刷新中的进度条贴在可见区域顶部\n progress: { position: 'sticky', top: 0, zIndex: 1, height: 0 },\n skeletonRow: { paddingBlock: '_xs' },\n skeleton: { display: 'block', width: '_full' },\n});\n\n// 总大小与位置是任意值,用动态样式(编译为 CSS 变量);单独一个 create,见 COMPONENTS.md §4\n/** VirtualList 的样式:height、width、y、x、skeleton */\nexport const virtualListSizeStyles = css.create({\n height: (px: number) => ({ height: px }),\n width: (px: number) => ({ width: px, height: '_full' }),\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n x: (px: number) => ({ transform: `translateX(${px}px)` }),\n // 骨架行比一项略矮:上下留出间隔\n skeleton: (px: number) => ({ height: Math.max(8, px - 8) }),\n});\n\n/** 虚拟列表:只渲染可见范围内的项,行高渲染后自动测量 */\nexport const VirtualList = _component(\n <T,>({\n items,\n children,\n getKey,\n estimateSize = 40,\n overscan = 4,\n scroll = 'container',\n horizontal = false,\n empty,\n loading = false,\n propsSkeleton,\n handle,\n onRangeChange,\n propsList,\n propsItem,\n ...rest\n }: VirtualListProps<T>) => {\n const lang = useLang();\n // 骨架、进度条防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let list = _state.raw<HTMLElement | null>(null);\n const virtual = useVirtualizer(() => {\n const data = items;\n const estimate = estimateSize;\n const keyOf = getKey;\n return {\n count: data.length,\n overscan,\n horizontal,\n scrollElement: scroll === 'window' ? window : root,\n list,\n estimateSize: (index: number) =>\n typeof estimate === 'number' ? estimate : estimate(data[index]!, index),\n getItemKey: keyOf ? (index: number) => keyOf(data[index]!, index) : undefined,\n onRangeChange,\n };\n });\n\n // 挂载之后才交出 handle:使用方在回调里立即滚动(恢复上次的位置)也能生效。\n // handle 不放进监听源:父级写的箭头函数每次渲染都是新的,不该因此重新交出\n const api: VirtualListHandle = {\n scrollToIndex: virtual.scrollToIndex,\n scrollToOffset: virtual.scrollToOffset,\n };\n watch(\n () => root,\n (element) => {\n if (element) handle?.(api);\n },\n { flush: 'post' },\n );\n\n return () => {\n const isContainer = scroll === 'container';\n const hasItems = items.length > 0;\n const props = mergeProps<'div'>(\n {\n sx: [\n virtualListStyles.root,\n isContainer && [virtualListStyles.container, focusRingStyles.visible],\n ],\n },\n rest,\n {\n ref: _toRef(root),\n // 没有数据(空状态、首次加载的骨架)时不是列表;仍给 group 角色,写在外层的 aria-label 才有效\n role: hasItems ? 'list' : 'group',\n 'aria-busy': loading || undefined,\n // 容器模式下可以用键盘滚动\n tabindex: isContainer ? 0 : undefined,\n },\n );\n const content = (\n <>\n {indicator.value && hasItems && (\n <div aria-hidden=\"true\" {...css.attrs(virtualListStyles.progress)}>\n <Progress size=\"xs\" aria-label={lang.loading} />\n </div>\n )}\n {hasItems ? (\n <div\n {...mergeAttrs<'div'>({ sx: virtualListStyles.list }, propsList, {\n sx: horizontal\n ? virtualListSizeStyles.width(virtual.total)\n : virtualListSizeStyles.height(virtual.total),\n ref: _toRef(list),\n role: 'presentation',\n })}\n >\n {virtual.items.map((item) => {\n const data = items[item.index]!;\n const own =\n typeof propsItem === 'function' ? propsItem(data, item.index) : propsItem;\n return (\n <div\n key={item.key}\n {...mergeAttrs<'div'>(\n {\n sx: [\n virtualListStyles.item,\n horizontal\n ? virtualListStyles.itemHorizontal\n : virtualListStyles.itemVertical,\n ],\n },\n own,\n {\n sx: horizontal\n ? virtualListSizeStyles.x(item.start)\n : virtualListSizeStyles.y(item.start),\n ref: virtual.measure,\n role: 'listitem',\n 'aria-setsize': items.length,\n 'aria-posinset': item.index + 1,\n },\n )}\n data-index={item.index}\n >\n {children(data, item.index)}\n </div>\n );\n })}\n </div>\n ) : loading ? (\n indicator.value &&\n Array.from({ length: SKELETON_ROWS }, (_, index) => (\n <div key={index} aria-hidden=\"true\" {...css.attrs(virtualListStyles.skeletonRow)}>\n <Skeleton\n {...mergePartProps<SkeletonProps>(\n {\n sx: [\n virtualListStyles.skeleton,\n virtualListSizeStyles.skeleton(\n typeof estimateSize === 'number' ? estimateSize : 40,\n ),\n ],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n </div>\n ))\n ) : (\n empty\n )}\n </>\n );\n // 容器模式:列表元素就是 ScrollArea 的视口,滚动条是浮层的;跟着页面滚动时就是普通的 div\n return isContainer ? (\n <ScrollArea\n axis={horizontal ? 'x' : 'y'}\n focusable={false}\n propsViewport={props as PartProps<'div'>}\n >\n {content}\n </ScrollArea>\n ) : (\n <div {...elementProps(props)}>{content}</div>\n );\n };\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"VirtualList.js","names":["_isVNode","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_Fragment"],"sources":["../../src/display/VirtualList.tsx"],"sourcesContent":["/**\n * 虚拟列表:只渲染可见范围内的项,上万条数据也流畅。行高可以不固定,渲染后自动测量(COMPONENTS.md §10.7)。\n *\n * <VirtualList items={users} getKey={(user) => user.id} estimateSize={48} sx={styles.list}>\n * {(user) => <UserRow user={user} />}\n * </VirtualList>\n *\n * 默认组件自己是滚动容器,高度由使用方给(sx、class 或 style);scroll=\"window\" 时随页面滚动。\n * 读屏:role=\"list\",每项带 aria-setsize / aria-posinset,未渲染的项也算在总数里;没有数据时是 role=\"group\"(空状态、骨架不是列表项)。\n * loading:只用骨架行(按 estimateSize 的高度):没有数据时铺满,已有数据(加载更多)时接在列表末尾,不另加进度条;带 aria-busy。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport {\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport type { VirtualScrollOptions, VisibleRange } from '../utils/virtual/virtualizer';\n\n/** 跟着谁滚动 */\nexport interface VirtualListScrolls {\n /** 组件自己是滚动容器(默认):要给它高度(横向时给宽度) */\n container: unknown;\n /** 随页面(window)滚动:列表按总高度撑开,放在页面的普通流里 */\n window: unknown;\n}\n\n/** handle 拿到的方法 */\nexport interface VirtualListHandle {\n /** 滚动到第 index 项;align 默认 auto(已经完整可见就不动) */\n scrollToIndex: (index: number, options?: VirtualScrollOptions) => void;\n /** 滚动到相对列表开头的位置(px) */\n scrollToOffset: (offset: number, options?: VirtualScrollOptions) => void;\n}\n\n/** 受管属性:角色与可聚焦由组件决定 */\nexport type VirtualListManaged = 'role' | 'tabindex';\n\n/** 未声明的属性落到外层元素(滚动容器)上;aria-label 写在这里就是列表的名称 */\nexport interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged> {\n /** 数据 */\n items: readonly T[];\n /** 渲染一项:`{(item, index) => …}` */\n children: (item: T, index: number) => VNodeChild;\n /** 每项的 key,默认用序号;数据会重排、增删时一定要给,测量结果与组件状态才对得上 */\n getKey?: (item: T, index: number) => PropertyKey;\n /** 每项的估计大小(px),数字或按项计算;渲染后以实际大小为准,默认 40 */\n estimateSize?: number | ((item: T, index: number) => number);\n /** 可见范围前后各多渲染几项,默认 4 */\n overscan?: number;\n /** 跟着谁滚动,默认 container */\n scroll?: keyof VirtualListScrolls;\n /** 横向排列与滚动 */\n horizontal?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 加载中:没有数据时铺几行骨架,已有数据(加载更多)时骨架接在末尾 */\n loading?: boolean;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n /** 拿到滚动方法:`handle={(list) => (listHandle = list)}` */\n handle?: (handle: VirtualListHandle) => void;\n /** 可见范围变化(不含 overscan):接近末尾时加载更多 */\n onRangeChange?: (range: VisibleRange) => void;\n /** 列表层(撑开总高度、role=\"list\" 的项都在它里面)的部件属性 */\n propsList?: PartProps<'div', 'role'>;\n /**\n * 每一项外层(role=\"listitem\")的部件属性;可以按项计算:`(item, index) => ({ class: … })`。\n * 位置、序号与无障碍属性由组件管理\n */\n propsItem?: ItemProps | ((item: T, index: number) => ItemProps);\n}\n\n/** 项外层可以定制的属性 */\ntype ItemProps = PartProps<'div', 'role' | 'aria-setsize' | 'aria-posinset'>;\n\n/** 加载中显示几行骨架:没有数据时铺几行,加载更多时接在末尾几行 */\nconst SKELETON_ROWS = 5;\nconst MORE_SKELETON_ROWS = 2;\n\n/** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */\nexport const virtualListStyles = css.create({\n // 自己调整滚动位置(见 virtualizer.ts),不要浏览器再按锚点调整一次\n root: { overflowAnchor: 'none' },\n // 容器模式:列表元素是 ScrollArea 的视口(浮层滚动条)\n container: { overscrollBehavior: 'contain' },\n list: { position: 'relative', overflowAnchor: 'none' },\n item: { position: 'absolute', top: 0, left: 0, overflowAnchor: 'none' },\n itemVertical: { width: '_full' },\n itemHorizontal: { height: '_full' },\n skeletonRow: { paddingBlock: '_xs' },\n skeleton: { display: 'block', width: '_full' },\n});\n\n// 总大小与位置是任意值,用动态样式(编译为 CSS 变量);单独一个 create,见 COMPONENTS.md §4\n/** VirtualList 的样式:height、width、y、x、skeleton */\nexport const virtualListSizeStyles = css.create({\n height: (px: number) => ({ height: px }),\n width: (px: number) => ({ width: px, height: '_full' }),\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n x: (px: number) => ({ transform: `translateX(${px}px)` }),\n // 骨架行比一项略矮:上下留出间隔\n skeleton: (px: number) => ({ height: Math.max(8, px - 8) }),\n});\n\n/** 虚拟列表:只渲染可见范围内的项,行高渲染后自动测量 */\nexport const VirtualList = _component(\n <T,>({\n items,\n children,\n getKey,\n estimateSize = 40,\n overscan = 4,\n scroll = 'container',\n horizontal = false,\n empty,\n loading = false,\n propsSkeleton,\n handle,\n onRangeChange,\n propsList,\n propsItem,\n ...rest\n }: VirtualListProps<T>) => {\n // 骨架防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let list = _state.raw<HTMLElement | null>(null);\n const skeletonRows = (count: number) =>\n Array.from({ length: count }, (_, index) => (\n <div\n key={`skeleton-${index}`}\n aria-hidden=\"true\"\n {...css.attrs(virtualListStyles.skeletonRow)}\n >\n <Skeleton\n {...mergePartProps<SkeletonProps>(\n {\n sx: [\n virtualListStyles.skeleton,\n virtualListSizeStyles.skeleton(\n typeof estimateSize === 'number' ? estimateSize : 40,\n ),\n ],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n </div>\n ));\n const virtual = useVirtualizer(() => {\n const data = items;\n const estimate = estimateSize;\n const keyOf = getKey;\n return {\n count: data.length,\n overscan,\n horizontal,\n scrollElement: scroll === 'window' ? window : root,\n list,\n estimateSize: (index: number) =>\n typeof estimate === 'number' ? estimate : estimate(data[index]!, index),\n getItemKey: keyOf ? (index: number) => keyOf(data[index]!, index) : undefined,\n onRangeChange,\n };\n });\n\n // 挂载之后才交出 handle:使用方在回调里立即滚动(恢复上次的位置)也能生效。\n // handle 不放进监听源:父级写的箭头函数每次渲染都是新的,不该因此重新交出\n const api: VirtualListHandle = {\n scrollToIndex: virtual.scrollToIndex,\n scrollToOffset: virtual.scrollToOffset,\n };\n watch(\n () => root,\n (element) => {\n if (element) handle?.(api);\n },\n { flush: 'post' },\n );\n\n return () => {\n const isContainer = scroll === 'container';\n const hasItems = items.length > 0;\n const props = mergeProps<'div'>(\n {\n sx: [\n virtualListStyles.root,\n isContainer && [virtualListStyles.container, focusRingStyles.visible],\n ],\n },\n rest,\n {\n ref: _toRef(root),\n // 没有数据(空状态、首次加载的骨架)时不是列表;仍给 group 角色,写在外层的 aria-label 才有效\n role: hasItems ? 'list' : 'group',\n 'aria-busy': loading || undefined,\n // 容器模式下可以用键盘滚动\n tabindex: isContainer ? 0 : undefined,\n },\n );\n const content = (\n <>\n {hasItems ? (\n <div\n {...mergeAttrs<'div'>({ sx: virtualListStyles.list }, propsList, {\n sx: horizontal\n ? virtualListSizeStyles.width(virtual.total)\n : virtualListSizeStyles.height(virtual.total),\n ref: _toRef(list),\n role: 'presentation',\n })}\n >\n {virtual.items.map((item) => {\n const data = items[item.index]!;\n const own =\n typeof propsItem === 'function' ? propsItem(data, item.index) : propsItem;\n return (\n <div\n key={item.key}\n {...mergeAttrs<'div'>(\n {\n sx: [\n virtualListStyles.item,\n horizontal\n ? virtualListStyles.itemHorizontal\n : virtualListStyles.itemVertical,\n ],\n },\n own,\n {\n sx: horizontal\n ? virtualListSizeStyles.x(item.start)\n : virtualListSizeStyles.y(item.start),\n ref: virtual.measure,\n role: 'listitem',\n 'aria-setsize': items.length,\n 'aria-posinset': item.index + 1,\n },\n )}\n data-index={item.index}\n >\n {children(data, item.index)}\n </div>\n );\n })}\n </div>\n ) : loading ? (\n indicator.value && skeletonRows(SKELETON_ROWS)\n ) : (\n empty\n )}\n {/* 加载更多:骨架接在已有的项后面(列表元素的高度就是全部项的总高度) */}\n {hasItems && indicator.value && skeletonRows(MORE_SKELETON_ROWS)}\n </>\n );\n // 容器模式:列表元素就是 ScrollArea 的视口,滚动条是浮层的;跟着页面滚动时就是普通的 div\n return isContainer ? (\n <ScrollArea\n axis={horizontal ? 'x' : 'y'}\n focusable={false}\n propsViewport={props as PartProps<'div'>}\n >\n {content}\n </ScrollArea>\n ) : (\n <div {...elementProps(props)}>{content}</div>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -34,7 +34,7 @@ export interface ComboboxBaseProps<T> extends PartProps<'input', ComboboxManaged
34
34
  showToggle?: boolean;
35
35
  /** 没有可选的项时不弹出列表(不显示“没有匹配的选项”):只当带建议的输入框用时(Autocomplete) */
36
36
  hideEmpty?: boolean;
37
- /** 正在取选项(异步搜索):没有选项时列表里是骨架行,已有选项时顶部显示进度条 */
37
+ /** 正在取选项(异步搜索):列表里换成骨架行 */
38
38
  loading?: boolean;
39
39
  /** 弹出位置,默认 bottom-start */
40
40
  placement?: FloatingPlacement;
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_ref","_vue_shallowRef","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/Combobox.tsx"],"sourcesContent":["/**\n * 组合框(WAI-ARIA Combobox,aria-autocomplete=\"list\"):边输入边从列表里选,或者只当带建议的输入框用。\n *\n * <Field label=\"城市\"><Combobox items={cities} _value={city} /></Field>\n * <Combobox items={results} filter={false} loading={searching} _inputValue={keyword} aria-label=\"搜索用户\" />\n *\n * 组合已有的部件:外壳同 TextField(controlParts),弹出层用 useAnchoredPopover(与外壳同宽),列表是 ListboxView。\n * 焦点一直在输入框里,高亮项经 aria-activedescendant 告诉读屏。\n *\n * - 过滤:默认本地按显示文字包含输入(Intl.Collator,忽略大小写与重音);filter={false} 时不过滤,\n * 由调用方按 inputValue 换 items(异步搜索,配合 loading)。给 load 时组件自己请求:打开时与输入停下 300 ms 后\n * 按输入请求,只采用最后一次(remote-items.ts,同 Select)。\n * - 中文输入法:输入经 v-model 写回,组合输入期间 inputValue 不变,所以不会拿拼音去过滤。\n * - 选中后输入框显示选项文字;失焦时如果文字不是某个选项(且没开 allowCustomValue),恢复成已选项的文字,清空则取消选择。\n * - 键盘:↓ 打开、↑↓ / Page Up / Down 移动,Enter 选中,Esc 关闭(关闭时再按 Esc 清空);Home / End 照常移动光标。\n *\n * 部件:Root(外壳)、Input(主元素)、Clear、Chevron(切换按钮)、Popover、Listbox。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { useLang, useTextMatcher } from '../config/context';\nimport { isComposingKey } from '../utils/dom/hotkeys';\nimport { useAnchoredPopover, type FloatingPlacement } from '../utils/dom/floating';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { ListPopover } from './ListPopover';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\nimport { useFieldControl } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { activeOptionId, type ListboxViewProps, type OptionRenderState } from './Listbox';\nimport {\n handleListKeydown,\n useListState,\n useResultAnnouncement,\n useSelectedOptions,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\nimport { useRemoteItems, type OptionsLoader } from './remote-items';\n\n/** 角色、展开状态、值与可用性由组件管理 */\nexport type ComboboxManaged =\n | 'value'\n | 'role'\n | 'size'\n | 'disabled'\n | 'readonly'\n | 'required'\n | 'autocomplete'\n | 'aria-autocomplete'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-describedby';\n\n/** Combobox 除 toOption 外的属性;未声明的属性落到 input(主元素)上,外壳用 propsRoot */\nexport interface ComboboxBaseProps<T>\n extends PartProps<'input', ComboboxManaged>, ControlOptions, ControlIconOptions, OpenChangeProps {\n /** 数据:ListboxOption、字符串,或任意数据加 toOption;有 load 时是第一次加载之前的选项(编辑页放上已选的那一项) */\n items?: readonly T[];\n /** 选中项的值:父级写 _value={x};没有选中时是 null */\n _value?: string | null;\n /** 输入框里的文字:父级可以写 _inputValue={x}(异步搜索时据此取数据) */\n _inputValue?: string;\n /** 是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 过滤:默认按显示文字包含输入(忽略大小写与重音);函数自定义;false 不过滤(异步搜索) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 远程选项:打开时与输入停下 300 ms 后按输入调用,结果代替 items;只采用最后一次,被顶掉的请求 signal 中止 */\n load?: OptionsLoader<T>;\n /** 允许输入不在选项里的文字:失焦时保留,value 为 null */\n allowCustomValue?: boolean;\n /**\n * 显示右侧的展开按钮;只当带建议的输入框用时(Autocomplete)关掉,列表随输入出现\n * @default true\n */\n showToggle?: boolean;\n /** 没有可选的项时不弹出列表(不显示“没有匹配的选项”):只当带建议的输入框用时(Autocomplete) */\n hideEmpty?: boolean;\n /** 正在取选项(异步搜索):没有选项时列表里是骨架行,已有选项时顶部显示进度条 */\n loading?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有内容时显示清空按钮 */\n clearable?: boolean;\n /** 表单提交时的 name(用隐藏的 input 提交 value) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义选项内容:`{(item, { selected, active }) => …}`,拿到 items 里的原始数据 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有匹配的选项时列表里显示的内容 */\n empty?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 切换按钮(下拉箭头)的部件属性 */\n propsChevron?: Partial<IconButtonProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 组合框的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type ComboboxProps<T = ListboxOption> = ComboboxBaseProps<T> & OptionMapping<T>;\n\n/** Combobox 的样式:chevronOpen */\nexport const comboboxStyles = css.create({\n chevronOpen: { rotate: '180deg' },\n});\n\n/** 组合框:输入框 + 过滤后的选项列表 */\nexport const Combobox = _component(\n <T,>({\n items = [],\n toOption,\n _value: value = _bindable<string | null>(null),\n _inputValue: inputValue = _bindable(''),\n _open: open = _bindable(false),\n onOpenChange,\n filter,\n load,\n allowCustomValue = false,\n showToggle = true,\n hideEmpty = false,\n loading = false,\n placement = 'bottom-start',\n clearable = false,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n children,\n empty,\n propsRoot,\n icon,\n propsIcon,\n propsClear,\n propsChevron,\n propsPopover,\n propsListbox,\n ...rest\n }: ComboboxProps<T>) => {\n const lang = useLang();\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n // 用户输入的过滤词:选中、用箭头打开时清空(显示全部选项)\n let query = _state('');\n // 组件自己改 inputValue(选中后写入选项文字)时,不当作用户输入\n let writing = false;\n let shell = _state.raw<HTMLElement | null>(null);\n let input = _state.raw<HTMLInputElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n const optionOf = (item: T) => toListboxOption(item, toOption);\n\n function matches(item: T, text: string) {\n if (!text) return true;\n return filter ? filter(item, text) : textMatcher()(optionOf(item).label, text);\n }\n\n const remote = useRemoteItems({\n load: () => load,\n query: () => query,\n open: () => open,\n items: () => items,\n });\n const list = useListState({\n // 有 load 或 filter={false} 时由服务端按输入过滤\n items: () =>\n load || filter === false\n ? remote.items()\n : remote.items().filter((item) => matches(item, query)),\n toOption: optionOf,\n selected: () => (value != null ? [value] : []),\n mode: () => 'single',\n // 再选已选的项不取消\n required: () => true,\n onChange: (next) => (value = next[0] ?? null),\n });\n\n // 已选项的文字:远程搜索换了 items 之后,仍按之前见过的文字显示\n const selectedOf = useSelectedOptions({ items: remote.items, toOption: optionOf });\n const selectedLabel = () => (value == null ? '' : selectedOf([value])[0]!.label);\n\n function setText(text: string) {\n if (inputValue === text) return;\n writing = true;\n inputValue = text;\n }\n\n useResultAnnouncement(() => ({ query, count: list.options.length }));\n\n // 用户输入(含输入法组合结束):打开并按输入过滤,高亮第一项\n watch(\n () => inputValue,\n (text) => {\n if (writing) {\n writing = false;\n return;\n }\n query = text;\n // 只在输入框聚焦时打开:父级把 inputValue 改掉(重置成空)不该弹出列表\n if (!control().readonly && document.activeElement === input) setOpen(true, 'trigger');\n list.move('first');\n },\n );\n\n // 父级改了 value,或选项后到(编辑页先拿到值、再拿到选项):输入框跟着显示它的文字;用户正在输入时不打断\n watch(\n () => [value, selectedLabel()],\n () => {\n if (query) return;\n if (!allowCustomValue || value != null) setText(selectedLabel());\n },\n { immediate: true },\n );\n\n function pick(choice: string, event?: Event) {\n value = choice;\n setText(selectedLabel());\n query = '';\n setOpen(false, 'select', event);\n }\n\n function show(event?: Event) {\n query = '';\n list.activateDefault();\n setOpen(true, 'trigger', event);\n }\n\n // 失焦:文字不是选项时恢复(或清空选择)\n function commit(event: FocusEvent) {\n setOpen(false, 'focus', event);\n if (allowCustomValue) {\n if (inputValue !== selectedLabel()) value = null;\n return;\n }\n if (!inputValue) value = null;\n else setText(selectedLabel());\n query = '';\n }\n\n function onKeydown(event: KeyboardEvent) {\n const state = control();\n if (state.disabled || state.readonly || isComposingKey(event)) return;\n if (event.key === 'Escape') {\n // 打开时交给原生 popover 关闭;关闭时清空\n if (!open && inputValue) {\n event.preventDefault();\n setText('');\n value = null;\n }\n return;\n }\n if (!open) {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n show(event);\n }\n return;\n }\n if (handleListKeydown(list, event, { homeEnd: false }, (choice) => pick(choice, event)))\n event.preventDefault();\n }\n\n // 列表是否真的弹出:hideEmpty 时没有可选的项(也不在加载)就不弹\n const shown = () =>\n open && (!hideEmpty || list.options.length > 0 || loading || remote.loading());\n\n // 弹层贴着外壳、与它同宽;焦点一直在输入框上,关闭时不用归还\n const { onToggle } = useAnchoredPopover(\n () => ({\n open: shown(),\n anchor: shell,\n panel,\n floating: { placement, matchWidth: true, fitHeight: true },\n restoreFocus: false,\n }),\n { close: (reason, event) => setOpen(false, reason, event) },\n );\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n return (\n <>\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n ref: _toRef(shell),\n onPointerdown: focusOnShell(() => input),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {/* _bind 编译为 v-model:输入法组合期间不写回 */}\n <input\n {...mergeAttrs<'input'>(s.input, rest, {\n ref: _toRef(input),\n id: state.id,\n role: 'combobox',\n autocomplete: 'off',\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-autocomplete': 'list',\n 'aria-expanded': shown(),\n 'aria-controls': listId,\n 'aria-activedescendant': open ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': state.describedBy,\n onKeydown,\n onBlur: commit,\n })}\n _value={inputValue}\n />\n {clearable &&\n inputValue &&\n editable &&\n controlClear(s, propsClear, () => {\n setText('');\n value = null;\n input?.focus();\n })}\n {showToggle && (\n <IconButton\n {...mergePartProps<IconButtonProps>(\n {\n ...s.button,\n sx: [s.button.sx, disclosureStyles.icon, open && comboboxStyles.chevronOpen],\n radius: 'full',\n icon: ChevronDown,\n 'aria-label': lang.comboboxToggle,\n },\n propsChevron,\n {\n tabindex: -1,\n disabled: !editable,\n 'aria-expanded': shown(),\n 'aria-controls': listId,\n // 不让输入框失焦(失焦会提交并关闭)\n onPointerdown: (event: PointerEvent) => event.preventDefault(),\n onClick: (event: MouseEvent) => {\n if (open) setOpen(false, 'trigger', event);\n else show(event);\n input?.focus();\n },\n },\n )}\n />\n )}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading || remote.loading()}\n onPick={(choice) => pick(choice)}\n listbox={{\n list,\n id: listId,\n renderOption: children,\n empty: remote.failure() ?? empty,\n }}\n propsListbox={propsListbox}\n propsPopover={propsPopover}\n />\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_ref","_vue_shallowRef","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/Combobox.tsx"],"sourcesContent":["/**\n * 组合框(WAI-ARIA Combobox,aria-autocomplete=\"list\"):边输入边从列表里选,或者只当带建议的输入框用。\n *\n * <Field label=\"城市\"><Combobox items={cities} _value={city} /></Field>\n * <Combobox items={results} filter={false} loading={searching} _inputValue={keyword} aria-label=\"搜索用户\" />\n *\n * 组合已有的部件:外壳同 TextField(controlParts),弹出层用 useAnchoredPopover(与外壳同宽),列表是 ListboxView。\n * 焦点一直在输入框里,高亮项经 aria-activedescendant 告诉读屏。\n *\n * - 过滤:默认本地按显示文字包含输入(Intl.Collator,忽略大小写与重音);filter={false} 时不过滤,\n * 由调用方按 inputValue 换 items(异步搜索,配合 loading)。给 load 时组件自己请求:打开时与输入停下 300 ms 后\n * 按输入请求,只采用最后一次(remote-items.ts,同 Select)。\n * - 中文输入法:输入经 v-model 写回,组合输入期间 inputValue 不变,所以不会拿拼音去过滤。\n * - 选中后输入框显示选项文字;失焦时如果文字不是某个选项(且没开 allowCustomValue),恢复成已选项的文字,清空则取消选择。\n * - 键盘:↓ 打开、↑↓ / Page Up / Down 移动,Enter 选中,Esc 关闭(关闭时再按 Esc 清空);Home / End 照常移动光标。\n *\n * 部件:Root(外壳)、Input(主元素)、Clear、Chevron(切换按钮)、Popover、Listbox。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { useLang, useTextMatcher } from '../config/context';\nimport { isComposingKey } from '../utils/dom/hotkeys';\nimport { useAnchoredPopover, type FloatingPlacement } from '../utils/dom/floating';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { ListPopover } from './ListPopover';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\nimport { useFieldControl } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { activeOptionId, type ListboxViewProps, type OptionRenderState } from './Listbox';\nimport {\n handleListKeydown,\n useListState,\n useResultAnnouncement,\n useSelectedOptions,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\nimport { useRemoteItems, type OptionsLoader } from './remote-items';\n\n/** 角色、展开状态、值与可用性由组件管理 */\nexport type ComboboxManaged =\n | 'value'\n | 'role'\n | 'size'\n | 'disabled'\n | 'readonly'\n | 'required'\n | 'autocomplete'\n | 'aria-autocomplete'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-describedby';\n\n/** Combobox 除 toOption 外的属性;未声明的属性落到 input(主元素)上,外壳用 propsRoot */\nexport interface ComboboxBaseProps<T>\n extends PartProps<'input', ComboboxManaged>, ControlOptions, ControlIconOptions, OpenChangeProps {\n /** 数据:ListboxOption、字符串,或任意数据加 toOption;有 load 时是第一次加载之前的选项(编辑页放上已选的那一项) */\n items?: readonly T[];\n /** 选中项的值:父级写 _value={x};没有选中时是 null */\n _value?: string | null;\n /** 输入框里的文字:父级可以写 _inputValue={x}(异步搜索时据此取数据) */\n _inputValue?: string;\n /** 是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 过滤:默认按显示文字包含输入(忽略大小写与重音);函数自定义;false 不过滤(异步搜索) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 远程选项:打开时与输入停下 300 ms 后按输入调用,结果代替 items;只采用最后一次,被顶掉的请求 signal 中止 */\n load?: OptionsLoader<T>;\n /** 允许输入不在选项里的文字:失焦时保留,value 为 null */\n allowCustomValue?: boolean;\n /**\n * 显示右侧的展开按钮;只当带建议的输入框用时(Autocomplete)关掉,列表随输入出现\n * @default true\n */\n showToggle?: boolean;\n /** 没有可选的项时不弹出列表(不显示“没有匹配的选项”):只当带建议的输入框用时(Autocomplete) */\n hideEmpty?: boolean;\n /** 正在取选项(异步搜索):列表里换成骨架行 */\n loading?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有内容时显示清空按钮 */\n clearable?: boolean;\n /** 表单提交时的 name(用隐藏的 input 提交 value) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义选项内容:`{(item, { selected, active }) => …}`,拿到 items 里的原始数据 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有匹配的选项时列表里显示的内容 */\n empty?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 切换按钮(下拉箭头)的部件属性 */\n propsChevron?: Partial<IconButtonProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 组合框的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type ComboboxProps<T = ListboxOption> = ComboboxBaseProps<T> & OptionMapping<T>;\n\n/** Combobox 的样式:chevronOpen */\nexport const comboboxStyles = css.create({\n chevronOpen: { rotate: '180deg' },\n});\n\n/** 组合框:输入框 + 过滤后的选项列表 */\nexport const Combobox = _component(\n <T,>({\n items = [],\n toOption,\n _value: value = _bindable<string | null>(null),\n _inputValue: inputValue = _bindable(''),\n _open: open = _bindable(false),\n onOpenChange,\n filter,\n load,\n allowCustomValue = false,\n showToggle = true,\n hideEmpty = false,\n loading = false,\n placement = 'bottom-start',\n clearable = false,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n children,\n empty,\n propsRoot,\n icon,\n propsIcon,\n propsClear,\n propsChevron,\n propsPopover,\n propsListbox,\n ...rest\n }: ComboboxProps<T>) => {\n const lang = useLang();\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n // 用户输入的过滤词:选中、用箭头打开时清空(显示全部选项)\n let query = _state('');\n // 组件自己改 inputValue(选中后写入选项文字)时,不当作用户输入\n let writing = false;\n let shell = _state.raw<HTMLElement | null>(null);\n let input = _state.raw<HTMLInputElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n const optionOf = (item: T) => toListboxOption(item, toOption);\n\n function matches(item: T, text: string) {\n if (!text) return true;\n return filter ? filter(item, text) : textMatcher()(optionOf(item).label, text);\n }\n\n const remote = useRemoteItems({\n load: () => load,\n query: () => query,\n open: () => open,\n items: () => items,\n });\n const list = useListState({\n // 有 load 或 filter={false} 时由服务端按输入过滤\n items: () =>\n load || filter === false\n ? remote.items()\n : remote.items().filter((item) => matches(item, query)),\n toOption: optionOf,\n selected: () => (value != null ? [value] : []),\n mode: () => 'single',\n // 再选已选的项不取消\n required: () => true,\n onChange: (next) => (value = next[0] ?? null),\n });\n\n // 已选项的文字:远程搜索换了 items 之后,仍按之前见过的文字显示\n const selectedOf = useSelectedOptions({ items: remote.items, toOption: optionOf });\n const selectedLabel = () => (value == null ? '' : selectedOf([value])[0]!.label);\n\n function setText(text: string) {\n if (inputValue === text) return;\n writing = true;\n inputValue = text;\n }\n\n useResultAnnouncement(() => ({ query, count: list.options.length }));\n\n // 用户输入(含输入法组合结束):打开并按输入过滤,高亮第一项\n watch(\n () => inputValue,\n (text) => {\n if (writing) {\n writing = false;\n return;\n }\n query = text;\n // 只在输入框聚焦时打开:父级把 inputValue 改掉(重置成空)不该弹出列表\n if (!control().readonly && document.activeElement === input) setOpen(true, 'trigger');\n list.move('first');\n },\n );\n\n // 父级改了 value,或选项后到(编辑页先拿到值、再拿到选项):输入框跟着显示它的文字;用户正在输入时不打断\n watch(\n () => [value, selectedLabel()],\n () => {\n if (query) return;\n if (!allowCustomValue || value != null) setText(selectedLabel());\n },\n { immediate: true },\n );\n\n function pick(choice: string, event?: Event) {\n value = choice;\n setText(selectedLabel());\n query = '';\n setOpen(false, 'select', event);\n }\n\n function show(event?: Event) {\n query = '';\n list.activateDefault();\n setOpen(true, 'trigger', event);\n }\n\n // 失焦:文字不是选项时恢复(或清空选择)\n function commit(event: FocusEvent) {\n setOpen(false, 'focus', event);\n if (allowCustomValue) {\n if (inputValue !== selectedLabel()) value = null;\n return;\n }\n if (!inputValue) value = null;\n else setText(selectedLabel());\n query = '';\n }\n\n function onKeydown(event: KeyboardEvent) {\n const state = control();\n if (state.disabled || state.readonly || isComposingKey(event)) return;\n if (event.key === 'Escape') {\n // 打开时交给原生 popover 关闭;关闭时清空\n if (!open && inputValue) {\n event.preventDefault();\n setText('');\n value = null;\n }\n return;\n }\n if (!open) {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n show(event);\n }\n return;\n }\n if (handleListKeydown(list, event, { homeEnd: false }, (choice) => pick(choice, event)))\n event.preventDefault();\n }\n\n // 列表是否真的弹出:hideEmpty 时没有可选的项(也不在加载)就不弹\n const shown = () =>\n open && (!hideEmpty || list.options.length > 0 || loading || remote.loading());\n\n // 弹层贴着外壳、与它同宽;焦点一直在输入框上,关闭时不用归还\n const { onToggle } = useAnchoredPopover(\n () => ({\n open: shown(),\n anchor: shell,\n panel,\n floating: { placement, matchWidth: true, fitHeight: true },\n restoreFocus: false,\n }),\n { close: (reason, event) => setOpen(false, reason, event) },\n );\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n return (\n <>\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n ref: _toRef(shell),\n onPointerdown: focusOnShell(() => input),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {/* _bind 编译为 v-model:输入法组合期间不写回 */}\n <input\n {...mergeAttrs<'input'>(s.input, rest, {\n ref: _toRef(input),\n id: state.id,\n role: 'combobox',\n autocomplete: 'off',\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-autocomplete': 'list',\n 'aria-expanded': shown(),\n 'aria-controls': listId,\n 'aria-activedescendant': open ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': state.describedBy,\n onKeydown,\n onBlur: commit,\n })}\n _value={inputValue}\n />\n {clearable &&\n inputValue &&\n editable &&\n controlClear(s, propsClear, () => {\n setText('');\n value = null;\n input?.focus();\n })}\n {showToggle && (\n <IconButton\n {...mergePartProps<IconButtonProps>(\n {\n ...s.button,\n sx: [s.button.sx, disclosureStyles.icon, open && comboboxStyles.chevronOpen],\n radius: 'full',\n icon: ChevronDown,\n 'aria-label': lang.comboboxToggle,\n },\n propsChevron,\n {\n tabindex: -1,\n disabled: !editable,\n 'aria-expanded': shown(),\n 'aria-controls': listId,\n // 不让输入框失焦(失焦会提交并关闭)\n onPointerdown: (event: PointerEvent) => event.preventDefault(),\n onClick: (event: MouseEvent) => {\n if (open) setOpen(false, 'trigger', event);\n else show(event);\n input?.focus();\n },\n },\n )}\n />\n )}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading || remote.loading()}\n onPick={(choice) => pick(choice)}\n listbox={{\n list,\n id: listId,\n renderOption: children,\n empty: remote.failure() ?? empty,\n }}\n propsListbox={propsListbox}\n propsPopover={propsPopover}\n />\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -17,7 +17,7 @@ export interface ListPopoverProps<T> {
17
17
  labelledBy?: string;
18
18
  /** 列表的名称:没有外部标签时用这段文字(aria-label) */
19
19
  label?: string;
20
- /** 加载中:列表显示骨架或顶部进度条 */
20
+ /** 加载中:列表换成骨架行 */
21
21
  loading?: boolean;
22
22
  /** 点选了一项 */
23
23
  onPick?: (value: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"ListPopover.js","names":["_vtp_readonlyProp","_vue_shallowRef","_createVNode"],"sources":["../../src/input/ListPopover.tsx"],"sourcesContent":["/**\n * 选项弹出层(div popover=\"auto\"):Select、Combobox、TagsInput 共用。锚定的浮层面板、浮层滚动条,\n * 里面是虚拟滚动的选项列表(ListboxView,滚动容器就是这里的 ScrollArea 视口)。\n *\n * const { onToggle } = useAnchoredPopover(…, panel, …);\n * <ListPopover panelRef={_toRef(panel)} onToggle={onToggle} size={size} label={…}\n * listbox={{ list, id, empty, renderOption }} propsListbox={propsListbox} onPick={pick} />\n *\n * 开关与定位由父组件的 useAnchoredPopover 管(它要拿到面板元素);这里只负责渲染。\n * 弹层渲染在顶层、不是输入框外壳的后代,不继承外壳的字号:按同一个 size 设置。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { Ref, VNodeChild } from 'vue';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { anchoredPanelSx, floatingStyles } from '../utils/dom/floating';\nimport { toElement } from '../utils/dom/element';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { TextSize } from '../utils/style/typography';\nimport { ListboxView, type ListboxViewProps } from './Listbox';\n\n/** 弹层的属性由组件管理 */\nexport type ListPopoverManaged = 'popover';\n\n/** ListPopover 的属性 */\nexport interface ListPopoverProps<T> {\n /** 面板元素(父组件里 _toRef(panel)):交给 useAnchoredPopover 定位 */\n panelRef: Ref<HTMLElement | null>;\n /** 面板的 toggle 事件:交给 useAnchoredPopover,同步开关状态 */\n onToggle: (event: ToggleEvent) => void;\n /** 字号档位:同输入框外壳 */\n size?: TextSize;\n /** 列表的名称:外部标签的 id(有它时优先) */\n labelledBy?: string;\n /** 列表的名称:没有外部标签时用这段文字(aria-label) */\n label?: string;\n /** 加载中:列表显示骨架或顶部进度条 */\n loading?: boolean;\n /** 点选了一项 */\n onPick?: (value: string) => void;\n /** 列表的组件自己给的属性(list、id、渲染方式…),使用方的 propsListbox 可以覆盖 */\n listbox: ListboxViewProps<T>;\n /** 列表上方、不随列表滚动的内容(Select 的搜索框) */\n header?: VNodeChild;\n /** 使用方给列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n /** 使用方给弹层面板的部件属性 */\n propsPopover?: PartProps<'div', ListPopoverManaged>;\n}\n\n/** ListPopover 的样式:panel、header */\nexport const listPopoverStyles = css.create({\n panel: { maxWidth: 'calc(100vw - 2em)' },\n header: {\n flexShrink: 0,\n padding: '_xs',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_borderMuted',\n },\n});\n\n/** 选项弹出层:锚定的面板、浮层滚动条与选项列表 */\nexport const ListPopover = _component(\n <T,>({\n panelRef,\n onToggle,\n size,\n labelledBy,\n label,\n loading,\n onPick,\n listbox,\n header,\n propsListbox,\n propsPopover,\n }: ListPopoverProps<T>) => {\n let scroller = _state.raw<HTMLElement | null>(null);\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [anchoredPanelSx(size), floatingStyles.scrollPanel, listPopoverStyles.panel],\n },\n propsPopover,\n { ref: panelRef, popover: 'auto', onToggle },\n )}\n >\n {/* 使用方常写 cond && <…/>:false 同 undefined,不渲染带分隔线的空头部 */}\n {hasContent(header) && <div {...css.attrs(listPopoverStyles.header)}>{header}</div>}\n <ScrollArea\n focusable={false}\n sx={floatingStyles.scrollArea}\n propsViewport={{ ref: (element: unknown) => (scroller = toElement(element)) }}\n >\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(listbox, propsListbox, {\n id: listbox.id,\n // 这里的 ScrollArea 视口是滚动容器:选项虚拟滚动\n scroller,\n loading,\n 'aria-labelledby': labelledBy,\n 'aria-label': labelledBy ? undefined : label,\n onPick,\n })}\n />\n </ScrollArea>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;AAmDA,IAAa,oBAAoB,IAAI,OAAO;CAC1C,OAAO,EAAE,UAAU,oBAAoB;CACvC,QAAQ;EACN,YAAY;EACZ,SAAS;EACT,qBAAqB;EACrB,qBAAqB;EACrB,qBAAqB;CACvB;AACF,CAAC;;AAGD,IAAa,cAAc,0BACzB,QAAA,SAY2B;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CACzB,IAAI,WAAQC,WAAkC,IAAI;CAClD,aAAOC,YAAA,OAEC,WACF,EACE,IAAI;EAAC,gBAAe,KAAA,KAAK;EAAG,eAAe;EAAa,kBAAkB;CAAK,EACjF,GAAC,aAAA,OAED;EAAE,KAAG,SAAA;EAAY,SAAS;EAAQ,UAAQ,SAAA;CAAC,CAC7C,GAAC,CAGA,WAAU,OAAA,KAAO,KAACA,YAAA,OAAa,IAAI,MAAM,kBAAkB,MAAM,GAAC,CAAA,OAAA,KAAA,CAAA,GAAgBA,YAAA,YAAA;EAAA,aAEtE;EAAK,MACZ,eAAe;EAAU,iBAAA,OAAA,OAAA,OAAA,KACd,EAAE,MAAM,YAAsB,SAAA,QAAW,UAAU,OAAO,EAAG;EAAC,OAAA;CAAA,GAAA;EAAA,eAAA,CAAAA,YAAA,aAGvE,eAAc,QAAA,OAAA,aAAA,OAA6C;GAC7D,IAAI,QAAA,MAAQ;GAEZ,UAAQ,SAAA;GACR,SAAO,QAAA;GACP,mBAAiB,WAAA;GACjB,cAAc,WAAA,QAAa,KAAA,IAAS,MAAA;GACpC,QAAM,OAAA;EACR,CAAC,GAAC,IAAA,CAAA;EAAA,SAAA;CAAA,CAAA,CAAA,CAAA;AAKZ,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
1
+ {"version":3,"file":"ListPopover.js","names":["_vtp_readonlyProp","_vue_shallowRef","_createVNode"],"sources":["../../src/input/ListPopover.tsx"],"sourcesContent":["/**\n * 选项弹出层(div popover=\"auto\"):Select、Combobox、TagsInput 共用。锚定的浮层面板、浮层滚动条,\n * 里面是虚拟滚动的选项列表(ListboxView,滚动容器就是这里的 ScrollArea 视口)。\n *\n * const { onToggle } = useAnchoredPopover(…, panel, …);\n * <ListPopover panelRef={_toRef(panel)} onToggle={onToggle} size={size} label={…}\n * listbox={{ list, id, empty, renderOption }} propsListbox={propsListbox} onPick={pick} />\n *\n * 开关与定位由父组件的 useAnchoredPopover 管(它要拿到面板元素);这里只负责渲染。\n * 弹层渲染在顶层、不是输入框外壳的后代,不继承外壳的字号:按同一个 size 设置。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { Ref, VNodeChild } from 'vue';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { anchoredPanelSx, floatingStyles } from '../utils/dom/floating';\nimport { toElement } from '../utils/dom/element';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { TextSize } from '../utils/style/typography';\nimport { ListboxView, type ListboxViewProps } from './Listbox';\n\n/** 弹层的属性由组件管理 */\nexport type ListPopoverManaged = 'popover';\n\n/** ListPopover 的属性 */\nexport interface ListPopoverProps<T> {\n /** 面板元素(父组件里 _toRef(panel)):交给 useAnchoredPopover 定位 */\n panelRef: Ref<HTMLElement | null>;\n /** 面板的 toggle 事件:交给 useAnchoredPopover,同步开关状态 */\n onToggle: (event: ToggleEvent) => void;\n /** 字号档位:同输入框外壳 */\n size?: TextSize;\n /** 列表的名称:外部标签的 id(有它时优先) */\n labelledBy?: string;\n /** 列表的名称:没有外部标签时用这段文字(aria-label) */\n label?: string;\n /** 加载中:列表换成骨架行 */\n loading?: boolean;\n /** 点选了一项 */\n onPick?: (value: string) => void;\n /** 列表的组件自己给的属性(list、id、渲染方式…),使用方的 propsListbox 可以覆盖 */\n listbox: ListboxViewProps<T>;\n /** 列表上方、不随列表滚动的内容(Select 的搜索框) */\n header?: VNodeChild;\n /** 使用方给列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n /** 使用方给弹层面板的部件属性 */\n propsPopover?: PartProps<'div', ListPopoverManaged>;\n}\n\n/** ListPopover 的样式:panel、header */\nexport const listPopoverStyles = css.create({\n panel: { maxWidth: 'calc(100vw - 2em)' },\n header: {\n flexShrink: 0,\n padding: '_xs',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_borderMuted',\n },\n});\n\n/** 选项弹出层:锚定的面板、浮层滚动条与选项列表 */\nexport const ListPopover = _component(\n <T,>({\n panelRef,\n onToggle,\n size,\n labelledBy,\n label,\n loading,\n onPick,\n listbox,\n header,\n propsListbox,\n propsPopover,\n }: ListPopoverProps<T>) => {\n let scroller = _state.raw<HTMLElement | null>(null);\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [anchoredPanelSx(size), floatingStyles.scrollPanel, listPopoverStyles.panel],\n },\n propsPopover,\n { ref: panelRef, popover: 'auto', onToggle },\n )}\n >\n {/* 使用方常写 cond && <…/>:false 同 undefined,不渲染带分隔线的空头部 */}\n {hasContent(header) && <div {...css.attrs(listPopoverStyles.header)}>{header}</div>}\n <ScrollArea\n focusable={false}\n sx={floatingStyles.scrollArea}\n propsViewport={{ ref: (element: unknown) => (scroller = toElement(element)) }}\n >\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(listbox, propsListbox, {\n id: listbox.id,\n // 这里的 ScrollArea 视口是滚动容器:选项虚拟滚动\n scroller,\n loading,\n 'aria-labelledby': labelledBy,\n 'aria-label': labelledBy ? undefined : label,\n onPick,\n })}\n />\n </ScrollArea>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;AAmDA,IAAa,oBAAoB,IAAI,OAAO;CAC1C,OAAO,EAAE,UAAU,oBAAoB;CACvC,QAAQ;EACN,YAAY;EACZ,SAAS;EACT,qBAAqB;EACrB,qBAAqB;EACrB,qBAAqB;CACvB;AACF,CAAC;;AAGD,IAAa,cAAc,0BACzB,QAAA,SAY2B;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CACzB,IAAI,WAAQC,WAAkC,IAAI;CAClD,aAAOC,YAAA,OAEC,WACF,EACE,IAAI;EAAC,gBAAe,KAAA,KAAK;EAAG,eAAe;EAAa,kBAAkB;CAAK,EACjF,GAAC,aAAA,OAED;EAAE,KAAG,SAAA;EAAY,SAAS;EAAQ,UAAQ,SAAA;CAAC,CAC7C,GAAC,CAGA,WAAU,OAAA,KAAO,KAACA,YAAA,OAAa,IAAI,MAAM,kBAAkB,MAAM,GAAC,CAAA,OAAA,KAAA,CAAA,GAAgBA,YAAA,YAAA;EAAA,aAEtE;EAAK,MACZ,eAAe;EAAU,iBAAA,OAAA,OAAA,OAAA,KACd,EAAE,MAAM,YAAsB,SAAA,QAAW,UAAU,OAAO,EAAG;EAAC,OAAA;CAAA,GAAA;EAAA,eAAA,CAAAA,YAAA,aAGvE,eAAc,QAAA,OAAA,aAAA,OAA6C;GAC7D,IAAI,QAAA,MAAQ;GAEZ,UAAQ,SAAA;GACR,SAAO,QAAA;GACP,mBAAiB,WAAA;GACjB,cAAc,WAAA,QAAa,KAAA,IAAS,MAAA;GACpC,QAAM,OAAA;EACR,CAAC,GAAC,IAAA,CAAA;EAAA,SAAA;CAAA,CAAA,CAAA,CAAA;AAKZ,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
@@ -26,7 +26,7 @@ export interface ListboxViewProps<T> extends PartProps<'div', 'role'> {
26
26
  multiple?: boolean;
27
27
  /** 滚动容器:Select、Combobox 的弹出层;不给时列表自己滚动(ScrollArea 的浮层滚动条,最高 20em,用 sx 改) */
28
28
  scroller?: HTMLElement | null;
29
- /** 加载中:没有选项时显示骨架行,有选项时顶部显示进度条 */
29
+ /** 加载中:选项换成骨架行 */
30
30
  loading?: boolean;
31
31
  /** 选项的估计高度(px),默认 36;渲染后以实际高度为准 */
32
32
  estimateSize?: number;
@@ -54,7 +54,6 @@ export declare const listboxStyles: Readonly<{
54
54
  description: css.CompiledStyles;
55
55
  groupLabel: css.CompiledStyles;
56
56
  hidden: css.CompiledStyles;
57
- progress: css.CompiledStyles;
58
57
  root: css.CompiledStyles;
59
58
  row: css.CompiledStyles;
60
59
  scroll: css.CompiledStyles;
@@ -8,7 +8,6 @@ import { Text as Text$1 } from "../base/Text.js";
8
8
  import { selectedValues } from "../utils/data/selection.js";
9
9
  import "../utils/style/shimmer.js";
10
10
  import { Skeleton } from "../feedback/Skeleton.js";
11
- import { Progress } from "../feedback/Progress.js";
12
11
  import { Empty } from "../feedback/Empty.js";
13
12
  import { useFieldControl } from "../form/Field.js";
14
13
  import { handleListKeydown, toListboxOption, useListState } from "./list-state.js";
@@ -35,7 +34,7 @@ import { Check } from "@lucide/icons";
35
34
  * 虚拟滚动(useVirtualizer):高亮项总在渲染范围里(aria-activedescendant 才指得到),键盘移动时滚进可见范围;
36
35
  * 选项带 aria-setsize / aria-posinset,读屏知道总数与位置。分组标题只是视觉上的一行(各行独立定位,没法用
37
36
  * role="group" 包住组里的选项),组名经 aria-describedby 交给组里的每个选项。
38
- * loading:没有选项时显示骨架行;已有选项(刷新中)时顶部显示一条进度条;列表带 aria-busy。
37
+ * loading:选项换成骨架行(首次加载与刷新一样,不再另加进度条);列表带 aria-busy。
39
38
  *
40
39
  * 部件:Root(div,role="listbox")、GroupLabel、Option(div,role="option")、Check(已选标记)、Skeleton(加载中的占位行)。
41
40
  */
@@ -86,12 +85,6 @@ var listboxStyles = css.create({
86
85
  description: { color: "_textMuted" },
87
86
  check: { flexShrink: 0 },
88
87
  hidden: { visibility: "hidden" },
89
- progress: {
90
- position: "absolute",
91
- top: 0,
92
- insetInline: 0,
93
- zIndex: 1
94
- },
95
88
  skeleton: {
96
89
  display: "block",
97
90
  height: "1em",
@@ -249,14 +242,12 @@ var ListboxView = /*#__PURE__*/ _component((_props, _ctx) => {
249
242
  }, propsCheck.value), null)]);
250
243
  }
251
244
  function renderBody() {
245
+ if (indicator.value) return Array.from({ length: SKELETON_ROWS }, (_, index) => createVNode(Skeleton, mergeJsxProps({ "key": index }, mergePartProps({
246
+ sx: [listboxStyles.skeleton, listboxPositionStyles.width(`${85 - index * 15}%`)],
247
+ delay: index * 120
248
+ }, propsSkeleton.value)), null));
252
249
  if (rows.value.length === 0) {
253
- if (loading.value) {
254
- if (!indicator.value) return null;
255
- return Array.from({ length: SKELETON_ROWS }, (_, index) => createVNode(Skeleton, mergeJsxProps({ "key": index }, mergePartProps({
256
- sx: [listboxStyles.skeleton, listboxPositionStyles.width(`${85 - index * 15}%`)],
257
- delay: index * 120
258
- }, propsSkeleton.value)), null));
259
- }
250
+ if (loading.value) return null;
260
251
  return empty.value ?? createVNode(Empty, mergePartProps({
261
252
  size: "sm",
262
253
  icon: false,
@@ -296,10 +287,7 @@ var ListboxView = /*#__PURE__*/ _component((_props, _ctx) => {
296
287
  "aria-multiselectable": multiple.value || void 0,
297
288
  "aria-busy": loading.value || void 0
298
289
  });
299
- const body = [indicator.value && rows.value.length > 0 && createVNode("div", mergeJsxProps({ "aria-hidden": "true" }, css.attrs(listboxStyles.progress)), [createVNode(Progress, {
300
- "size": "xs",
301
- "aria-label": lang.loading
302
- }, null)]), renderBody()];
290
+ const body = renderBody();
303
291
  if (!own) return createVNode("div", elementProps(props), [body]);
304
292
  return createVNode(ScrollArea, {
305
293
  "focusable": false,
@@ -1 +1 @@
1
- {"version":3,"file":"Listbox.js","names":["_isVNode","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps","_Fragment","Text","_vtp_bindable","_vtp_childrenProp"],"sources":["../../src/input/Listbox.tsx"],"sourcesContent":["/**\n * 列表框(role=\"listbox\"):在一列选项里单选或多选。数据驱动,始终虚拟滚动(上万个选项也只渲染看得见的几十个):\n *\n * <Listbox items={cities} _value={city} aria-label=\"城市\" />\n * <Listbox items={tags} selectionMode=\"multiple\" _value={picked}>{(tag) => <Badge>{tag.label}</Badge>}</Listbox>\n * <Listbox items={users} toOption={(user) => ({ value: user.id, label: user.name })} _value={id} aria-label=\"成员\" />\n *\n * 选项是 { value, label, disabled?, description?, icon?, group? }(ListboxOption);别的形状的数据用 toOption 转换,\n * 自定义内容的渲染函数拿到的仍是原始数据。\n * 焦点在列表上,高亮项经 aria-activedescendant 告诉读屏:↑↓、Home / End、Page Up / Down 移动,\n * Enter / 空格选中,按字母跳到以它开头的项。状态与键盘见 list-state.ts;Select、Combobox 用同一个 ListboxView 渲染。\n *\n * 虚拟滚动(useVirtualizer):高亮项总在渲染范围里(aria-activedescendant 才指得到),键盘移动时滚进可见范围;\n * 选项带 aria-setsize / aria-posinset,读屏知道总数与位置。分组标题只是视觉上的一行(各行独立定位,没法用\n * role=\"group\" 包住组里的选项),组名经 aria-describedby 交给组里的每个选项。\n * loading:没有选项时显示骨架行;已有选项(刷新中)时顶部显示一条进度条;列表带 aria-busy。\n *\n * 部件:Root(div,role=\"listbox\")、GroupLabel、Option(div,role=\"option\")、Check(已选标记)、Skeleton(加载中的占位行)。\n */\nimport { Check } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, withMemo, type VNode, type VNodeChild } from 'vue';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { useLang } from '../config/context';\nimport { Empty, type EmptyProps } from '../feedback/Empty';\nimport { Progress } from '../feedback/Progress';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport { itemStyles } from '../utils/style/item-styles';\nimport {\n defaultLabelledBy,\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { selectedValues, type SelectionProps } from '../utils/data/selection';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport { defaultRangeExtractor } from '../utils/virtual/virtualizer';\nimport { useFieldControl } from '../form/Field';\nimport {\n handleListKeydown,\n useListState,\n type ListboxOption,\n type ListStateEntry,\n type ListState,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\n\n/** 选项的渲染状态:渲染函数据此画高亮、已选的样子 */\nexport interface OptionRenderState {\n /** 已选 */\n selected: boolean;\n /** 高亮(键盘或指针所在) */\n active: boolean;\n}\n\n/** 选项的部件属性:可以按选项(items 里的原始数据)计算 */\ntype OptionProps<T> =\n | PartProps<'div', 'role' | 'id' | 'aria-selected' | 'aria-disabled'>\n | ((item: T, state: OptionRenderState) => PartProps<'div'>);\n\n/** ListboxView 的属性:Listbox、Select、Combobox 共用的渲染层 */\nexport interface ListboxViewProps<T> extends PartProps<'div', 'role'> {\n /** 列表状态(useListState) */\n list: ListState<T>;\n /** 列表的 id;选项的 id 是 `${id}-${序号}`(aria-activedescendant 指向它) */\n id: string;\n /** 多选:aria-multiselectable */\n multiple?: boolean;\n /** 滚动容器:Select、Combobox 的弹出层;不给时列表自己滚动(ScrollArea 的浮层滚动条,最高 20em,用 sx 改) */\n scroller?: HTMLElement | null;\n /** 加载中:没有选项时显示骨架行,有选项时顶部显示进度条 */\n loading?: boolean;\n /** 选项的估计高度(px),默认 36;渲染后以实际高度为准 */\n estimateSize?: number;\n /** 自定义选项内容:拿到 items 里的原始数据 */\n renderOption?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有选项时显示的内容;默认是 Empty(文案取语言包) */\n empty?: VNodeChild;\n /** 点选了一项(点击或 Enter,再选已选的那项也会调用):Select、弹层里的列表据此关闭 */\n onPick?: (value: string) => void;\n /** 选项的部件属性 */\n propsOption?: OptionProps<T>;\n /** 分组标题的部件属性 */\n propsGroupLabel?: Partial<TextProps>;\n /** 已选标记的部件属性 */\n propsCheck?: Partial<IconProps>;\n /** 空状态的部件属性 */\n propsEmpty?: Partial<EmptyProps>;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n}\n\n/** 渲染用的一行:分组标题或选项 */\ntype Row<T> =\n | { kind: 'group'; key: string; label: string; id: string }\n | {\n kind: 'option';\n key: string;\n option: ListStateEntry<T>;\n index: number;\n groupId: string | undefined;\n };\n\n/** 加载中显示几行骨架 */\nconst SKELETON_ROWS = 4;\n\n/** Listbox 的样式:root、scroll、sizer、row、groupLabel、content 等 */\nexport const listboxStyles = css.create({\n root: { position: 'relative', padding: '_xs', lineHeight: '_normal', outlineStyle: 'none' },\n // 自己是滚动容器时(单独使用的 Listbox):限制高度,滚动不传给页面\n scroll: { maxHeight: '20em', overflowY: 'auto', overscrollBehavior: 'contain' },\n sizer: { position: 'relative', width: '_full' },\n row: { position: 'absolute', top: 0, insetInline: 0 },\n groupLabel: {\n display: 'block',\n paddingTop: '_sm',\n paddingBottom: '_xs',\n paddingInline: '_sm',\n fontWeight: '_semibold',\n },\n content: { display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0 },\n description: { color: '_textMuted' },\n check: { flexShrink: 0 },\n hidden: { visibility: 'hidden' },\n // 刷新中的进度条贴在列表顶部,不占位\n progress: { position: 'absolute', top: 0, insetInline: 0, zIndex: 1 },\n skeleton: { display: 'block', height: '1em', marginBlock: '0.625em', marginInline: '_sm' },\n});\n\n// 行的位置是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Listbox 的样式:y、height、width */\nexport const listboxPositionStyles = css.create({\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n height: (px: number) => ({ height: px }),\n width: (percent: string) => ({ width: percent }),\n});\n\n/** 列表的渲染层:虚拟滚动的选项、分组标题、已选标记、空状态与加载中;不管焦点,指针悬停高亮、点击选中 */\nexport const ListboxView = _component(\n <T,>({\n list,\n id,\n multiple = false,\n scroller,\n loading = false,\n estimateSize = 36,\n renderOption,\n empty,\n onPick,\n propsOption,\n propsGroupLabel,\n propsCheck,\n propsEmpty,\n propsSkeleton,\n ...rest\n }: ListboxViewProps<T>) => {\n const lang = useLang();\n // 骨架、进度条防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let sizer = _state.raw<HTMLElement | null>(null);\n\n /** 选项按分组排好后展开成行:每组前插一行标题 */\n const rows = _derived.by<Row<T>[]>(() => {\n const result: Row<T>[] = [];\n let group: string | undefined;\n let headerId: string | undefined;\n list.options.forEach((option, index) => {\n if (option.group != null && option.group !== group) {\n headerId = `${id}-group-${result.length}`;\n result.push({\n kind: 'group',\n key: `group:${option.group}`,\n label: option.group,\n id: headerId,\n });\n }\n group = option.group;\n result.push({\n kind: 'option',\n key: option.value,\n option,\n index,\n groupId: option.group != null ? headerId : undefined,\n });\n });\n return result;\n });\n\n /** 值 → 行号:高亮项的行号一次查到,不在每次渲染时扫一遍 */\n const rowOf = _derived.by(() => {\n const map = new Map<string, number>();\n rows.forEach((row, index) => {\n if (row.kind === 'option') map.set(row.option.value, index);\n });\n return map;\n });\n const activeRow = () => (list.active === undefined ? -1 : (rowOf.get(list.active) ?? -1));\n\n const virtual = useVirtualizer(() => {\n const current = rows;\n // 读一下高亮项:它变了要重新算渲染范围(高亮项总要渲染)\n const active = activeRow();\n // 先读出来:每一项都要调用一次估计高度,不要每次都经过 props 的响应式读取\n const size = estimateSize;\n return {\n count: current.length,\n scrollElement: scroller === undefined ? root : scroller,\n list: sizer,\n estimateSize: (index: number) => (current[index]?.kind === 'group' ? 30 : size),\n getItemKey: (index: number) => current[index]!.key,\n overscan: 6,\n rangeExtractor: (range) => {\n const indices = defaultRangeExtractor(range);\n if (active >= 0 && !indices.includes(active)) {\n indices.push(active);\n indices.sort((a, b) => a - b);\n }\n return indices;\n },\n };\n });\n\n // 高亮项变了(键盘移动)时滚进可见范围;已经完整可见就不动(指针悬停触发的不会滚)。\n // 高亮的是第一项、上面是组名时滚到最顶上:组名不被挡住(搜索过滤后高亮第一个匹配的)\n watch(\n () => list.active,\n () => {\n const index = activeRow();\n if (index < 0) return;\n virtual.scrollToIndex(index === 1 && rows[0]?.kind === 'group' ? 0 : index, {\n align: 'auto',\n });\n },\n { flush: 'post' },\n );\n\n /**\n * 选项行按“会影响它的值”缓存(Vue 的 withMemo,同模板的 v-memo):方向键移动高亮时只重建前后两行,\n * 不再重建全部可见行与其中的图标。自定义了 renderOption 或按项计算的 propsOption 时不缓存:\n * 使用方的函数可能读别的状态,缓存了会显示旧内容\n */\n const memoCache: VNode[] = [];\n function optionRow(row: Extract<Row<T>, { kind: 'option' }>) {\n if (renderOption || typeof propsOption === 'function') return renderOptionRow(row);\n const { option } = row;\n return withMemo(\n [\n option,\n row.index,\n row.groupId,\n list.isSelected(option.value),\n list.active === option.value,\n list.options.length,\n propsOption,\n propsCheck,\n ],\n () => renderOptionRow(row),\n memoCache,\n row.index,\n );\n }\n\n function renderOptionRow(row: Extract<Row<T>, { kind: 'option' }>) {\n const { option, index } = row;\n const state = {\n selected: list.isSelected(option.value),\n active: list.active === option.value,\n };\n const own = typeof propsOption === 'function' ? propsOption(option.item, state) : propsOption;\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n itemStyles.row,\n state.active && itemStyles.highlighted,\n state.selected && itemStyles.selected,\n option.disabled && itemStyles.disabled,\n ],\n },\n own,\n {\n id: `${id}-${index}`,\n role: 'option',\n 'aria-selected': state.selected,\n 'aria-disabled': option.disabled || undefined,\n 'aria-setsize': list.options.length,\n 'aria-posinset': index + 1,\n 'aria-describedby': row.groupId,\n // 焦点留在列表、触发按钮或输入框上\n onPointerdown: (event: PointerEvent) => event.preventDefault(),\n onPointermove: () => {\n if (!option.disabled && list.active !== option.value) list.setActive(option.value);\n },\n onClick: () => {\n if (option.disabled) return;\n list.select(option.value);\n onPick?.(option.value);\n },\n },\n )}\n data-state={state.selected ? 'checked' : 'unchecked'}\n data-active={state.active || undefined}\n data-disabled={option.disabled || undefined}\n >\n {renderOption ? (\n renderOption(option.item, state)\n ) : (\n <>\n {option.icon && <Icon icon={option.icon} />}\n <span {...css.attrs(listboxStyles.content)}>\n <span {...css.attrs(itemStyles.label)}>{option.label}</span>\n {option.description && (\n <Text size=\"sm\" sx={listboxStyles.description}>\n {option.description}\n </Text>\n )}\n </span>\n </>\n )}\n <Icon\n {...mergePartProps<IconProps>(\n { icon: Check, sx: [listboxStyles.check, !state.selected && listboxStyles.hidden] },\n propsCheck,\n )}\n />\n </div>\n );\n }\n\n function renderBody() {\n if (rows.length === 0) {\n if (loading) {\n if (!indicator.value) return null;\n return Array.from({ length: SKELETON_ROWS }, (_, index) => (\n <Skeleton\n key={index}\n {...mergePartProps<SkeletonProps>(\n {\n sx: [listboxStyles.skeleton, listboxPositionStyles.width(`${85 - index * 15}%`)],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n ));\n }\n return (\n empty ?? (\n <Empty\n {...mergePartProps<EmptyProps>(\n { size: 'sm', icon: false, description: lang.selectEmpty },\n propsEmpty,\n )}\n />\n )\n );\n }\n return (\n <div\n ref={_toRef(sizer)}\n role=\"presentation\"\n {...css.attrs(listboxStyles.sizer, listboxPositionStyles.height(virtual.total))}\n >\n {virtual.items.map((item) => {\n const row = rows[item.index]!;\n return (\n <div\n key={item.key}\n ref={virtual.measure}\n data-index={item.index}\n role=\"presentation\"\n {...css.attrs(listboxStyles.row, listboxPositionStyles.y(item.start))}\n >\n {row.kind === 'group' ? (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', size: 'sm', sx: listboxStyles.groupLabel },\n propsGroupLabel,\n // 只是视觉上的标题:组名经各选项的 aria-describedby 念出\n { id: row.id, 'aria-hidden': true },\n )}\n >\n {row.label}\n </Text>\n ) : (\n optionRow(row)\n )}\n </div>\n );\n })}\n </div>\n );\n }\n\n return () => {\n const own = scroller === undefined;\n // 先不编译 sx:自己滚动时这些属性交给 ScrollArea 的视口,和视口自己的样式一次合并\n const props = mergeProps<'div'>(\n { sx: [listboxStyles.root, own && listboxStyles.scroll, focusRingStyles.visible] },\n rest,\n {\n ref: _toRef(root),\n id,\n role: 'listbox',\n 'aria-multiselectable': multiple || undefined,\n 'aria-busy': loading || undefined,\n },\n );\n const body = [\n indicator.value && rows.length > 0 && (\n <div aria-hidden=\"true\" {...css.attrs(listboxStyles.progress)}>\n <Progress size=\"xs\" aria-label={lang.loading} />\n </div>\n ),\n renderBody(),\n ];\n // 别处滚动(Select、Combobox 的弹出层):只渲染列表本身\n if (!own) return <div {...elementProps(props)}>{body}</div>;\n // 自己滚动:列表元素就是 ScrollArea 的视口,焦点与 aria-activedescendant 仍在它上面,滚动条换成浮层的\n return (\n <ScrollArea focusable={false} propsViewport={props as PartProps<'div'>}>\n {body}\n </ScrollArea>\n );\n };\n },\n { name: 'ListboxView' },\n);\n\n/** 高亮项的 id(给 aria-activedescendant);没有高亮时 undefined */\nexport function activeOptionId<T>(list: ListState<T>, id: string): string | undefined {\n const index = list.active === undefined ? -1 : list.indexOf(list.active);\n return index < 0 ? undefined : `${id}-${index}`;\n}\n\n/** 列表框共有的属性;未声明的属性落到 role=\"listbox\" 的 div 上 */\nexport interface ListboxBaseProps<T> extends Omit<\n ListboxViewProps<T>,\n 'list' | 'id' | 'multiple' | 'renderOption'\n> {\n /** 列表的 id;在 Field 里时默认用 Field 给的 */\n id?: string;\n /** 数据:ListboxOption、字符串,或任意数据加 toOption */\n items: readonly T[];\n /** 必须有选中项:单选时再点已选项不取消;在必填的 Field 里默认开启 */\n required?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 自定义选项内容:`{(item, { selected, active }) => …}`,拿到 items 里的原始数据 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n}\n\n/** 单选或多选;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type ListboxProps<T = ListboxOption> = ListboxBaseProps<T> &\n SelectionProps &\n OptionMapping<T>;\n\n/** 列表框:焦点在列表上,方向键移动高亮、Enter / 空格选中 */\nexport const Listbox = _component(\n <T,>({\n items,\n toOption,\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n required,\n disabled,\n id,\n children,\n onPick,\n ...rest\n }: ListboxProps<T>) => {\n const generatedId = useId();\n const control = useFieldControl(() => ({ id, disabled, required }));\n const list = useListState({\n items: () => items,\n toOption: (item) => toListboxOption(item, toOption),\n selected: () => selectedValues(value),\n mode: () => selectionMode,\n required: () => control().required,\n onChange: (next) => (value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null)),\n });\n\n function onKeydown(event: KeyboardEvent) {\n if (control().disabled) return;\n if (handleListKeydown(list, event, { space: true, typeahead: true }, onPick)) {\n event.preventDefault();\n }\n }\n\n return () => {\n const state = control();\n const prefix = state.id ?? generatedId;\n return (\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(\n {\n list,\n id: prefix,\n multiple: selectionMode === 'multiple',\n renderOption: children,\n onPick,\n },\n rest as Partial<ListboxViewProps<T>>,\n {\n id: prefix,\n tabindex: state.disabled ? -1 : 0,\n 'aria-labelledby': defaultLabelledBy(rest, state.labelledBy),\n 'aria-describedby': state.describedBy,\n 'aria-disabled': state.disabled || undefined,\n 'aria-required': state.required || undefined,\n 'aria-activedescendant': activeOptionId(list, prefix),\n onFocus: () => {\n if (list.active === undefined) list.activateDefault();\n },\n onKeydown,\n },\n )}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"Listbox.js","names":["_isVNode","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps","_Fragment","Text","_vtp_bindable","_vtp_childrenProp"],"sources":["../../src/input/Listbox.tsx"],"sourcesContent":["/**\n * 列表框(role=\"listbox\"):在一列选项里单选或多选。数据驱动,始终虚拟滚动(上万个选项也只渲染看得见的几十个):\n *\n * <Listbox items={cities} _value={city} aria-label=\"城市\" />\n * <Listbox items={tags} selectionMode=\"multiple\" _value={picked}>{(tag) => <Badge>{tag.label}</Badge>}</Listbox>\n * <Listbox items={users} toOption={(user) => ({ value: user.id, label: user.name })} _value={id} aria-label=\"成员\" />\n *\n * 选项是 { value, label, disabled?, description?, icon?, group? }(ListboxOption);别的形状的数据用 toOption 转换,\n * 自定义内容的渲染函数拿到的仍是原始数据。\n * 焦点在列表上,高亮项经 aria-activedescendant 告诉读屏:↑↓、Home / End、Page Up / Down 移动,\n * Enter / 空格选中,按字母跳到以它开头的项。状态与键盘见 list-state.ts;Select、Combobox 用同一个 ListboxView 渲染。\n *\n * 虚拟滚动(useVirtualizer):高亮项总在渲染范围里(aria-activedescendant 才指得到),键盘移动时滚进可见范围;\n * 选项带 aria-setsize / aria-posinset,读屏知道总数与位置。分组标题只是视觉上的一行(各行独立定位,没法用\n * role=\"group\" 包住组里的选项),组名经 aria-describedby 交给组里的每个选项。\n * loading:选项换成骨架行(首次加载与刷新一样,不再另加进度条);列表带 aria-busy。\n *\n * 部件:Root(div,role=\"listbox\")、GroupLabel、Option(div,role=\"option\")、Check(已选标记)、Skeleton(加载中的占位行)。\n */\nimport { Check } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, withMemo, type VNode, type VNodeChild } from 'vue';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { useLang } from '../config/context';\nimport { Empty, type EmptyProps } from '../feedback/Empty';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport { itemStyles } from '../utils/style/item-styles';\nimport {\n defaultLabelledBy,\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { selectedValues, type SelectionProps } from '../utils/data/selection';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport { defaultRangeExtractor } from '../utils/virtual/virtualizer';\nimport { useFieldControl } from '../form/Field';\nimport {\n handleListKeydown,\n useListState,\n type ListboxOption,\n type ListStateEntry,\n type ListState,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\n\n/** 选项的渲染状态:渲染函数据此画高亮、已选的样子 */\nexport interface OptionRenderState {\n /** 已选 */\n selected: boolean;\n /** 高亮(键盘或指针所在) */\n active: boolean;\n}\n\n/** 选项的部件属性:可以按选项(items 里的原始数据)计算 */\ntype OptionProps<T> =\n | PartProps<'div', 'role' | 'id' | 'aria-selected' | 'aria-disabled'>\n | ((item: T, state: OptionRenderState) => PartProps<'div'>);\n\n/** ListboxView 的属性:Listbox、Select、Combobox 共用的渲染层 */\nexport interface ListboxViewProps<T> extends PartProps<'div', 'role'> {\n /** 列表状态(useListState) */\n list: ListState<T>;\n /** 列表的 id;选项的 id 是 `${id}-${序号}`(aria-activedescendant 指向它) */\n id: string;\n /** 多选:aria-multiselectable */\n multiple?: boolean;\n /** 滚动容器:Select、Combobox 的弹出层;不给时列表自己滚动(ScrollArea 的浮层滚动条,最高 20em,用 sx 改) */\n scroller?: HTMLElement | null;\n /** 加载中:选项换成骨架行 */\n loading?: boolean;\n /** 选项的估计高度(px),默认 36;渲染后以实际高度为准 */\n estimateSize?: number;\n /** 自定义选项内容:拿到 items 里的原始数据 */\n renderOption?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有选项时显示的内容;默认是 Empty(文案取语言包) */\n empty?: VNodeChild;\n /** 点选了一项(点击或 Enter,再选已选的那项也会调用):Select、弹层里的列表据此关闭 */\n onPick?: (value: string) => void;\n /** 选项的部件属性 */\n propsOption?: OptionProps<T>;\n /** 分组标题的部件属性 */\n propsGroupLabel?: Partial<TextProps>;\n /** 已选标记的部件属性 */\n propsCheck?: Partial<IconProps>;\n /** 空状态的部件属性 */\n propsEmpty?: Partial<EmptyProps>;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n}\n\n/** 渲染用的一行:分组标题或选项 */\ntype Row<T> =\n | { kind: 'group'; key: string; label: string; id: string }\n | {\n kind: 'option';\n key: string;\n option: ListStateEntry<T>;\n index: number;\n groupId: string | undefined;\n };\n\n/** 加载中显示几行骨架 */\nconst SKELETON_ROWS = 4;\n\n/** Listbox 的样式:root、scroll、sizer、row、groupLabel、content 等 */\nexport const listboxStyles = css.create({\n root: { position: 'relative', padding: '_xs', lineHeight: '_normal', outlineStyle: 'none' },\n // 自己是滚动容器时(单独使用的 Listbox):限制高度,滚动不传给页面\n scroll: { maxHeight: '20em', overflowY: 'auto', overscrollBehavior: 'contain' },\n sizer: { position: 'relative', width: '_full' },\n row: { position: 'absolute', top: 0, insetInline: 0 },\n groupLabel: {\n display: 'block',\n paddingTop: '_sm',\n paddingBottom: '_xs',\n paddingInline: '_sm',\n fontWeight: '_semibold',\n },\n content: { display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0 },\n description: { color: '_textMuted' },\n check: { flexShrink: 0 },\n hidden: { visibility: 'hidden' },\n skeleton: { display: 'block', height: '1em', marginBlock: '0.625em', marginInline: '_sm' },\n});\n\n// 行的位置是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Listbox 的样式:y、height、width */\nexport const listboxPositionStyles = css.create({\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n height: (px: number) => ({ height: px }),\n width: (percent: string) => ({ width: percent }),\n});\n\n/** 列表的渲染层:虚拟滚动的选项、分组标题、已选标记、空状态与加载中;不管焦点,指针悬停高亮、点击选中 */\nexport const ListboxView = _component(\n <T,>({\n list,\n id,\n multiple = false,\n scroller,\n loading = false,\n estimateSize = 36,\n renderOption,\n empty,\n onPick,\n propsOption,\n propsGroupLabel,\n propsCheck,\n propsEmpty,\n propsSkeleton,\n ...rest\n }: ListboxViewProps<T>) => {\n const lang = useLang();\n // 骨架防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let sizer = _state.raw<HTMLElement | null>(null);\n\n /** 选项按分组排好后展开成行:每组前插一行标题 */\n const rows = _derived.by<Row<T>[]>(() => {\n const result: Row<T>[] = [];\n let group: string | undefined;\n let headerId: string | undefined;\n list.options.forEach((option, index) => {\n if (option.group != null && option.group !== group) {\n headerId = `${id}-group-${result.length}`;\n result.push({\n kind: 'group',\n key: `group:${option.group}`,\n label: option.group,\n id: headerId,\n });\n }\n group = option.group;\n result.push({\n kind: 'option',\n key: option.value,\n option,\n index,\n groupId: option.group != null ? headerId : undefined,\n });\n });\n return result;\n });\n\n /** 值 → 行号:高亮项的行号一次查到,不在每次渲染时扫一遍 */\n const rowOf = _derived.by(() => {\n const map = new Map<string, number>();\n rows.forEach((row, index) => {\n if (row.kind === 'option') map.set(row.option.value, index);\n });\n return map;\n });\n const activeRow = () => (list.active === undefined ? -1 : (rowOf.get(list.active) ?? -1));\n\n const virtual = useVirtualizer(() => {\n const current = rows;\n // 读一下高亮项:它变了要重新算渲染范围(高亮项总要渲染)\n const active = activeRow();\n // 先读出来:每一项都要调用一次估计高度,不要每次都经过 props 的响应式读取\n const size = estimateSize;\n return {\n count: current.length,\n scrollElement: scroller === undefined ? root : scroller,\n list: sizer,\n estimateSize: (index: number) => (current[index]?.kind === 'group' ? 30 : size),\n getItemKey: (index: number) => current[index]!.key,\n overscan: 6,\n rangeExtractor: (range) => {\n const indices = defaultRangeExtractor(range);\n if (active >= 0 && !indices.includes(active)) {\n indices.push(active);\n indices.sort((a, b) => a - b);\n }\n return indices;\n },\n };\n });\n\n // 高亮项变了(键盘移动)时滚进可见范围;已经完整可见就不动(指针悬停触发的不会滚)。\n // 高亮的是第一项、上面是组名时滚到最顶上:组名不被挡住(搜索过滤后高亮第一个匹配的)\n watch(\n () => list.active,\n () => {\n const index = activeRow();\n if (index < 0) return;\n virtual.scrollToIndex(index === 1 && rows[0]?.kind === 'group' ? 0 : index, {\n align: 'auto',\n });\n },\n { flush: 'post' },\n );\n\n /**\n * 选项行按“会影响它的值”缓存(Vue 的 withMemo,同模板的 v-memo):方向键移动高亮时只重建前后两行,\n * 不再重建全部可见行与其中的图标。自定义了 renderOption 或按项计算的 propsOption 时不缓存:\n * 使用方的函数可能读别的状态,缓存了会显示旧内容\n */\n const memoCache: VNode[] = [];\n function optionRow(row: Extract<Row<T>, { kind: 'option' }>) {\n if (renderOption || typeof propsOption === 'function') return renderOptionRow(row);\n const { option } = row;\n return withMemo(\n [\n option,\n row.index,\n row.groupId,\n list.isSelected(option.value),\n list.active === option.value,\n list.options.length,\n propsOption,\n propsCheck,\n ],\n () => renderOptionRow(row),\n memoCache,\n row.index,\n );\n }\n\n function renderOptionRow(row: Extract<Row<T>, { kind: 'option' }>) {\n const { option, index } = row;\n const state = {\n selected: list.isSelected(option.value),\n active: list.active === option.value,\n };\n const own = typeof propsOption === 'function' ? propsOption(option.item, state) : propsOption;\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n itemStyles.row,\n state.active && itemStyles.highlighted,\n state.selected && itemStyles.selected,\n option.disabled && itemStyles.disabled,\n ],\n },\n own,\n {\n id: `${id}-${index}`,\n role: 'option',\n 'aria-selected': state.selected,\n 'aria-disabled': option.disabled || undefined,\n 'aria-setsize': list.options.length,\n 'aria-posinset': index + 1,\n 'aria-describedby': row.groupId,\n // 焦点留在列表、触发按钮或输入框上\n onPointerdown: (event: PointerEvent) => event.preventDefault(),\n onPointermove: () => {\n if (!option.disabled && list.active !== option.value) list.setActive(option.value);\n },\n onClick: () => {\n if (option.disabled) return;\n list.select(option.value);\n onPick?.(option.value);\n },\n },\n )}\n data-state={state.selected ? 'checked' : 'unchecked'}\n data-active={state.active || undefined}\n data-disabled={option.disabled || undefined}\n >\n {renderOption ? (\n renderOption(option.item, state)\n ) : (\n <>\n {option.icon && <Icon icon={option.icon} />}\n <span {...css.attrs(listboxStyles.content)}>\n <span {...css.attrs(itemStyles.label)}>{option.label}</span>\n {option.description && (\n <Text size=\"sm\" sx={listboxStyles.description}>\n {option.description}\n </Text>\n )}\n </span>\n </>\n )}\n <Icon\n {...mergePartProps<IconProps>(\n { icon: Check, sx: [listboxStyles.check, !state.selected && listboxStyles.hidden] },\n propsCheck,\n )}\n />\n </div>\n );\n }\n\n function renderBody() {\n if (indicator.value) {\n return Array.from({ length: SKELETON_ROWS }, (_, index) => (\n <Skeleton\n key={index}\n {...mergePartProps<SkeletonProps>(\n {\n sx: [listboxStyles.skeleton, listboxPositionStyles.width(`${85 - index * 15}%`)],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n ));\n }\n if (rows.length === 0) {\n // 加载刚开始、骨架还没到显示的时候(防闪烁):先空着\n if (loading) return null;\n return (\n empty ?? (\n <Empty\n {...mergePartProps<EmptyProps>(\n { size: 'sm', icon: false, description: lang.selectEmpty },\n propsEmpty,\n )}\n />\n )\n );\n }\n return (\n <div\n ref={_toRef(sizer)}\n role=\"presentation\"\n {...css.attrs(listboxStyles.sizer, listboxPositionStyles.height(virtual.total))}\n >\n {virtual.items.map((item) => {\n const row = rows[item.index]!;\n return (\n <div\n key={item.key}\n ref={virtual.measure}\n data-index={item.index}\n role=\"presentation\"\n {...css.attrs(listboxStyles.row, listboxPositionStyles.y(item.start))}\n >\n {row.kind === 'group' ? (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', size: 'sm', sx: listboxStyles.groupLabel },\n propsGroupLabel,\n // 只是视觉上的标题:组名经各选项的 aria-describedby 念出\n { id: row.id, 'aria-hidden': true },\n )}\n >\n {row.label}\n </Text>\n ) : (\n optionRow(row)\n )}\n </div>\n );\n })}\n </div>\n );\n }\n\n return () => {\n const own = scroller === undefined;\n // 先不编译 sx:自己滚动时这些属性交给 ScrollArea 的视口,和视口自己的样式一次合并\n const props = mergeProps<'div'>(\n { sx: [listboxStyles.root, own && listboxStyles.scroll, focusRingStyles.visible] },\n rest,\n {\n ref: _toRef(root),\n id,\n role: 'listbox',\n 'aria-multiselectable': multiple || undefined,\n 'aria-busy': loading || undefined,\n },\n );\n const body = renderBody();\n // 别处滚动(Select、Combobox 的弹出层):只渲染列表本身\n if (!own) return <div {...elementProps(props)}>{body}</div>;\n // 自己滚动:列表元素就是 ScrollArea 的视口,焦点与 aria-activedescendant 仍在它上面,滚动条换成浮层的\n return (\n <ScrollArea focusable={false} propsViewport={props as PartProps<'div'>}>\n {body}\n </ScrollArea>\n );\n };\n },\n { name: 'ListboxView' },\n);\n\n/** 高亮项的 id(给 aria-activedescendant);没有高亮时 undefined */\nexport function activeOptionId<T>(list: ListState<T>, id: string): string | undefined {\n const index = list.active === undefined ? -1 : list.indexOf(list.active);\n return index < 0 ? undefined : `${id}-${index}`;\n}\n\n/** 列表框共有的属性;未声明的属性落到 role=\"listbox\" 的 div 上 */\nexport interface ListboxBaseProps<T> extends Omit<\n ListboxViewProps<T>,\n 'list' | 'id' | 'multiple' | 'renderOption'\n> {\n /** 列表的 id;在 Field 里时默认用 Field 给的 */\n id?: string;\n /** 数据:ListboxOption、字符串,或任意数据加 toOption */\n items: readonly T[];\n /** 必须有选中项:单选时再点已选项不取消;在必填的 Field 里默认开启 */\n required?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 自定义选项内容:`{(item, { selected, active }) => …}`,拿到 items 里的原始数据 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n}\n\n/** 单选或多选;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type ListboxProps<T = ListboxOption> = ListboxBaseProps<T> &\n SelectionProps &\n OptionMapping<T>;\n\n/** 列表框:焦点在列表上,方向键移动高亮、Enter / 空格选中 */\nexport const Listbox = _component(\n <T,>({\n items,\n toOption,\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n required,\n disabled,\n id,\n children,\n onPick,\n ...rest\n }: ListboxProps<T>) => {\n const generatedId = useId();\n const control = useFieldControl(() => ({ id, disabled, required }));\n const list = useListState({\n items: () => items,\n toOption: (item) => toListboxOption(item, toOption),\n selected: () => selectedValues(value),\n mode: () => selectionMode,\n required: () => control().required,\n onChange: (next) => (value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null)),\n });\n\n function onKeydown(event: KeyboardEvent) {\n if (control().disabled) return;\n if (handleListKeydown(list, event, { space: true, typeahead: true }, onPick)) {\n event.preventDefault();\n }\n }\n\n return () => {\n const state = control();\n const prefix = state.id ?? generatedId;\n return (\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(\n {\n list,\n id: prefix,\n multiple: selectionMode === 'multiple',\n renderOption: children,\n onPick,\n },\n rest as Partial<ListboxViewProps<T>>,\n {\n id: prefix,\n tabindex: state.disabled ? -1 : 0,\n 'aria-labelledby': defaultLabelledBy(rest, state.labelledBy),\n 'aria-describedby': state.describedBy,\n 'aria-disabled': state.disabled || undefined,\n 'aria-required': state.required || undefined,\n 'aria-activedescendant': activeOptionId(list, prefix),\n onFocus: () => {\n if (list.active === undefined) list.activateDefault();\n },\n onKeydown,\n },\n )}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -58,7 +58,7 @@ export interface SelectBaseProps<T> extends PartProps<'button', SelectManaged>,
58
58
  children?: (item: T, state: OptionRenderState) => VNodeChild;
59
59
  /** 自定义触发框里显示的内容(已选的选项);默认单选显示文字、多选显示标签 */
60
60
  renderValue?: (selected: readonly T[]) => VNodeChild;
61
- /** 正在取选项(异步加载):没有选项时列表里是骨架行,已有选项时顶部显示进度条 */
61
+ /** 正在取选项(异步加载):列表里换成骨架行 */
62
62
  loading?: boolean;
63
63
  /** 没有选项时列表里显示的内容 */
64
64
  empty?: VNodeChild;