@zerodep-dev/vue-tsx-pro-ui 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/AreaChart.d.ts +1 -1
- package/dist/charts/AreaChart.js +6 -48
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +6 -49
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.d.ts +7 -2
- package/dist/charts/ChordChart.js +151 -60
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.d.ts +2 -1
- package/dist/charts/ComboChart.js +17 -93
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.d.ts +6 -1
- package/dist/charts/DonutChart.js +134 -51
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.d.ts +22 -10
- package/dist/charts/GraphChart.js +415 -175
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.d.ts +1 -29
- package/dist/charts/LineChart.js +6 -54
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.d.ts +21 -48
- package/dist/charts/MapChart.js +426 -353
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.d.ts +7 -2
- package/dist/charts/SankeyChart.js +164 -76
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.d.ts +7 -1
- package/dist/charts/ScatterChart.js +45 -30
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.d.ts +8 -1
- package/dist/charts/TimelineChart.js +141 -89
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.d.ts +6 -0
- package/dist/charts/TreemapChart.js +174 -58
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.d.ts +6 -7
- package/dist/charts/WaterfallChart.js +46 -51
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.d.ts +44 -35
- package/dist/charts/XYChart.js +493 -281
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/axis-ticks.d.ts +1 -1
- package/dist/charts/axis-ticks.js +4 -5
- package/dist/charts/axis-ticks.js.map +1 -1
- package/dist/charts/flow.d.ts +4 -11
- package/dist/charts/flow.js +4 -4
- package/dist/charts/flow.js.map +1 -1
- package/dist/charts/frame.d.ts +6 -30
- package/dist/charts/frame.js +6 -99
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/geo/projection.d.ts +45 -0
- package/dist/charts/geo/projection.js +201 -0
- package/dist/charts/geo/projection.js.map +1 -0
- package/dist/charts/geo/topojson.d.ts +50 -0
- package/dist/charts/geo/topojson.js +119 -0
- package/dist/charts/geo/topojson.js.map +1 -0
- package/dist/charts/index.d.ts +21 -9
- package/dist/charts/index.js +19 -8
- package/dist/charts/label-layout.js.map +1 -1
- package/dist/charts/layout/chord.d.ts +44 -0
- package/dist/charts/layout/chord.js +45 -0
- package/dist/charts/layout/chord.js.map +1 -0
- package/dist/charts/layout/graph.d.ts +49 -0
- package/dist/charts/layout/graph.js +268 -0
- package/dist/charts/layout/graph.js.map +1 -0
- package/dist/charts/layout/sankey.d.ts +79 -0
- package/dist/charts/layout/sankey.js +200 -0
- package/dist/charts/layout/sankey.js.map +1 -0
- package/dist/charts/layout/treemap.d.ts +46 -0
- package/dist/charts/layout/treemap.js +152 -0
- package/dist/charts/layout/treemap.js.map +1 -0
- package/dist/charts/marks.d.ts +79 -0
- package/dist/charts/marks.js +231 -0
- package/dist/charts/marks.js.map +1 -0
- package/dist/charts/plot.d.ts +107 -0
- package/dist/charts/plot.js +231 -0
- package/dist/charts/plot.js.map +1 -0
- package/dist/charts/scale.d.ts +29 -0
- package/dist/charts/scale.js +244 -0
- package/dist/charts/scale.js.map +1 -0
- package/dist/charts/shape.d.ts +46 -0
- package/dist/charts/shape.js +131 -0
- package/dist/charts/shape.js.map +1 -0
- package/dist/charts/stack.d.ts +17 -0
- package/dist/charts/stack.js +35 -0
- package/dist/charts/stack.js.map +1 -0
- package/dist/charts/text-size.d.ts +12 -0
- package/dist/charts/text-size.js +32 -0
- package/dist/charts/text-size.js.map +1 -0
- package/dist/display/VirtualList.d.ts +1 -2
- package/dist/display/VirtualList.js +11 -21
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/input/Combobox.d.ts +1 -1
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ListPopover.d.ts +1 -1
- package/dist/input/ListPopover.js.map +1 -1
- package/dist/input/Listbox.d.ts +1 -2
- package/dist/input/Listbox.js +7 -19
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Select.d.ts +1 -1
- package/dist/input/Select.js.map +1 -1
- package/dist/maps/china-provinces.js +35680 -0
- package/dist/maps/china-provinces.js.map +1 -0
- package/dist/maps/china.d.ts +13 -0
- package/dist/maps/china.js +8 -0
- package/dist/maps/china.js.map +1 -0
- package/dist/maps/index.d.ts +9 -0
- package/dist/maps/index.js +3 -0
- package/dist/maps/world-countries.js +22273 -0
- package/dist/maps/world-countries.js.map +1 -0
- package/dist/maps/world.d.ts +13 -0
- package/dist/maps/world.js +8 -0
- package/dist/maps/world.js.map +1 -0
- package/dist/overlay/CommandPalette.d.ts +1 -1
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/detached.js +14 -1
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/QueryTable.js +0 -2
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/table/DataTable.js +1 -1
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.d.ts +0 -3
- package/dist/table/DataTableView.js +32 -26
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/table-styles.d.ts +1 -1
- package/dist/table/table-styles.js +0 -13
- package/dist/table/table-styles.js.map +1 -1
- package/package.json +7 -8
- package/dist/charts/single.d.ts +0 -41
- package/dist/charts/single.js +0 -66
- package/dist/charts/single.js.map +0 -1
|
@@ -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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"}
|
|
@@ -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 /**
|
|
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"}
|
package/dist/input/Listbox.d.ts
CHANGED
|
@@ -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;
|
package/dist/input/Listbox.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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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"}
|
package/dist/input/Select.d.ts
CHANGED
|
@@ -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;
|
package/dist/input/Select.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/input/Select.tsx"],"sourcesContent":["/**\n * 下拉选择(WAI-ARIA 的 select-only combobox):点触发框弹出选项列表,单选或多选。\n *\n * <Field label=\"城市\"><Select items={['北京', '上海', '广州']} _value={city} placeholder=\"选择城市\" /></Field>\n * <Select items={tags} selectionMode=\"multiple\" _value={picked} clearable aria-label=\"标签\" />\n * <Select items={users} toOption={(user) => ({ value: user.id, label: user.name })} _value={id} />\n * <Select items={suppliers} searchable _value={supplier} /> 选项多时:弹出层顶部带搜索框\n *\n * <Select items={tags} selectionMode=\"multiple\" maxTagCount=\"responsive\" creatable onCreate={(text) => tags.push(text)} … />\n *\n * 组合已有的部件:外壳是输入框外壳(controlParts),弹出层用 useAnchoredPopover(与外壳同宽、最高到视口剩余空间),\n * 列表是 ListboxView(状态与键盘见 list-state.ts)。焦点一直在触发按钮上,高亮项经 aria-activedescendant 告诉读屏。\n *\n * 多选时已选的项是触发按钮前面的一排 Tag(不能放进 button 里:按钮里不能有按钮),各带移除按钮。\n * 这排标签只给指针用(aria-hidden、移除按钮不进 Tab 顺序):读屏从触发按钮里念出全部已选内容,\n * 键盘在列表里取消勾选,或在触发按钮(及空的搜索框)里按 Backspace 移除最后一项(同 TagsInput)。\n * maxTagCount 是数字时只显示前几个,'responsive' 时排成一行按宽度放,其余收进“+N”(SelectedTags,TreeSelect 共用)。\n *\n * 键盘:关闭时 ↓ / Enter / 空格打开(↑ 打开并高亮最后一项),单选时直接按字母选中以它开头的项(同原生 select);\n * 打开时 ↑↓、Home / End、Page Up / Down 移动,Enter / 空格选中(单选随后关闭),Esc、Tab 关闭。\n * 写了 name 时用隐藏的 input 随表单提交(多选时每个值一个)。\n *\n * searchable 时弹出层顶部有搜索框:打开后焦点在搜索框里,输入就过滤选项(按显示文字,忽略大小写与重音,或用 filter),\n * ↑↓ 移动、Enter 选中、Esc 关闭并把焦点还给触发按钮。已选的项被过滤掉、或 items 换了(远程搜索)时,\n * 触发框里仍显示它的文字(useSelectedOptions)。\n *\n * 远程选项:给 load 时打开就按搜索词请求,输入停下 300 ms 再请求,只采用最后一次(remote-items.ts);\n * 自己管请求时写 _search={keyword} 拿到搜索词、filter={false} 关掉本地过滤,items 与 loading 自己给。\n *\n * <Select searchable load={(q, signal) => http.get<User[]>('/users', { query: { q }, signal })} toOption={…} _value={owner} />\n *\n * creatable 时自带搜索框,输入的文字没有对应的选项时列表末尾多一项“新建“…””:选中后值就是这段文字,onCreate 报告它。\n *\n * 部件:Root(外壳)、Tags(多选时的那排标签)、Tag、Trigger(button,主元素)、Value、Clear、Chevron、Popover、\n * Search(TextField)、Listbox。\n */\nimport { ChevronDown, Plus, Search } 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 { buttonBaseStyles } from '../base/button-core';\nimport { Icon, type IconProps } from '../base/Icon';\nimport type { TagProps } from '../base/Tag';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang, useTextMatcher } from '../config/context';\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 { selectedValues, type SelectionProps } from '../utils/data/selection';\nimport {\n controlClear,\n controlParts,\n clickOnShell,\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';\nimport { SelectedTags, type SelectedTagsProps } from './SelectedTags';\nimport { TextField, type TextFieldProps } from './TextField';\n\n/** 角色、展开状态与可用性由组件管理 */\nexport type SelectManaged =\n | 'type'\n | 'role'\n | 'value'\n | 'disabled'\n | 'aria-haspopup'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-required'\n | 'aria-describedby';\n\n/** 下拉选择共有的属性;未声明的属性落到触发按钮(主元素)上,外壳用 propsRoot */\nexport interface SelectBaseProps<T>\n extends PartProps<'button', SelectManaged>, ControlOptions, ControlIconOptions, OpenChangeProps {\n /** 数据:ListboxOption、字符串,或任意数据加 toOption;有 load 时是第一次加载之前的选项(编辑页放上已选的那一项) */\n items?: readonly T[];\n /** 没有选择时显示的文字,默认取语言包(请选择) */\n placeholder?: string;\n /** 是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有选择时显示清空按钮 */\n clearable?: boolean;\n /** 弹出层顶部带搜索框,输入过滤选项:选项多(供应商、人员、件号)时用 */\n searchable?: boolean;\n /** 搜索时怎样算匹配;默认显示文字包含输入(忽略大小写与重音)。false 时不在本地过滤(服务端按 search 过滤) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 搜索框里的文字:打开时清空;父级写 _search={x} 拿到它(自己发请求时) */\n _search?: string;\n /** 远程选项:打开时与搜索词停下 300 ms 后调用,结果代替 items;只采用最后一次,被顶掉的请求 signal 中止 */\n load?: OptionsLoader<T>;\n /**\n * 可以新建选项(自带搜索框):输入的文字没有对应的选项时,列表末尾多一项“新建“…””,选中后值就是这段文字。\n * 在 onCreate 里把它加进 items(或存到服务端);不加时触发框里照样显示这段文字\n */\n creatable?: boolean;\n /** 新建了一项(creatable 时):参数是输入的文字,也就是选中的值 */\n onCreate?: (text: string) => void;\n /** 多选时触发框里最多显示几个标签,其余收进“+N”;'responsive' 时排成一行、按宽度放,放不下的收起 */\n maxTagCount?: number | 'responsive';\n /** 表单提交时的 name(用隐藏的 input) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:能聚焦、能看到已选的内容,不能打开、不能清空 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义选项内容:`{(option, { selected, active }) => …}` */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 自定义触发框里显示的内容(已选的选项);默认单选显示文字、多选显示标签 */\n renderValue?: (selected: readonly T[]) => VNodeChild;\n /** 正在取选项(异步加载):没有选项时列表里是骨架行,已有选项时顶部显示进度条 */\n loading?: boolean;\n /** 没有选项时列表里显示的内容 */\n empty?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 已选内容那一层的部件属性 */\n propsValue?: PartProps<'span'>;\n /** 多选时每个标签的部件属性 */\n propsTag?: Partial<TagProps>;\n /** 多选时那排标签的部件属性 */\n propsTags?: Partial<SelectedTagsProps>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 下拉箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n /** 搜索框的部件属性(searchable 时) */\n propsSearch?: Partial<TextFieldProps>;\n}\n\n/** 单选或多选;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type SelectProps<T = ListboxOption> = SelectBaseProps<T> & SelectionProps & OptionMapping<T>;\n\n/** Select 的样式:root、trigger、value、placeholder、chevronOpen 等 */\nexport const selectStyles = css.create({\n // 在 ControlGroup 里也按内容宽(输入框才分剩余宽度)\n root: { cursor: 'pointer', flexGrow: 0 },\n // 去掉按钮默认外观用 buttonBaseStyles;像输入框一样没有自己的焦点环与波纹(焦点环画在外壳上)\n trigger: {\n display: 'flex',\n alignItems: 'center',\n flexGrow: 1,\n minWidth: 0,\n alignSelf: 'stretch',\n textAlign: 'start',\n cursor: 'inherit',\n outlineStyle: 'none',\n },\n value: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: '_xs',\n minWidth: 0,\n paddingBlock: '_xs',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n // 是真正的文字(不是原生 placeholder),要满足 4.5:1:用 textMuted 而不是 textPlaceholder\n placeholder: { color: '_textMuted' },\n // 多选时标签排在触发按钮前面(SelectedTags)占剩余宽度,触发按钮里只剩读屏用的文字\n triggerBehindTags: { flexGrow: 0 },\n chevronOpen: { rotate: '180deg' },\n // 弹出层顶部的搜索框占满宽度\n search: { width: '_full' },\n});\n\n/** creatable 时列表末尾那一项“新建“…””:不是 items 里的数据 */\nclass Creation {\n readonly text: string;\n constructor(text: string) {\n this.text = text;\n }\n}\n\n/** 下拉选择:触发框 + 弹出的选项列表 */\nexport const Select = _component(\n <T,>({\n items = [],\n toOption,\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n _open: open = _bindable(false),\n onOpenChange,\n placeholder,\n placement = 'bottom-start',\n clearable = false,\n searchable = false,\n filter,\n _search: search = _bindable(''),\n load,\n creatable = false,\n onCreate,\n maxTagCount,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n icon,\n children,\n renderValue,\n empty,\n loading = false,\n propsRoot,\n propsIcon,\n propsValue,\n propsTag,\n propsTags,\n propsClear,\n propsChevron,\n propsPopover,\n propsListbox,\n propsSearch,\n ...rest\n }: SelectProps<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 const multiple = () => selectionMode === 'multiple';\n const optionOf = (item: T) => toListboxOption(item, toOption);\n const remote = useRemoteItems({\n load: () => load,\n query: () => search,\n open: () => open,\n items: () => items,\n });\n // 本地过滤:有 load 或 filter={false} 时交给服务端\n const localFilter = () => !load && filter !== false;\n const matches = (item: T, text: string) =>\n filter ? filter(item, text) : textMatcher()(optionOf(item).label, text);\n // creatable 自带搜索框\n const withSearch = () => searchable || creatable;\n // creatable:输入的文字没有同名(或同值)的选项时,列表末尾多一项“新建”\n const creation = () => {\n const text = search.trim();\n if (!creatable || !text) return undefined;\n const exists = remote.items().some((item) => {\n const option = optionOf(item);\n return option.value === text || option.label.toLowerCase() === text.toLowerCase();\n });\n return exists ? undefined : new Creation(text);\n };\n const list = useListState<T | Creation>({\n items: () => {\n const all = remote.items();\n const shown =\n withSearch() && search && localFilter()\n ? all.filter((item) => matches(item, search))\n : all;\n const created = creation();\n return created ? [...shown, created] : shown;\n },\n toOption: (item) =>\n item instanceof Creation\n ? { value: item.text, label: lang.selectCreate(item.text), icon: Plus }\n : optionOf(item),\n selected: () => selectedValues(value),\n mode: () => selectionMode,\n onChange: (next) => {\n const created = creation();\n if (created && next.includes(created.text)) {\n onCreate?.(created.text);\n search = '';\n }\n value = multiple() ? [...next] : (next[0] ?? null);\n },\n // 单选时再点已选的一项不取消(同原生 select);要清空用清除按钮\n required: () => !multiple(),\n });\n // 已选项的文字取自全部 items(过滤掉的、远程搜索换掉的也在)\n const selectedOptions = useSelectedOptions({ items: remote.items, toOption: optionOf });\n let shell = _state.raw<HTMLElement | null>(null);\n let trigger = _state.raw<HTMLElement | 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 /** 多选时移除一项(标签的移除按钮、Backspace) */\n function remove(target: string) {\n value = selectedValues(value).filter((item) => item !== target);\n }\n\n function removeLast() {\n const last = selectedValues(value).at(-1);\n if (last !== undefined) remove(last);\n }\n\n function show(highlight: 'default' | 'last' = 'default', event?: Event) {\n search = '';\n list.activateDefault();\n if (highlight === 'last' && selectedValues(value).length === 0) list.move('last');\n setOpen(true, 'trigger', event);\n }\n\n useResultAnnouncement(() => ({ query: search, count: list.options.length }));\n\n // 输入搜索词:高亮第一个匹配的。打开时清空搜索词不算:那时高亮的是已选项\n watch(\n () => search,\n (text) => text && list.move('first'),\n );\n\n // 选中一项:单选时关闭\n function picked(event?: Event) {\n if (!multiple()) setOpen(false, 'select', event);\n }\n\n /** Ctrl / Cmd / Alt 组合键是浏览器的(刷新、复制、查找),不做按字母跳转 */\n const hasModifier = (event: KeyboardEvent) => event.altKey || event.ctrlKey || event.metaKey;\n\n function onKeydown(event: KeyboardEvent) {\n if (control().disabled || control().readonly) return;\n if (event.key === 'Backspace' && multiple()) {\n event.preventDefault();\n removeLast();\n return;\n }\n if (!open) {\n if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n show('default', event);\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n show('last', event);\n } else if (!multiple() && !hasModifier(event) && list.typeahead(event.key)) {\n // 同原生 select:关闭时按字母直接选中\n event.preventDefault();\n if (list.active !== undefined && !list.isSelected(list.active)) list.select(list.active);\n }\n return;\n }\n if (event.key === 'Tab') {\n setOpen(false, 'focus', event);\n return;\n }\n if (handleListKeydown(list, event, { space: true, typeahead: true }, () => picked(event))) {\n event.preventDefault();\n }\n }\n\n // 搜索框里:↑↓ 移动、Enter 选中(Home / End、空格留给输入),Tab 关闭后照常移到下一个;\n // 多选时空的搜索框里按 Backspace 移除最后一项\n function onSearchKeydown(event: KeyboardEvent) {\n if (event.key === 'Tab') {\n setOpen(false, 'focus', event);\n return;\n }\n if (event.key === 'Backspace' && !search && multiple()) {\n event.preventDefault();\n removeLast();\n return;\n }\n if (handleListKeydown(list, event, { homeEnd: false }, () => picked(event)))\n event.preventDefault();\n }\n\n function clear() {\n value = multiple() ? [] : null;\n trigger?.focus();\n }\n\n // 弹层贴着外壳、与它同宽;焦点一直在触发按钮上,关闭时不用归还(searchable 时见 onPanelToggle)\n const { onToggle } = useAnchoredPopover(\n () => ({\n open,\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 // searchable:打开后焦点进搜索框;关闭时焦点还在弹层里(Esc、选中、点外面)就还给触发按钮\n function onPanelToggle(event: ToggleEvent) {\n onToggle(event);\n if (!withSearch()) return;\n if (event.newState === 'open') {\n panel?.querySelector<HTMLInputElement>('input[type=\"search\"]')?.focus();\n return;\n }\n const active = document.activeElement;\n if (!active || active === document.body || panel?.contains(active)) trigger?.focus();\n }\n\n return () => {\n const state = control();\n const values = selectedValues(value);\n const selected = selectedOptions(values);\n const off = state.disabled;\n // 只读:能聚焦,不能打开、不能改\n const locked = off || state.readonly;\n // 多选的默认显示:触发按钮前面一排标签,按钮里只留给读屏的文字\n const tagged = multiple() && !renderValue && selected.length > 0;\n const shown = tagged ? (\n <span {...css.attrs(visuallyHiddenStyles.hidden)}>\n {lang.selectSummary(selected.map((option) => option.label))}\n </span>\n ) : (\n (renderValue?.(\n selected.flatMap((option) => (option.item === undefined ? [] : [option.item])),\n ) ??\n (selected.length === 0 ? (\n <span {...css.attrs(selectStyles.placeholder)}>\n {placeholder ?? lang.selectPlaceholder}\n </span>\n ) : (\n selected[0]!.label\n )))\n );\n const tags = tagged && (\n <SelectedTags\n {...mergePartProps<SelectedTagsProps>(\n {\n tags: selected,\n maxTagCount,\n onRemove: locked ? undefined : remove,\n propsTag,\n },\n propsTags,\n )}\n />\n );\n const s = controlParts(appearance(), state);\n return (\n <>\n <div\n {...mergeAttrs<'div'>({ ...s.root, sx: [s.root.sx, selectStyles.root] }, propsRoot, {\n ref: _toRef(shell),\n // 点在外壳空白处(含多选的标签):焦点交给触发按钮,并与点触发按钮一样切换打开(只读时只聚焦)\n onPointerdown: off ? undefined : focusOnShell(() => trigger),\n onClick: locked\n ? undefined\n : clickOnShell(\n () => trigger,\n (event) => (open ? setOpen(false, 'trigger', event) : show('default', event)),\n ),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={off || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {tags}\n <button\n {...mergeAttrs<'button'>(\n {\n sx: [\n buttonBaseStyles.root,\n selectStyles.trigger,\n tagged && selectStyles.triggerBehindTags,\n ],\n },\n rest,\n {\n ref: _toRef(trigger),\n id: state.id,\n type: 'button',\n role: 'combobox',\n disabled: off,\n 'aria-haspopup': 'listbox',\n 'aria-expanded': open,\n 'aria-controls': listId,\n 'aria-activedescendant': open ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-required': state.required || undefined,\n 'aria-readonly': state.readonly || undefined,\n 'aria-describedby': state.describedBy,\n onClick: (event: MouseEvent) => {\n // Safari 点击按钮不会聚焦它:焦点要在触发按钮上,键盘与 aria-activedescendant 才有效\n trigger?.focus();\n if (locked) return;\n if (open) setOpen(false, 'trigger', event);\n else show('default', event);\n },\n onKeydown,\n },\n )}\n >\n <span {...mergeAttrs<'span'>({ sx: selectStyles.value }, propsValue)}>{shown}</span>\n </button>\n {clearable && values.length > 0 && !locked && controlClear(s, propsClear, clear)}\n <Icon\n {...mergePartProps<IconProps>(\n {\n ...s.icon,\n icon: ChevronDown,\n sx: [s.icon.sx, disclosureStyles.icon, open && selectStyles.chevronOpen],\n },\n propsChevron,\n )}\n />\n <FormValue name={name} value={values} disabled={state.disabled} />\n </div>\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onPanelToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading || remote.loading()}\n onPick={() => picked()}\n header={\n withSearch() && (\n <TextField\n {...mergePartProps<TextFieldProps>(\n {\n icon: Search,\n variant: 'soft',\n placeholder: lang.selectSearch,\n 'aria-label': lang.selectSearch,\n propsRoot: { sx: selectStyles.search },\n },\n propsSearch,\n )}\n type=\"search\"\n _value={search}\n aria-controls={listId}\n aria-activedescendant={open ? activeOptionId(list, listId) : undefined}\n onKeydown={onSearchKeydown}\n />\n )\n }\n listbox={{\n list,\n id: listId,\n empty: remote.failure() ?? (search ? lang.selectNoMatch : empty),\n renderOption:\n children &&\n ((item: T | Creation, state: OptionRenderState) =>\n item instanceof Creation ? lang.selectCreate(item.text) : children(item, state)),\n multiple: multiple(),\n }}\n // 使用方按 T 写列表的部件属性;creatable 时多出的“新建”那一项由上面的 renderOption 处理\n propsListbox={propsListbox as Partial<ListboxViewProps<T | Creation>> | undefined}\n propsPopover={propsPopover}\n />\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Select.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/input/Select.tsx"],"sourcesContent":["/**\n * 下拉选择(WAI-ARIA 的 select-only combobox):点触发框弹出选项列表,单选或多选。\n *\n * <Field label=\"城市\"><Select items={['北京', '上海', '广州']} _value={city} placeholder=\"选择城市\" /></Field>\n * <Select items={tags} selectionMode=\"multiple\" _value={picked} clearable aria-label=\"标签\" />\n * <Select items={users} toOption={(user) => ({ value: user.id, label: user.name })} _value={id} />\n * <Select items={suppliers} searchable _value={supplier} /> 选项多时:弹出层顶部带搜索框\n *\n * <Select items={tags} selectionMode=\"multiple\" maxTagCount=\"responsive\" creatable onCreate={(text) => tags.push(text)} … />\n *\n * 组合已有的部件:外壳是输入框外壳(controlParts),弹出层用 useAnchoredPopover(与外壳同宽、最高到视口剩余空间),\n * 列表是 ListboxView(状态与键盘见 list-state.ts)。焦点一直在触发按钮上,高亮项经 aria-activedescendant 告诉读屏。\n *\n * 多选时已选的项是触发按钮前面的一排 Tag(不能放进 button 里:按钮里不能有按钮),各带移除按钮。\n * 这排标签只给指针用(aria-hidden、移除按钮不进 Tab 顺序):读屏从触发按钮里念出全部已选内容,\n * 键盘在列表里取消勾选,或在触发按钮(及空的搜索框)里按 Backspace 移除最后一项(同 TagsInput)。\n * maxTagCount 是数字时只显示前几个,'responsive' 时排成一行按宽度放,其余收进“+N”(SelectedTags,TreeSelect 共用)。\n *\n * 键盘:关闭时 ↓ / Enter / 空格打开(↑ 打开并高亮最后一项),单选时直接按字母选中以它开头的项(同原生 select);\n * 打开时 ↑↓、Home / End、Page Up / Down 移动,Enter / 空格选中(单选随后关闭),Esc、Tab 关闭。\n * 写了 name 时用隐藏的 input 随表单提交(多选时每个值一个)。\n *\n * searchable 时弹出层顶部有搜索框:打开后焦点在搜索框里,输入就过滤选项(按显示文字,忽略大小写与重音,或用 filter),\n * ↑↓ 移动、Enter 选中、Esc 关闭并把焦点还给触发按钮。已选的项被过滤掉、或 items 换了(远程搜索)时,\n * 触发框里仍显示它的文字(useSelectedOptions)。\n *\n * 远程选项:给 load 时打开就按搜索词请求,输入停下 300 ms 再请求,只采用最后一次(remote-items.ts);\n * 自己管请求时写 _search={keyword} 拿到搜索词、filter={false} 关掉本地过滤,items 与 loading 自己给。\n *\n * <Select searchable load={(q, signal) => http.get<User[]>('/users', { query: { q }, signal })} toOption={…} _value={owner} />\n *\n * creatable 时自带搜索框,输入的文字没有对应的选项时列表末尾多一项“新建“…””:选中后值就是这段文字,onCreate 报告它。\n *\n * 部件:Root(外壳)、Tags(多选时的那排标签)、Tag、Trigger(button,主元素)、Value、Clear、Chevron、Popover、\n * Search(TextField)、Listbox。\n */\nimport { ChevronDown, Plus, Search } 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 { buttonBaseStyles } from '../base/button-core';\nimport { Icon, type IconProps } from '../base/Icon';\nimport type { TagProps } from '../base/Tag';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang, useTextMatcher } from '../config/context';\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 { selectedValues, type SelectionProps } from '../utils/data/selection';\nimport {\n controlClear,\n controlParts,\n clickOnShell,\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';\nimport { SelectedTags, type SelectedTagsProps } from './SelectedTags';\nimport { TextField, type TextFieldProps } from './TextField';\n\n/** 角色、展开状态与可用性由组件管理 */\nexport type SelectManaged =\n | 'type'\n | 'role'\n | 'value'\n | 'disabled'\n | 'aria-haspopup'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-required'\n | 'aria-describedby';\n\n/** 下拉选择共有的属性;未声明的属性落到触发按钮(主元素)上,外壳用 propsRoot */\nexport interface SelectBaseProps<T>\n extends PartProps<'button', SelectManaged>, ControlOptions, ControlIconOptions, OpenChangeProps {\n /** 数据:ListboxOption、字符串,或任意数据加 toOption;有 load 时是第一次加载之前的选项(编辑页放上已选的那一项) */\n items?: readonly T[];\n /** 没有选择时显示的文字,默认取语言包(请选择) */\n placeholder?: string;\n /** 是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有选择时显示清空按钮 */\n clearable?: boolean;\n /** 弹出层顶部带搜索框,输入过滤选项:选项多(供应商、人员、件号)时用 */\n searchable?: boolean;\n /** 搜索时怎样算匹配;默认显示文字包含输入(忽略大小写与重音)。false 时不在本地过滤(服务端按 search 过滤) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 搜索框里的文字:打开时清空;父级写 _search={x} 拿到它(自己发请求时) */\n _search?: string;\n /** 远程选项:打开时与搜索词停下 300 ms 后调用,结果代替 items;只采用最后一次,被顶掉的请求 signal 中止 */\n load?: OptionsLoader<T>;\n /**\n * 可以新建选项(自带搜索框):输入的文字没有对应的选项时,列表末尾多一项“新建“…””,选中后值就是这段文字。\n * 在 onCreate 里把它加进 items(或存到服务端);不加时触发框里照样显示这段文字\n */\n creatable?: boolean;\n /** 新建了一项(creatable 时):参数是输入的文字,也就是选中的值 */\n onCreate?: (text: string) => void;\n /** 多选时触发框里最多显示几个标签,其余收进“+N”;'responsive' 时排成一行、按宽度放,放不下的收起 */\n maxTagCount?: number | 'responsive';\n /** 表单提交时的 name(用隐藏的 input) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:能聚焦、能看到已选的内容,不能打开、不能清空 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义选项内容:`{(option, { selected, active }) => …}` */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 自定义触发框里显示的内容(已选的选项);默认单选显示文字、多选显示标签 */\n renderValue?: (selected: readonly T[]) => VNodeChild;\n /** 正在取选项(异步加载):列表里换成骨架行 */\n loading?: boolean;\n /** 没有选项时列表里显示的内容 */\n empty?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 已选内容那一层的部件属性 */\n propsValue?: PartProps<'span'>;\n /** 多选时每个标签的部件属性 */\n propsTag?: Partial<TagProps>;\n /** 多选时那排标签的部件属性 */\n propsTags?: Partial<SelectedTagsProps>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 下拉箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n /** 搜索框的部件属性(searchable 时) */\n propsSearch?: Partial<TextFieldProps>;\n}\n\n/** 单选或多选;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type SelectProps<T = ListboxOption> = SelectBaseProps<T> & SelectionProps & OptionMapping<T>;\n\n/** Select 的样式:root、trigger、value、placeholder、chevronOpen 等 */\nexport const selectStyles = css.create({\n // 在 ControlGroup 里也按内容宽(输入框才分剩余宽度)\n root: { cursor: 'pointer', flexGrow: 0 },\n // 去掉按钮默认外观用 buttonBaseStyles;像输入框一样没有自己的焦点环与波纹(焦点环画在外壳上)\n trigger: {\n display: 'flex',\n alignItems: 'center',\n flexGrow: 1,\n minWidth: 0,\n alignSelf: 'stretch',\n textAlign: 'start',\n cursor: 'inherit',\n outlineStyle: 'none',\n },\n value: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: '_xs',\n minWidth: 0,\n paddingBlock: '_xs',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n // 是真正的文字(不是原生 placeholder),要满足 4.5:1:用 textMuted 而不是 textPlaceholder\n placeholder: { color: '_textMuted' },\n // 多选时标签排在触发按钮前面(SelectedTags)占剩余宽度,触发按钮里只剩读屏用的文字\n triggerBehindTags: { flexGrow: 0 },\n chevronOpen: { rotate: '180deg' },\n // 弹出层顶部的搜索框占满宽度\n search: { width: '_full' },\n});\n\n/** creatable 时列表末尾那一项“新建“…””:不是 items 里的数据 */\nclass Creation {\n readonly text: string;\n constructor(text: string) {\n this.text = text;\n }\n}\n\n/** 下拉选择:触发框 + 弹出的选项列表 */\nexport const Select = _component(\n <T,>({\n items = [],\n toOption,\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n _open: open = _bindable(false),\n onOpenChange,\n placeholder,\n placement = 'bottom-start',\n clearable = false,\n searchable = false,\n filter,\n _search: search = _bindable(''),\n load,\n creatable = false,\n onCreate,\n maxTagCount,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n icon,\n children,\n renderValue,\n empty,\n loading = false,\n propsRoot,\n propsIcon,\n propsValue,\n propsTag,\n propsTags,\n propsClear,\n propsChevron,\n propsPopover,\n propsListbox,\n propsSearch,\n ...rest\n }: SelectProps<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 const multiple = () => selectionMode === 'multiple';\n const optionOf = (item: T) => toListboxOption(item, toOption);\n const remote = useRemoteItems({\n load: () => load,\n query: () => search,\n open: () => open,\n items: () => items,\n });\n // 本地过滤:有 load 或 filter={false} 时交给服务端\n const localFilter = () => !load && filter !== false;\n const matches = (item: T, text: string) =>\n filter ? filter(item, text) : textMatcher()(optionOf(item).label, text);\n // creatable 自带搜索框\n const withSearch = () => searchable || creatable;\n // creatable:输入的文字没有同名(或同值)的选项时,列表末尾多一项“新建”\n const creation = () => {\n const text = search.trim();\n if (!creatable || !text) return undefined;\n const exists = remote.items().some((item) => {\n const option = optionOf(item);\n return option.value === text || option.label.toLowerCase() === text.toLowerCase();\n });\n return exists ? undefined : new Creation(text);\n };\n const list = useListState<T | Creation>({\n items: () => {\n const all = remote.items();\n const shown =\n withSearch() && search && localFilter()\n ? all.filter((item) => matches(item, search))\n : all;\n const created = creation();\n return created ? [...shown, created] : shown;\n },\n toOption: (item) =>\n item instanceof Creation\n ? { value: item.text, label: lang.selectCreate(item.text), icon: Plus }\n : optionOf(item),\n selected: () => selectedValues(value),\n mode: () => selectionMode,\n onChange: (next) => {\n const created = creation();\n if (created && next.includes(created.text)) {\n onCreate?.(created.text);\n search = '';\n }\n value = multiple() ? [...next] : (next[0] ?? null);\n },\n // 单选时再点已选的一项不取消(同原生 select);要清空用清除按钮\n required: () => !multiple(),\n });\n // 已选项的文字取自全部 items(过滤掉的、远程搜索换掉的也在)\n const selectedOptions = useSelectedOptions({ items: remote.items, toOption: optionOf });\n let shell = _state.raw<HTMLElement | null>(null);\n let trigger = _state.raw<HTMLElement | 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 /** 多选时移除一项(标签的移除按钮、Backspace) */\n function remove(target: string) {\n value = selectedValues(value).filter((item) => item !== target);\n }\n\n function removeLast() {\n const last = selectedValues(value).at(-1);\n if (last !== undefined) remove(last);\n }\n\n function show(highlight: 'default' | 'last' = 'default', event?: Event) {\n search = '';\n list.activateDefault();\n if (highlight === 'last' && selectedValues(value).length === 0) list.move('last');\n setOpen(true, 'trigger', event);\n }\n\n useResultAnnouncement(() => ({ query: search, count: list.options.length }));\n\n // 输入搜索词:高亮第一个匹配的。打开时清空搜索词不算:那时高亮的是已选项\n watch(\n () => search,\n (text) => text && list.move('first'),\n );\n\n // 选中一项:单选时关闭\n function picked(event?: Event) {\n if (!multiple()) setOpen(false, 'select', event);\n }\n\n /** Ctrl / Cmd / Alt 组合键是浏览器的(刷新、复制、查找),不做按字母跳转 */\n const hasModifier = (event: KeyboardEvent) => event.altKey || event.ctrlKey || event.metaKey;\n\n function onKeydown(event: KeyboardEvent) {\n if (control().disabled || control().readonly) return;\n if (event.key === 'Backspace' && multiple()) {\n event.preventDefault();\n removeLast();\n return;\n }\n if (!open) {\n if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n show('default', event);\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n show('last', event);\n } else if (!multiple() && !hasModifier(event) && list.typeahead(event.key)) {\n // 同原生 select:关闭时按字母直接选中\n event.preventDefault();\n if (list.active !== undefined && !list.isSelected(list.active)) list.select(list.active);\n }\n return;\n }\n if (event.key === 'Tab') {\n setOpen(false, 'focus', event);\n return;\n }\n if (handleListKeydown(list, event, { space: true, typeahead: true }, () => picked(event))) {\n event.preventDefault();\n }\n }\n\n // 搜索框里:↑↓ 移动、Enter 选中(Home / End、空格留给输入),Tab 关闭后照常移到下一个;\n // 多选时空的搜索框里按 Backspace 移除最后一项\n function onSearchKeydown(event: KeyboardEvent) {\n if (event.key === 'Tab') {\n setOpen(false, 'focus', event);\n return;\n }\n if (event.key === 'Backspace' && !search && multiple()) {\n event.preventDefault();\n removeLast();\n return;\n }\n if (handleListKeydown(list, event, { homeEnd: false }, () => picked(event)))\n event.preventDefault();\n }\n\n function clear() {\n value = multiple() ? [] : null;\n trigger?.focus();\n }\n\n // 弹层贴着外壳、与它同宽;焦点一直在触发按钮上,关闭时不用归还(searchable 时见 onPanelToggle)\n const { onToggle } = useAnchoredPopover(\n () => ({\n open,\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 // searchable:打开后焦点进搜索框;关闭时焦点还在弹层里(Esc、选中、点外面)就还给触发按钮\n function onPanelToggle(event: ToggleEvent) {\n onToggle(event);\n if (!withSearch()) return;\n if (event.newState === 'open') {\n panel?.querySelector<HTMLInputElement>('input[type=\"search\"]')?.focus();\n return;\n }\n const active = document.activeElement;\n if (!active || active === document.body || panel?.contains(active)) trigger?.focus();\n }\n\n return () => {\n const state = control();\n const values = selectedValues(value);\n const selected = selectedOptions(values);\n const off = state.disabled;\n // 只读:能聚焦,不能打开、不能改\n const locked = off || state.readonly;\n // 多选的默认显示:触发按钮前面一排标签,按钮里只留给读屏的文字\n const tagged = multiple() && !renderValue && selected.length > 0;\n const shown = tagged ? (\n <span {...css.attrs(visuallyHiddenStyles.hidden)}>\n {lang.selectSummary(selected.map((option) => option.label))}\n </span>\n ) : (\n (renderValue?.(\n selected.flatMap((option) => (option.item === undefined ? [] : [option.item])),\n ) ??\n (selected.length === 0 ? (\n <span {...css.attrs(selectStyles.placeholder)}>\n {placeholder ?? lang.selectPlaceholder}\n </span>\n ) : (\n selected[0]!.label\n )))\n );\n const tags = tagged && (\n <SelectedTags\n {...mergePartProps<SelectedTagsProps>(\n {\n tags: selected,\n maxTagCount,\n onRemove: locked ? undefined : remove,\n propsTag,\n },\n propsTags,\n )}\n />\n );\n const s = controlParts(appearance(), state);\n return (\n <>\n <div\n {...mergeAttrs<'div'>({ ...s.root, sx: [s.root.sx, selectStyles.root] }, propsRoot, {\n ref: _toRef(shell),\n // 点在外壳空白处(含多选的标签):焦点交给触发按钮,并与点触发按钮一样切换打开(只读时只聚焦)\n onPointerdown: off ? undefined : focusOnShell(() => trigger),\n onClick: locked\n ? undefined\n : clickOnShell(\n () => trigger,\n (event) => (open ? setOpen(false, 'trigger', event) : show('default', event)),\n ),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={off || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {tags}\n <button\n {...mergeAttrs<'button'>(\n {\n sx: [\n buttonBaseStyles.root,\n selectStyles.trigger,\n tagged && selectStyles.triggerBehindTags,\n ],\n },\n rest,\n {\n ref: _toRef(trigger),\n id: state.id,\n type: 'button',\n role: 'combobox',\n disabled: off,\n 'aria-haspopup': 'listbox',\n 'aria-expanded': open,\n 'aria-controls': listId,\n 'aria-activedescendant': open ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-required': state.required || undefined,\n 'aria-readonly': state.readonly || undefined,\n 'aria-describedby': state.describedBy,\n onClick: (event: MouseEvent) => {\n // Safari 点击按钮不会聚焦它:焦点要在触发按钮上,键盘与 aria-activedescendant 才有效\n trigger?.focus();\n if (locked) return;\n if (open) setOpen(false, 'trigger', event);\n else show('default', event);\n },\n onKeydown,\n },\n )}\n >\n <span {...mergeAttrs<'span'>({ sx: selectStyles.value }, propsValue)}>{shown}</span>\n </button>\n {clearable && values.length > 0 && !locked && controlClear(s, propsClear, clear)}\n <Icon\n {...mergePartProps<IconProps>(\n {\n ...s.icon,\n icon: ChevronDown,\n sx: [s.icon.sx, disclosureStyles.icon, open && selectStyles.chevronOpen],\n },\n propsChevron,\n )}\n />\n <FormValue name={name} value={values} disabled={state.disabled} />\n </div>\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onPanelToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading || remote.loading()}\n onPick={() => picked()}\n header={\n withSearch() && (\n <TextField\n {...mergePartProps<TextFieldProps>(\n {\n icon: Search,\n variant: 'soft',\n placeholder: lang.selectSearch,\n 'aria-label': lang.selectSearch,\n propsRoot: { sx: selectStyles.search },\n },\n propsSearch,\n )}\n type=\"search\"\n _value={search}\n aria-controls={listId}\n aria-activedescendant={open ? activeOptionId(list, listId) : undefined}\n onKeydown={onSearchKeydown}\n />\n )\n }\n listbox={{\n list,\n id: listId,\n empty: remote.failure() ?? (search ? lang.selectNoMatch : empty),\n renderOption:\n children &&\n ((item: T | Creation, state: OptionRenderState) =>\n item instanceof Creation ? lang.selectCreate(item.text) : children(item, state)),\n multiple: multiple(),\n }}\n // 使用方按 T 写列表的部件属性;creatable 时多出的“新建”那一项由上面的 renderOption 处理\n propsListbox={propsListbox as Partial<ListboxViewProps<T | Creation>> | undefined}\n propsPopover={propsPopover}\n />\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|