@neut/ui 0.0.11 → 0.0.12
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/components/label/index.d.ts +1 -0
- package/dist/components/select/Select/Select.context.d.ts +3 -0
- package/dist/components/select/Select/Select.context.js +12 -0
- package/dist/components/select/Select/Select.context.js.map +1 -0
- package/dist/components/select/Select/Select.d.ts +19 -0
- package/dist/components/select/Select/Select.js +48 -0
- package/dist/components/select/Select/Select.js.map +1 -0
- package/dist/components/select/Select/Select.types.d.ts +41 -0
- package/dist/components/select/Select/index.d.ts +3 -0
- package/dist/components/select/SelectContent/SelectContent.d.ts +2 -0
- package/dist/components/select/SelectContent/SelectContent.js +80 -0
- package/dist/components/select/SelectContent/SelectContent.js.map +1 -0
- package/dist/components/select/SelectContent/SelectContent.types.d.ts +22 -0
- package/dist/components/select/SelectContent/SelectContent.utils.d.ts +35 -0
- package/dist/components/select/SelectContent/SelectContent.utils.js +66 -0
- package/dist/components/select/SelectContent/SelectContent.utils.js.map +1 -0
- package/dist/components/select/SelectContent/index.d.ts +6 -0
- package/dist/components/select/SelectContent/useSelectContent.d.ts +28 -0
- package/dist/components/select/SelectContent/useSelectContent.js +46 -0
- package/dist/components/select/SelectContent/useSelectContent.js.map +1 -0
- package/dist/components/select/SelectGroup/SelectGroup.d.ts +2 -0
- package/dist/components/select/SelectGroup/SelectGroup.js +13 -0
- package/dist/components/select/SelectGroup/SelectGroup.js.map +1 -0
- package/dist/components/select/SelectGroup/index.d.ts +1 -0
- package/dist/components/select/SelectItem/SelectItem.d.ts +2 -0
- package/dist/components/select/SelectItem/SelectItem.js +52 -0
- package/dist/components/select/SelectItem/SelectItem.js.map +1 -0
- package/dist/components/select/SelectItem/SelectItem.types.d.ts +9 -0
- package/dist/components/select/SelectItem/index.d.ts +4 -0
- package/dist/components/select/SelectItem/useSelectItem.d.ts +11 -0
- package/dist/components/select/SelectItem/useSelectItem.js +32 -0
- package/dist/components/select/SelectItem/useSelectItem.js.map +1 -0
- package/dist/components/select/SelectLabel/SelectLabel.d.ts +2 -0
- package/dist/components/select/SelectLabel/SelectLabel.js +13 -0
- package/dist/components/select/SelectLabel/SelectLabel.js.map +1 -0
- package/dist/components/select/SelectLabel/index.d.ts +1 -0
- package/dist/components/select/SelectSeparator/SelectSeparator.d.ts +2 -0
- package/dist/components/select/SelectSeparator/SelectSeparator.js +13 -0
- package/dist/components/select/SelectSeparator/SelectSeparator.js.map +1 -0
- package/dist/components/select/SelectSeparator/index.d.ts +1 -0
- package/dist/components/select/SelectTrigger/SelectTrigger.d.ts +4 -0
- package/dist/components/select/SelectTrigger/SelectTrigger.js +50 -0
- package/dist/components/select/SelectTrigger/SelectTrigger.js.map +1 -0
- package/dist/components/select/SelectTrigger/SelectTrigger.types.d.ts +3 -0
- package/dist/components/select/SelectTrigger/index.d.ts +4 -0
- package/dist/components/select/SelectTrigger/useSelectTrigger.d.ts +15 -0
- package/dist/components/select/SelectTrigger/useSelectTrigger.js +37 -0
- package/dist/components/select/SelectTrigger/useSelectTrigger.js.map +1 -0
- package/dist/components/select/SelectValue/SelectValue.d.ts +2 -0
- package/dist/components/select/SelectValue/SelectValue.js +20 -0
- package/dist/components/select/SelectValue/SelectValue.js.map +1 -0
- package/dist/components/select/SelectValue/SelectValue.types.d.ts +4 -0
- package/dist/components/select/SelectValue/index.d.ts +2 -0
- package/dist/components/select/index.d.ts +13 -0
- package/dist/components/select/index.js +9 -0
- package/dist/components/tooltip/TooltipContent/TooltipContent.js +25 -27
- package/dist/components/tooltip/TooltipContent/TooltipContent.js.map +1 -1
- package/dist/components/tooltip/TooltipContent/useTooltipContent.js +3 -3
- package/dist/components/tooltip/TooltipTrigger/TooltipTrigger.js +11 -15
- package/dist/components/tooltip/TooltipTrigger/TooltipTrigger.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +167 -158
- package/dist/lib/positioner/middleware/size.js +21 -0
- package/dist/lib/positioner/middleware/size.js.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +3 -2
- package/dist/utils/ref.d.ts +11 -0
- package/dist/utils/ref.js +10 -0
- package/dist/utils/ref.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createContext as e, useContext as t } from "solid-js";
|
|
2
|
+
//#region src/components/select/Select/Select.context.ts
|
|
3
|
+
var n = e();
|
|
4
|
+
function r(e) {
|
|
5
|
+
let r = t(n);
|
|
6
|
+
if (!r) throw Error(`<${e}> 必须渲染在 <Select> 内部`);
|
|
7
|
+
return r;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { n as SelectContext, r as useSelectContext };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=Select.context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.context.js","names":[],"sources":["../../../../src/components/select/Select/Select.context.ts"],"sourcesContent":["import { createContext, useContext } from \"solid-js\";\nimport type { SelectContextValue } from \"./Select.types\";\n\nexport const SelectContext = createContext<SelectContextValue>();\n\nexport function useSelectContext(component: string): SelectContextValue {\n const ctx = useContext(SelectContext);\n if (!ctx) {\n throw new Error(`<${component}> 必须渲染在 <Select> 内部`);\n }\n return ctx;\n}\n"],"mappings":";;AAGA,IAAa,IAAgB,EAAkC;AAE/D,SAAgB,EAAiB,GAAuC;CACtE,IAAM,IAAM,EAAW,CAAa;CACpC,IAAI,CAAC,GACH,MAAU,MAAM,IAAI,EAAU,oBAAoB;CAEpD,OAAO;AACT"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JSX } from 'solid-js';
|
|
2
|
+
import { SelectProps } from './Select.types';
|
|
3
|
+
/**
|
|
4
|
+
* Select 根组件:不渲染任何 DOM,只负责状态管理 + 提供 context。
|
|
5
|
+
* 真正的展示交给 <SelectTrigger>/<SelectValue>/<SelectContent>/<SelectItem>。
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <Select value={fruit()} onValueChange={setFruit}>
|
|
10
|
+
* <SelectTrigger>
|
|
11
|
+
* <SelectValue placeholder="选择一个水果" />
|
|
12
|
+
* </SelectTrigger>
|
|
13
|
+
* <SelectContent>
|
|
14
|
+
* <SelectItem value="apple">苹果</SelectItem>
|
|
15
|
+
* </SelectContent>
|
|
16
|
+
* </Select>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function Select(props: SelectProps): JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SelectContext as e } from "./Select.context.js";
|
|
2
|
+
import { createComponent as t } from "solid-js/web";
|
|
3
|
+
import { createMemo as n, createSignal as r, createUniqueId as i } from "solid-js";
|
|
4
|
+
import { createStore as a, produce as o } from "solid-js/store";
|
|
5
|
+
//#region src/components/select/Select/Select.tsx
|
|
6
|
+
function s(s) {
|
|
7
|
+
let [c, l] = r(s.defaultValue), u = n(() => s.value === void 0 ? c() : s.value), d = (e) => {
|
|
8
|
+
s.value === void 0 && l(e), s.onValueChange?.(e);
|
|
9
|
+
}, [f, p] = r(s.defaultOpen ?? !1), m = n(() => s.open === void 0 ? f() : s.open), h = n(() => !!s.disabled), g = (e) => {
|
|
10
|
+
s.open === void 0 && p(e), s.onOpenChange?.(e);
|
|
11
|
+
}, [_, v] = r(), [y, b] = r(), [x, S] = r(), [C, w] = a([]), T = `select-content-${i()}`, E = (e) => (w(o((t) => t.push(e))), () => w(o((t) => {
|
|
12
|
+
let n = t.findIndex((t) => t.value === e.value);
|
|
13
|
+
n !== -1 && t.splice(n, 1);
|
|
14
|
+
}))), D = () => g(!1), O = () => {
|
|
15
|
+
g(!1), _()?.focus?.();
|
|
16
|
+
}, k = {
|
|
17
|
+
value: u,
|
|
18
|
+
setValue: d,
|
|
19
|
+
open: m,
|
|
20
|
+
setOpen: g,
|
|
21
|
+
disabled: h,
|
|
22
|
+
contentId: T,
|
|
23
|
+
reference: _,
|
|
24
|
+
setReference: v,
|
|
25
|
+
floating: y,
|
|
26
|
+
setFloating: b,
|
|
27
|
+
get items() {
|
|
28
|
+
return C;
|
|
29
|
+
},
|
|
30
|
+
registerItem: E,
|
|
31
|
+
activeValue: x,
|
|
32
|
+
setActiveValue: S,
|
|
33
|
+
selectValue: (e) => {
|
|
34
|
+
d(e), O();
|
|
35
|
+
},
|
|
36
|
+
close: D
|
|
37
|
+
};
|
|
38
|
+
return t(e.Provider, {
|
|
39
|
+
value: k,
|
|
40
|
+
get children() {
|
|
41
|
+
return s.children;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { s as Select };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=Select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.js","names":["createSignal","createMemo","createUniqueId","JSX","createStore","produce","SelectContext","SelectContextValue","SelectItemMeta","SelectProps","Select","props","Element","internalValue","setInternalValue","defaultValue","value","undefined","setValue","v","onValueChange","internalOpen","setInternalOpen","defaultOpen","open","disabled","setOpen","next","onOpenChange","reference","setReference","floating","setFloating","HTMLElement","activeValue","setActiveValue","items","setItems","contentId","registerItem","item","list","push","idx","findIndex","it","splice","close","closeAndFocusTrigger","focus","selectValue","ctx","_$createComponent","Provider","children"],"sources":["../../../../src/components/select/Select/Select.tsx"],"sourcesContent":["import { createSignal, createMemo, createUniqueId, type JSX } from \"solid-js\";\nimport { createStore, produce } from \"solid-js/store\";\nimport { SelectContext } from \"./Select.context\";\nimport type {\n SelectContextValue,\n SelectItemMeta,\n SelectProps,\n} from \"./Select.types\";\n\n/**\n * Select 根组件:不渲染任何 DOM,只负责状态管理 + 提供 context。\n * 真正的展示交给 <SelectTrigger>/<SelectValue>/<SelectContent>/<SelectItem>。\n *\n * @example\n * ```tsx\n * <Select value={fruit()} onValueChange={setFruit}>\n * <SelectTrigger>\n * <SelectValue placeholder=\"选择一个水果\" />\n * </SelectTrigger>\n * <SelectContent>\n * <SelectItem value=\"apple\">苹果</SelectItem>\n * </SelectContent>\n * </Select>\n * ```\n */\nexport function Select(props: SelectProps): JSX.Element {\n const [internalValue, setInternalValue] = createSignal(props.defaultValue);\n const value = createMemo(() =>\n props.value !== undefined ? props.value : internalValue(),\n );\n\n const setValue = (v: string) => {\n if (props.value === undefined) setInternalValue(v);\n props.onValueChange?.(v);\n };\n\n const [internalOpen, setInternalOpen] = createSignal(\n props.defaultOpen ?? false,\n );\n const open = createMemo(() =>\n props.open !== undefined ? props.open : internalOpen(),\n );\n const disabled = createMemo(() => !!props.disabled);\n\n const setOpen = (next: boolean) => {\n if (props.open === undefined) setInternalOpen(next);\n props.onOpenChange?.(next);\n };\n\n const [reference, setReference] = createSignal<Element>();\n const [floating, setFloating] = createSignal<HTMLElement>();\n const [activeValue, setActiveValue] = createSignal<string>();\n const [items, setItems] = createStore<SelectItemMeta[]>([]);\n const contentId = `select-content-${createUniqueId()}`;\n\n const registerItem = (item: SelectItemMeta) => {\n setItems(produce((list) => list.push(item)));\n return () =>\n setItems(\n produce((list) => {\n const idx = list.findIndex((it) => it.value === item.value);\n if (idx !== -1) list.splice(idx, 1);\n }),\n );\n };\n\n const close = () => setOpen(false);\n\n const closeAndFocusTrigger = () => {\n setOpen(false);\n (reference() as HTMLElement | undefined)?.focus?.();\n };\n\n const selectValue = (v: string) => {\n setValue(v);\n closeAndFocusTrigger();\n };\n\n const ctx: SelectContextValue = {\n value,\n setValue,\n open,\n setOpen,\n disabled,\n contentId,\n reference,\n setReference,\n floating,\n setFloating,\n get items() {\n return items;\n },\n registerItem,\n activeValue,\n setActiveValue,\n selectValue,\n close,\n };\n\n return (\n <SelectContext.Provider value={ctx}>\n {props.children}\n </SelectContext.Provider>\n );\n}\n"],"mappings":";;;;;AAyBA,SAAgBU,EAAOC,GAAiC;CACtD,IAAM,CAACE,GAAeC,KAAoBd,EAAaW,EAAMI,YAAY,GACnEC,IAAQf,QACZU,EAAMK,UAAUC,KAAAA,IAA0BJ,EAAc,IAA5BF,EAAMK,KACpC,GAEME,KAAYC,MAAc;EAE9BR,AADIA,EAAMK,UAAUC,KAAAA,KAAWH,EAAiBK,CAAC,GACjDR,EAAMS,gBAAgBD,CAAC;CACzB,GAEM,CAACE,GAAcC,KAAmBtB,EACtCW,EAAMY,eAAe,EACvB,GACMC,IAAOvB,QACXU,EAAMa,SAASP,KAAAA,IAAyBI,EAAa,IAA1BV,EAAMa,IACnC,GACMC,IAAWxB,QAAiB,CAAC,CAACU,EAAMc,QAAQ,GAE5CC,KAAWC,MAAkB;EAEjChB,AADIA,EAAMa,SAASP,KAAAA,KAAWK,EAAgBK,CAAI,GAClDhB,EAAMiB,eAAeD,CAAI;CAC3B,GAEM,CAACE,GAAWC,KAAgB9B,EAAsB,GAClD,CAAC+B,GAAUC,KAAehC,EAA0B,GACpD,CAACkC,GAAaC,KAAkBnC,EAAqB,GACrD,CAACoC,GAAOC,KAAYjC,EAA8B,CAAA,CAAE,GACpDkC,IAAY,kBAAkBpC,EAAe,KAE7CqC,KAAgBC,OACpBH,EAAShC,GAASoC,MAASA,EAAKC,KAAKF,CAAI,CAAC,CAAC,SAEzCH,EACEhC,GAASoC,MAAS;EAChB,IAAME,IAAMF,EAAKG,WAAWC,MAAOA,EAAG7B,UAAUwB,EAAKxB,KAAK;EAC1D,AAAI2B,MAAQ,MAAIF,EAAKK,OAAOH,GAAK,CAAC;CACpC,CAAC,CACH,IAGEI,UAAcrB,EAAQ,EAAK,GAE3BsB,UAA6B;EAEjC,AADAtB,EAAQ,EAAK,GACb,EAAW,CAAC,EAA8BuB,QAAQ;CACpD,GAOME,IAA0B;EAC9BnC;EACAE;EACAM;EACAE;EACAD;EACAa;EACAT;EACAC;EACAC;EACAC;EACA,IAAII,QAAQ;GACV,OAAOA;EACT;EACAG;EACAL;EACAC;EACAe,cAtBmB/B,MAAc;GAEjC6B,AADA9B,EAASC,CAAC,GACV6B,EAAqB;EACvB;EAoBED;CACF;CAEA,OAAAK,EACG9C,EAAc+C,UAAQ;EAACrC,OAAOmC;EAAG,IAAAG,WAAA;GAAA,OAC/B3C,EAAM2C;EAAQ;CAAA,CAAA;AAGrB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Accessor, ParentProps } from 'solid-js';
|
|
2
|
+
export interface SelectItemMeta {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SelectProps extends ParentProps {
|
|
8
|
+
/** 受控选中值;不传则内部自管理(非受控模式) */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** 非受控模式下的初始选中值 */
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
onValueChange?: (value: string) => void;
|
|
13
|
+
/** 受控 open 状态;不传则内部自管理 */
|
|
14
|
+
open?: boolean;
|
|
15
|
+
defaultOpen?: boolean;
|
|
16
|
+
onOpenChange?: (open: boolean) => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface SelectContextValue {
|
|
20
|
+
value: Accessor<string | undefined>;
|
|
21
|
+
setValue: (v: string) => void;
|
|
22
|
+
open: Accessor<boolean>;
|
|
23
|
+
setOpen: (v: boolean) => void;
|
|
24
|
+
disabled: Accessor<boolean>;
|
|
25
|
+
contentId: string;
|
|
26
|
+
reference: Accessor<Element | undefined>;
|
|
27
|
+
setReference: (el: Element | undefined) => void;
|
|
28
|
+
floating: Accessor<HTMLElement | undefined>;
|
|
29
|
+
setFloating: (el: HTMLElement | undefined) => void;
|
|
30
|
+
/** 已注册的选项列表(响应式 store),SelectContent/SelectValue 都要读 */
|
|
31
|
+
items: SelectItemMeta[];
|
|
32
|
+
/** SelectItem 挂载时注册自己,返回取消注册函数(卸载时调用) */
|
|
33
|
+
registerItem: (item: SelectItemMeta) => () => void;
|
|
34
|
+
/** 键盘/鼠标悬停高亮的项 */
|
|
35
|
+
activeValue: Accessor<string | undefined>;
|
|
36
|
+
setActiveValue: (v: string | undefined) => void;
|
|
37
|
+
/** 选中某项后调用:设值、关闭、把 focus 还给 trigger */
|
|
38
|
+
selectValue: (v: string) => void;
|
|
39
|
+
/** 单纯关闭(不选择任何值),键盘 Esc / 点击外部时用 */
|
|
40
|
+
close: () => void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { clsx as e } from "../../../utils/clsx.js";
|
|
2
|
+
import { useSelectContent as t } from "./useSelectContent.js";
|
|
3
|
+
import { Portal as n, createComponent as r, effect as i, insert as a, mergeProps as o, spread as s, style as c, template as l, use as u } from "solid-js/web";
|
|
4
|
+
import { createEffect as d, createMemo as f, onCleanup as p, splitProps as m } from "solid-js";
|
|
5
|
+
//#region src/components/select/SelectContent/SelectContent.tsx
|
|
6
|
+
var h = /*#__PURE__*/ l("<div style=z-index:1000><div role=listbox>");
|
|
7
|
+
function g(l) {
|
|
8
|
+
let { ctx: g, pos: _, maxHeight: v, isVisible: y, animationState: b, contentElement: x, setContentElement: S } = t(() => l), [C, w] = m(l, [
|
|
9
|
+
"placement",
|
|
10
|
+
"collisionPadding",
|
|
11
|
+
"class",
|
|
12
|
+
"style",
|
|
13
|
+
"children"
|
|
14
|
+
]), T = f(() => g.items.filter((e) => !e.disabled)), E = (e) => {
|
|
15
|
+
let t = T();
|
|
16
|
+
if (t.length === 0) return;
|
|
17
|
+
let n = (t.findIndex((e) => e.value === g.activeValue()) + e + t.length) % t.length;
|
|
18
|
+
g.setActiveValue(t[n].value);
|
|
19
|
+
}, D = (e) => {
|
|
20
|
+
switch (e.key) {
|
|
21
|
+
case "ArrowDown":
|
|
22
|
+
e.preventDefault(), E(1);
|
|
23
|
+
break;
|
|
24
|
+
case "ArrowUp":
|
|
25
|
+
e.preventDefault(), E(-1);
|
|
26
|
+
break;
|
|
27
|
+
case "Enter":
|
|
28
|
+
case " ":
|
|
29
|
+
e.preventDefault(), g.activeValue() !== void 0 && g.selectValue(g.activeValue());
|
|
30
|
+
break;
|
|
31
|
+
case "Escape":
|
|
32
|
+
e.preventDefault(), g.close();
|
|
33
|
+
break;
|
|
34
|
+
case "Tab": g.setOpen(!1);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return d(() => {
|
|
38
|
+
if (!g.open()) return;
|
|
39
|
+
let e = (e) => {
|
|
40
|
+
let t = e.target;
|
|
41
|
+
!g.reference()?.contains(t) && !g.floating()?.contains(t) && (g.setOpen(!1), e.stopPropagation());
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener("pointerdown", e, { capture: !0 }), p(() => document.removeEventListener("pointerdown", e, { capture: !0 }));
|
|
44
|
+
}), d(() => {
|
|
45
|
+
g.open() && (document.addEventListener("keydown", D), p(() => document.removeEventListener("keydown", D)));
|
|
46
|
+
}), d(() => {
|
|
47
|
+
let e = g.activeValue(), t = x();
|
|
48
|
+
!e || !t || t.querySelector(`[data-value="${CSS.escape(e)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
49
|
+
}), r(n, { get children() {
|
|
50
|
+
var t = h(), n = t.firstChild;
|
|
51
|
+
return u((e) => {
|
|
52
|
+
g.setFloating(e), p(() => g.setFloating(void 0));
|
|
53
|
+
}, t), u(S, n), s(n, o({
|
|
54
|
+
get "data-state"() {
|
|
55
|
+
return b();
|
|
56
|
+
},
|
|
57
|
+
get style() {
|
|
58
|
+
return {
|
|
59
|
+
position: "relative",
|
|
60
|
+
width: _.middlewareData().matchWidth?.width ? `${_.middlewareData().matchWidth.width}px` : void 0,
|
|
61
|
+
"max-height": v() ? `${v()}px` : void 0,
|
|
62
|
+
overflow: "auto",
|
|
63
|
+
...typeof C.style == "object" ? C.style : void 0
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
get class() {
|
|
67
|
+
return e("min-w-36", "rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100", "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", C.class);
|
|
68
|
+
}
|
|
69
|
+
}, w), !1, !0), a(n, () => C.children), i((e) => c(t, {
|
|
70
|
+
..._.floatingStyles(),
|
|
71
|
+
opacity: y() && _.isPositioned() ? 1 : 0,
|
|
72
|
+
"pointer-events": y() ? "auto" : "none",
|
|
73
|
+
visibility: y() ? "visible" : "hidden"
|
|
74
|
+
}, e)), t;
|
|
75
|
+
} });
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { g as SelectContent };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=SelectContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectContent.js","names":["onCleanup","splitProps","createMemo","createEffect","Portal","useSelectContent","SelectContentProps","clsx","SelectContent","props","ctx","pos","maxHeight","isVisible","animationState","contentElement","setContentElement","local","rest","enabledItems","items","filter","it","disabled","moveActive","dir","list","length","currentIndex","findIndex","value","activeValue","nextIndex","setActiveValue","onKeyDown","e","KeyboardEvent","key","preventDefault","undefined","selectValue","close","setOpen","open","onPointerDown","PointerEvent","target","Node","reference","contains","floating","stopPropagation","document","addEventListener","capture","removeEventListener","active","el","querySelector","HTMLElement","CSS","escape","scrollIntoView","block","_$createComponent","children","_el$","_tmpl$","_el$2","firstChild","_$use","setFloating","_$spread","_$mergeProps","data-state","style","position","width","middlewareData","matchWidth","overflow","class","_$insert","_$effect","_$p","_$style","floatingStyles","opacity","isPositioned","visibility"],"sources":["../../../../src/components/select/SelectContent/SelectContent.tsx"],"sourcesContent":["import { onCleanup, splitProps, createMemo, createEffect } from \"solid-js\";\nimport { Portal } from \"solid-js/web\";\nimport { useSelectContent } from \"./useSelectContent\";\nimport type { SelectContentProps } from \"./SelectContent.types\";\nimport { clsx } from \"~/utils\";\n\nexport function SelectContent(props: SelectContentProps) {\n const {\n ctx,\n pos,\n maxHeight,\n isVisible,\n animationState,\n contentElement,\n setContentElement,\n } = useSelectContent(() => props);\n\n const [local, rest] = splitProps(props, [\n \"placement\",\n \"collisionPadding\",\n \"class\",\n \"style\",\n \"children\",\n ]);\n\n const enabledItems = createMemo(() => ctx.items.filter((it) => !it.disabled));\n\n const moveActive = (dir: 1 | -1) => {\n const list = enabledItems();\n if (list.length === 0) return;\n const currentIndex = list.findIndex((it) => it.value === ctx.activeValue());\n const nextIndex = (currentIndex + dir + list.length) % list.length;\n ctx.setActiveValue(list[nextIndex].value);\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n moveActive(1);\n break;\n case \"ArrowUp\":\n e.preventDefault();\n moveActive(-1);\n break;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (ctx.activeValue() !== undefined)\n ctx.selectValue(ctx.activeValue()!);\n break;\n case \"Escape\":\n e.preventDefault();\n ctx.close();\n break;\n case \"Tab\":\n ctx.setOpen(false);\n break;\n }\n };\n\n // 点击外部关闭:捕获阶段监听 + stopPropagation,在事件还没下沉到真正的目标\n // 元素之前就拦截掉,避免点击穿透到浮层下面被盖住的元素(细节见\n // production-tooltip 那边点击穿透问题的排查记录,这里直接复用同一个模式)。\n createEffect(() => {\n if (!ctx.open()) return;\n const onPointerDown = (e: PointerEvent) => {\n const target = e.target as Node;\n if (\n !ctx.reference()?.contains(target) &&\n !ctx.floating()?.contains(target)\n ) {\n ctx.setOpen(false);\n e.stopPropagation();\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown, { capture: true });\n onCleanup(() =>\n document.removeEventListener(\"pointerdown\", onPointerDown, {\n capture: true,\n }),\n );\n });\n\n // 打开时把键盘事件绑到 document,这样不需要抢 focus 也能用方向键\n createEffect(() => {\n if (!ctx.open()) return;\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => document.removeEventListener(\"keydown\", onKeyDown));\n });\n\n // 高亮项变化时自动滚动到可见区域\n createEffect(() => {\n const active = ctx.activeValue();\n const el = contentElement();\n if (!active || !el) return;\n el.querySelector<HTMLElement>(\n `[data-value=\"${CSS.escape(active)}\"]`,\n )?.scrollIntoView({ block: \"nearest\" });\n });\n\n return (\n <Portal>\n {/*\n 外层:只负责定位(position:fixed 来自 floatingStyles),不参与动画。\n 它的 transform 专门用来做 translate(x,y) 定位——如果内层的 zoom-in-95\n 动画也放在这一层,两者会互相冲突(细节见 SelectContent.utils.ts 顶部\n 的注释)。\n\n 关闭时不再卸载整个 Portal,而是用 visibility/pointer-events 隐藏:\n SelectItem 通过 onMount 向 ctx 注册 item 元数据,如果关闭时把子节点\n 卸载掉,ctx.items 会被清空,SelectValue 就无法再用 item.label 渲染\n 已选值。让子节点持续挂载,items 的注册生命周期才和 SelectContent\n 本身一致;隐藏只是视觉/交互上的关闭,不影响数据。\n */}\n <div\n ref={(el) => {\n ctx.setFloating(el);\n // 元素卸载时清空 ctx.floating(),避免后续读到已从 DOM 摘除的\n // 僵尸节点(Solid 的 ref 回调只在创建时触发一次,不会自动在\n // 卸载时传 undefined)。\n onCleanup(() => ctx.setFloating(undefined));\n }}\n style={{\n ...pos.floatingStyles(),\n \"z-index\": 1000,\n opacity: isVisible() && pos.isPositioned() ? 1 : 0,\n \"pointer-events\": isVisible() ? \"auto\" : \"none\",\n visibility: isVisible() ? \"visible\" : \"hidden\",\n }}\n >\n {/*\n 内层:真正的视觉样式 + overflow:auto 滚动 + 进出场动画。\n position: relative 是为了让它成为内部所有选项的 offsetParent——\n \"选中项对齐\"这套定位策略要用 offsetTop 算选项在列表里的位置,\n 这个值必须相对\"真正会滚动的这个元素\"才准确,不能让它被外层\n (不滚动)抢走 offsetParent 的身份。\n */}\n <div\n ref={setContentElement}\n role=\"listbox\"\n data-state={animationState()}\n style={{\n position: \"relative\",\n width: pos.middlewareData().matchWidth?.width\n ? `${pos.middlewareData().matchWidth.width}px`\n : undefined,\n \"max-height\": maxHeight() ? `${maxHeight()}px` : undefined,\n overflow: \"auto\",\n ...(typeof local.style === \"object\" ? local.style : undefined),\n }}\n class={clsx(\n \"min-w-36\",\n \"rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100\",\n // 只保留入场动画,没有退场动画:关闭瞬间外层直接 visibility:hidden,\n // data-state 也立即切回 closed,没有退场动画阶段,自然不会出现\n // 选中项变更时\"面板先跳一下再消失\"的问题。duration-100(100ms)\n // 比默认的 150ms 更短——下拉菜单这种高频交互的组件,动画应该比\n // tooltip 更快、更利落。\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n local.class,\n )}\n {...rest}\n >\n {local.children}\n </div>\n </div>\n </Portal>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ParentProps } from 'solid-js';
|
|
2
|
+
import { Placement } from '../../../lib';
|
|
3
|
+
export interface SelectContentProps extends ParentProps {
|
|
4
|
+
/**
|
|
5
|
+
* 还没有选中值时(占位符状态)的首选摆放位置,默认 'bottom-start'。
|
|
6
|
+
* 一旦有选中值,定位策略会切换成"选中项对齐"(细节见 SelectContent.utils.ts
|
|
7
|
+
* 里的 alignSelectedItem),这个 prop 在那种情况下不生效——这也是 SelectContent
|
|
8
|
+
* 没有跟 TooltipContent 一样改成 side/align 两个独立 prop 的原因:
|
|
9
|
+
* "选中项对齐"这套定位模型本身就没有 side 的概念(面板不是贴在 trigger
|
|
10
|
+
* 的某一侧,而是让选中项本身盖在 trigger 上),用 side/align 强行套用
|
|
11
|
+
* 反而不准确。
|
|
12
|
+
*/
|
|
13
|
+
placement?: Placement;
|
|
14
|
+
/** 距视口边缘的最小间距(像素),用于 flip/shift 的边界检测,默认 8 */
|
|
15
|
+
collisionPadding?: number;
|
|
16
|
+
class?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 其余任意原生 div 属性都会原样透传到最终渲染的元素上——和 SelectTrigger
|
|
19
|
+
* 是同一套约定。
|
|
20
|
+
*/
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Middleware, Placement } from '../../../lib';
|
|
2
|
+
export interface BuildSelectMiddlewareOptions {
|
|
3
|
+
/** 当前是否已经有选中值——决定走"选中项对齐"还是普通的贴边下拉 */
|
|
4
|
+
hasValue: boolean;
|
|
5
|
+
selectedValue: () => string | undefined;
|
|
6
|
+
/** 真正会滚动、承载选项列表的那个元素(内层),不是外层的定位容器 */
|
|
7
|
+
scrollElement: () => HTMLElement | undefined;
|
|
8
|
+
placement: Placement;
|
|
9
|
+
collisionPadding: number;
|
|
10
|
+
onAvailableHeightChange: (availableHeight: number) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Select 面板的定位管线,按"有没有选中值"分两套配方:
|
|
14
|
+
*
|
|
15
|
+
* - **有选中值**:matchWidth → size(算可用高度,配合 overflow:auto 做滚动——
|
|
16
|
+
* 和 Tooltip 不同,下拉列表内容天然可能很长,滚动是合理需求,不是过度设计)
|
|
17
|
+
* → itemAlign(核心:选中项盖在 trigger 上)→ shift(只处理横向,纵向已经被
|
|
18
|
+
* itemAlign 自己夹过了)→ containingBlockOffset。
|
|
19
|
+
* - **没有选中值**(占位符状态):offset → flip → shift → matchWidth → size
|
|
20
|
+
* → containingBlockOffset,这是标准的"贴边下拉"策略,和 Dropdown 用的是
|
|
21
|
+
* 同一套思路。
|
|
22
|
+
*
|
|
23
|
+
* 顺序不能随便换:size 必须在 shift/itemAlign 之后(要用最终坐标反推可用高度),
|
|
24
|
+
* containingBlockOffset 必须在所有其他 middleware 之后。
|
|
25
|
+
*
|
|
26
|
+
* SelectContent.tsx 里把面板拆成了两层:外层(position:fixed,负责 translate(x,y)
|
|
27
|
+
* 定位)和内层(真正的样式、overflow:auto 滚动、data-state 驱动的进出场动画)。
|
|
28
|
+
* 这不是为了美观随手拆的——同一个元素如果既承担定位用的 transform:translate(),
|
|
29
|
+
* 又承担 zoom-in-95 这类靠 transform:scale() 做的动画,CSS 的 transform 一个
|
|
30
|
+
* 元素只能有一份,动画播放期间会完全接管这个属性,把定位用的 translate(x,y)
|
|
31
|
+
* 整个顶掉,表现为"入场从视口左上角飞入、退场飞向左上角消失"这种明显错误的
|
|
32
|
+
* 观感(这正是这个组件早期实现踩过的一个真实 bug)。scrollElement 传的是
|
|
33
|
+
* 内层(真正会滚动的那个),不是 state.elements.floating(外层,它自己不滚动)。
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSelectMiddleware(options: BuildSelectMiddlewareOptions): Middleware[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getViewportBoundary as e } from "../../../lib/positioner/utils/dom.js";
|
|
2
|
+
import { offset as t } from "../../../lib/positioner/middleware/offset.js";
|
|
3
|
+
import { shift as n } from "../../../lib/positioner/middleware/shift.js";
|
|
4
|
+
import { flip as r } from "../../../lib/positioner/middleware/flip.js";
|
|
5
|
+
import { size as i } from "../../../lib/positioner/middleware/size.js";
|
|
6
|
+
import { hide as a } from "../../../lib/positioner/middleware/hide.js";
|
|
7
|
+
import { containingBlockOffset as o } from "../../../lib/positioner/middleware/containing-block-offset.js";
|
|
8
|
+
//#region src/components/select/SelectContent/SelectContent.utils.ts
|
|
9
|
+
function s() {
|
|
10
|
+
return {
|
|
11
|
+
name: "matchWidth",
|
|
12
|
+
fn(e) {
|
|
13
|
+
return { data: { width: e.rects.reference.width } };
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function c(t, n) {
|
|
18
|
+
return {
|
|
19
|
+
name: "itemAlign",
|
|
20
|
+
fn(r) {
|
|
21
|
+
let i = t(), a = n();
|
|
22
|
+
if (i === void 0 || !a) return {};
|
|
23
|
+
let o = a.querySelector(`[data-value="${CSS.escape(i)}"]`);
|
|
24
|
+
if (!o) return {};
|
|
25
|
+
let { rects: s, strategy: c } = r, l = e(c, 8), u = o.offsetTop, d = o.offsetHeight, f = a.scrollHeight, p = s.reference.y + s.reference.height / 2 - d / 2, m = u, h = f - u - d, g = p - l.y, _ = l.y + l.height - (p + d), v = Math.max(0, Math.min(m, g)), y = Math.max(0, Math.min(h, _)), b = p - v, x = getComputedStyle(a), S = Number.parseFloat(x.borderTopWidth) || 0, C = Number.parseFloat(x.borderBottomWidth) || 0, w = v + d + y + S + C;
|
|
26
|
+
return a.scrollTop = m - v, {
|
|
27
|
+
x: s.reference.x,
|
|
28
|
+
y: b,
|
|
29
|
+
data: {
|
|
30
|
+
aligned: !0,
|
|
31
|
+
panelHeight: w
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function l(e) {
|
|
38
|
+
return e.hasValue ? [
|
|
39
|
+
s(),
|
|
40
|
+
c(e.selectedValue, e.scrollElement),
|
|
41
|
+
n({
|
|
42
|
+
padding: e.collisionPadding,
|
|
43
|
+
mainAxis: !1,
|
|
44
|
+
crossAxis: !0
|
|
45
|
+
}),
|
|
46
|
+
a(),
|
|
47
|
+
o()
|
|
48
|
+
] : [
|
|
49
|
+
t(4),
|
|
50
|
+
r(),
|
|
51
|
+
n({ padding: e.collisionPadding }),
|
|
52
|
+
s(),
|
|
53
|
+
i({
|
|
54
|
+
padding: e.collisionPadding,
|
|
55
|
+
apply({ availableHeight: t }) {
|
|
56
|
+
e.onAvailableHeightChange(t);
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
a(),
|
|
60
|
+
o()
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { l as createSelectMiddleware };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=SelectContent.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectContent.utils.js","names":[],"sources":["../../../../src/components/select/SelectContent/SelectContent.utils.ts"],"sourcesContent":["import {\n offset,\n flip,\n shift,\n size,\n hide,\n containingBlockOffset,\n type Middleware,\n type Placement,\n} from \"~/lib\";\nimport { getViewportBoundary } from \"~/lib/positioner/utils/dom\";\n\n/** 让浮层宽度和 trigger 对齐——不改坐标,只把 reference 宽度透传给上层当 style.width 用 */\nfunction matchReferenceWidth(): Middleware {\n return {\n name: \"matchWidth\",\n fn(state) {\n return { data: { width: state.rects.reference.width } };\n },\n };\n}\n\n/**\n * \"选中项对齐\":让已选中的那一项精确覆盖在 trigger 上方,这是原生 <select>/\n * shadcn Select 重新打开时的经典效果,和 offset+flip+shift 是完全不同的定位\n * 策略,所以单独写一个 middleware,而不是复用 shift/flip。\n *\n * 核心思路是把\"面板高度、面板位置、列表滚动量\"三者一起算,而不是先假设一个\n * 面板高度、算完位置再回头夹 scrollTop——后者试过两版都有遗留问题(面板和\n * trigger 之间留一截空隙、或者选中项因为 scrollTop 静默越界被推到面板边缘)。\n *\n * 具体算法:选中项\"理应\"贴着 trigger 垂直居中。以这个位置为基准,\n * 分别看\"上方\"和\"下方\"能展开多少:\n * - 上方能展开的量 = min(选中项上方还有多少内容, 视口上方还剩多少空间)\n * - 下方能展开的量 = min(选中项下方还有多少内容, 视口下方还剩多少空间)\n * 面板高度就是\"上方展开量 + 选中项自身高度 + 下方展开量\",面板顶部位置和\n * 滚动量都由这两个\"展开量\"直接算出来,不需要再额外夹取——因为:\n * - 展开量永远不会超过\"内容还剩多少\",所以 scrollTop 永远落在合法范围内;\n * - 展开量永远不会超过\"视口还剩多少空间\",所以面板永远不会超出视口;\n * - 选中项贴 trigger 是这个算法的出发点,天然成立,不需要单独判断\"要不要\n * 贴 trigger\"。\n * 对列表两端的项(第一项/最后一项)会自动退化成正确的行为:比如选中第一项,\n * \"上方还有多少内容\"是 0,上方展开量恒为 0,面板顶部直接落在选中项该在的\n * 位置,不会凭空往上多留空间。\n */\nfunction alignSelectedItem(\n getValue: () => string | undefined,\n getScrollElement: () => HTMLElement | undefined,\n): Middleware {\n return {\n name: \"itemAlign\",\n fn(state) {\n const value = getValue();\n const scrollEl = getScrollElement();\n if (value === undefined || !scrollEl) return {};\n\n const itemEl = scrollEl.querySelector<HTMLElement>(\n `[data-value=\"${CSS.escape(value)}\"]`,\n );\n if (!itemEl) return {};\n\n const { rects, strategy } = state;\n const boundary = getViewportBoundary(strategy, 8);\n\n const itemOffsetTop = itemEl.offsetTop;\n const itemHeight = itemEl.offsetHeight;\n const scrollHeight = scrollEl.scrollHeight;\n\n // 选中项理想情况下应该落在的位置:和 trigger 垂直居中对齐\n const idealItemTop =\n rects.reference.y + rects.reference.height / 2 - itemHeight / 2;\n\n const contentAbove = itemOffsetTop; // 选中项上方还有多少内容(px)\n const contentBelow = scrollHeight - itemOffsetTop - itemHeight; // 下方还有多少内容\n const spaceAbove = idealItemTop - boundary.y; // 从理想位置到视口顶部的可用空间\n const spaceBelow =\n boundary.y + boundary.height - (idealItemTop + itemHeight); // 到视口底部的可用空间\n\n const expandAbove = Math.max(0, Math.min(contentAbove, spaceAbove));\n const expandBelow = Math.max(0, Math.min(contentBelow, spaceBelow));\n\n const panelTop = idealItemTop - expandAbove;\n // panelHeight 最终会作为 CSS max-height 写到 scrollEl 上。scrollEl\n // 有 border(类名里带 border),而 Tailwind preflight 会把 box-sizing\n // 设为 border-box,此时 max-height 约束的是 border-box 高度;但\n // scrollHeight 只包含内容 + padding、不包含 border。直接拿\n // scrollHeight 当 max-height 会让 clientHeight 比 scrollHeight 少\n // 一个上下 border 的宽度,列表本来刚好能放下时也会多出 1~2px 的\n // overflow,从而被 overflow:auto 画出一条纵向滚动条。这里把 border\n // 宽度补回去,保证 border-box 的 max-height 能完整容纳所有内容。\n const scrollStyle = getComputedStyle(scrollEl);\n const borderTop = Number.parseFloat(scrollStyle.borderTopWidth) || 0;\n const borderBottom =\n Number.parseFloat(scrollStyle.borderBottomWidth) || 0;\n const panelHeight =\n expandAbove + itemHeight + expandBelow + borderTop + borderBottom;\n const scrollTop = contentAbove - expandAbove;\n\n // scrollTop 是纯 DOM 副作用,不影响坐标计算本身,这里直接设置是安全的。\n scrollEl.scrollTop = scrollTop;\n\n return {\n x: rects.reference.x,\n y: panelTop,\n data: { aligned: true, panelHeight },\n };\n },\n };\n}\n\nexport interface BuildSelectMiddlewareOptions {\n /** 当前是否已经有选中值——决定走\"选中项对齐\"还是普通的贴边下拉 */\n hasValue: boolean;\n selectedValue: () => string | undefined;\n /** 真正会滚动、承载选项列表的那个元素(内层),不是外层的定位容器 */\n scrollElement: () => HTMLElement | undefined;\n placement: Placement;\n collisionPadding: number;\n onAvailableHeightChange: (availableHeight: number) => void;\n}\n\n/**\n * Select 面板的定位管线,按\"有没有选中值\"分两套配方:\n *\n * - **有选中值**:matchWidth → size(算可用高度,配合 overflow:auto 做滚动——\n * 和 Tooltip 不同,下拉列表内容天然可能很长,滚动是合理需求,不是过度设计)\n * → itemAlign(核心:选中项盖在 trigger 上)→ shift(只处理横向,纵向已经被\n * itemAlign 自己夹过了)→ containingBlockOffset。\n * - **没有选中值**(占位符状态):offset → flip → shift → matchWidth → size\n * → containingBlockOffset,这是标准的\"贴边下拉\"策略,和 Dropdown 用的是\n * 同一套思路。\n *\n * 顺序不能随便换:size 必须在 shift/itemAlign 之后(要用最终坐标反推可用高度),\n * containingBlockOffset 必须在所有其他 middleware 之后。\n *\n * SelectContent.tsx 里把面板拆成了两层:外层(position:fixed,负责 translate(x,y)\n * 定位)和内层(真正的样式、overflow:auto 滚动、data-state 驱动的进出场动画)。\n * 这不是为了美观随手拆的——同一个元素如果既承担定位用的 transform:translate(),\n * 又承担 zoom-in-95 这类靠 transform:scale() 做的动画,CSS 的 transform 一个\n * 元素只能有一份,动画播放期间会完全接管这个属性,把定位用的 translate(x,y)\n * 整个顶掉,表现为\"入场从视口左上角飞入、退场飞向左上角消失\"这种明显错误的\n * 观感(这正是这个组件早期实现踩过的一个真实 bug)。scrollElement 传的是\n * 内层(真正会滚动的那个),不是 state.elements.floating(外层,它自己不滚动)。\n */\nexport function createSelectMiddleware(\n options: BuildSelectMiddlewareOptions,\n): Middleware[] {\n if (options.hasValue) {\n return [\n matchReferenceWidth(),\n // 注意这里没有 size 中间件:alignSelectedItem 自己已经把面板高度、\n // 位置、滚动量一起算好了(见它的注释),面板高度是通过\n // middlewareData.itemAlign.panelHeight 传出去的,不需要 size 再用\n // \"只看视口空间、不看内容长度\"那套算法独立算一遍——两边算出来的值\n // 一旦不一致,CSS max-height 和定位计算假设的面板高度就会对不上,\n // 重新引入\"选中项没对齐\"这类问题(这正是这次要修的 bug 的根源)。\n alignSelectedItem(options.selectedValue, options.scrollElement),\n shift({\n padding: options.collisionPadding,\n mainAxis: false,\n crossAxis: true,\n }),\n hide(),\n containingBlockOffset(),\n ];\n }\n\n return [\n offset(4),\n flip(),\n shift({ padding: options.collisionPadding }),\n matchReferenceWidth(),\n size({\n padding: options.collisionPadding,\n apply({ availableHeight }) {\n options.onAvailableHeightChange(availableHeight);\n },\n }),\n hide(),\n containingBlockOffset(),\n ];\n}\n"],"mappings":";;;;;;;;AAaA,SAAS,IAAkC;CACzC,OAAO;EACL,MAAM;EACN,GAAG,GAAO;GACR,OAAO,EAAE,MAAM,EAAE,OAAO,EAAM,MAAM,UAAU,MAAM,EAAE;EACxD;CACF;AACF;AAyBA,SAAS,EACP,GACA,GACY;CACZ,OAAO;EACL,MAAM;EACN,GAAG,GAAO;GACR,IAAM,IAAQ,EAAS,GACjB,IAAW,EAAiB;GAClC,IAAI,MAAU,KAAA,KAAa,CAAC,GAAU,OAAO,CAAC;GAE9C,IAAM,IAAS,EAAS,cACtB,gBAAgB,IAAI,OAAO,CAAK,EAAE,GACpC;GACA,IAAI,CAAC,GAAQ,OAAO,CAAC;GAErB,IAAM,EAAE,UAAO,gBAAa,GACtB,IAAW,EAAoB,GAAU,CAAC,GAE1C,IAAgB,EAAO,WACvB,IAAa,EAAO,cACpB,IAAe,EAAS,cAGxB,IACJ,EAAM,UAAU,IAAI,EAAM,UAAU,SAAS,IAAI,IAAa,GAE1D,IAAe,GACf,IAAe,IAAe,IAAgB,GAC9C,IAAa,IAAe,EAAS,GACrC,IACJ,EAAS,IAAI,EAAS,UAAU,IAAe,IAE3C,IAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAc,CAAU,CAAC,GAC5D,IAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAc,CAAU,CAAC,GAE5D,IAAW,IAAe,GAS1B,IAAc,iBAAiB,CAAQ,GACvC,IAAY,OAAO,WAAW,EAAY,cAAc,KAAK,GAC7D,IACJ,OAAO,WAAW,EAAY,iBAAiB,KAAK,GAChD,IACJ,IAAc,IAAa,IAAc,IAAY;GAMvD,OAFA,EAAS,YAHS,IAAe,GAK1B;IACL,GAAG,EAAM,UAAU;IACnB,GAAG;IACH,MAAM;KAAE,SAAS;KAAM;IAAY;GACrC;EACF;CACF;AACF;AAoCA,SAAgB,EACd,GACc;CAqBd,OApBI,EAAQ,WACH;EACL,EAAoB;EAOpB,EAAkB,EAAQ,eAAe,EAAQ,aAAa;EAC9D,EAAM;GACJ,SAAS,EAAQ;GACjB,UAAU;GACV,WAAW;EACb,CAAC;EACD,EAAK;EACL,EAAsB;CACxB,IAGK;EACL,EAAO,CAAC;EACR,EAAK;EACL,EAAM,EAAE,SAAS,EAAQ,iBAAiB,CAAC;EAC3C,EAAoB;EACpB,EAAK;GACH,SAAS,EAAQ;GACjB,MAAM,EAAE,sBAAmB;IACzB,EAAQ,wBAAwB,CAAe;GACjD;EACF,CAAC;EACD,EAAK;EACL,EAAsB;CACxB;AACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SelectContent } from './SelectContent';
|
|
2
|
+
export type { SelectContentProps } from './SelectContent.types';
|
|
3
|
+
export { createSelectMiddleware } from './SelectContent.utils';
|
|
4
|
+
export type { BuildSelectMiddlewareOptions } from './SelectContent.utils';
|
|
5
|
+
export { useSelectContent } from './useSelectContent';
|
|
6
|
+
export type { UseSelectContentResult } from './useSelectContent';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Positioner } from '../../../lib';
|
|
2
|
+
import { useSelectContext } from '../Select/Select.context';
|
|
3
|
+
import { SelectContentProps } from './SelectContent.types';
|
|
4
|
+
export interface UseSelectContentResult {
|
|
5
|
+
ctx: ReturnType<typeof useSelectContext>;
|
|
6
|
+
pos: Positioner;
|
|
7
|
+
/**
|
|
8
|
+
* 面板应该用的最大高度:有选中值时,直接读 alignSelectedItem 自己算好的
|
|
9
|
+
* panelHeight(不经过 size 中间件);没有选中值(占位符状态、走贴边下拉)
|
|
10
|
+
* 时,用 size 中间件算出来的可用空间。两种场景用的是不同的高度来源,
|
|
11
|
+
* 统一在这里合并成一个对外的值,展示层不需要关心具体走的是哪一个。
|
|
12
|
+
*/
|
|
13
|
+
maxHeight: () => number | undefined;
|
|
14
|
+
/** 综合了 open 状态 + hide 中间件的 referenceHidden 判断 */
|
|
15
|
+
isVisible: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 兼容字段:SelectContent 现在关闭时不再卸载 Portal(为了保住 ctx.items 的
|
|
18
|
+
* 注册信息),因此 mounted 已不再被展示层用来控制 <Show>。该值目前等价于
|
|
19
|
+
* isVisible,保留只是为了不破坏已导出的 useSelectContent 返回类型。
|
|
20
|
+
*/
|
|
21
|
+
mounted: () => boolean;
|
|
22
|
+
/** 驱动 data-state 的状态,打开时比 isVisible 晚一帧,给"选中项对齐"这类
|
|
23
|
+
* 需要先读到子元素(选项)才能算准位置的定位策略留出时间。 */
|
|
24
|
+
animationState: () => "open" | "closed";
|
|
25
|
+
contentElement: () => HTMLElement | undefined;
|
|
26
|
+
setContentElement: (el: HTMLElement) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function useSelectContent(props: () => SelectContentProps): UseSelectContentResult;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useSelectContext as e } from "../Select/Select.context.js";
|
|
2
|
+
import { createPositioner as t } from "../../../lib/positioner/create-positioner.js";
|
|
3
|
+
import { createSelectMiddleware as n } from "./SelectContent.utils.js";
|
|
4
|
+
import { createEffect as r, createMemo as i, createSignal as a, onCleanup as o } from "solid-js";
|
|
5
|
+
//#region src/components/select/SelectContent/useSelectContent.ts
|
|
6
|
+
function s(s) {
|
|
7
|
+
let c = e("SelectContent"), [l, u] = a(), [d, f] = a(), p = i(() => c.value() !== void 0), m = t(c.reference, c.floating, {
|
|
8
|
+
placement: () => s().placement ?? "bottom-start",
|
|
9
|
+
strategy: "fixed",
|
|
10
|
+
middleware: () => n({
|
|
11
|
+
hasValue: p(),
|
|
12
|
+
selectedValue: c.value,
|
|
13
|
+
scrollElement: l,
|
|
14
|
+
placement: s().placement ?? "bottom-start",
|
|
15
|
+
collisionPadding: s().collisionPadding ?? 8,
|
|
16
|
+
onAvailableHeightChange: (e) => {
|
|
17
|
+
f(Math.max(e, 120));
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}), h = i(() => {
|
|
21
|
+
if (p()) {
|
|
22
|
+
let e = m.middlewareData().itemAlign?.panelHeight;
|
|
23
|
+
return e === void 0 ? void 0 : Math.max(e, 120);
|
|
24
|
+
}
|
|
25
|
+
return d();
|
|
26
|
+
}), g = i(() => c.open() && !m.middlewareData().hide?.referenceHidden), [_, v] = a("closed");
|
|
27
|
+
return r(() => {
|
|
28
|
+
if (g()) {
|
|
29
|
+
let e = requestAnimationFrame(() => v("open"));
|
|
30
|
+
o(() => cancelAnimationFrame(e));
|
|
31
|
+
} else v("closed");
|
|
32
|
+
}), {
|
|
33
|
+
ctx: c,
|
|
34
|
+
pos: m,
|
|
35
|
+
maxHeight: h,
|
|
36
|
+
isVisible: g,
|
|
37
|
+
mounted: g,
|
|
38
|
+
animationState: _,
|
|
39
|
+
contentElement: l,
|
|
40
|
+
setContentElement: u
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { s as useSelectContent };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=useSelectContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSelectContent.js","names":[],"sources":["../../../../src/components/select/SelectContent/useSelectContent.ts"],"sourcesContent":["import { createSignal, createMemo, createEffect, onCleanup } from \"solid-js\";\nimport { createPositioner, type Positioner } from \"~/lib\";\nimport { useSelectContext } from \"../Select/Select.context\";\nimport { createSelectMiddleware } from \"./SelectContent.utils\";\nimport type { SelectContentProps } from \"./SelectContent.types\";\n\nexport interface UseSelectContentResult {\n ctx: ReturnType<typeof useSelectContext>;\n pos: Positioner;\n /**\n * 面板应该用的最大高度:有选中值时,直接读 alignSelectedItem 自己算好的\n * panelHeight(不经过 size 中间件);没有选中值(占位符状态、走贴边下拉)\n * 时,用 size 中间件算出来的可用空间。两种场景用的是不同的高度来源,\n * 统一在这里合并成一个对外的值,展示层不需要关心具体走的是哪一个。\n */\n maxHeight: () => number | undefined;\n /** 综合了 open 状态 + hide 中间件的 referenceHidden 判断 */\n isVisible: () => boolean;\n /**\n * 兼容字段:SelectContent 现在关闭时不再卸载 Portal(为了保住 ctx.items 的\n * 注册信息),因此 mounted 已不再被展示层用来控制 <Show>。该值目前等价于\n * isVisible,保留只是为了不破坏已导出的 useSelectContent 返回类型。\n */\n mounted: () => boolean;\n /** 驱动 data-state 的状态,打开时比 isVisible 晚一帧,给\"选中项对齐\"这类\n * 需要先读到子元素(选项)才能算准位置的定位策略留出时间。 */\n animationState: () => \"open\" | \"closed\";\n contentElement: () => HTMLElement | undefined;\n setContentElement: (el: HTMLElement) => void;\n}\n\nexport function useSelectContent(\n props: () => SelectContentProps,\n): UseSelectContentResult {\n const ctx = useSelectContext(\"SelectContent\");\n const [contentElement, setContentElement] = createSignal<HTMLElement>();\n const [fallbackMaxHeight, setFallbackMaxHeight] = createSignal<number>();\n\n const hasValue = createMemo(() => ctx.value() !== undefined);\n\n const pos = createPositioner(ctx.reference, ctx.floating, {\n placement: () => props().placement ?? \"bottom-start\",\n strategy: \"fixed\",\n middleware: () =>\n createSelectMiddleware({\n hasValue: hasValue(),\n selectedValue: ctx.value,\n scrollElement: contentElement,\n placement: props().placement ?? \"bottom-start\",\n collisionPadding: props().collisionPadding ?? 8,\n onAvailableHeightChange: (availableHeight) => {\n setFallbackMaxHeight(Math.max(availableHeight, 120));\n },\n }),\n });\n\n const maxHeight = createMemo(() => {\n if (hasValue()) {\n const panelHeight = pos.middlewareData().itemAlign?.panelHeight as\n | number\n | undefined;\n return panelHeight !== undefined ? Math.max(panelHeight, 120) : undefined;\n }\n return fallbackMaxHeight();\n });\n\n const isVisible = createMemo(\n () => ctx.open() && !pos.middlewareData().hide?.referenceHidden,\n );\n\n // --- 打开时延迟一帧再触发动画 class ---\n // \"选中项对齐\"策略要读子元素(SelectItem 渲染出来的 <li data-value>)才能\n // 算出正确的位置,如果动画在这次定位算完之前就开始播放,面板会在动画播到\n // 一半时才\"跳\"到正确位置——等一帧,给这次定位计算留出时间(细节和\n // production-tooltip 里箭头位置的处理是同一个道理)。\n const [animationState, setAnimationState] = createSignal<\"open\" | \"closed\">(\n \"closed\",\n );\n\n createEffect(() => {\n if (isVisible()) {\n const raf = requestAnimationFrame(() => setAnimationState(\"open\"));\n onCleanup(() => cancelAnimationFrame(raf));\n } else {\n setAnimationState(\"closed\");\n }\n });\n\n // SelectContent 关闭时不再卸载子节点(否则 ctx.items 会被清空,SelectValue\n // 读不到 label),因此 mounted 已经没有\"延迟卸载/立即卸载\"的语义,直接跟\n // isVisible 保持一致即可。保留该字段只是为了兼容已导出的 useSelectContent\n // 返回类型;新的展示层逻辑一律使用 isVisible。\n const mounted = isVisible;\n\n return {\n ctx,\n pos,\n maxHeight,\n isVisible,\n mounted,\n animationState,\n contentElement,\n setContentElement,\n };\n}\n"],"mappings":";;;;;AA+BA,SAAgB,EACd,GACwB;CACxB,IAAM,IAAM,EAAiB,eAAe,GACtC,CAAC,GAAgB,KAAqB,EAA0B,GAChE,CAAC,GAAmB,KAAwB,EAAqB,GAEjE,IAAW,QAAiB,EAAI,MAAM,MAAM,KAAA,CAAS,GAErD,IAAM,EAAiB,EAAI,WAAW,EAAI,UAAU;EACxD,iBAAiB,EAAM,CAAC,CAAC,aAAa;EACtC,UAAU;EACV,kBACE,EAAuB;GACrB,UAAU,EAAS;GACnB,eAAe,EAAI;GACnB,eAAe;GACf,WAAW,EAAM,CAAC,CAAC,aAAa;GAChC,kBAAkB,EAAM,CAAC,CAAC,oBAAoB;GAC9C,0BAA0B,MAAoB;IAC5C,EAAqB,KAAK,IAAI,GAAiB,GAAG,CAAC;GACrD;EACF,CAAC;CACL,CAAC,GAEK,IAAY,QAAiB;EACjC,IAAI,EAAS,GAAG;GACd,IAAM,IAAc,EAAI,eAAe,CAAC,CAAC,WAAW;GAGpD,OAAO,MAAgB,KAAA,IAAyC,KAAA,IAA7B,KAAK,IAAI,GAAa,GAAG;EAC9D;EACA,OAAO,EAAkB;CAC3B,CAAC,GAEK,IAAY,QACV,EAAI,KAAK,KAAK,CAAC,EAAI,eAAe,CAAC,CAAC,MAAM,eAClD,GAOM,CAAC,GAAgB,KAAqB,EAC1C,QACF;CAiBA,OAfA,QAAmB;EACjB,IAAI,EAAU,GAAG;GACf,IAAM,IAAM,4BAA4B,EAAkB,MAAM,CAAC;GACjE,QAAgB,qBAAqB,CAAG,CAAC;EAC3C,OACE,EAAkB,QAAQ;CAE9B,CAAC,GAQM;EACL;EACA;EACA;EACA;EACA,SAAA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { insert as e, template as t } from "solid-js/web";
|
|
2
|
+
//#region src/components/select/SelectGroup/SelectGroup.tsx
|
|
3
|
+
var n = /*#__PURE__*/ t("<li role=group>");
|
|
4
|
+
function r(t) {
|
|
5
|
+
return (() => {
|
|
6
|
+
var r = n();
|
|
7
|
+
return e(r, () => t.children), r;
|
|
8
|
+
})();
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { r as SelectGroup };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=SelectGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectGroup.js","names":["ParentProps","SelectGroup","props","_el$","_tmpl$","_$insert","children"],"sources":["../../../../src/components/select/SelectGroup/SelectGroup.tsx"],"sourcesContent":["import type { ParentProps } from \"solid-js\";\n\nexport function SelectGroup(props: ParentProps) {\n return <li role=\"group\">{props.children}</li>;\n}\n"],"mappings":";;;AAEA,SAAgBC,EAAYC,GAAoB;CAC9C,cAAA;EAAA,IAAAC,IAAAC,EAAA;EAAuC,OAAvCC,EAAAF,SAAyBD,EAAMI,QAAQ,GAAAH;CAAA,EAAA,CAAA;AACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SelectGroup } from './SelectGroup';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { clsx as e } from "../../../utils/clsx.js";
|
|
2
|
+
import { useSelectItem as t } from "./useSelectItem.js";
|
|
3
|
+
import { createComponent as n, insert as r, mergeProps as i, spread as a, template as o, use as s } from "solid-js/web";
|
|
4
|
+
import { Show as c, onCleanup as l, splitProps as u } from "solid-js";
|
|
5
|
+
import { Check as d } from "lucide-solid";
|
|
6
|
+
//#region src/components/select/SelectItem/SelectItem.tsx
|
|
7
|
+
var f = /*#__PURE__*/ o("<div role=option><span class=\"absolute right-2 flex h-3.5 w-3.5 items-center justify-center\">");
|
|
8
|
+
function p(o) {
|
|
9
|
+
let { isSelected: p, isActive: m, select: h, onMouseEnter: g } = t(() => o), [_, v] = u(o, [
|
|
10
|
+
"value",
|
|
11
|
+
"label",
|
|
12
|
+
"disabled",
|
|
13
|
+
"class",
|
|
14
|
+
"children"
|
|
15
|
+
]), y = (e) => {
|
|
16
|
+
let t = () => h(), n = () => g();
|
|
17
|
+
e.addEventListener("click", t), e.addEventListener("mouseenter", n), l(() => {
|
|
18
|
+
e.removeEventListener("click", t), e.removeEventListener("mouseenter", n);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
return (() => {
|
|
22
|
+
var t = f(), o = t.firstChild;
|
|
23
|
+
return s(y, t), a(t, i({
|
|
24
|
+
get tabIndex() {
|
|
25
|
+
return p() ? 0 : -1;
|
|
26
|
+
},
|
|
27
|
+
get "data-value"() {
|
|
28
|
+
return _.value;
|
|
29
|
+
},
|
|
30
|
+
get "aria-selected"() {
|
|
31
|
+
return p();
|
|
32
|
+
},
|
|
33
|
+
get "aria-disabled"() {
|
|
34
|
+
return _.disabled;
|
|
35
|
+
},
|
|
36
|
+
get class() {
|
|
37
|
+
return e("relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-6 text-sm outline-none", m() && "bg-neutral-100 dark:bg-neutral-800", _.disabled && "pointer-events-none opacity-50", _.class);
|
|
38
|
+
}
|
|
39
|
+
}, v), !1, !0), r(t, () => _.children ?? _.label ?? _.value, o), r(o, n(c, {
|
|
40
|
+
get when() {
|
|
41
|
+
return p();
|
|
42
|
+
},
|
|
43
|
+
get children() {
|
|
44
|
+
return n(d, { size: 14 });
|
|
45
|
+
}
|
|
46
|
+
})), t;
|
|
47
|
+
})();
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { p as SelectItem };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=SelectItem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectItem.js","names":["Show","splitProps","onCleanup","useSelectItem","SelectItemProps","clsx","Check","SelectItem","props","isSelected","isActive","select","onMouseEnter","local","rest","attachListeners","el","HTMLDivElement","handleClick","handleMouseEnter","addEventListener","removeEventListener","_el$","_tmpl$","_el$2","firstChild","_$use","_$spread","_$mergeProps","tabIndex","data-value","value","aria-selected","aria-disabled","disabled","class","_$insert","children","label","_$createComponent","when","size"],"sources":["../../../../src/components/select/SelectItem/SelectItem.tsx"],"sourcesContent":["import { Show, splitProps, onCleanup } from \"solid-js\";\nimport { useSelectItem } from \"./useSelectItem\";\nimport type { SelectItemProps } from \"./SelectItem.types\";\nimport { clsx } from \"~/utils\";\nimport { Check } from \"lucide-solid\";\n\nexport function SelectItem(props: SelectItemProps) {\n const { isSelected, isActive, select, onMouseEnter } = useSelectItem(\n () => props,\n );\n\n const [local, rest] = splitProps(props, [\n \"value\",\n \"label\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n // click/mouseenter 走 addEventListener(不是 JSX onClick),和 SelectTrigger\n // 是同一套约定,不占用 onXxx prop 名,调用方自己传的 onClick 不会被覆盖。\n const attachListeners = (el: HTMLDivElement) => {\n const handleClick = () => select();\n const handleMouseEnter = () => onMouseEnter();\n el.addEventListener(\"click\", handleClick);\n el.addEventListener(\"mouseenter\", handleMouseEnter);\n onCleanup(() => {\n el.removeEventListener(\"click\", handleClick);\n el.removeEventListener(\"mouseenter\", handleMouseEnter);\n });\n };\n\n return (\n <div\n ref={attachListeners}\n role=\"option\"\n tabIndex={isSelected() ? 0 : -1}\n data-value={local.value}\n aria-selected={isSelected()}\n aria-disabled={local.disabled}\n class={clsx(\n \"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-6 text-sm outline-none\",\n isActive() && \"bg-neutral-100 dark:bg-neutral-800\",\n local.disabled && \"pointer-events-none opacity-50\",\n local.class,\n )}\n {...rest}\n >\n {local.children ?? local.label ?? local.value}\n <span class=\"absolute right-2 flex h-3.5 w-3.5 items-center justify-center\">\n <Show when={isSelected()}>\n <Check size={14} />\n </Show>\n </span>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAMA,SAAgBO,EAAWC,GAAwB;CACjD,IAAM,EAAEC,eAAYC,aAAUC,WAAQC,oBAAiBT,QAC/CK,CACR,GAEM,CAACK,GAAOC,KAAQb,EAAWO,GAAO;EACtC;EACA;EACA;EACA;EACA;CAAU,CACX,GAIKO,KAAmBC,MAAuB;EAC9C,IAAME,UAAoBP,EAAO,GAC3BQ,UAAyBP,EAAa;EAG5CV,AAFAc,EAAGI,iBAAiB,SAASF,CAAW,GACxCF,EAAGI,iBAAiB,cAAcD,CAAgB,GAClDjB,QAAgB;GAEdc,AADAA,EAAGK,oBAAoB,SAASH,CAAW,GAC3CF,EAAGK,oBAAoB,cAAcF,CAAgB;EACvD,CAAC;CACH;CAEA,cAAA;EAAA,IAAAG,IAAAC,EAAA,GAAAC,IAAAF,EAAAG;EAmBuB,OAnBvBC,EAESX,GAAeO,CAAA,GAAAK,EAAAL,GAAAM,EAAA;GAAA,IAEpBC,WAAQ;IAAA,OAAEpB,EAAW,IAAI,IAAI;GAAE;GAAA,IAAA,eAAA;IAAA,OACnBI,EAAMkB;GAAK;GAAA,IAAA,kBAAA;IAAA,OACRtB,EAAW;GAAC;GAAA,IAAA,kBAAA;IAAA,OACZI,EAAMqB;GAAQ;GAAA,IAAA,QAAA;IAAA,OACtB7B,EACL,0GACAK,EAAS,KAAK,sCACdG,EAAMqB,YAAY,kCAClBrB,EAAMsB,KACR;GAAC;EAAA,GACGrB,CAAI,GAAA,IAAA,EAAA,GAAAsB,EAAAd,SAEPT,EAAMwB,YAAYxB,EAAMyB,SAASzB,EAAMkB,OAAKP,CAAA,GAAAY,EAAAZ,GAAAe,EAE1CvC,GAAI;GAAA,IAACwC,OAAI;IAAA,OAAE/B,EAAW;GAAC;GAAA,IAAA4B,WAAA;IAAA,OAAAE,EACrBjC,GAAK,EAACmC,MAAM,GAAE,CAAA;GAAA;EAAA,CAAA,CAAA,GAAAnB;CAAA,EAAA,CAAA;AAKzB"}
|