klun-ui 0.1.9 → 0.1.11
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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/{chunk-PRDLMM4A.cjs → chunk-F7D5VPDL.cjs} +417 -26
- package/dist/chunk-F7D5VPDL.cjs.map +1 -0
- package/dist/{chunk-N3A4NTMY.js → chunk-N4NBEKYR.js} +414 -27
- package/dist/chunk-N4NBEKYR.js.map +1 -0
- package/dist/index.cjs +160 -144
- package/dist/index.d.cts +115 -2
- package/dist/index.d.ts +115 -2
- package/dist/index.js +1 -1
- package/dist/styles.css +286 -0
- package/dist/templates/index.cjs +1047 -82
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.cts +3 -1
- package/dist/templates/index.d.ts +3 -1
- package/dist/templates/index.js +966 -2
- package/dist/templates/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-N3A4NTMY.js.map +0 -1
- package/dist/chunk-PRDLMM4A.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to **klun-ui** are documented here. The format follows
|
|
|
5
5
|
[Semantic Versioning](https://semver.org/) (pre-1.0: minor/patch bumps may carry
|
|
6
6
|
small breaking changes).
|
|
7
7
|
|
|
8
|
+
## [0.1.10] — 2026-06-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`ImageViewer` — a full-screen image lightbox.** Single image or gallery
|
|
12
|
+
(prev/next + counter), wheel & button zoom, rotate, drag-to-pan when zoomed,
|
|
13
|
+
double-click to toggle fit/100%, optional download, and keyboard control
|
|
14
|
+
(Esc / ←→ / `+` `-` / `0`). Portals to `<body>` and locks body scroll.
|
|
15
|
+
- **`ContextMenu` + `useContextMenu` — a cursor-positioned right-click menu.**
|
|
16
|
+
Nested submenus via `item.children` (open on hover or `→`, any depth, each
|
|
17
|
+
level flips to stay on-screen), clamps/flips to the viewport, keyboard
|
|
18
|
+
navigation (↑ ↓ → ← Enter Esc Home End), disabled items, shortcuts, dividers
|
|
19
|
+
and danger items. The `useContextMenu` hook wires up `onContextMenu` →
|
|
20
|
+
open-at-cursor.
|
|
21
|
+
|
|
8
22
|
## [0.1.9] — 2026-06-21
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ You can also override tokens directly:
|
|
|
75
75
|
| **Feedback** | Alert, Banner, InlineTip, Toast, NotificationItem, ProgressBar, GaugeBar, CircularProgress, SegmentedProgress, Spinner, Skeleton, EmptyState |
|
|
76
76
|
| **Navigation** | Tabs, SegmentedControl, Breadcrumb, Pagination, StepIndicator, Divider, ContentDivider, HorizontalFilter, PageHeader |
|
|
77
77
|
| **Overlays** | Modal, Drawer, Dropdown, Popover, CommandMenu |
|
|
78
|
-
| **Disclosure** | Accordion |
|
|
78
|
+
| **Disclosure** | Accordion, Collapse |
|
|
79
79
|
| **Layout** | Card, ContentCard, FeatureCard |
|
|
80
80
|
|
|
81
81
|
All components and their prop types are named exports; a `cx` classnames helper is exported too.
|
|
@@ -86,7 +86,7 @@ Full-screen example compositions are available from the `klun-ui/templates` subp
|
|
|
86
86
|
|
|
87
87
|
```tsx
|
|
88
88
|
import "klun-ui/styles.css";
|
|
89
|
-
import { DashboardTemplate, AIAssistantTemplate, CryptoTemplate, MarketingTemplate } from "klun-ui/templates";
|
|
89
|
+
import { DashboardTemplate, AIAssistantTemplate, AgentChatTemplate, CryptoTemplate, MarketingTemplate } from "klun-ui/templates";
|
|
90
90
|
|
|
91
91
|
export default function App() {
|
|
92
92
|
return <DashboardTemplate />;
|
|
@@ -4372,9 +4372,9 @@ function useExpandedRows({
|
|
|
4372
4372
|
list.forEach((row, i) => {
|
|
4373
4373
|
const id = getRowId(row, i);
|
|
4374
4374
|
const kids = row[childrenKey];
|
|
4375
|
-
const
|
|
4376
|
-
out.push({ row, id, depth, hasChildren });
|
|
4377
|
-
if (
|
|
4375
|
+
const hasChildren2 = Array.isArray(kids) && kids.length > 0;
|
|
4376
|
+
out.push({ row, id, depth, hasChildren: hasChildren2 });
|
|
4377
|
+
if (hasChildren2 && treeOpen.has(id)) walk(kids, depth + 1);
|
|
4378
4378
|
});
|
|
4379
4379
|
};
|
|
4380
4380
|
walk(pageRows, 0);
|
|
@@ -4912,7 +4912,7 @@ var Tree = react.forwardRef(function Tree2({
|
|
|
4912
4912
|
className: chunkTPGAXYFU_cjs.cx("klun-tree", showLine && "klun-tree--line", className),
|
|
4913
4913
|
...props,
|
|
4914
4914
|
children: rows.filter((r) => !r.hidden).map(({ node, level }) => {
|
|
4915
|
-
const
|
|
4915
|
+
const hasChildren2 = !!(node.children && node.children.length);
|
|
4916
4916
|
const isExpanded = exp.includes(node.key);
|
|
4917
4917
|
const selected = selectedKey === node.key;
|
|
4918
4918
|
const cs = checkable ? checkState(node) : null;
|
|
@@ -4920,7 +4920,7 @@ var Tree = react.forwardRef(function Tree2({
|
|
|
4920
4920
|
"div",
|
|
4921
4921
|
{
|
|
4922
4922
|
role: "treeitem",
|
|
4923
|
-
"aria-expanded":
|
|
4923
|
+
"aria-expanded": hasChildren2 ? isExpanded : void 0,
|
|
4924
4924
|
"aria-selected": selected,
|
|
4925
4925
|
className: chunkTPGAXYFU_cjs.cx(
|
|
4926
4926
|
"klun-tree__item",
|
|
@@ -4939,12 +4939,12 @@ var Tree = react.forwardRef(function Tree2({
|
|
|
4939
4939
|
{
|
|
4940
4940
|
className: chunkTPGAXYFU_cjs.cx(
|
|
4941
4941
|
"klun-tree__caret",
|
|
4942
|
-
|
|
4942
|
+
hasChildren2 && isExpanded && "klun-tree__caret--expanded"
|
|
4943
4943
|
),
|
|
4944
|
-
"data-leaf":
|
|
4944
|
+
"data-leaf": hasChildren2 ? void 0 : "",
|
|
4945
4945
|
onClick: (e) => {
|
|
4946
4946
|
e.stopPropagation();
|
|
4947
|
-
if (
|
|
4947
|
+
if (hasChildren2) toggle(node.key);
|
|
4948
4948
|
},
|
|
4949
4949
|
children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-right-s-line" })
|
|
4950
4950
|
}
|
|
@@ -5018,6 +5018,56 @@ var Accordion = react.forwardRef(function Accordion2({ items: items2 = [], defau
|
|
|
5018
5018
|
}) });
|
|
5019
5019
|
});
|
|
5020
5020
|
Accordion.displayName = "Accordion";
|
|
5021
|
+
var Collapse = react.forwardRef(function Collapse2({
|
|
5022
|
+
title,
|
|
5023
|
+
icon,
|
|
5024
|
+
extra,
|
|
5025
|
+
defaultOpen = false,
|
|
5026
|
+
open,
|
|
5027
|
+
onOpenChange,
|
|
5028
|
+
hideChevron = false,
|
|
5029
|
+
contentStyle,
|
|
5030
|
+
contentClassName,
|
|
5031
|
+
className,
|
|
5032
|
+
children,
|
|
5033
|
+
...props
|
|
5034
|
+
}, ref) {
|
|
5035
|
+
const isControlled = open !== void 0;
|
|
5036
|
+
const [internal, setInternal] = react.useState(defaultOpen);
|
|
5037
|
+
const isOpen = isControlled ? open : internal;
|
|
5038
|
+
const toggle = () => {
|
|
5039
|
+
const next = !isOpen;
|
|
5040
|
+
if (!isControlled) setInternal(next);
|
|
5041
|
+
onOpenChange?.(next);
|
|
5042
|
+
};
|
|
5043
|
+
const onKeyDown = (e) => {
|
|
5044
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
5045
|
+
e.preventDefault();
|
|
5046
|
+
toggle();
|
|
5047
|
+
}
|
|
5048
|
+
};
|
|
5049
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: chunkTPGAXYFU_cjs.cx("klun-collapse", className), "data-state": isOpen ? "open" : "closed", ...props, children: [
|
|
5050
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5051
|
+
"div",
|
|
5052
|
+
{
|
|
5053
|
+
className: "klun-collapse__trigger",
|
|
5054
|
+
role: "button",
|
|
5055
|
+
tabIndex: 0,
|
|
5056
|
+
"aria-expanded": isOpen,
|
|
5057
|
+
onClick: toggle,
|
|
5058
|
+
onKeyDown,
|
|
5059
|
+
children: [
|
|
5060
|
+
icon != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-collapse__icon", children: icon }) : null,
|
|
5061
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-collapse__title", children: title }),
|
|
5062
|
+
extra != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-collapse__extra", onClick: (e) => e.stopPropagation(), children: extra }) : null,
|
|
5063
|
+
!hideChevron ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-collapse__chevron", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 8l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) : null
|
|
5064
|
+
]
|
|
5065
|
+
}
|
|
5066
|
+
),
|
|
5067
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-collapse__panel", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkTPGAXYFU_cjs.cx("klun-collapse__content", contentClassName), style: contentStyle, children }) })
|
|
5068
|
+
] });
|
|
5069
|
+
});
|
|
5070
|
+
Collapse.displayName = "Collapse";
|
|
5021
5071
|
var Alert = react.forwardRef(function Alert2({ status = "information", variant = "lighter", icon, title, action, onClose, className, children, ...props }, ref) {
|
|
5022
5072
|
const { alert } = useLocale();
|
|
5023
5073
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -5920,7 +5970,7 @@ var Cascader = react.forwardRef(function Cascader2({
|
|
|
5920
5970
|
),
|
|
5921
5971
|
children: col.map((opt) => {
|
|
5922
5972
|
const selected = active[ci] === opt.value;
|
|
5923
|
-
const
|
|
5973
|
+
const hasChildren2 = !!(opt.children && opt.children.length);
|
|
5924
5974
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5925
5975
|
"button",
|
|
5926
5976
|
{
|
|
@@ -5931,7 +5981,7 @@ var Cascader = react.forwardRef(function Cascader2({
|
|
|
5931
5981
|
"data-selected": selected || void 0,
|
|
5932
5982
|
children: [
|
|
5933
5983
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-cascader__option-label", children: opt.label }),
|
|
5934
|
-
|
|
5984
|
+
hasChildren2 ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-right-s-line klun-cascader__option-arrow" }) : selected ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-check-line klun-cascader__option-check" }) : null
|
|
5935
5985
|
]
|
|
5936
5986
|
},
|
|
5937
5987
|
opt.value
|
|
@@ -9838,6 +9888,203 @@ function Confirm(opts = {}) {
|
|
|
9838
9888
|
root.render(/* @__PURE__ */ jsxRuntime.jsx(ConfirmDialog, { opts, onClose: close }));
|
|
9839
9889
|
});
|
|
9840
9890
|
}
|
|
9891
|
+
var isSelectable = (it) => !it.divider && !it.disabled;
|
|
9892
|
+
var hasChildren = (it) => !!it.children && it.children.length > 0;
|
|
9893
|
+
var GAP2 = 8;
|
|
9894
|
+
function place(w, h, opts) {
|
|
9895
|
+
const vw = window.innerWidth;
|
|
9896
|
+
const vh = window.innerHeight;
|
|
9897
|
+
let left;
|
|
9898
|
+
let top;
|
|
9899
|
+
if (opts.rect) {
|
|
9900
|
+
const r = opts.rect;
|
|
9901
|
+
left = r.right - 4 + w > vw - GAP2 ? r.left - w + 4 : r.right - 4;
|
|
9902
|
+
top = r.top - 4;
|
|
9903
|
+
} else {
|
|
9904
|
+
const p = opts.point ?? { x: 0, y: 0 };
|
|
9905
|
+
left = p.x + w > vw - GAP2 ? p.x - w : p.x;
|
|
9906
|
+
top = p.y + h > vh - GAP2 ? p.y - h : p.y;
|
|
9907
|
+
}
|
|
9908
|
+
left = Math.min(Math.max(GAP2, left), vw - w - GAP2);
|
|
9909
|
+
top = Math.min(Math.max(GAP2, top), vh - h - GAP2);
|
|
9910
|
+
return { left, top };
|
|
9911
|
+
}
|
|
9912
|
+
function MenuPanel({ items: items2, width, className, point, anchorEl, autoActive, depth, onCloseAll, onCloseSelf }) {
|
|
9913
|
+
const ref = react.useRef(null);
|
|
9914
|
+
const itemEls = react.useRef([]);
|
|
9915
|
+
const [pos, setPos] = react.useState(null);
|
|
9916
|
+
const [active, setActive] = react.useState(autoActive ? items2.findIndex(isSelectable) : -1);
|
|
9917
|
+
const [sub, setSub] = react.useState(null);
|
|
9918
|
+
const isLeaf = sub === null;
|
|
9919
|
+
react.useLayoutEffect(() => {
|
|
9920
|
+
const el = ref.current;
|
|
9921
|
+
const w = el?.offsetWidth ?? width;
|
|
9922
|
+
const h = el?.offsetHeight ?? 0;
|
|
9923
|
+
setPos(place(w, h, anchorEl ? { rect: anchorEl.getBoundingClientRect() } : { point }));
|
|
9924
|
+
}, [width, point?.x, point?.y, anchorEl, items2]);
|
|
9925
|
+
const move = react.useCallback(
|
|
9926
|
+
(dir) => {
|
|
9927
|
+
setActive((cur) => {
|
|
9928
|
+
const n = items2.length;
|
|
9929
|
+
for (let s = 1; s <= n; s++) {
|
|
9930
|
+
const i = (cur + dir * s + n * s) % n;
|
|
9931
|
+
if (isSelectable(items2[i])) return i;
|
|
9932
|
+
}
|
|
9933
|
+
return cur;
|
|
9934
|
+
});
|
|
9935
|
+
},
|
|
9936
|
+
[items2]
|
|
9937
|
+
);
|
|
9938
|
+
const activate = react.useCallback(
|
|
9939
|
+
(i, viaKeyboard) => {
|
|
9940
|
+
const it = items2[i];
|
|
9941
|
+
if (!it || !isSelectable(it)) return;
|
|
9942
|
+
if (hasChildren(it)) setSub({ i, kbd: viaKeyboard });
|
|
9943
|
+
else {
|
|
9944
|
+
onCloseAll();
|
|
9945
|
+
it.onClick?.();
|
|
9946
|
+
}
|
|
9947
|
+
},
|
|
9948
|
+
[items2, onCloseAll]
|
|
9949
|
+
);
|
|
9950
|
+
react.useEffect(() => {
|
|
9951
|
+
if (!isLeaf) return;
|
|
9952
|
+
const onKey = (e) => {
|
|
9953
|
+
if (e.key === "ArrowDown") {
|
|
9954
|
+
e.preventDefault();
|
|
9955
|
+
move(1);
|
|
9956
|
+
} else if (e.key === "ArrowUp") {
|
|
9957
|
+
e.preventDefault();
|
|
9958
|
+
move(-1);
|
|
9959
|
+
} else if (e.key === "Home") {
|
|
9960
|
+
e.preventDefault();
|
|
9961
|
+
setActive(items2.findIndex(isSelectable));
|
|
9962
|
+
} else if (e.key === "End") {
|
|
9963
|
+
e.preventDefault();
|
|
9964
|
+
for (let i = items2.length - 1; i >= 0; i--)
|
|
9965
|
+
if (isSelectable(items2[i])) {
|
|
9966
|
+
setActive(i);
|
|
9967
|
+
break;
|
|
9968
|
+
}
|
|
9969
|
+
} else if (e.key === "ArrowRight") {
|
|
9970
|
+
if (active >= 0 && hasChildren(items2[active])) {
|
|
9971
|
+
e.preventDefault();
|
|
9972
|
+
setSub({ i: active, kbd: true });
|
|
9973
|
+
}
|
|
9974
|
+
} else if (e.key === "ArrowLeft") {
|
|
9975
|
+
if (depth > 0) {
|
|
9976
|
+
e.preventDefault();
|
|
9977
|
+
onCloseSelf?.();
|
|
9978
|
+
}
|
|
9979
|
+
} else if (e.key === "Enter") {
|
|
9980
|
+
if (active >= 0) {
|
|
9981
|
+
e.preventDefault();
|
|
9982
|
+
activate(active, true);
|
|
9983
|
+
}
|
|
9984
|
+
} else if (e.key === "Escape") {
|
|
9985
|
+
e.preventDefault();
|
|
9986
|
+
onCloseAll();
|
|
9987
|
+
}
|
|
9988
|
+
};
|
|
9989
|
+
window.addEventListener("keydown", onKey, true);
|
|
9990
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
9991
|
+
}, [isLeaf, active, items2, depth, move, activate, onCloseAll, onCloseSelf]);
|
|
9992
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9993
|
+
"div",
|
|
9994
|
+
{
|
|
9995
|
+
ref,
|
|
9996
|
+
role: "menu",
|
|
9997
|
+
className: chunkTPGAXYFU_cjs.cx("klun-context-menu", className),
|
|
9998
|
+
style: pos ? { left: pos.left, top: pos.top, width } : { left: -9999, top: -9999, width },
|
|
9999
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
10000
|
+
children: [
|
|
10001
|
+
items2.map(
|
|
10002
|
+
(it, i) => it.divider ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-context-menu__divider", role: "separator" }, i) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10003
|
+
"button",
|
|
10004
|
+
{
|
|
10005
|
+
ref: (el) => {
|
|
10006
|
+
itemEls.current[i] = el;
|
|
10007
|
+
},
|
|
10008
|
+
type: "button",
|
|
10009
|
+
role: "menuitem",
|
|
10010
|
+
"aria-haspopup": hasChildren(it) || void 0,
|
|
10011
|
+
"aria-expanded": hasChildren(it) ? sub?.i === i : void 0,
|
|
10012
|
+
disabled: it.disabled,
|
|
10013
|
+
className: chunkTPGAXYFU_cjs.cx(
|
|
10014
|
+
"klun-context-menu__item",
|
|
10015
|
+
it.danger && "klun-context-menu__item--danger",
|
|
10016
|
+
i === active && "is-active"
|
|
10017
|
+
),
|
|
10018
|
+
onMouseEnter: () => {
|
|
10019
|
+
setActive(i);
|
|
10020
|
+
if (isSelectable(it)) setSub(hasChildren(it) ? { i, kbd: false } : null);
|
|
10021
|
+
},
|
|
10022
|
+
onClick: () => activate(i, false),
|
|
10023
|
+
children: [
|
|
10024
|
+
it.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-context-menu__icon", children: it.icon }) : null,
|
|
10025
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-context-menu__label", children: it.label }),
|
|
10026
|
+
hasChildren(it) ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-right-s-line klun-context-menu__arrow" }) : it.shortcut ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-context-menu__shortcut", children: it.shortcut }) : null
|
|
10027
|
+
]
|
|
10028
|
+
},
|
|
10029
|
+
i
|
|
10030
|
+
)
|
|
10031
|
+
),
|
|
10032
|
+
sub && items2[sub.i]?.children ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10033
|
+
MenuPanel,
|
|
10034
|
+
{
|
|
10035
|
+
items: items2[sub.i].children,
|
|
10036
|
+
width,
|
|
10037
|
+
anchorEl: itemEls.current[sub.i],
|
|
10038
|
+
autoActive: sub.kbd,
|
|
10039
|
+
depth: depth + 1,
|
|
10040
|
+
onCloseAll,
|
|
10041
|
+
onCloseSelf: () => setSub(null)
|
|
10042
|
+
},
|
|
10043
|
+
`sub-${sub.i}`
|
|
10044
|
+
) : null
|
|
10045
|
+
]
|
|
10046
|
+
}
|
|
10047
|
+
);
|
|
10048
|
+
}
|
|
10049
|
+
function ContextMenu2({ open, x, y, items: items2, onClose, width = 208, className }) {
|
|
10050
|
+
if (!open || typeof document === "undefined") return null;
|
|
10051
|
+
return reactDom.createPortal(
|
|
10052
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10053
|
+
"div",
|
|
10054
|
+
{
|
|
10055
|
+
className: "klun-context-menu__layer",
|
|
10056
|
+
onMouseDown: onClose,
|
|
10057
|
+
onContextMenu: (e) => {
|
|
10058
|
+
e.preventDefault();
|
|
10059
|
+
onClose();
|
|
10060
|
+
},
|
|
10061
|
+
onWheel: onClose,
|
|
10062
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10063
|
+
MenuPanel,
|
|
10064
|
+
{
|
|
10065
|
+
items: items2,
|
|
10066
|
+
width,
|
|
10067
|
+
className,
|
|
10068
|
+
point: { x, y },
|
|
10069
|
+
depth: 0,
|
|
10070
|
+
onCloseAll: onClose
|
|
10071
|
+
}
|
|
10072
|
+
)
|
|
10073
|
+
}
|
|
10074
|
+
),
|
|
10075
|
+
document.body
|
|
10076
|
+
);
|
|
10077
|
+
}
|
|
10078
|
+
ContextMenu2.displayName = "ContextMenu";
|
|
10079
|
+
function useContextMenu() {
|
|
10080
|
+
const [state, setState] = react.useState(null);
|
|
10081
|
+
const open = react.useCallback((e, items2) => {
|
|
10082
|
+
e.preventDefault();
|
|
10083
|
+
setState({ x: e.clientX, y: e.clientY, items: items2 });
|
|
10084
|
+
}, []);
|
|
10085
|
+
const close = react.useCallback(() => setState(null), []);
|
|
10086
|
+
return { state, open, close };
|
|
10087
|
+
}
|
|
9841
10088
|
var Drawer = react.forwardRef(function Drawer2({
|
|
9842
10089
|
open,
|
|
9843
10090
|
onClose,
|
|
@@ -9861,8 +10108,8 @@ var Drawer = react.forwardRef(function Drawer2({
|
|
|
9861
10108
|
...props
|
|
9862
10109
|
}, ref) {
|
|
9863
10110
|
const { drawer } = useLocale();
|
|
9864
|
-
const
|
|
9865
|
-
const horizontal =
|
|
10111
|
+
const place2 = placement ?? side ?? "right";
|
|
10112
|
+
const horizontal = place2 === "left" || place2 === "right";
|
|
9866
10113
|
const prevOpen = react.useRef(open);
|
|
9867
10114
|
react.useEffect(() => {
|
|
9868
10115
|
if (!open) return;
|
|
@@ -9889,7 +10136,7 @@ var Drawer = react.forwardRef(function Drawer2({
|
|
|
9889
10136
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9890
10137
|
"div",
|
|
9891
10138
|
{
|
|
9892
|
-
className: chunkTPGAXYFU_cjs.cx("klun-drawer", `klun-drawer--${
|
|
10139
|
+
className: chunkTPGAXYFU_cjs.cx("klun-drawer", `klun-drawer--${place2}`, !mask && "klun-drawer--no-mask"),
|
|
9893
10140
|
style: zIndex != null ? { zIndex } : void 0,
|
|
9894
10141
|
onClick: maskClosable ? onClose : void 0,
|
|
9895
10142
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -9972,6 +10219,146 @@ var Dropdown = react.forwardRef(function Dropdown2({ trigger, items: items2 = []
|
|
|
9972
10219
|
] });
|
|
9973
10220
|
});
|
|
9974
10221
|
Dropdown.displayName = "Dropdown";
|
|
10222
|
+
var MIN = 0.25;
|
|
10223
|
+
var MAX = 6;
|
|
10224
|
+
var clampScale = (s) => Math.min(MAX, Math.max(MIN, +s.toFixed(2)));
|
|
10225
|
+
function ImageViewer({
|
|
10226
|
+
open,
|
|
10227
|
+
src,
|
|
10228
|
+
alt,
|
|
10229
|
+
items: items2,
|
|
10230
|
+
index,
|
|
10231
|
+
onIndexChange,
|
|
10232
|
+
onClose,
|
|
10233
|
+
onDownload,
|
|
10234
|
+
title,
|
|
10235
|
+
zoomable = true,
|
|
10236
|
+
rotatable = true,
|
|
10237
|
+
closeOnBackdrop = true,
|
|
10238
|
+
className
|
|
10239
|
+
}) {
|
|
10240
|
+
const gallery = items2 && items2.length > 0 ? items2 : src != null ? [{ src, alt }] : [];
|
|
10241
|
+
const [innerIndex, setInnerIndex] = react.useState(0);
|
|
10242
|
+
const idx = index ?? innerIndex;
|
|
10243
|
+
const safeIdx = Math.min(Math.max(idx, 0), Math.max(0, gallery.length - 1));
|
|
10244
|
+
const current = gallery[safeIdx];
|
|
10245
|
+
const [scale, setScale] = react.useState(1);
|
|
10246
|
+
const [rot, setRot] = react.useState(0);
|
|
10247
|
+
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
10248
|
+
const drag = react.useRef(null);
|
|
10249
|
+
const reset = react.useCallback(() => {
|
|
10250
|
+
setScale(1);
|
|
10251
|
+
setRot(0);
|
|
10252
|
+
setPan({ x: 0, y: 0 });
|
|
10253
|
+
}, []);
|
|
10254
|
+
const goTo = react.useCallback(
|
|
10255
|
+
(next) => {
|
|
10256
|
+
if (gallery.length < 2) return;
|
|
10257
|
+
const n = (next + gallery.length) % gallery.length;
|
|
10258
|
+
if (index === void 0) setInnerIndex(n);
|
|
10259
|
+
onIndexChange?.(n);
|
|
10260
|
+
reset();
|
|
10261
|
+
},
|
|
10262
|
+
[gallery.length, index, onIndexChange, reset]
|
|
10263
|
+
);
|
|
10264
|
+
react.useEffect(() => {
|
|
10265
|
+
reset();
|
|
10266
|
+
}, [safeIdx, open, reset]);
|
|
10267
|
+
react.useEffect(() => {
|
|
10268
|
+
if (!open) return;
|
|
10269
|
+
const prev = document.body.style.overflow;
|
|
10270
|
+
document.body.style.overflow = "hidden";
|
|
10271
|
+
const onKey = (e) => {
|
|
10272
|
+
if (e.key === "Escape") onClose();
|
|
10273
|
+
else if (e.key === "ArrowLeft") goTo(safeIdx - 1);
|
|
10274
|
+
else if (e.key === "ArrowRight") goTo(safeIdx + 1);
|
|
10275
|
+
else if (zoomable && (e.key === "+" || e.key === "=")) setScale((s) => clampScale(s + 0.25));
|
|
10276
|
+
else if (zoomable && (e.key === "-" || e.key === "_")) setScale((s) => clampScale(s - 0.25));
|
|
10277
|
+
else if (e.key === "0") reset();
|
|
10278
|
+
};
|
|
10279
|
+
window.addEventListener("keydown", onKey);
|
|
10280
|
+
return () => {
|
|
10281
|
+
document.body.style.overflow = prev;
|
|
10282
|
+
window.removeEventListener("keydown", onKey);
|
|
10283
|
+
};
|
|
10284
|
+
}, [open, safeIdx, zoomable, onClose, goTo, reset]);
|
|
10285
|
+
if (!open || !current || typeof document === "undefined") return null;
|
|
10286
|
+
const onWheel = (e) => {
|
|
10287
|
+
if (!zoomable) return;
|
|
10288
|
+
e.preventDefault();
|
|
10289
|
+
setScale((s) => clampScale(s + (e.deltaY < 0 ? 0.2 : -0.2)));
|
|
10290
|
+
};
|
|
10291
|
+
const onImgDown = (e) => {
|
|
10292
|
+
if (scale <= 1) return;
|
|
10293
|
+
e.preventDefault();
|
|
10294
|
+
drag.current = { x: e.clientX, y: e.clientY, px: pan.x, py: pan.y };
|
|
10295
|
+
};
|
|
10296
|
+
const onMove = (e) => {
|
|
10297
|
+
if (!drag.current) return;
|
|
10298
|
+
setPan({ x: drag.current.px + (e.clientX - drag.current.x), y: drag.current.py + (e.clientY - drag.current.y) });
|
|
10299
|
+
};
|
|
10300
|
+
const endDrag = () => {
|
|
10301
|
+
drag.current = null;
|
|
10302
|
+
};
|
|
10303
|
+
const toggleZoom = () => scale > 1 ? reset() : setScale(2);
|
|
10304
|
+
const heading = title ?? current.title ?? current.alt;
|
|
10305
|
+
return reactDom.createPortal(
|
|
10306
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10307
|
+
"div",
|
|
10308
|
+
{
|
|
10309
|
+
className: chunkTPGAXYFU_cjs.cx("klun-image-viewer", className),
|
|
10310
|
+
onMouseDown: (e) => {
|
|
10311
|
+
if (closeOnBackdrop && e.target === e.currentTarget) onClose();
|
|
10312
|
+
},
|
|
10313
|
+
onMouseMove: onMove,
|
|
10314
|
+
onMouseUp: endDrag,
|
|
10315
|
+
onMouseLeave: endDrag,
|
|
10316
|
+
children: [
|
|
10317
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__bar", onMouseDown: (e) => e.stopPropagation(), children: [
|
|
10318
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__title", children: [
|
|
10319
|
+
heading,
|
|
10320
|
+
gallery.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "klun-image-viewer__counter", children: [
|
|
10321
|
+
safeIdx + 1,
|
|
10322
|
+
" / ",
|
|
10323
|
+
gallery.length
|
|
10324
|
+
] }) : null
|
|
10325
|
+
] }),
|
|
10326
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__tools", children: [
|
|
10327
|
+
zoomable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10328
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Zoom out", onClick: () => setScale((s) => clampScale(s - 0.25)), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-zoom-out-line" }) }),
|
|
10329
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "klun-image-viewer__zoom", children: [
|
|
10330
|
+
Math.round(scale * 100),
|
|
10331
|
+
"%"
|
|
10332
|
+
] }),
|
|
10333
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Zoom in", onClick: () => setScale((s) => clampScale(s + 0.25)), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-zoom-in-line" }) })
|
|
10334
|
+
] }) : null,
|
|
10335
|
+
rotatable ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Rotate", onClick: () => setRot((r) => (r + 90) % 360), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-anticlockwise-line" }) }) : null,
|
|
10336
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Reset", onClick: reset, children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-aspect-ratio-line" }) }),
|
|
10337
|
+
onDownload ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Download", onClick: () => onDownload(current, safeIdx), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-download-2-line" }) }) : null,
|
|
10338
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__btn", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-close-line" }) })
|
|
10339
|
+
] })
|
|
10340
|
+
] }),
|
|
10341
|
+
gallery.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__nav klun-image-viewer__nav--prev", "aria-label": "Previous", onMouseDown: (e) => e.stopPropagation(), onClick: () => goTo(safeIdx - 1), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-left-s-line" }) }) : null,
|
|
10342
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-image-viewer__stage", onMouseDown: (e) => e.stopPropagation(), onWheel, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10343
|
+
"img",
|
|
10344
|
+
{
|
|
10345
|
+
className: chunkTPGAXYFU_cjs.cx("klun-image-viewer__img", scale > 1 && "is-zoomed"),
|
|
10346
|
+
src: current.src,
|
|
10347
|
+
alt: current.alt ?? "",
|
|
10348
|
+
draggable: false,
|
|
10349
|
+
onDoubleClick: toggleZoom,
|
|
10350
|
+
onMouseDown: onImgDown,
|
|
10351
|
+
style: { transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale}) rotate(${rot}deg)` }
|
|
10352
|
+
}
|
|
10353
|
+
) }),
|
|
10354
|
+
gallery.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-image-viewer__nav klun-image-viewer__nav--next", "aria-label": "Next", onMouseDown: (e) => e.stopPropagation(), onClick: () => goTo(safeIdx + 1), children: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-right-s-line" }) }) : null
|
|
10355
|
+
]
|
|
10356
|
+
}
|
|
10357
|
+
),
|
|
10358
|
+
document.body
|
|
10359
|
+
);
|
|
10360
|
+
}
|
|
10361
|
+
ImageViewer.displayName = "ImageViewer";
|
|
9975
10362
|
var Modal = react.forwardRef(function Modal2({
|
|
9976
10363
|
open,
|
|
9977
10364
|
onClose,
|
|
@@ -10101,7 +10488,7 @@ var Modal = react.forwardRef(function Modal2({
|
|
|
10101
10488
|
);
|
|
10102
10489
|
});
|
|
10103
10490
|
Modal.displayName = "Modal";
|
|
10104
|
-
var
|
|
10491
|
+
var GAP3 = 8;
|
|
10105
10492
|
var MARGIN = 8;
|
|
10106
10493
|
var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
10107
10494
|
title: titleProp,
|
|
@@ -10150,7 +10537,7 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10150
10537
|
setPos(null);
|
|
10151
10538
|
return;
|
|
10152
10539
|
}
|
|
10153
|
-
const
|
|
10540
|
+
const place2 = () => {
|
|
10154
10541
|
const trigger = innerRef.current;
|
|
10155
10542
|
const bubble = bubbleRef.current;
|
|
10156
10543
|
if (!trigger || !bubble) return;
|
|
@@ -10163,31 +10550,31 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10163
10550
|
let left;
|
|
10164
10551
|
switch (placement) {
|
|
10165
10552
|
case "bottom":
|
|
10166
|
-
top = tr.bottom +
|
|
10553
|
+
top = tr.bottom + GAP3;
|
|
10167
10554
|
left = tr.left + tr.width / 2 - bw / 2;
|
|
10168
10555
|
break;
|
|
10169
10556
|
case "left":
|
|
10170
|
-
left = tr.left - bw -
|
|
10557
|
+
left = tr.left - bw - GAP3;
|
|
10171
10558
|
top = tr.top + tr.height / 2 - bh / 2;
|
|
10172
10559
|
break;
|
|
10173
10560
|
case "right":
|
|
10174
|
-
left = tr.right +
|
|
10561
|
+
left = tr.right + GAP3;
|
|
10175
10562
|
top = tr.top + tr.height / 2 - bh / 2;
|
|
10176
10563
|
break;
|
|
10177
10564
|
default:
|
|
10178
|
-
top = tr.top - bh -
|
|
10565
|
+
top = tr.top - bh - GAP3;
|
|
10179
10566
|
left = tr.left + tr.width / 2 - bw / 2;
|
|
10180
10567
|
}
|
|
10181
10568
|
left = Math.max(MARGIN, Math.min(left, vw - bw - MARGIN));
|
|
10182
10569
|
top = Math.max(MARGIN, Math.min(top, vh - bh - MARGIN));
|
|
10183
10570
|
setPos({ top, left });
|
|
10184
10571
|
};
|
|
10185
|
-
|
|
10186
|
-
window.addEventListener("scroll",
|
|
10187
|
-
window.addEventListener("resize",
|
|
10572
|
+
place2();
|
|
10573
|
+
window.addEventListener("scroll", place2, true);
|
|
10574
|
+
window.addEventListener("resize", place2);
|
|
10188
10575
|
return () => {
|
|
10189
|
-
window.removeEventListener("scroll",
|
|
10190
|
-
window.removeEventListener("resize",
|
|
10576
|
+
window.removeEventListener("scroll", place2, true);
|
|
10577
|
+
window.removeEventListener("resize", place2);
|
|
10191
10578
|
};
|
|
10192
10579
|
}, [open, placement, title, description]);
|
|
10193
10580
|
const setRefs = (node) => {
|
|
@@ -10299,6 +10686,7 @@ exports.Chip = Chip;
|
|
|
10299
10686
|
exports.CircularProgress = CircularProgress;
|
|
10300
10687
|
exports.CodeViewer = CodeViewer;
|
|
10301
10688
|
exports.Col = Col;
|
|
10689
|
+
exports.Collapse = Collapse;
|
|
10302
10690
|
exports.ColorDot = ColorDot;
|
|
10303
10691
|
exports.ColorPicker = ColorPicker;
|
|
10304
10692
|
exports.ColorSlider = ColorSlider;
|
|
@@ -10308,6 +10696,7 @@ exports.CompactSelectForInput = CompactSelectForInput;
|
|
|
10308
10696
|
exports.ConfigProvider = ConfigProvider;
|
|
10309
10697
|
exports.Confirm = Confirm;
|
|
10310
10698
|
exports.ContentLabel = ContentLabel;
|
|
10699
|
+
exports.ContextMenu = ContextMenu2;
|
|
10311
10700
|
exports.CopyButton = CopyButton;
|
|
10312
10701
|
exports.CounterInput = CounterInput;
|
|
10313
10702
|
exports.DatePicker = DatePicker;
|
|
@@ -10330,6 +10719,7 @@ exports.Grid = Grid;
|
|
|
10330
10719
|
exports.Hint = Hint;
|
|
10331
10720
|
exports.HorizontalFilter = HorizontalFilter;
|
|
10332
10721
|
exports.ImageUpload = ImageUpload;
|
|
10722
|
+
exports.ImageViewer = ImageViewer;
|
|
10333
10723
|
exports.InlineInput = InlineInput;
|
|
10334
10724
|
exports.InlineSelect = InlineSelect;
|
|
10335
10725
|
exports.Input = Input;
|
|
@@ -10409,6 +10799,7 @@ exports.toast = toast;
|
|
|
10409
10799
|
exports.trTR = trTR;
|
|
10410
10800
|
exports.useBreakpoint = useBreakpoint;
|
|
10411
10801
|
exports.useConfig = useConfig;
|
|
10802
|
+
exports.useContextMenu = useContextMenu;
|
|
10412
10803
|
exports.useForm = useForm;
|
|
10413
10804
|
exports.useLocale = useLocale;
|
|
10414
10805
|
exports.useMappedSize = useMappedSize;
|
|
@@ -10418,5 +10809,5 @@ exports.useSize = useSize;
|
|
|
10418
10809
|
exports.viVN = viVN;
|
|
10419
10810
|
exports.zhCN = zhCN;
|
|
10420
10811
|
exports.zhTW = zhTW;
|
|
10421
|
-
//# sourceMappingURL=chunk-
|
|
10422
|
-
//# sourceMappingURL=chunk-
|
|
10812
|
+
//# sourceMappingURL=chunk-F7D5VPDL.cjs.map
|
|
10813
|
+
//# sourceMappingURL=chunk-F7D5VPDL.cjs.map
|