dishui 0.0.71 → 0.0.73
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/Button/index.d.ts +4 -4
- package/dist/components/Button/index.js +18 -17
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Card/Metric.d.ts +17 -0
- package/dist/components/Card/Metric2.js +50 -0
- package/dist/components/Card/Metric2.js.map +1 -0
- package/dist/components/Card/index.d.ts +3 -0
- package/dist/components/Card/index.js +118 -117
- package/dist/components/Card/index.js.map +1 -1
- package/dist/components/Group/index.d.ts +2 -0
- package/dist/components/GroupButton/index.d.ts +16 -0
- package/dist/components/GroupButton/index.js +41 -0
- package/dist/components/GroupButton/index.js.map +1 -0
- package/dist/components/GroupedTable/index.d.ts +38 -0
- package/dist/components/GroupedTable/index.js +92 -0
- package/dist/components/GroupedTable/index.js.map +1 -0
- package/dist/components/Layout/index.d.ts +4 -2
- package/dist/components/Layout/index.js +1 -1
- package/dist/components/Layout/index.js.map +1 -1
- package/dist/components/NavigationList/index.d.ts +17 -0
- package/dist/components/NavigationList/index.js +48 -0
- package/dist/components/NavigationList/index.js.map +1 -0
- package/dist/components/Section/index.d.ts +28 -0
- package/dist/components/Section/index.js +58 -0
- package/dist/components/Section/index.js.map +1 -0
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.js +83 -82
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/SidebarToggleButton/index.d.ts +16 -1
- package/dist/components/SidebarToggleButton/index.js +90 -30
- package/dist/components/SidebarToggleButton/index.js.map +1 -1
- package/dist/components/Skeleton/index.d.ts +7 -0
- package/dist/components/Skeleton/index.js +21 -0
- package/dist/components/Skeleton/index.js.map +1 -0
- package/dist/components/Tree/Tree.js +5 -2
- package/dist/components/Tree/Tree.js.map +1 -1
- package/dist/components/pro/RichTextEditor/AIRewrite.js +114 -72
- package/dist/components/pro/RichTextEditor/AIRewrite.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BlockHandle.js +315 -196
- package/dist/components/pro/RichTextEditor/BlockHandle.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.d.ts +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js +69 -33
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPanel.d.ts +2 -1
- package/dist/components/pro/RichTextEditor/CommentPanel.js +100 -81
- package/dist/components/pro/RichTextEditor/CommentPanel.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPopover.d.ts +2 -1
- package/dist/components/pro/RichTextEditor/CommentPopover.js +92 -68
- package/dist/components/pro/RichTextEditor/CommentPopover.js.map +1 -1
- package/dist/components/pro/RichTextEditor/RichTextEditor.js +24 -22
- package/dist/components/pro/RichTextEditor/RichTextEditor.js.map +1 -1
- package/dist/components/pro/RichTextEditor/SearchReplace.d.ts +7 -0
- package/dist/components/pro/RichTextEditor/SlashMenu.js +136 -73
- package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.d.ts +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js +346 -313
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js +114 -146
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CommentMark.js +2 -2
- package/dist/components/pro/RichTextEditor/extensions/CommentMark.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js +178 -163
- package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FontSize.js +8 -5
- package/dist/components/pro/RichTextEditor/extensions/FontSize.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.d.ts +6 -0
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js +85 -74
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js.map +1 -1
- package/dist/components/pro/RichTextEditor/searchUtils.d.ts +23 -0
- package/dist/contexts/AppContext.js +47 -45
- package/dist/contexts/AppContext.js.map +1 -1
- package/dist/dishui.css +1 -1
- package/dist/index-nostyled.d.ts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +119 -114
- package/dist/layouts/MainLayout.js +9 -10
- package/dist/layouts/MainLayout.js.map +1 -1
- package/dist/layouts/Sidebar.d.ts +9 -0
- package/dist/layouts/Sidebar.js +99 -80
- package/dist/layouts/Sidebar.js.map +1 -1
- package/dist/layouts/headbar/Headbar.d.ts +1 -1
- package/dist/layouts/headbar/Headbar.js +2 -2
- package/dist/layouts/headbar/Headbar.js.map +1 -1
- package/dist/lite.d.ts +19 -1
- package/dist/lite.js +17 -8
- package/dist/styles/themeStyles.js +6 -6
- package/dist/styles/themeStyles.js.map +1 -1
- package/dist/utils/i18n.d.ts +3 -0
- package/dist/utils/i18n.js +78 -0
- package/dist/utils/i18n.js.map +1 -1
- package/dist/utils/menu.js +23 -6
- package/dist/utils/menu.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type SectionProps = React.HTMLAttributes<HTMLElement> & {
|
|
3
|
+
/** 区块标题(渲染为 h2) */
|
|
4
|
+
title?: React.ReactNode;
|
|
5
|
+
/** 标题下方的辅助说明 */
|
|
6
|
+
description?: React.ReactNode;
|
|
7
|
+
/** 头部右侧操作区(按钮、筛选等) */
|
|
8
|
+
actions?: React.ReactNode;
|
|
9
|
+
/** 底部脚注区 */
|
|
10
|
+
footer?: React.ReactNode;
|
|
11
|
+
/** `card`(默认,带边框/圆角/背景)| `plain`(无外框,仅分组语义与间距) */
|
|
12
|
+
variant?: 'card' | 'plain';
|
|
13
|
+
/** 移除主体内边距(用于表格等需要贴边的内容) */
|
|
14
|
+
flush?: boolean;
|
|
15
|
+
/** 标题层级,默认 2 */
|
|
16
|
+
headingLevel?: 2 | 3 | 4;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
export type SectionSlotProps = React.HTMLAttributes<HTMLDivElement>;
|
|
20
|
+
interface SectionCompound extends React.FC<SectionProps> {
|
|
21
|
+
Header: React.FC<SectionSlotProps>;
|
|
22
|
+
Body: React.FC<SectionSlotProps & {
|
|
23
|
+
flush?: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
Footer: React.FC<SectionSlotProps>;
|
|
26
|
+
}
|
|
27
|
+
declare const SectionWithCompounds: SectionCompound;
|
|
28
|
+
export default SectionWithCompounds;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
/* empty css */
|
|
3
|
+
import "react";
|
|
4
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/Section/index.tsx
|
|
6
|
+
var r = ({ className: n, children: r, ...i }) => /* @__PURE__ */ t("div", {
|
|
7
|
+
...i,
|
|
8
|
+
className: e("blade-section__header", n),
|
|
9
|
+
children: r
|
|
10
|
+
}), i = ({ className: n, children: r, ...i }) => {
|
|
11
|
+
let { flush: a, ...o } = i;
|
|
12
|
+
return /* @__PURE__ */ t("div", {
|
|
13
|
+
...o,
|
|
14
|
+
className: e("blade-section__body", a && "blade-section__body--flush", n),
|
|
15
|
+
children: r
|
|
16
|
+
});
|
|
17
|
+
}, a = ({ className: n, children: r, ...i }) => /* @__PURE__ */ t("div", {
|
|
18
|
+
...i,
|
|
19
|
+
className: e("blade-section__footer", n),
|
|
20
|
+
children: r
|
|
21
|
+
}), o = ({ title: r, description: i, actions: a, footer: o, variant: s = "card", flush: c = !1, headingLevel: l = 2, className: u, children: d, ...f }) => {
|
|
22
|
+
let p = `h${l}`, m = r != null || i != null || a != null;
|
|
23
|
+
return /* @__PURE__ */ n("section", {
|
|
24
|
+
...f,
|
|
25
|
+
className: e("blade-section", s === "plain" && "blade-section--plain", u),
|
|
26
|
+
children: [
|
|
27
|
+
m && /* @__PURE__ */ n("div", {
|
|
28
|
+
className: "blade-section__header",
|
|
29
|
+
children: [/* @__PURE__ */ n("div", {
|
|
30
|
+
className: "blade-section__headings",
|
|
31
|
+
children: [r != null && /* @__PURE__ */ t(p, {
|
|
32
|
+
className: "blade-section__title",
|
|
33
|
+
children: r
|
|
34
|
+
}), i != null && /* @__PURE__ */ t("p", {
|
|
35
|
+
className: "blade-section__description",
|
|
36
|
+
children: i
|
|
37
|
+
})]
|
|
38
|
+
}), a != null && /* @__PURE__ */ t("div", {
|
|
39
|
+
className: "blade-section__actions",
|
|
40
|
+
children: a
|
|
41
|
+
})]
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ t("div", {
|
|
44
|
+
className: e("blade-section__body", c && "blade-section__body--flush"),
|
|
45
|
+
children: d
|
|
46
|
+
}),
|
|
47
|
+
o != null && /* @__PURE__ */ t("div", {
|
|
48
|
+
className: "blade-section__footer",
|
|
49
|
+
children: o
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
o.Header = r, o.Body = i, o.Footer = a;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { o as default };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Section/index.tsx"],"sourcesContent":["import React from 'react';\nimport { cn } from '../../utils/cn';\nimport './Section.css';\n\nexport type SectionProps = React.HTMLAttributes<HTMLElement> & {\n /** 区块标题(渲染为 h2) */\n title?: React.ReactNode;\n /** 标题下方的辅助说明 */\n description?: React.ReactNode;\n /** 头部右侧操作区(按钮、筛选等) */\n actions?: React.ReactNode;\n /** 底部脚注区 */\n footer?: React.ReactNode;\n /** `card`(默认,带边框/圆角/背景)| `plain`(无外框,仅分组语义与间距) */\n variant?: 'card' | 'plain';\n /** 移除主体内边距(用于表格等需要贴边的内容) */\n flush?: boolean;\n /** 标题层级,默认 2 */\n headingLevel?: 2 | 3 | 4;\n children?: React.ReactNode;\n};\n\nexport type SectionSlotProps = React.HTMLAttributes<HTMLDivElement>;\n\nconst SectionHeader = ({ className, children, ...rest }: SectionSlotProps) => (\n <div {...rest} className={cn('blade-section__header', className)}>{children}</div>\n);\n\nconst SectionBody = ({ className, children, ...rest }: SectionSlotProps & { flush?: boolean }) => {\n const { flush, ...divProps } = rest as SectionSlotProps & { flush?: boolean };\n return (\n <div {...divProps} className={cn('blade-section__body', flush && 'blade-section__body--flush', className)}>\n {children}\n </div>\n );\n};\n\nconst SectionFooter = ({ className, children, ...rest }: SectionSlotProps) => (\n <div {...rest} className={cn('blade-section__footer', className)}>{children}</div>\n);\n\ninterface SectionCompound extends React.FC<SectionProps> {\n Header: React.FC<SectionSlotProps>;\n Body: React.FC<SectionSlotProps & { flush?: boolean }>;\n Footer: React.FC<SectionSlotProps>;\n}\n\n/**\n * Section 区块容器\n * ----------------------------------------------------------------------------\n * 用于按区块封装内容:可传 `title` / `description` / `actions` / `footer` 走\n * 便捷模式,或用 `Section.Header` / `Section.Body` / `Section.Footer` 复合组合。\n */\nconst Section = ({\n title, description, actions, footer,\n variant = 'card', flush = false, headingLevel = 2,\n className, children, ...rest\n}: SectionProps) => {\n const HeadingTag = `h${headingLevel}` as 'h2' | 'h3' | 'h4';\n const hasHeader = title != null || description != null || actions != null;\n\n return (\n <section {...rest} className={cn('blade-section', variant === 'plain' && 'blade-section--plain', className)}>\n {hasHeader && (\n <div className=\"blade-section__header\">\n <div className=\"blade-section__headings\">\n {title != null && <HeadingTag className=\"blade-section__title\">{title}</HeadingTag>}\n {description != null && <p className=\"blade-section__description\">{description}</p>}\n </div>\n {actions != null && <div className=\"blade-section__actions\">{actions}</div>}\n </div>\n )}\n <div className={cn('blade-section__body', flush && 'blade-section__body--flush')}>{children}</div>\n {footer != null && <div className=\"blade-section__footer\">{footer}</div>}\n </section>\n );\n};\n\nconst SectionWithCompounds = Section as SectionCompound;\nSectionWithCompounds.Header = SectionHeader;\nSectionWithCompounds.Body = SectionBody;\nSectionWithCompounds.Footer = SectionFooter;\n\nexport default SectionWithCompounds;\n"],"mappings":";;;;;AAwBA,IAAM,KAAiB,EAAE,cAAW,aAAU,GAAG,QAC/C,kBAAC,OAAD;CAAK,GAAI;CAAM,WAAW,EAAG,yBAAyB,CAAS;CAAI;AAAc,CAAA,GAG7E,KAAe,EAAE,cAAW,aAAU,GAAG,QAAmD;CAChG,IAAM,EAAE,UAAO,GAAG,MAAa;CAC/B,OACE,kBAAC,OAAD;EAAK,GAAI;EAAU,WAAW,EAAG,uBAAuB,KAAS,8BAA8B,CAAS;EACrG;CACE,CAAA;AAET,GAEM,KAAiB,EAAE,cAAW,aAAU,GAAG,QAC/C,kBAAC,OAAD;CAAK,GAAI;CAAM,WAAW,EAAG,yBAAyB,CAAS;CAAI;AAAc,CAAA,GAwC7E,KAzBW,EACf,UAAO,gBAAa,YAAS,WAC7B,aAAU,QAAQ,WAAQ,IAAO,kBAAe,GAChD,cAAW,aAAU,GAAG,QACN;CAClB,IAAM,IAAa,IAAI,KACjB,IAAY,KAAS,QAAQ,KAAe,QAAQ,KAAW;CAErE,OACE,kBAAC,WAAD;EAAS,GAAI;EAAM,WAAW,EAAG,iBAAiB,MAAY,WAAW,wBAAwB,CAAS;EAA1G,UAAA;GACG,KACC,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,KAAS,QAAQ,kBAAC,GAAD;MAAY,WAAU;MAAwB,UAAA;KAAkB,CAAA,GACjF,KAAe,QAAQ,kBAAC,KAAD;MAAG,WAAU;MAA8B,UAAA;KAAe,CAAA,CAC/E;IACJ,CAAA,GAAA,KAAW,QAAQ,kBAAC,OAAD;KAAK,WAAU;KAA0B,UAAA;IAAa,CAAA,CACvE;;GAEP,kBAAC,OAAD;IAAK,WAAW,EAAG,uBAAuB,KAAS,4BAA4B;IAAI;GAAc,CAAA;GAChG,KAAU,QAAQ,kBAAC,OAAD;IAAK,WAAU;IAAyB,UAAA;GAAY,CAAA;EAChE;;AAEb;AAGA,EAAqB,SAAS,GAC9B,EAAqB,OAAO,GAC5B,EAAqB,SAAS"}
|
|
@@ -4,6 +4,8 @@ export interface Option {
|
|
|
4
4
|
label: string;
|
|
5
5
|
}
|
|
6
6
|
export interface SelectProps {
|
|
7
|
+
/** Accessible name for the combobox. */
|
|
8
|
+
'aria-label'?: string;
|
|
7
9
|
options: Option[];
|
|
8
10
|
value?: string | number | (string | number)[];
|
|
9
11
|
onChange: (value: string | number | (string | number)[]) => void;
|
|
@@ -33,25 +33,25 @@ var c = ({ isOpen: e }) => /* @__PURE__ */ a("svg", {
|
|
|
33
33
|
strokeLinejoin: "round",
|
|
34
34
|
d: "M6 18L18 6M6 6l12 12"
|
|
35
35
|
})
|
|
36
|
-
}), u = ({ options: u, value: d, onChange: f,
|
|
37
|
-
let [
|
|
36
|
+
}), u = ({ options: u, value: d, onChange: f, "aria-label": p, placeholder: m = "Select...", multiple: h = !1, fixedHeight: g = !1, style: _, className: v }) => {
|
|
37
|
+
let [y, b] = i(!1), [x, S] = i(-1), [C, w] = i(""), [T, E] = i(!0), [D, O] = i({}), k = r(null), A = r(null), j = r(null), M = r(null);
|
|
38
38
|
n(() => {
|
|
39
|
-
if (!
|
|
39
|
+
if (!y) return;
|
|
40
40
|
let e = (e) => {
|
|
41
41
|
let t = e.target;
|
|
42
|
-
|
|
42
|
+
k.current?.contains(t) || j.current?.contains(t) || b(!1);
|
|
43
43
|
};
|
|
44
44
|
return document.addEventListener("pointerdown", e), document.addEventListener("mousedown", e), () => {
|
|
45
45
|
document.removeEventListener("pointerdown", e), document.removeEventListener("mousedown", e);
|
|
46
46
|
};
|
|
47
|
-
}, [
|
|
48
|
-
let
|
|
49
|
-
e(
|
|
50
|
-
let
|
|
51
|
-
let e =
|
|
47
|
+
}, [y]);
|
|
48
|
+
let N = Array.isArray(d) ? d : d == null ? [] : [d];
|
|
49
|
+
e(k, t(() => b(!1), []));
|
|
50
|
+
let P = t(() => {
|
|
51
|
+
let e = k.current;
|
|
52
52
|
if (!e) return;
|
|
53
53
|
let t = e.getBoundingClientRect(), n = Math.min(u.length, 8) * 36 + 8, r = t.bottom + 4 + n <= window.innerHeight;
|
|
54
|
-
|
|
54
|
+
O({
|
|
55
55
|
position: "fixed",
|
|
56
56
|
left: t.left,
|
|
57
57
|
top: r ? t.bottom + 4 : Math.max(8, t.top - n - 4),
|
|
@@ -60,103 +60,103 @@ var c = ({ isOpen: e }) => /* @__PURE__ */ a("svg", {
|
|
|
60
60
|
});
|
|
61
61
|
}, [u.length]);
|
|
62
62
|
n(() => {
|
|
63
|
-
if (
|
|
64
|
-
window.removeEventListener("resize",
|
|
63
|
+
if (y) return P(), window.addEventListener("resize", P), window.addEventListener("scroll", P, !0), () => {
|
|
64
|
+
window.removeEventListener("resize", P), window.removeEventListener("scroll", P, !0);
|
|
65
65
|
};
|
|
66
|
-
}, [
|
|
67
|
-
let e =
|
|
66
|
+
}, [y, P]), n(() => {
|
|
67
|
+
let e = k.current;
|
|
68
68
|
if (!e) return;
|
|
69
|
-
let t = () =>
|
|
69
|
+
let t = () => E(e.clientWidth >= 88);
|
|
70
70
|
t();
|
|
71
71
|
let n = new ResizeObserver(t);
|
|
72
72
|
return n.observe(e), () => n.disconnect();
|
|
73
73
|
}, []), n(() => {
|
|
74
|
-
if (!
|
|
74
|
+
if (!y) return;
|
|
75
75
|
let e = (e) => {
|
|
76
76
|
switch (e.key) {
|
|
77
77
|
case "ArrowDown":
|
|
78
|
-
e.preventDefault(),
|
|
78
|
+
e.preventDefault(), S((e) => e < u.length - 1 ? e + 1 : 0), F();
|
|
79
79
|
break;
|
|
80
80
|
case "ArrowUp":
|
|
81
|
-
e.preventDefault(),
|
|
81
|
+
e.preventDefault(), S((e) => e > 0 ? e - 1 : u.length - 1), F();
|
|
82
82
|
break;
|
|
83
83
|
case "Enter":
|
|
84
84
|
case " ":
|
|
85
|
-
e.preventDefault(),
|
|
85
|
+
e.preventDefault(), x >= 0 && x < u.length && L(u[x]), h || b(!1);
|
|
86
86
|
break;
|
|
87
|
-
case "Escape":
|
|
87
|
+
case "Escape": b(!1);
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
return document.addEventListener("keydown", e), () => {
|
|
91
91
|
document.removeEventListener("keydown", e);
|
|
92
92
|
};
|
|
93
93
|
}, [
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
y,
|
|
95
|
+
x,
|
|
96
96
|
u,
|
|
97
|
-
|
|
97
|
+
h
|
|
98
98
|
]);
|
|
99
|
-
let
|
|
100
|
-
if (
|
|
101
|
-
let e =
|
|
99
|
+
let F = t(() => {
|
|
100
|
+
if (j.current && x >= 0) {
|
|
101
|
+
let e = j.current, t = e.children[x];
|
|
102
102
|
if (t) {
|
|
103
103
|
let n = e.getBoundingClientRect(), r = t.getBoundingClientRect();
|
|
104
104
|
r.bottom > n.bottom ? e.scrollTop += r.bottom - n.bottom : r.top < n.top && (e.scrollTop -= n.top - r.top);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
}, [
|
|
108
|
-
let e = !
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
if (
|
|
107
|
+
}, [x]), I = () => {
|
|
108
|
+
let e = !y;
|
|
109
|
+
b(e), S(e ? 0 : -1), e && P();
|
|
110
|
+
}, L = (e) => {
|
|
111
|
+
if (h) {
|
|
112
112
|
let t = Array.isArray(d) ? d : [], n;
|
|
113
113
|
n = t.includes(e.value) ? t.filter((t) => t !== e.value) : [...t, e.value], f(n);
|
|
114
|
-
} else f(e.value),
|
|
115
|
-
},
|
|
114
|
+
} else f(e.value), b(!1);
|
|
115
|
+
}, R = (e, t) => {
|
|
116
116
|
if (e.pointerType === "mouse") {
|
|
117
|
-
e.preventDefault(), e.stopPropagation(),
|
|
117
|
+
e.preventDefault(), e.stopPropagation(), L(t);
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
|
-
e.stopPropagation(),
|
|
120
|
+
e.stopPropagation(), M.current = {
|
|
121
121
|
pointerId: e.pointerId,
|
|
122
122
|
x: e.clientX,
|
|
123
123
|
y: e.clientY
|
|
124
124
|
};
|
|
125
|
-
},
|
|
126
|
-
let n =
|
|
127
|
-
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
},
|
|
125
|
+
}, z = (e, t) => {
|
|
126
|
+
let n = M.current;
|
|
127
|
+
M.current = null, n && e.pointerId === n.pointerId && Math.hypot(e.clientX - n.x, e.clientY - n.y) < 10 && (e.stopPropagation(), L(t));
|
|
128
|
+
}, B = () => {
|
|
129
|
+
M.current = null;
|
|
130
|
+
}, V = (e, t) => {
|
|
131
131
|
e.stopPropagation(), f((Array.isArray(d) ? d : []).filter((e) => e !== t));
|
|
132
132
|
};
|
|
133
133
|
n(() => {
|
|
134
|
-
if (
|
|
135
|
-
let e =
|
|
136
|
-
|
|
137
|
-
} else
|
|
134
|
+
if (h && A.current) {
|
|
135
|
+
let e = A.current, t = u.filter((e) => N.includes(e.value)).map((e) => e.label).join(", "), n = e.scrollWidth > e.clientWidth;
|
|
136
|
+
w(n ? t : "");
|
|
137
|
+
} else w("");
|
|
138
138
|
}, [
|
|
139
139
|
d,
|
|
140
|
-
|
|
140
|
+
h,
|
|
141
141
|
u,
|
|
142
|
-
|
|
142
|
+
N
|
|
143
143
|
]);
|
|
144
|
-
let
|
|
145
|
-
if (
|
|
144
|
+
let H = () => {
|
|
145
|
+
if (N.length === 0) return /* @__PURE__ */ a("span", {
|
|
146
146
|
className: "text-base-content/60 italic",
|
|
147
|
-
children:
|
|
147
|
+
children: m
|
|
148
148
|
});
|
|
149
|
-
let e = u.filter((e) =>
|
|
150
|
-
if (
|
|
151
|
-
let t =
|
|
149
|
+
let e = u.filter((e) => N.includes(e.value));
|
|
150
|
+
if (h) {
|
|
151
|
+
let t = g;
|
|
152
152
|
return /* @__PURE__ */ a("div", {
|
|
153
|
-
ref:
|
|
153
|
+
ref: A,
|
|
154
154
|
className: `
|
|
155
155
|
flex gap-1
|
|
156
156
|
${t ? "flex-nowrap overflow-x-auto overflow-y-hidden" : "flex-wrap overflow-hidden"} // Enable horizontal scroll, hide vertical scrollbar if any
|
|
157
|
-
${
|
|
157
|
+
${C ? "tooltip tooltip-bottom" : ""} // Keep tooltip for overflow
|
|
158
158
|
`,
|
|
159
|
-
"data-tip":
|
|
159
|
+
"data-tip": C || void 0,
|
|
160
160
|
style: {
|
|
161
161
|
scrollbarWidth: "thin",
|
|
162
162
|
...t ? { paddingRight: "1.75rem" } : {}
|
|
@@ -165,7 +165,7 @@ var c = ({ isOpen: e }) => /* @__PURE__ */ a("svg", {
|
|
|
165
165
|
className: "badge badge-ghost gap-1 text-xs items-center whitespace-nowrap flex-shrink-0",
|
|
166
166
|
children: [e.label, /* @__PURE__ */ a("button", {
|
|
167
167
|
type: "button",
|
|
168
|
-
onClick: (t) =>
|
|
168
|
+
onClick: (t) => V(t, e.value),
|
|
169
169
|
className: "btn btn-xs btn-ghost btn-circle p-0 m-0 hover:bg-error hover:text-error-content",
|
|
170
170
|
"aria-label": `Remove ${e.label}`,
|
|
171
171
|
children: /* @__PURE__ */ a(l, {})
|
|
@@ -174,41 +174,42 @@ var c = ({ isOpen: e }) => /* @__PURE__ */ a("svg", {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
{
|
|
177
|
-
let e = u.find((e) => e.value ===
|
|
177
|
+
let e = u.find((e) => e.value === N[0]);
|
|
178
178
|
return e ? /* @__PURE__ */ a("span", {
|
|
179
179
|
className: "truncate",
|
|
180
180
|
children: e.label
|
|
181
181
|
}) : /* @__PURE__ */ a("span", {
|
|
182
182
|
className: "text-base-content/60 italic",
|
|
183
|
-
children:
|
|
183
|
+
children: m
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
|
-
},
|
|
186
|
+
}, U = `
|
|
187
187
|
relative border border-base-300 rounded-lg bg-base-100 w-full
|
|
188
188
|
flex justify-between items-center cursor-pointer px-3 text-sm
|
|
189
189
|
focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50
|
|
190
190
|
hover:border-base-content/30
|
|
191
|
-
${
|
|
191
|
+
${h ? g ? "h-8" : "min-h-8 h-auto py-1" : "h-8"}
|
|
192
192
|
`;
|
|
193
193
|
return /* @__PURE__ */ o("div", {
|
|
194
|
-
ref:
|
|
195
|
-
style:
|
|
196
|
-
className: `relative w-full font-sans select-none ${
|
|
194
|
+
ref: k,
|
|
195
|
+
style: _,
|
|
196
|
+
className: `relative w-full font-sans select-none ${v || ""}`,
|
|
197
197
|
children: [/* @__PURE__ */ o("div", {
|
|
198
198
|
tabIndex: 0,
|
|
199
|
-
className:
|
|
200
|
-
onClick:
|
|
199
|
+
className: U,
|
|
200
|
+
onClick: I,
|
|
201
201
|
onKeyDown: (e) => {
|
|
202
|
-
(e.key === "Enter" || e.key === " ") &&
|
|
202
|
+
(e.key === "Enter" || e.key === " ") && I();
|
|
203
203
|
},
|
|
204
204
|
role: "combobox",
|
|
205
|
+
"aria-label": p,
|
|
205
206
|
"aria-haspopup": "listbox",
|
|
206
|
-
"aria-expanded":
|
|
207
|
+
"aria-expanded": y,
|
|
207
208
|
children: [/* @__PURE__ */ a("div", {
|
|
208
209
|
className: "flex-1 min-w-0 overflow-hidden",
|
|
209
|
-
style: { paddingRight:
|
|
210
|
-
children:
|
|
211
|
-
}),
|
|
210
|
+
style: { paddingRight: T ? 28 : 0 },
|
|
211
|
+
children: H()
|
|
212
|
+
}), T && /* @__PURE__ */ a("div", {
|
|
212
213
|
"aria-hidden": "true",
|
|
213
214
|
style: {
|
|
214
215
|
position: "absolute",
|
|
@@ -222,31 +223,31 @@ var c = ({ isOpen: e }) => /* @__PURE__ */ a("svg", {
|
|
|
222
223
|
width: 16,
|
|
223
224
|
height: 16
|
|
224
225
|
},
|
|
225
|
-
children: /* @__PURE__ */ a(c, { isOpen:
|
|
226
|
+
children: /* @__PURE__ */ a(c, { isOpen: y })
|
|
226
227
|
})]
|
|
227
|
-
}),
|
|
228
|
-
ref:
|
|
228
|
+
}), y && typeof document < "u" && s(/* @__PURE__ */ a("ul", {
|
|
229
|
+
ref: j,
|
|
229
230
|
className: "bg-base-100 border border-base-300 rounded-md shadow-lg list-none p-0 m-0 max-h-60 overflow-y-auto focus:outline-none",
|
|
230
231
|
style: {
|
|
231
|
-
...
|
|
232
|
+
...D,
|
|
232
233
|
touchAction: "pan-y"
|
|
233
234
|
},
|
|
234
235
|
onPointerDown: (e) => e.stopPropagation(),
|
|
235
236
|
role: "listbox",
|
|
236
|
-
"aria-activedescendant":
|
|
237
|
+
"aria-activedescendant": x >= 0 ? `option-${u[x]?.value}` : void 0,
|
|
237
238
|
tabIndex: -1,
|
|
238
239
|
children: u.length > 0 ? u.map((e, t) => /* @__PURE__ */ a("li", {
|
|
239
240
|
id: `option-${e.value}`,
|
|
240
|
-
className: `p-2 cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis hover:bg-base-200 ${t ===
|
|
241
|
-
onPointerDown: (t) =>
|
|
242
|
-
onPointerUp: (t) =>
|
|
243
|
-
onPointerCancel:
|
|
241
|
+
className: `p-2 cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis hover:bg-base-200 ${t === x ? "bg-base-300" : ""} ${N.includes(e.value) && !h ? "font-semibold bg-base-200" : ""} ${N.includes(e.value) && h ? "font-semibold" : ""}`,
|
|
242
|
+
onPointerDown: (t) => R(t, e),
|
|
243
|
+
onPointerUp: (t) => z(t, e),
|
|
244
|
+
onPointerCancel: B,
|
|
244
245
|
onClick: (e) => {
|
|
245
246
|
e.preventDefault(), e.stopPropagation();
|
|
246
247
|
},
|
|
247
|
-
onMouseEnter: () =>
|
|
248
|
+
onMouseEnter: () => S(t),
|
|
248
249
|
role: "option",
|
|
249
|
-
"aria-selected":
|
|
250
|
+
"aria-selected": N.includes(e.value),
|
|
250
251
|
children: e.label
|
|
251
252
|
}, e.value)) : /* @__PURE__ */ a("li", {
|
|
252
253
|
className: "p-2 text-base-content/60 italic",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Select/index.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useClickOutside } from '../../hooks/useClickOutside';\n\nexport interface Option {\n value: string | number;\n label: string;\n}\n\nexport interface SelectProps {\n options: Option[];\n value?: string | number | (string | number)[];\n onChange: (value: string | number | (string | number)[]) => void;\n placeholder?: string;\n multiple?: boolean;\n fixedHeight?: boolean;\n style?: React.CSSProperties; // Allow passing inline styles\n className?: string; // Allow passing CSS classes for the wrapper\n}\n\n// SVG component for the dropdown arrow\nconst DropdownArrow: React.FC<{ isOpen: boolean }> = ({ isOpen }) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n style={{\n display: 'block',\n width: 16,\n height: 16,\n transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',\n transition: 'transform 200ms ease-in-out',\n }}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n);\n\n// SVG component for the remove button 'x'\nconst RemoveIcon: React.FC = () => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n);\n\n// Select Component Implementation using daisyUI/Tailwind\nconst Select: React.FC<SelectProps> = ({\n options,\n value,\n onChange,\n placeholder = 'Select...',\n multiple = false,\n fixedHeight = false,\n style,\n className // This className applies to the wrapper\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [tooltipText, setTooltipText] = useState('');\n const [showArrow, setShowArrow] = useState(true);\n const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});\n const wrapperRef = useRef<HTMLDivElement>(null);\n const headerRef = useRef<HTMLDivElement>(null); // Ref for the header div\n const optionsListRef = useRef<HTMLUListElement>(null);\n // Tracks a touch/pen press on an option so a tap selects while a drag scrolls.\n // (pointerdown on touch must NOT preventDefault — that blocks native scrolling.)\n const touchPressRef = useRef<{ pointerId: number; x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (event: MouseEvent) => {\n const target = event.target as Node;\n if (wrapperRef.current?.contains(target) || optionsListRef.current?.contains(target)) return;\n setIsOpen(false);\n };\n document.addEventListener('pointerdown', handlePointerDown);\n document.addEventListener('mousedown', handlePointerDown);\n return () => {\n document.removeEventListener('pointerdown', handlePointerDown);\n document.removeEventListener('mousedown', handlePointerDown);\n };\n }, [isOpen]);\n\n const selectedValuesArray = Array.isArray(value) ? value : (value !== undefined && value !== null ? [value] : []);\n\n // Close dropdown when clicking outside\n useClickOutside(wrapperRef, useCallback(() => setIsOpen(false), []));\n\n const updateDropdownPosition = useCallback(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const rect = wrapper.getBoundingClientRect();\n const estimatedHeight = Math.min(options.length, 8) * 36 + 8;\n const fitsBelow = rect.bottom + 4 + estimatedHeight <= window.innerHeight;\n setDropdownStyle({\n position: 'fixed',\n left: rect.left,\n top: fitsBelow ? rect.bottom + 4 : Math.max(8, rect.top - estimatedHeight - 4),\n width: rect.width,\n zIndex: 10000,\n });\n }, [options.length]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n window.addEventListener('resize', updateDropdownPosition);\n window.addEventListener('scroll', updateDropdownPosition, true);\n return () => {\n window.removeEventListener('resize', updateDropdownPosition);\n window.removeEventListener('scroll', updateDropdownPosition, true);\n };\n }, [isOpen, updateDropdownPosition]);\n\n // Hide the decorative arrow when the control becomes too narrow to reserve\n // space for both it and a useful amount of label text.\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const update = () => setShowArrow(wrapper.clientWidth >= 88);\n update();\n const observer = new ResizeObserver(update);\n observer.observe(wrapper);\n return () => observer.disconnect();\n }, []);\n\n // Keyboard navigation\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n setHighlightedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0));\n scrollToHighlightedOption();\n break;\n case 'ArrowUp':\n event.preventDefault();\n setHighlightedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1));\n scrollToHighlightedOption();\n break;\n case 'Enter':\n case ' ': // Space key\n event.preventDefault();\n if (highlightedIndex >= 0 && highlightedIndex < options.length) {\n handleOptionClick(options[highlightedIndex]);\n if (!multiple) setIsOpen(false); // Close dropdown on selection if single mode\n } else if (!multiple) {\n setIsOpen(false); // Close dropdown if Enter/Space is pressed without a highlighted option in single mode\n }\n break;\n case 'Escape':\n setIsOpen(false);\n break;\n default:\n // Optional: Implement type-ahead find\n break;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, highlightedIndex, options, multiple]); // Added multiple to dependencies\n\n const scrollToHighlightedOption = useCallback(() => {\n if (optionsListRef.current && highlightedIndex >= 0) {\n const list = optionsListRef.current;\n const optionElement = list.children[highlightedIndex] as HTMLLIElement;\n if (optionElement) {\n const listRect = list.getBoundingClientRect();\n const optionRect = optionElement.getBoundingClientRect();\n\n if (optionRect.bottom > listRect.bottom) {\n list.scrollTop += optionRect.bottom - listRect.bottom;\n } else if (optionRect.top < listRect.top) {\n list.scrollTop -= listRect.top - optionRect.top;\n }\n }\n }\n }, [highlightedIndex]);\n\n const toggleDropdown = () => {\n const next = !isOpen;\n setIsOpen(next);\n setHighlightedIndex(next ? 0 : -1); // Reset or set initial highlight\n if (next) updateDropdownPosition();\n };\n\n const handleOptionClick = (option: Option) => {\n if (multiple) {\n const currentValues = Array.isArray(value) ? value : [];\n let newValue;\n if (currentValues.includes(option.value)) {\n newValue = currentValues.filter(v => v !== option.value);\n } else {\n newValue = [...currentValues, option.value];\n }\n onChange(newValue);\n } else {\n onChange(option.value);\n setIsOpen(false); // Close dropdown on single select\n }\n // Keep highlight on the selected item in multi-select? Maybe not.\n // setHighlightedIndex(options.findIndex(o => o.value === option.value));\n };\n\n // Mouse: select immediately on press (existing desktop behavior).\n // Touch/pen: only record the press — selection happens on pointerup if the\n // gesture turned out to be a tap rather than a scroll. preventDefault() on a\n // touch pointerdown suppresses the browser's native pan gesture, which made\n // long dropdowns impossible to scroll on mobile (and every scroll attempt\n // selected an option instead).\n const handleOptionPointerDown = (event: React.PointerEvent<HTMLLIElement>, option: Option) => {\n if (event.pointerType === 'mouse') {\n event.preventDefault();\n event.stopPropagation();\n handleOptionClick(option);\n return;\n }\n event.stopPropagation();\n touchPressRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };\n };\n\n const handleOptionPointerUp = (event: React.PointerEvent<HTMLLIElement>, option: Option) => {\n const press = touchPressRef.current;\n touchPressRef.current = null;\n if (!press || event.pointerId !== press.pointerId) return;\n // Select only if the finger did not move much — otherwise it was a scroll.\n const moved = Math.hypot(event.clientX - press.x, event.clientY - press.y);\n if (moved < 10) {\n event.stopPropagation();\n handleOptionClick(option);\n }\n };\n\n const handleOptionPointerCancel = () => {\n touchPressRef.current = null;\n };\n\n const handleRemoveItem = (e: React.MouseEvent, itemValue: string | number) => {\n e.stopPropagation(); // Prevent dropdown from opening/closing\n const currentValues = Array.isArray(value) ? value : [];\n onChange(currentValues.filter(v => v !== itemValue));\n };\n\n // Update tooltip text and check for overflow\n useEffect(() => {\n if (multiple && headerRef.current) {\n const header = headerRef.current;\n const selectedLabels = options\n .filter(opt => selectedValuesArray.includes(opt.value))\n .map(opt => opt.label)\n .join(', ');\n\n // Check if the content (scrollWidth) exceeds the visible area (clientWidth)\n const isOverflowing = header.scrollWidth > header.clientWidth;\n setTooltipText(isOverflowing ? selectedLabels : ''); // Only set text if overflowing\n } else {\n setTooltipText(''); // Clear tooltip for single select or empty multi-select\n }\n }, [value, multiple, options, selectedValuesArray]);\n\n\n const renderSelectedValue = () => {\n if (selectedValuesArray.length === 0) {\n return <span className=\"text-base-content/60 italic\">{placeholder}</span>;\n }\n\n const selectedOptions = options.filter(opt => selectedValuesArray.includes(opt.value));\n\n if (multiple) {\n const isFixed = fixedHeight;\n // Apply overflow-x-auto for horizontal scrolling in fixedHeight mode\n const containerClasses = `\n flex gap-1 \n ${isFixed ? 'flex-nowrap overflow-x-auto overflow-y-hidden' : 'flex-wrap overflow-hidden'} // Enable horizontal scroll, hide vertical scrollbar if any\n ${tooltipText ? 'tooltip tooltip-bottom' : ''} // Keep tooltip for overflow\n `;\n // Removed showEllipsisPrefix\n\n return (\n <div\n ref={headerRef}\n className={containerClasses}\n data-tip={tooltipText ? tooltipText : undefined} // Keep tooltip logic\n // Add scrollbar styling if desired (e.g., using ::-webkit-scrollbar pseudo-element via CSS or a utility)\n style={{\n scrollbarWidth: 'thin',\n // 为固定高度模式添加右侧内边距,避免与下拉图标重叠\n ...(isFixed ? { paddingRight: '1.75rem' } : {})\n }}\n >\n {/* Removed wrapper for ellipsis and its popup */}\n\n {/* Render the badges */}\n {selectedOptions.map(option => (\n <span key={option.value} className=\"badge badge-ghost gap-1 text-xs items-center whitespace-nowrap flex-shrink-0\">\n {option.label}\n <button\n type=\"button\"\n onClick={(e) => handleRemoveItem(e, option.value)}\n className=\"btn btn-xs btn-ghost btn-circle p-0 m-0 hover:bg-error hover:text-error-content\"\n aria-label={`Remove ${option.label}`}\n >\n <RemoveIcon />\n </button>\n </span>\n ))}\n </div>\n );\n } else {\n const selectedOption = options.find(opt => opt.value === selectedValuesArray[0]);\n return selectedOption ? <span className=\"truncate\">{selectedOption.label}</span> : <span className=\"text-base-content/60 italic\">{placeholder}</span>;\n }\n };\n\n // Match the default control height to a normal line of form text. Multi-select\n // may still grow when wrapping tags unless fixedHeight is requested.\n const selectClasses = `\n relative border border-base-300 rounded-lg bg-base-100 w-full\n flex justify-between items-center cursor-pointer px-3 text-sm\n focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50\n hover:border-base-content/30\n ${multiple\n ? (fixedHeight ? 'h-8' : 'min-h-8 h-auto py-1')\n : 'h-8'}\n `;\n\n return (\n <div\n ref={wrapperRef}\n style={style}\n className={`relative w-full font-sans select-none ${className || ''}`}\n >\n {/* Header */}\n <div\n tabIndex={0}\n className={selectClasses}\n onClick={toggleDropdown}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') toggleDropdown(); }}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n >\n <div\n className=\"flex-1 min-w-0 overflow-hidden\"\n style={{ paddingRight: showArrow ? 28 : 0 }}\n >\n {renderSelectedValue()}\n </div>\n {/* Inline positioning avoids utility CSS/theme overrides moving the icon. */}\n {showArrow && (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n right: 10,\n top: '50%',\n transform: 'translateY(-50%)',\n pointerEvents: 'none',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 16,\n height: 16,\n }}\n >\n <DropdownArrow isOpen={isOpen} />\n </div>\n )}\n </div>\n\n {/* Dropdown Options */}\n {isOpen && typeof document !== 'undefined' && createPortal(\n <ul\n ref={optionsListRef}\n className=\"bg-base-100 border border-base-300 rounded-md shadow-lg list-none p-0 m-0 max-h-60 overflow-y-auto focus:outline-none\"\n style={{ ...dropdownStyle, touchAction: 'pan-y' }}\n onPointerDown={(event) => event.stopPropagation()}\n role=\"listbox\" // ARIA role\n aria-activedescendant={highlightedIndex >= 0 ? `option-${options[highlightedIndex]?.value}` : undefined} // For screen readers\n tabIndex={-1} // Allows focus programmatically but not via tab\n >\n {options.length > 0 ? (\n options.map((option, index) => (\n <li\n key={option.value}\n id={`option-${option.value}`} // ID for aria-activedescendant\n className={`p-2 cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis hover:bg-base-200 ${index === highlightedIndex ? 'bg-base-300' : '' // Highlight style\n } ${selectedValuesArray.includes(option.value) && !multiple ? 'font-semibold bg-base-200' : '' // Style for selected single item\n } ${selectedValuesArray.includes(option.value) && multiple ? 'font-semibold' : '' // Style for selected multi items (less prominent background)\n }`}\n onPointerDown={(event) => handleOptionPointerDown(event, option)}\n onPointerUp={(event) => handleOptionPointerUp(event, option)}\n onPointerCancel={handleOptionPointerCancel}\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n }}\n onMouseEnter={() => setHighlightedIndex(index)} // Highlight on hover\n role=\"option\" // ARIA role\n aria-selected={selectedValuesArray.includes(option.value)}\n >\n {/* Optional: Add a checkmark or similar for selected items, especially in multi-select */}\n {option.label}\n </li>\n ))\n ) : (\n <li className=\"p-2 text-base-content/60 italic\">\n No options available\n </li>\n )}\n </ul>,\n document.body,\n )}\n </div>\n );\n};\n\nexport default Select; "],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Select/index.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useClickOutside } from '../../hooks/useClickOutside';\n\nexport interface Option {\n value: string | number;\n label: string;\n}\n\nexport interface SelectProps {\n /** Accessible name for the combobox. */\n 'aria-label'?: string;\n options: Option[];\n value?: string | number | (string | number)[];\n onChange: (value: string | number | (string | number)[]) => void;\n placeholder?: string;\n multiple?: boolean;\n fixedHeight?: boolean;\n style?: React.CSSProperties; // Allow passing inline styles\n className?: string; // Allow passing CSS classes for the wrapper\n}\n\n// SVG component for the dropdown arrow\nconst DropdownArrow: React.FC<{ isOpen: boolean }> = ({ isOpen }) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n style={{\n display: 'block',\n width: 16,\n height: 16,\n transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',\n transition: 'transform 200ms ease-in-out',\n }}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n);\n\n// SVG component for the remove button 'x'\nconst RemoveIcon: React.FC = () => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n);\n\n// Select Component Implementation using daisyUI/Tailwind\nconst Select: React.FC<SelectProps> = ({\n options,\n value,\n onChange,\n 'aria-label': ariaLabel,\n placeholder = 'Select...',\n multiple = false,\n fixedHeight = false,\n style,\n className // This className applies to the wrapper\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [tooltipText, setTooltipText] = useState('');\n const [showArrow, setShowArrow] = useState(true);\n const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});\n const wrapperRef = useRef<HTMLDivElement>(null);\n const headerRef = useRef<HTMLDivElement>(null); // Ref for the header div\n const optionsListRef = useRef<HTMLUListElement>(null);\n // Tracks a touch/pen press on an option so a tap selects while a drag scrolls.\n // (pointerdown on touch must NOT preventDefault — that blocks native scrolling.)\n const touchPressRef = useRef<{ pointerId: number; x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (event: MouseEvent) => {\n const target = event.target as Node;\n if (wrapperRef.current?.contains(target) || optionsListRef.current?.contains(target)) return;\n setIsOpen(false);\n };\n document.addEventListener('pointerdown', handlePointerDown);\n document.addEventListener('mousedown', handlePointerDown);\n return () => {\n document.removeEventListener('pointerdown', handlePointerDown);\n document.removeEventListener('mousedown', handlePointerDown);\n };\n }, [isOpen]);\n\n const selectedValuesArray = Array.isArray(value) ? value : (value !== undefined && value !== null ? [value] : []);\n\n // Close dropdown when clicking outside\n useClickOutside(wrapperRef, useCallback(() => setIsOpen(false), []));\n\n const updateDropdownPosition = useCallback(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const rect = wrapper.getBoundingClientRect();\n const estimatedHeight = Math.min(options.length, 8) * 36 + 8;\n const fitsBelow = rect.bottom + 4 + estimatedHeight <= window.innerHeight;\n setDropdownStyle({\n position: 'fixed',\n left: rect.left,\n top: fitsBelow ? rect.bottom + 4 : Math.max(8, rect.top - estimatedHeight - 4),\n width: rect.width,\n zIndex: 10000,\n });\n }, [options.length]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n window.addEventListener('resize', updateDropdownPosition);\n window.addEventListener('scroll', updateDropdownPosition, true);\n return () => {\n window.removeEventListener('resize', updateDropdownPosition);\n window.removeEventListener('scroll', updateDropdownPosition, true);\n };\n }, [isOpen, updateDropdownPosition]);\n\n // Hide the decorative arrow when the control becomes too narrow to reserve\n // space for both it and a useful amount of label text.\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const update = () => setShowArrow(wrapper.clientWidth >= 88);\n update();\n const observer = new ResizeObserver(update);\n observer.observe(wrapper);\n return () => observer.disconnect();\n }, []);\n\n // Keyboard navigation\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n setHighlightedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0));\n scrollToHighlightedOption();\n break;\n case 'ArrowUp':\n event.preventDefault();\n setHighlightedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1));\n scrollToHighlightedOption();\n break;\n case 'Enter':\n case ' ': // Space key\n event.preventDefault();\n if (highlightedIndex >= 0 && highlightedIndex < options.length) {\n handleOptionClick(options[highlightedIndex]);\n if (!multiple) setIsOpen(false); // Close dropdown on selection if single mode\n } else if (!multiple) {\n setIsOpen(false); // Close dropdown if Enter/Space is pressed without a highlighted option in single mode\n }\n break;\n case 'Escape':\n setIsOpen(false);\n break;\n default:\n // Optional: Implement type-ahead find\n break;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, highlightedIndex, options, multiple]); // Added multiple to dependencies\n\n const scrollToHighlightedOption = useCallback(() => {\n if (optionsListRef.current && highlightedIndex >= 0) {\n const list = optionsListRef.current;\n const optionElement = list.children[highlightedIndex] as HTMLLIElement;\n if (optionElement) {\n const listRect = list.getBoundingClientRect();\n const optionRect = optionElement.getBoundingClientRect();\n\n if (optionRect.bottom > listRect.bottom) {\n list.scrollTop += optionRect.bottom - listRect.bottom;\n } else if (optionRect.top < listRect.top) {\n list.scrollTop -= listRect.top - optionRect.top;\n }\n }\n }\n }, [highlightedIndex]);\n\n const toggleDropdown = () => {\n const next = !isOpen;\n setIsOpen(next);\n setHighlightedIndex(next ? 0 : -1); // Reset or set initial highlight\n if (next) updateDropdownPosition();\n };\n\n const handleOptionClick = (option: Option) => {\n if (multiple) {\n const currentValues = Array.isArray(value) ? value : [];\n let newValue;\n if (currentValues.includes(option.value)) {\n newValue = currentValues.filter(v => v !== option.value);\n } else {\n newValue = [...currentValues, option.value];\n }\n onChange(newValue);\n } else {\n onChange(option.value);\n setIsOpen(false); // Close dropdown on single select\n }\n // Keep highlight on the selected item in multi-select? Maybe not.\n // setHighlightedIndex(options.findIndex(o => o.value === option.value));\n };\n\n // Mouse: select immediately on press (existing desktop behavior).\n // Touch/pen: only record the press — selection happens on pointerup if the\n // gesture turned out to be a tap rather than a scroll. preventDefault() on a\n // touch pointerdown suppresses the browser's native pan gesture, which made\n // long dropdowns impossible to scroll on mobile (and every scroll attempt\n // selected an option instead).\n const handleOptionPointerDown = (event: React.PointerEvent<HTMLLIElement>, option: Option) => {\n if (event.pointerType === 'mouse') {\n event.preventDefault();\n event.stopPropagation();\n handleOptionClick(option);\n return;\n }\n event.stopPropagation();\n touchPressRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };\n };\n\n const handleOptionPointerUp = (event: React.PointerEvent<HTMLLIElement>, option: Option) => {\n const press = touchPressRef.current;\n touchPressRef.current = null;\n if (!press || event.pointerId !== press.pointerId) return;\n // Select only if the finger did not move much — otherwise it was a scroll.\n const moved = Math.hypot(event.clientX - press.x, event.clientY - press.y);\n if (moved < 10) {\n event.stopPropagation();\n handleOptionClick(option);\n }\n };\n\n const handleOptionPointerCancel = () => {\n touchPressRef.current = null;\n };\n\n const handleRemoveItem = (e: React.MouseEvent, itemValue: string | number) => {\n e.stopPropagation(); // Prevent dropdown from opening/closing\n const currentValues = Array.isArray(value) ? value : [];\n onChange(currentValues.filter(v => v !== itemValue));\n };\n\n // Update tooltip text and check for overflow\n useEffect(() => {\n if (multiple && headerRef.current) {\n const header = headerRef.current;\n const selectedLabels = options\n .filter(opt => selectedValuesArray.includes(opt.value))\n .map(opt => opt.label)\n .join(', ');\n\n // Check if the content (scrollWidth) exceeds the visible area (clientWidth)\n const isOverflowing = header.scrollWidth > header.clientWidth;\n setTooltipText(isOverflowing ? selectedLabels : ''); // Only set text if overflowing\n } else {\n setTooltipText(''); // Clear tooltip for single select or empty multi-select\n }\n }, [value, multiple, options, selectedValuesArray]);\n\n\n const renderSelectedValue = () => {\n if (selectedValuesArray.length === 0) {\n return <span className=\"text-base-content/60 italic\">{placeholder}</span>;\n }\n\n const selectedOptions = options.filter(opt => selectedValuesArray.includes(opt.value));\n\n if (multiple) {\n const isFixed = fixedHeight;\n // Apply overflow-x-auto for horizontal scrolling in fixedHeight mode\n const containerClasses = `\n flex gap-1 \n ${isFixed ? 'flex-nowrap overflow-x-auto overflow-y-hidden' : 'flex-wrap overflow-hidden'} // Enable horizontal scroll, hide vertical scrollbar if any\n ${tooltipText ? 'tooltip tooltip-bottom' : ''} // Keep tooltip for overflow\n `;\n // Removed showEllipsisPrefix\n\n return (\n <div\n ref={headerRef}\n className={containerClasses}\n data-tip={tooltipText ? tooltipText : undefined} // Keep tooltip logic\n // Add scrollbar styling if desired (e.g., using ::-webkit-scrollbar pseudo-element via CSS or a utility)\n style={{\n scrollbarWidth: 'thin',\n // 为固定高度模式添加右侧内边距,避免与下拉图标重叠\n ...(isFixed ? { paddingRight: '1.75rem' } : {})\n }}\n >\n {/* Removed wrapper for ellipsis and its popup */}\n\n {/* Render the badges */}\n {selectedOptions.map(option => (\n <span key={option.value} className=\"badge badge-ghost gap-1 text-xs items-center whitespace-nowrap flex-shrink-0\">\n {option.label}\n <button\n type=\"button\"\n onClick={(e) => handleRemoveItem(e, option.value)}\n className=\"btn btn-xs btn-ghost btn-circle p-0 m-0 hover:bg-error hover:text-error-content\"\n aria-label={`Remove ${option.label}`}\n >\n <RemoveIcon />\n </button>\n </span>\n ))}\n </div>\n );\n } else {\n const selectedOption = options.find(opt => opt.value === selectedValuesArray[0]);\n return selectedOption ? <span className=\"truncate\">{selectedOption.label}</span> : <span className=\"text-base-content/60 italic\">{placeholder}</span>;\n }\n };\n\n // Match the default control height to a normal line of form text. Multi-select\n // may still grow when wrapping tags unless fixedHeight is requested.\n const selectClasses = `\n relative border border-base-300 rounded-lg bg-base-100 w-full\n flex justify-between items-center cursor-pointer px-3 text-sm\n focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50\n hover:border-base-content/30\n ${multiple\n ? (fixedHeight ? 'h-8' : 'min-h-8 h-auto py-1')\n : 'h-8'}\n `;\n\n return (\n <div\n ref={wrapperRef}\n style={style}\n className={`relative w-full font-sans select-none ${className || ''}`}\n >\n {/* Header */}\n <div\n tabIndex={0}\n className={selectClasses}\n onClick={toggleDropdown}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') toggleDropdown(); }}\n role=\"combobox\"\n aria-label={ariaLabel}\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n >\n <div\n className=\"flex-1 min-w-0 overflow-hidden\"\n style={{ paddingRight: showArrow ? 28 : 0 }}\n >\n {renderSelectedValue()}\n </div>\n {/* Inline positioning avoids utility CSS/theme overrides moving the icon. */}\n {showArrow && (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n right: 10,\n top: '50%',\n transform: 'translateY(-50%)',\n pointerEvents: 'none',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 16,\n height: 16,\n }}\n >\n <DropdownArrow isOpen={isOpen} />\n </div>\n )}\n </div>\n\n {/* Dropdown Options */}\n {isOpen && typeof document !== 'undefined' && createPortal(\n <ul\n ref={optionsListRef}\n className=\"bg-base-100 border border-base-300 rounded-md shadow-lg list-none p-0 m-0 max-h-60 overflow-y-auto focus:outline-none\"\n style={{ ...dropdownStyle, touchAction: 'pan-y' }}\n onPointerDown={(event) => event.stopPropagation()}\n role=\"listbox\" // ARIA role\n aria-activedescendant={highlightedIndex >= 0 ? `option-${options[highlightedIndex]?.value}` : undefined} // For screen readers\n tabIndex={-1} // Allows focus programmatically but not via tab\n >\n {options.length > 0 ? (\n options.map((option, index) => (\n <li\n key={option.value}\n id={`option-${option.value}`} // ID for aria-activedescendant\n className={`p-2 cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis hover:bg-base-200 ${index === highlightedIndex ? 'bg-base-300' : '' // Highlight style\n } ${selectedValuesArray.includes(option.value) && !multiple ? 'font-semibold bg-base-200' : '' // Style for selected single item\n } ${selectedValuesArray.includes(option.value) && multiple ? 'font-semibold' : '' // Style for selected multi items (less prominent background)\n }`}\n onPointerDown={(event) => handleOptionPointerDown(event, option)}\n onPointerUp={(event) => handleOptionPointerUp(event, option)}\n onPointerCancel={handleOptionPointerCancel}\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n }}\n onMouseEnter={() => setHighlightedIndex(index)} // Highlight on hover\n role=\"option\" // ARIA role\n aria-selected={selectedValuesArray.includes(option.value)}\n >\n {/* Optional: Add a checkmark or similar for selected items, especially in multi-select */}\n {option.label}\n </li>\n ))\n ) : (\n <li className=\"p-2 text-base-content/60 italic\">\n No options available\n </li>\n )}\n </ul>,\n document.body,\n )}\n </div>\n );\n};\n\nexport default Select; "],"mappings":";;;;;AAuBA,IAAM,KAAgD,EAAE,gBACtD,kBAAC,OAAD;CACE,OAAM;CACN,MAAK;CACL,SAAQ;CACR,QAAO;CACP,aAAa;CACb,OAAO;EACL,SAAS;EACT,OAAO;EACP,QAAQ;EACR,WAAW,IAAS,mBAAmB;EACvC,YAAY;CACd;CAEA,UAAA,kBAAC,QAAD;EAAM,eAAc;EAAQ,gBAAe;EAAQ,GAAE;CAAkB,CAAA;AACpE,CAAA,GAID,UACJ,kBAAC,OAAD;CAAK,OAAM;CAA6B,WAAU;CAAU,MAAK;CAAO,SAAQ;CAAY,QAAO;CAAe,aAAa;CAC7H,UAAA,kBAAC,QAAD;EAAM,eAAc;EAAQ,gBAAe;EAAQ,GAAE;CAAwB,CAAA;AAC1E,CAAA,GAID,KAAiC,EACrC,YACA,UACA,aACA,cAAc,GACd,iBAAc,aACd,cAAW,IACX,iBAAc,IACd,UACA,mBACI;CACJ,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,CAAC,GAAkB,KAAuB,EAAS,EAAE,GACrD,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAW,KAAgB,EAAS,EAAI,GACzC,CAAC,GAAe,KAAoB,EAA8B,CAAC,CAAC,GACpE,IAAa,EAAuB,IAAI,GACxC,IAAY,EAAuB,IAAI,GACvC,IAAiB,EAAyB,IAAI,GAG9C,IAAgB,EAA2D,IAAI;CAErF,QAAgB;EACd,IAAI,CAAC,GAAQ;EACb,IAAM,KAAqB,MAAsB;GAC/C,IAAM,IAAS,EAAM;GACjB,EAAW,SAAS,SAAS,CAAM,KAAK,EAAe,SAAS,SAAS,CAAM,KACnF,EAAU,EAAK;EACjB;EAGA,OAFA,SAAS,iBAAiB,eAAe,CAAiB,GAC1D,SAAS,iBAAiB,aAAa,CAAiB,SAC3C;GAEX,AADA,SAAS,oBAAoB,eAAe,CAAiB,GAC7D,SAAS,oBAAoB,aAAa,CAAiB;EAC7D;CACF,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAsB,MAAM,QAAQ,CAAK,IAAI,IAAS,KAAiC,OAAiB,CAAC,IAAX,CAAC,CAAK;CAG1G,EAAgB,GAAY,QAAkB,EAAU,EAAK,GAAG,CAAC,CAAC,CAAC;CAEnE,IAAM,IAAyB,QAAkB;EAC/C,IAAM,IAAU,EAAW;EAC3B,IAAI,CAAC,GAAS;EACd,IAAM,IAAO,EAAQ,sBAAsB,GACrC,IAAkB,KAAK,IAAI,EAAQ,QAAQ,CAAC,IAAI,KAAK,GACrD,IAAY,EAAK,SAAS,IAAI,KAAmB,OAAO;EAC9D,EAAiB;GACf,UAAU;GACV,MAAM,EAAK;GACX,KAAK,IAAY,EAAK,SAAS,IAAI,KAAK,IAAI,GAAG,EAAK,MAAM,IAAkB,CAAC;GAC7E,OAAO,EAAK;GACZ,QAAQ;EACV,CAAC;CACH,GAAG,CAAC,EAAQ,MAAM,CAAC;CA0BnB,AAxBA,QAAgB;EACT,OAIL,OAHA,EAAuB,GACvB,OAAO,iBAAiB,UAAU,CAAsB,GACxD,OAAO,iBAAiB,UAAU,GAAwB,EAAI,SACjD;GAEX,AADA,OAAO,oBAAoB,UAAU,CAAsB,GAC3D,OAAO,oBAAoB,UAAU,GAAwB,EAAI;EACnE;CACF,GAAG,CAAC,GAAQ,CAAsB,CAAC,GAInC,QAAgB;EACd,IAAM,IAAU,EAAW;EAC3B,IAAI,CAAC,GAAS;EACd,IAAM,UAAe,EAAa,EAAQ,eAAe,EAAE;EAC3D,EAAO;EACP,IAAM,IAAW,IAAI,eAAe,CAAM;EAE1C,OADA,EAAS,QAAQ,CAAO,SACX,EAAS,WAAW;CACnC,GAAG,CAAC,CAAC,GAGL,QAAgB;EACd,IAAI,CAAC,GAAQ;EAEb,IAAM,KAAiB,MAAyB;GAC9C,QAAQ,EAAM,KAAd;IACE,KAAK;KAGH,AAFA,EAAM,eAAe,GACrB,GAAoB,MAAS,IAAO,EAAQ,SAAS,IAAI,IAAO,IAAI,CAAE,GACtE,EAA0B;KAC1B;IACF,KAAK;KAGH,AAFA,EAAM,eAAe,GACrB,GAAoB,MAAS,IAAO,IAAI,IAAO,IAAI,EAAQ,SAAS,CAAE,GACtE,EAA0B;KAC1B;IACF,KAAK;IACL,KAAK;KAEH,AADA,EAAM,eAAe,GACjB,KAAoB,KAAK,IAAmB,EAAQ,UACtD,EAAkB,EAAQ,EAAiB,GAEjC,KACV,EAAU,EAAK;KAEjB;IACF,KAAK,UACH,EAAU,EAAK;GAKnB;EACF;EAGA,OADA,SAAS,iBAAiB,WAAW,CAAa,SACrC;GACX,SAAS,oBAAoB,WAAW,CAAa;EACvD;CAEF,GAAG;EAAC;EAAQ;EAAkB;EAAS;CAAQ,CAAC;CAEhD,IAAM,IAA4B,QAAkB;EAClD,IAAI,EAAe,WAAW,KAAoB,GAAG;GACnD,IAAM,IAAO,EAAe,SACtB,IAAgB,EAAK,SAAS;GACpC,IAAI,GAAe;IACjB,IAAM,IAAW,EAAK,sBAAsB,GACtC,IAAa,EAAc,sBAAsB;IAEvD,AAAI,EAAW,SAAS,EAAS,SAC/B,EAAK,aAAa,EAAW,SAAS,EAAS,SACtC,EAAW,MAAM,EAAS,QACnC,EAAK,aAAa,EAAS,MAAM,EAAW;GAEhD;EACF;CACF,GAAG,CAAC,CAAgB,CAAC,GAEf,UAAuB;EAC3B,IAAM,IAAO,CAAC;EAGd,AAFA,EAAU,CAAI,GACd,EAAoB,IAAO,IAAI,EAAE,GAC7B,KAAM,EAAuB;CACnC,GAEM,KAAqB,MAAmB;EAC5C,IAAI,GAAU;GACZ,IAAM,IAAgB,MAAM,QAAQ,CAAK,IAAI,IAAQ,CAAC,GAClD;GAMJ,AALA,AAGE,IAHE,EAAc,SAAS,EAAO,KAAK,IAC1B,EAAc,QAAO,MAAK,MAAM,EAAO,KAAK,IAE5C,CAAC,GAAG,GAAe,EAAO,KAAK,GAE5C,EAAS,CAAQ;EACnB,OAEE,AADA,EAAS,EAAO,KAAK,GACrB,EAAU,EAAK;CAInB,GAQM,KAA2B,GAA0C,MAAmB;EAC5F,IAAI,EAAM,gBAAgB,SAAS;GAGjC,AAFA,EAAM,eAAe,GACrB,EAAM,gBAAgB,GACtB,EAAkB,CAAM;GACxB;EACF;EAEA,AADA,EAAM,gBAAgB,GACtB,EAAc,UAAU;GAAE,WAAW,EAAM;GAAW,GAAG,EAAM;GAAS,GAAG,EAAM;EAAQ;CAC3F,GAEM,KAAyB,GAA0C,MAAmB;EAC1F,IAAM,IAAQ,EAAc;EAC5B,EAAc,UAAU,MACnB,KAAS,EAAM,cAAc,EAAM,aAE1B,KAAK,MAAM,EAAM,UAAU,EAAM,GAAG,EAAM,UAAU,EAAM,CACpE,IAAQ,OACV,EAAM,gBAAgB,GACtB,EAAkB,CAAM;CAE5B,GAEM,UAAkC;EACtC,EAAc,UAAU;CAC1B,GAEM,KAAoB,GAAqB,MAA+B;EAG5E,AAFA,EAAE,gBAAgB,GAElB,GADsB,MAAM,QAAQ,CAAK,IAAI,IAAQ,CAAC,EAAA,CAC/B,QAAO,MAAK,MAAM,CAAS,CAAC;CACrD;CAGA,QAAgB;EACd,IAAI,KAAY,EAAU,SAAS;GACjC,IAAM,IAAS,EAAU,SACnB,IAAiB,EACpB,QAAO,MAAO,EAAoB,SAAS,EAAI,KAAK,CAAC,CAAC,CACtD,KAAI,MAAO,EAAI,KAAK,CAAC,CACrB,KAAK,IAAI,GAGN,IAAgB,EAAO,cAAc,EAAO;GAClD,EAAe,IAAgB,IAAiB,EAAE;EACpD,OACE,EAAe,EAAE;CAErB,GAAG;EAAC;EAAO;EAAU;EAAS;CAAmB,CAAC;CAGlD,IAAM,UAA4B;EAChC,IAAI,EAAoB,WAAW,GACjC,OAAO,kBAAC,QAAD;GAAM,WAAU;GAA+B,UAAA;EAAkB,CAAA;EAG1E,IAAM,IAAkB,EAAQ,QAAO,MAAO,EAAoB,SAAS,EAAI,KAAK,CAAC;EAErF,IAAI,GAAU;GACZ,IAAM,IAAU;GAShB,OACE,kBAAC,OAAD;IACE,KAAK;IACL,WAAW;;UARX,IAAU,kDAAkD,4BAA4B;UACxF,IAAc,2BAA2B,GAAG;;IAQ5C,YAAU,KAA4B,KAAA;IAEtC,OAAO;KACL,gBAAgB;KAEhB,GAAI,IAAU,EAAE,cAAc,UAAU,IAAI,CAAC;IAC/C;IAKC,UAAA,EAAgB,KAAI,MACnB,kBAAC,QAAD;KAAyB,WAAU;KAAnC,UAAA,CACG,EAAO,OACR,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,MAAM,EAAiB,GAAG,EAAO,KAAK;MAChD,WAAU;MACV,cAAY,UAAU,EAAO;MAE7B,UAAA,kBAAC,GAAD,CAAa,CAAA;KACP,CAAA,CACJ;IAVK,GAAA,EAAO,KAUZ,CACP;GACE,CAAA;EAET;EAAO;GACL,IAAM,IAAiB,EAAQ,MAAK,MAAO,EAAI,UAAU,EAAoB,EAAE;GAC/E,OAAO,IAAiB,kBAAC,QAAD;IAAM,WAAU;IAAY,UAAA,EAAe;GAAY,CAAA,IAAI,kBAAC,QAAD;IAAM,WAAU;IAA+B,UAAA;GAAkB,CAAA;EACtJ;CACF,GAIM,IAAgB;;;;;MAKlB,IACG,IAAc,QAAQ,wBACvB,MAAM;;CAGZ,OACE,kBAAC,OAAD;EACE,KAAK;EACE;EACP,WAAW,yCAAyC,KAAa;EAHnE,UAAA,CAME,kBAAC,OAAD;GACE,UAAU;GACV,WAAW;GACX,SAAS;GACT,YAAY,MAAM;IAAE,CAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,QAAK,EAAe;GAAG;GAC9E,MAAK;GACL,cAAY;GACZ,iBAAc;GACd,iBAAe;GARjB,UAAA,CAUE,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAE,cAAc,IAAY,KAAK,EAAE;IAEzC,UAAA,EAAoB;GAClB,CAAA,GAEJ,KACC,kBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU;KACV,OAAO;KACP,KAAK;KACL,WAAW;KACX,eAAe;KACf,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,OAAO;KACP,QAAQ;IACV;IAEA,UAAA,kBAAC,GAAD,EAAuB,UAAS,CAAA;GAC7B,CAAA,CAEJ;EAGJ,CAAA,GAAA,KAAU,OAAO,WAAa,OAAe,EAC5C,kBAAC,MAAD;GACE,KAAK;GACL,WAAU;GACV,OAAO;IAAE,GAAG;IAAe,aAAa;GAAQ;GAChD,gBAAgB,MAAU,EAAM,gBAAgB;GAChD,MAAK;GACL,yBAAuB,KAAoB,IAAI,UAAU,EAAQ,EAAiB,EAAE,UAAU,KAAA;GAC9F,UAAU;GAET,UAAA,EAAQ,SAAS,IAChB,EAAQ,KAAK,GAAQ,MACnB,kBAAC,MAAD;IAEE,IAAI,UAAU,EAAO;IACrB,WAAW,wFAAwF,MAAU,IAAmB,gBAAgB,GAC7I,GAAG,EAAoB,SAAS,EAAO,KAAK,KAAK,CAAC,IAAW,8BAA8B,GAC3F,GAAG,EAAoB,SAAS,EAAO,KAAK,KAAK,IAAW,kBAAkB;IAEjF,gBAAgB,MAAU,EAAwB,GAAO,CAAM;IAC/D,cAAc,MAAU,EAAsB,GAAO,CAAM;IAC3D,iBAAiB;IACjB,UAAU,MAAU;KAElB,AADA,EAAM,eAAe,GACrB,EAAM,gBAAgB;IACxB;IACA,oBAAoB,EAAoB,CAAK;IAC7C,MAAK;IACL,iBAAe,EAAoB,SAAS,EAAO,KAAK;IAGvD,UAAA,EAAO;GACN,GAnBG,EAAO,KAmBV,CACL,IAED,kBAAC,MAAD;IAAI,WAAU;IAAkC,UAAA;GAE5C,CAAA;EAEJ,CAAA,GACJ,SAAS,IACX,CACG;;AAET"}
|
|
@@ -8,9 +8,24 @@ interface SidebarToggleButtonProps {
|
|
|
8
8
|
sidebarWidth: number;
|
|
9
9
|
/** 折叠时的宽度 */
|
|
10
10
|
collapsedWidth?: number;
|
|
11
|
+
/** 拖拽调节宽度的回调(传入时支持拖拽缩放) */
|
|
12
|
+
onWidthChange?: (width: number) => void;
|
|
13
|
+
/** 拖拽时的最小宽度(px) */
|
|
14
|
+
minWidth?: number;
|
|
15
|
+
/** 拖拽时的最大宽度(px) */
|
|
16
|
+
maxWidth?: number;
|
|
17
|
+
/**
|
|
18
|
+
* 侧边栏容器元素的引用(用于拖拽时获取左边缘,使宽度计算与内嵌位置无关)。
|
|
19
|
+
*/
|
|
20
|
+
anchorRef?: React.RefObject<HTMLElement | null>;
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
23
|
+
* 桌面端侧边栏切换控件(与右侧纵向边界线一体)
|
|
24
|
+
* - 点击:折叠 / 展开侧边栏
|
|
25
|
+
* - 拖拽:任意调节侧边栏宽度(需传入 onWidthChange)
|
|
26
|
+
*
|
|
27
|
+
* 采用 `absolute` 定位内嵌在侧边栏(relative 容器)右边缘,随侧边栏布局自然移动——
|
|
28
|
+
* 无需测量/追赶,无延迟,也不受其它可变宽度组件影响。
|
|
14
29
|
*/
|
|
15
30
|
export declare const DesktopSidebarToggleButton: React.FC<SidebarToggleButtonProps>;
|
|
16
31
|
/**
|