klun-ui 0.1.8 → 0.1.10
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 +25 -0
- package/dist/{chunk-LDENBMG5.cjs → chunk-VFEV4TOL.cjs} +456 -51
- package/dist/chunk-VFEV4TOL.cjs.map +1 -0
- package/dist/{chunk-Z2TY6K3O.js → chunk-Y5BGEO2J.js} +454 -52
- package/dist/chunk-Y5BGEO2J.js.map +1 -0
- package/dist/index.cjs +156 -144
- package/dist/index.d.cts +87 -3
- package/dist/index.d.ts +87 -3
- package/dist/index.js +1 -1
- package/dist/styles.css +210 -0
- package/dist/templates/index.cjs +82 -82
- package/dist/templates/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LDENBMG5.cjs.map +0 -1
- package/dist/chunk-Z2TY6K3O.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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
|
+
|
|
22
|
+
## [0.1.9] — 2026-06-21
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **`Popconfirm` bubble is no longer clipped by scroll containers.** The bubble
|
|
26
|
+
rendered as an `absolute` child of the trigger, so when the trigger sat inside
|
|
27
|
+
a scrollable/`overflow` pane (e.g. an app content area) the bubble was clipped
|
|
28
|
+
at that pane's edge — near the left edge its title/description got cut off. It
|
|
29
|
+
is now portaled to `<body>`, positioned with fixed viewport coordinates, and
|
|
30
|
+
clamped to stay fully on-screen (recomputed on scroll/resize). Outside-click
|
|
31
|
+
and Escape dismissal still work across the portal boundary.
|
|
32
|
+
|
|
8
33
|
## [0.1.8] — 2026-06-21
|
|
9
34
|
|
|
10
35
|
### Fixed
|
|
@@ -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
|
}
|
|
@@ -5920,7 +5920,7 @@ var Cascader = react.forwardRef(function Cascader2({
|
|
|
5920
5920
|
),
|
|
5921
5921
|
children: col.map((opt) => {
|
|
5922
5922
|
const selected = active[ci] === opt.value;
|
|
5923
|
-
const
|
|
5923
|
+
const hasChildren2 = !!(opt.children && opt.children.length);
|
|
5924
5924
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5925
5925
|
"button",
|
|
5926
5926
|
{
|
|
@@ -5931,7 +5931,7 @@ var Cascader = react.forwardRef(function Cascader2({
|
|
|
5931
5931
|
"data-selected": selected || void 0,
|
|
5932
5932
|
children: [
|
|
5933
5933
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-cascader__option-label", children: opt.label }),
|
|
5934
|
-
|
|
5934
|
+
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
5935
|
]
|
|
5936
5936
|
},
|
|
5937
5937
|
opt.value
|
|
@@ -9838,6 +9838,203 @@ function Confirm(opts = {}) {
|
|
|
9838
9838
|
root.render(/* @__PURE__ */ jsxRuntime.jsx(ConfirmDialog, { opts, onClose: close }));
|
|
9839
9839
|
});
|
|
9840
9840
|
}
|
|
9841
|
+
var isSelectable = (it) => !it.divider && !it.disabled;
|
|
9842
|
+
var hasChildren = (it) => !!it.children && it.children.length > 0;
|
|
9843
|
+
var GAP2 = 8;
|
|
9844
|
+
function place(w, h, opts) {
|
|
9845
|
+
const vw = window.innerWidth;
|
|
9846
|
+
const vh = window.innerHeight;
|
|
9847
|
+
let left;
|
|
9848
|
+
let top;
|
|
9849
|
+
if (opts.rect) {
|
|
9850
|
+
const r = opts.rect;
|
|
9851
|
+
left = r.right - 4 + w > vw - GAP2 ? r.left - w + 4 : r.right - 4;
|
|
9852
|
+
top = r.top - 4;
|
|
9853
|
+
} else {
|
|
9854
|
+
const p = opts.point ?? { x: 0, y: 0 };
|
|
9855
|
+
left = p.x + w > vw - GAP2 ? p.x - w : p.x;
|
|
9856
|
+
top = p.y + h > vh - GAP2 ? p.y - h : p.y;
|
|
9857
|
+
}
|
|
9858
|
+
left = Math.min(Math.max(GAP2, left), vw - w - GAP2);
|
|
9859
|
+
top = Math.min(Math.max(GAP2, top), vh - h - GAP2);
|
|
9860
|
+
return { left, top };
|
|
9861
|
+
}
|
|
9862
|
+
function MenuPanel({ items: items2, width, className, point, anchorEl, autoActive, depth, onCloseAll, onCloseSelf }) {
|
|
9863
|
+
const ref = react.useRef(null);
|
|
9864
|
+
const itemEls = react.useRef([]);
|
|
9865
|
+
const [pos, setPos] = react.useState(null);
|
|
9866
|
+
const [active, setActive] = react.useState(autoActive ? items2.findIndex(isSelectable) : -1);
|
|
9867
|
+
const [sub, setSub] = react.useState(null);
|
|
9868
|
+
const isLeaf = sub === null;
|
|
9869
|
+
react.useLayoutEffect(() => {
|
|
9870
|
+
const el = ref.current;
|
|
9871
|
+
const w = el?.offsetWidth ?? width;
|
|
9872
|
+
const h = el?.offsetHeight ?? 0;
|
|
9873
|
+
setPos(place(w, h, anchorEl ? { rect: anchorEl.getBoundingClientRect() } : { point }));
|
|
9874
|
+
}, [width, point?.x, point?.y, anchorEl, items2]);
|
|
9875
|
+
const move = react.useCallback(
|
|
9876
|
+
(dir) => {
|
|
9877
|
+
setActive((cur) => {
|
|
9878
|
+
const n = items2.length;
|
|
9879
|
+
for (let s = 1; s <= n; s++) {
|
|
9880
|
+
const i = (cur + dir * s + n * s) % n;
|
|
9881
|
+
if (isSelectable(items2[i])) return i;
|
|
9882
|
+
}
|
|
9883
|
+
return cur;
|
|
9884
|
+
});
|
|
9885
|
+
},
|
|
9886
|
+
[items2]
|
|
9887
|
+
);
|
|
9888
|
+
const activate = react.useCallback(
|
|
9889
|
+
(i, viaKeyboard) => {
|
|
9890
|
+
const it = items2[i];
|
|
9891
|
+
if (!it || !isSelectable(it)) return;
|
|
9892
|
+
if (hasChildren(it)) setSub({ i, kbd: viaKeyboard });
|
|
9893
|
+
else {
|
|
9894
|
+
onCloseAll();
|
|
9895
|
+
it.onClick?.();
|
|
9896
|
+
}
|
|
9897
|
+
},
|
|
9898
|
+
[items2, onCloseAll]
|
|
9899
|
+
);
|
|
9900
|
+
react.useEffect(() => {
|
|
9901
|
+
if (!isLeaf) return;
|
|
9902
|
+
const onKey = (e) => {
|
|
9903
|
+
if (e.key === "ArrowDown") {
|
|
9904
|
+
e.preventDefault();
|
|
9905
|
+
move(1);
|
|
9906
|
+
} else if (e.key === "ArrowUp") {
|
|
9907
|
+
e.preventDefault();
|
|
9908
|
+
move(-1);
|
|
9909
|
+
} else if (e.key === "Home") {
|
|
9910
|
+
e.preventDefault();
|
|
9911
|
+
setActive(items2.findIndex(isSelectable));
|
|
9912
|
+
} else if (e.key === "End") {
|
|
9913
|
+
e.preventDefault();
|
|
9914
|
+
for (let i = items2.length - 1; i >= 0; i--)
|
|
9915
|
+
if (isSelectable(items2[i])) {
|
|
9916
|
+
setActive(i);
|
|
9917
|
+
break;
|
|
9918
|
+
}
|
|
9919
|
+
} else if (e.key === "ArrowRight") {
|
|
9920
|
+
if (active >= 0 && hasChildren(items2[active])) {
|
|
9921
|
+
e.preventDefault();
|
|
9922
|
+
setSub({ i: active, kbd: true });
|
|
9923
|
+
}
|
|
9924
|
+
} else if (e.key === "ArrowLeft") {
|
|
9925
|
+
if (depth > 0) {
|
|
9926
|
+
e.preventDefault();
|
|
9927
|
+
onCloseSelf?.();
|
|
9928
|
+
}
|
|
9929
|
+
} else if (e.key === "Enter") {
|
|
9930
|
+
if (active >= 0) {
|
|
9931
|
+
e.preventDefault();
|
|
9932
|
+
activate(active, true);
|
|
9933
|
+
}
|
|
9934
|
+
} else if (e.key === "Escape") {
|
|
9935
|
+
e.preventDefault();
|
|
9936
|
+
onCloseAll();
|
|
9937
|
+
}
|
|
9938
|
+
};
|
|
9939
|
+
window.addEventListener("keydown", onKey, true);
|
|
9940
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
9941
|
+
}, [isLeaf, active, items2, depth, move, activate, onCloseAll, onCloseSelf]);
|
|
9942
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9943
|
+
"div",
|
|
9944
|
+
{
|
|
9945
|
+
ref,
|
|
9946
|
+
role: "menu",
|
|
9947
|
+
className: chunkTPGAXYFU_cjs.cx("klun-context-menu", className),
|
|
9948
|
+
style: pos ? { left: pos.left, top: pos.top, width } : { left: -9999, top: -9999, width },
|
|
9949
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
9950
|
+
children: [
|
|
9951
|
+
items2.map(
|
|
9952
|
+
(it, i) => it.divider ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-context-menu__divider", role: "separator" }, i) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9953
|
+
"button",
|
|
9954
|
+
{
|
|
9955
|
+
ref: (el) => {
|
|
9956
|
+
itemEls.current[i] = el;
|
|
9957
|
+
},
|
|
9958
|
+
type: "button",
|
|
9959
|
+
role: "menuitem",
|
|
9960
|
+
"aria-haspopup": hasChildren(it) || void 0,
|
|
9961
|
+
"aria-expanded": hasChildren(it) ? sub?.i === i : void 0,
|
|
9962
|
+
disabled: it.disabled,
|
|
9963
|
+
className: chunkTPGAXYFU_cjs.cx(
|
|
9964
|
+
"klun-context-menu__item",
|
|
9965
|
+
it.danger && "klun-context-menu__item--danger",
|
|
9966
|
+
i === active && "is-active"
|
|
9967
|
+
),
|
|
9968
|
+
onMouseEnter: () => {
|
|
9969
|
+
setActive(i);
|
|
9970
|
+
if (isSelectable(it)) setSub(hasChildren(it) ? { i, kbd: false } : null);
|
|
9971
|
+
},
|
|
9972
|
+
onClick: () => activate(i, false),
|
|
9973
|
+
children: [
|
|
9974
|
+
it.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-context-menu__icon", children: it.icon }) : null,
|
|
9975
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-context-menu__label", children: it.label }),
|
|
9976
|
+
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
|
|
9977
|
+
]
|
|
9978
|
+
},
|
|
9979
|
+
i
|
|
9980
|
+
)
|
|
9981
|
+
),
|
|
9982
|
+
sub && items2[sub.i]?.children ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9983
|
+
MenuPanel,
|
|
9984
|
+
{
|
|
9985
|
+
items: items2[sub.i].children,
|
|
9986
|
+
width,
|
|
9987
|
+
anchorEl: itemEls.current[sub.i],
|
|
9988
|
+
autoActive: sub.kbd,
|
|
9989
|
+
depth: depth + 1,
|
|
9990
|
+
onCloseAll,
|
|
9991
|
+
onCloseSelf: () => setSub(null)
|
|
9992
|
+
},
|
|
9993
|
+
`sub-${sub.i}`
|
|
9994
|
+
) : null
|
|
9995
|
+
]
|
|
9996
|
+
}
|
|
9997
|
+
);
|
|
9998
|
+
}
|
|
9999
|
+
function ContextMenu2({ open, x, y, items: items2, onClose, width = 208, className }) {
|
|
10000
|
+
if (!open || typeof document === "undefined") return null;
|
|
10001
|
+
return reactDom.createPortal(
|
|
10002
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10003
|
+
"div",
|
|
10004
|
+
{
|
|
10005
|
+
className: "klun-context-menu__layer",
|
|
10006
|
+
onMouseDown: onClose,
|
|
10007
|
+
onContextMenu: (e) => {
|
|
10008
|
+
e.preventDefault();
|
|
10009
|
+
onClose();
|
|
10010
|
+
},
|
|
10011
|
+
onWheel: onClose,
|
|
10012
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10013
|
+
MenuPanel,
|
|
10014
|
+
{
|
|
10015
|
+
items: items2,
|
|
10016
|
+
width,
|
|
10017
|
+
className,
|
|
10018
|
+
point: { x, y },
|
|
10019
|
+
depth: 0,
|
|
10020
|
+
onCloseAll: onClose
|
|
10021
|
+
}
|
|
10022
|
+
)
|
|
10023
|
+
}
|
|
10024
|
+
),
|
|
10025
|
+
document.body
|
|
10026
|
+
);
|
|
10027
|
+
}
|
|
10028
|
+
ContextMenu2.displayName = "ContextMenu";
|
|
10029
|
+
function useContextMenu() {
|
|
10030
|
+
const [state, setState] = react.useState(null);
|
|
10031
|
+
const open = react.useCallback((e, items2) => {
|
|
10032
|
+
e.preventDefault();
|
|
10033
|
+
setState({ x: e.clientX, y: e.clientY, items: items2 });
|
|
10034
|
+
}, []);
|
|
10035
|
+
const close = react.useCallback(() => setState(null), []);
|
|
10036
|
+
return { state, open, close };
|
|
10037
|
+
}
|
|
9841
10038
|
var Drawer = react.forwardRef(function Drawer2({
|
|
9842
10039
|
open,
|
|
9843
10040
|
onClose,
|
|
@@ -9861,8 +10058,8 @@ var Drawer = react.forwardRef(function Drawer2({
|
|
|
9861
10058
|
...props
|
|
9862
10059
|
}, ref) {
|
|
9863
10060
|
const { drawer } = useLocale();
|
|
9864
|
-
const
|
|
9865
|
-
const horizontal =
|
|
10061
|
+
const place2 = placement ?? side ?? "right";
|
|
10062
|
+
const horizontal = place2 === "left" || place2 === "right";
|
|
9866
10063
|
const prevOpen = react.useRef(open);
|
|
9867
10064
|
react.useEffect(() => {
|
|
9868
10065
|
if (!open) return;
|
|
@@ -9889,7 +10086,7 @@ var Drawer = react.forwardRef(function Drawer2({
|
|
|
9889
10086
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9890
10087
|
"div",
|
|
9891
10088
|
{
|
|
9892
|
-
className: chunkTPGAXYFU_cjs.cx("klun-drawer", `klun-drawer--${
|
|
10089
|
+
className: chunkTPGAXYFU_cjs.cx("klun-drawer", `klun-drawer--${place2}`, !mask && "klun-drawer--no-mask"),
|
|
9893
10090
|
style: zIndex != null ? { zIndex } : void 0,
|
|
9894
10091
|
onClick: maskClosable ? onClose : void 0,
|
|
9895
10092
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -9972,6 +10169,146 @@ var Dropdown = react.forwardRef(function Dropdown2({ trigger, items: items2 = []
|
|
|
9972
10169
|
] });
|
|
9973
10170
|
});
|
|
9974
10171
|
Dropdown.displayName = "Dropdown";
|
|
10172
|
+
var MIN = 0.25;
|
|
10173
|
+
var MAX = 6;
|
|
10174
|
+
var clampScale = (s) => Math.min(MAX, Math.max(MIN, +s.toFixed(2)));
|
|
10175
|
+
function ImageViewer({
|
|
10176
|
+
open,
|
|
10177
|
+
src,
|
|
10178
|
+
alt,
|
|
10179
|
+
items: items2,
|
|
10180
|
+
index,
|
|
10181
|
+
onIndexChange,
|
|
10182
|
+
onClose,
|
|
10183
|
+
onDownload,
|
|
10184
|
+
title,
|
|
10185
|
+
zoomable = true,
|
|
10186
|
+
rotatable = true,
|
|
10187
|
+
closeOnBackdrop = true,
|
|
10188
|
+
className
|
|
10189
|
+
}) {
|
|
10190
|
+
const gallery = items2 && items2.length > 0 ? items2 : src != null ? [{ src, alt }] : [];
|
|
10191
|
+
const [innerIndex, setInnerIndex] = react.useState(0);
|
|
10192
|
+
const idx = index ?? innerIndex;
|
|
10193
|
+
const safeIdx = Math.min(Math.max(idx, 0), Math.max(0, gallery.length - 1));
|
|
10194
|
+
const current = gallery[safeIdx];
|
|
10195
|
+
const [scale, setScale] = react.useState(1);
|
|
10196
|
+
const [rot, setRot] = react.useState(0);
|
|
10197
|
+
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
10198
|
+
const drag = react.useRef(null);
|
|
10199
|
+
const reset = react.useCallback(() => {
|
|
10200
|
+
setScale(1);
|
|
10201
|
+
setRot(0);
|
|
10202
|
+
setPan({ x: 0, y: 0 });
|
|
10203
|
+
}, []);
|
|
10204
|
+
const goTo = react.useCallback(
|
|
10205
|
+
(next) => {
|
|
10206
|
+
if (gallery.length < 2) return;
|
|
10207
|
+
const n = (next + gallery.length) % gallery.length;
|
|
10208
|
+
if (index === void 0) setInnerIndex(n);
|
|
10209
|
+
onIndexChange?.(n);
|
|
10210
|
+
reset();
|
|
10211
|
+
},
|
|
10212
|
+
[gallery.length, index, onIndexChange, reset]
|
|
10213
|
+
);
|
|
10214
|
+
react.useEffect(() => {
|
|
10215
|
+
reset();
|
|
10216
|
+
}, [safeIdx, open, reset]);
|
|
10217
|
+
react.useEffect(() => {
|
|
10218
|
+
if (!open) return;
|
|
10219
|
+
const prev = document.body.style.overflow;
|
|
10220
|
+
document.body.style.overflow = "hidden";
|
|
10221
|
+
const onKey = (e) => {
|
|
10222
|
+
if (e.key === "Escape") onClose();
|
|
10223
|
+
else if (e.key === "ArrowLeft") goTo(safeIdx - 1);
|
|
10224
|
+
else if (e.key === "ArrowRight") goTo(safeIdx + 1);
|
|
10225
|
+
else if (zoomable && (e.key === "+" || e.key === "=")) setScale((s) => clampScale(s + 0.25));
|
|
10226
|
+
else if (zoomable && (e.key === "-" || e.key === "_")) setScale((s) => clampScale(s - 0.25));
|
|
10227
|
+
else if (e.key === "0") reset();
|
|
10228
|
+
};
|
|
10229
|
+
window.addEventListener("keydown", onKey);
|
|
10230
|
+
return () => {
|
|
10231
|
+
document.body.style.overflow = prev;
|
|
10232
|
+
window.removeEventListener("keydown", onKey);
|
|
10233
|
+
};
|
|
10234
|
+
}, [open, safeIdx, zoomable, onClose, goTo, reset]);
|
|
10235
|
+
if (!open || !current || typeof document === "undefined") return null;
|
|
10236
|
+
const onWheel = (e) => {
|
|
10237
|
+
if (!zoomable) return;
|
|
10238
|
+
e.preventDefault();
|
|
10239
|
+
setScale((s) => clampScale(s + (e.deltaY < 0 ? 0.2 : -0.2)));
|
|
10240
|
+
};
|
|
10241
|
+
const onImgDown = (e) => {
|
|
10242
|
+
if (scale <= 1) return;
|
|
10243
|
+
e.preventDefault();
|
|
10244
|
+
drag.current = { x: e.clientX, y: e.clientY, px: pan.x, py: pan.y };
|
|
10245
|
+
};
|
|
10246
|
+
const onMove = (e) => {
|
|
10247
|
+
if (!drag.current) return;
|
|
10248
|
+
setPan({ x: drag.current.px + (e.clientX - drag.current.x), y: drag.current.py + (e.clientY - drag.current.y) });
|
|
10249
|
+
};
|
|
10250
|
+
const endDrag = () => {
|
|
10251
|
+
drag.current = null;
|
|
10252
|
+
};
|
|
10253
|
+
const toggleZoom = () => scale > 1 ? reset() : setScale(2);
|
|
10254
|
+
const heading = title ?? current.title ?? current.alt;
|
|
10255
|
+
return reactDom.createPortal(
|
|
10256
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10257
|
+
"div",
|
|
10258
|
+
{
|
|
10259
|
+
className: chunkTPGAXYFU_cjs.cx("klun-image-viewer", className),
|
|
10260
|
+
onMouseDown: (e) => {
|
|
10261
|
+
if (closeOnBackdrop && e.target === e.currentTarget) onClose();
|
|
10262
|
+
},
|
|
10263
|
+
onMouseMove: onMove,
|
|
10264
|
+
onMouseUp: endDrag,
|
|
10265
|
+
onMouseLeave: endDrag,
|
|
10266
|
+
children: [
|
|
10267
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__bar", onMouseDown: (e) => e.stopPropagation(), children: [
|
|
10268
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__title", children: [
|
|
10269
|
+
heading,
|
|
10270
|
+
gallery.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "klun-image-viewer__counter", children: [
|
|
10271
|
+
safeIdx + 1,
|
|
10272
|
+
" / ",
|
|
10273
|
+
gallery.length
|
|
10274
|
+
] }) : null
|
|
10275
|
+
] }),
|
|
10276
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-image-viewer__tools", children: [
|
|
10277
|
+
zoomable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10278
|
+
/* @__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" }) }),
|
|
10279
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "klun-image-viewer__zoom", children: [
|
|
10280
|
+
Math.round(scale * 100),
|
|
10281
|
+
"%"
|
|
10282
|
+
] }),
|
|
10283
|
+
/* @__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" }) })
|
|
10284
|
+
] }) : null,
|
|
10285
|
+
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,
|
|
10286
|
+
/* @__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" }) }),
|
|
10287
|
+
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,
|
|
10288
|
+
/* @__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" }) })
|
|
10289
|
+
] })
|
|
10290
|
+
] }),
|
|
10291
|
+
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,
|
|
10292
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-image-viewer__stage", onMouseDown: (e) => e.stopPropagation(), onWheel, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10293
|
+
"img",
|
|
10294
|
+
{
|
|
10295
|
+
className: chunkTPGAXYFU_cjs.cx("klun-image-viewer__img", scale > 1 && "is-zoomed"),
|
|
10296
|
+
src: current.src,
|
|
10297
|
+
alt: current.alt ?? "",
|
|
10298
|
+
draggable: false,
|
|
10299
|
+
onDoubleClick: toggleZoom,
|
|
10300
|
+
onMouseDown: onImgDown,
|
|
10301
|
+
style: { transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale}) rotate(${rot}deg)` }
|
|
10302
|
+
}
|
|
10303
|
+
) }),
|
|
10304
|
+
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
|
|
10305
|
+
]
|
|
10306
|
+
}
|
|
10307
|
+
),
|
|
10308
|
+
document.body
|
|
10309
|
+
);
|
|
10310
|
+
}
|
|
10311
|
+
ImageViewer.displayName = "ImageViewer";
|
|
9975
10312
|
var Modal = react.forwardRef(function Modal2({
|
|
9976
10313
|
open,
|
|
9977
10314
|
onClose,
|
|
@@ -10101,6 +10438,8 @@ var Modal = react.forwardRef(function Modal2({
|
|
|
10101
10438
|
);
|
|
10102
10439
|
});
|
|
10103
10440
|
Modal.displayName = "Modal";
|
|
10441
|
+
var GAP3 = 8;
|
|
10442
|
+
var MARGIN = 8;
|
|
10104
10443
|
var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
10105
10444
|
title: titleProp,
|
|
10106
10445
|
description,
|
|
@@ -10123,11 +10462,15 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10123
10462
|
const cancelText = cancelTextProp ?? popconfirm.cancel;
|
|
10124
10463
|
const [open, setOpen] = react.useState(false);
|
|
10125
10464
|
const [busy, setBusy] = react.useState(false);
|
|
10465
|
+
const [pos, setPos] = react.useState(null);
|
|
10126
10466
|
const innerRef = react.useRef(null);
|
|
10467
|
+
const bubbleRef = react.useRef(null);
|
|
10127
10468
|
react.useEffect(() => {
|
|
10128
10469
|
if (!open) return;
|
|
10129
10470
|
const h = (e) => {
|
|
10130
|
-
|
|
10471
|
+
const t = e.target;
|
|
10472
|
+
if (innerRef.current?.contains(t) || bubbleRef.current?.contains(t)) return;
|
|
10473
|
+
setOpen(false);
|
|
10131
10474
|
};
|
|
10132
10475
|
const k = (e) => {
|
|
10133
10476
|
if (e.key === "Escape") setOpen(false);
|
|
@@ -10139,6 +10482,51 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10139
10482
|
document.removeEventListener("keydown", k);
|
|
10140
10483
|
};
|
|
10141
10484
|
}, [open]);
|
|
10485
|
+
react.useLayoutEffect(() => {
|
|
10486
|
+
if (!open) {
|
|
10487
|
+
setPos(null);
|
|
10488
|
+
return;
|
|
10489
|
+
}
|
|
10490
|
+
const place2 = () => {
|
|
10491
|
+
const trigger = innerRef.current;
|
|
10492
|
+
const bubble = bubbleRef.current;
|
|
10493
|
+
if (!trigger || !bubble) return;
|
|
10494
|
+
const tr = trigger.getBoundingClientRect();
|
|
10495
|
+
const bw = bubble.offsetWidth;
|
|
10496
|
+
const bh = bubble.offsetHeight;
|
|
10497
|
+
const vw = document.documentElement.clientWidth;
|
|
10498
|
+
const vh = document.documentElement.clientHeight;
|
|
10499
|
+
let top;
|
|
10500
|
+
let left;
|
|
10501
|
+
switch (placement) {
|
|
10502
|
+
case "bottom":
|
|
10503
|
+
top = tr.bottom + GAP3;
|
|
10504
|
+
left = tr.left + tr.width / 2 - bw / 2;
|
|
10505
|
+
break;
|
|
10506
|
+
case "left":
|
|
10507
|
+
left = tr.left - bw - GAP3;
|
|
10508
|
+
top = tr.top + tr.height / 2 - bh / 2;
|
|
10509
|
+
break;
|
|
10510
|
+
case "right":
|
|
10511
|
+
left = tr.right + GAP3;
|
|
10512
|
+
top = tr.top + tr.height / 2 - bh / 2;
|
|
10513
|
+
break;
|
|
10514
|
+
default:
|
|
10515
|
+
top = tr.top - bh - GAP3;
|
|
10516
|
+
left = tr.left + tr.width / 2 - bw / 2;
|
|
10517
|
+
}
|
|
10518
|
+
left = Math.max(MARGIN, Math.min(left, vw - bw - MARGIN));
|
|
10519
|
+
top = Math.max(MARGIN, Math.min(top, vh - bh - MARGIN));
|
|
10520
|
+
setPos({ top, left });
|
|
10521
|
+
};
|
|
10522
|
+
place2();
|
|
10523
|
+
window.addEventListener("scroll", place2, true);
|
|
10524
|
+
window.addEventListener("resize", place2);
|
|
10525
|
+
return () => {
|
|
10526
|
+
window.removeEventListener("scroll", place2, true);
|
|
10527
|
+
window.removeEventListener("resize", place2);
|
|
10528
|
+
};
|
|
10529
|
+
}, [open, placement, title, description]);
|
|
10142
10530
|
const setRefs = (node) => {
|
|
10143
10531
|
innerRef.current = node;
|
|
10144
10532
|
if (typeof ref === "function") ref(node);
|
|
@@ -10157,6 +10545,16 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10157
10545
|
onCancel?.();
|
|
10158
10546
|
setOpen(false);
|
|
10159
10547
|
};
|
|
10548
|
+
const bubbleStyle = {
|
|
10549
|
+
position: "fixed",
|
|
10550
|
+
top: pos ? pos.top : 0,
|
|
10551
|
+
left: pos ? pos.left : 0,
|
|
10552
|
+
right: "auto",
|
|
10553
|
+
bottom: "auto",
|
|
10554
|
+
transform: "none",
|
|
10555
|
+
visibility: pos ? "visible" : "hidden",
|
|
10556
|
+
["--klun-popconfirm-tint"]: `var(--klun-state-${status}-base)`
|
|
10557
|
+
};
|
|
10160
10558
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { ref: setRefs, className: chunkTPGAXYFU_cjs.cx("klun-popconfirm", className), ...props, children: [
|
|
10161
10559
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10162
10560
|
"span",
|
|
@@ -10169,42 +10567,46 @@ var Popconfirm = react.forwardRef(function Popconfirm2({
|
|
|
10169
10567
|
children
|
|
10170
10568
|
}
|
|
10171
10569
|
),
|
|
10172
|
-
open
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-
|
|
10183
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
10184
|
-
|
|
10570
|
+
open && typeof document !== "undefined" ? reactDom.createPortal(
|
|
10571
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10572
|
+
"div",
|
|
10573
|
+
{
|
|
10574
|
+
ref: bubbleRef,
|
|
10575
|
+
role: "dialog",
|
|
10576
|
+
className: chunkTPGAXYFU_cjs.cx("klun-popconfirm__bubble", `klun-popconfirm__bubble--${placement}`),
|
|
10577
|
+
style: bubbleStyle,
|
|
10578
|
+
onClick: (e) => e.stopPropagation(),
|
|
10579
|
+
children: [
|
|
10580
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-popconfirm__head", children: [
|
|
10581
|
+
icon ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: chunkTPGAXYFU_cjs.cx("klun-popconfirm__icon", icon) }) : null,
|
|
10582
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-popconfirm__body", children: [
|
|
10583
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-popconfirm__title", children: title }),
|
|
10584
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-popconfirm__desc", children: description }) : null
|
|
10585
|
+
] })
|
|
10586
|
+
] }),
|
|
10587
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-popconfirm__actions", children: [
|
|
10588
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "klun-popconfirm__cancel", onClick: cancel, children: cancelText }),
|
|
10589
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10590
|
+
"button",
|
|
10591
|
+
{
|
|
10592
|
+
type: "button",
|
|
10593
|
+
className: chunkTPGAXYFU_cjs.cx(
|
|
10594
|
+
"klun-popconfirm__confirm",
|
|
10595
|
+
okDanger && "klun-popconfirm__confirm--danger"
|
|
10596
|
+
),
|
|
10597
|
+
onClick: confirm,
|
|
10598
|
+
disabled: busy,
|
|
10599
|
+
children: [
|
|
10600
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-loader-4-line klun-popconfirm__spinner" }) : null,
|
|
10601
|
+
okText
|
|
10602
|
+
]
|
|
10603
|
+
}
|
|
10604
|
+
)
|
|
10185
10605
|
] })
|
|
10186
|
-
]
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
"button",
|
|
10191
|
-
{
|
|
10192
|
-
type: "button",
|
|
10193
|
-
className: chunkTPGAXYFU_cjs.cx(
|
|
10194
|
-
"klun-popconfirm__confirm",
|
|
10195
|
-
okDanger && "klun-popconfirm__confirm--danger"
|
|
10196
|
-
),
|
|
10197
|
-
onClick: confirm,
|
|
10198
|
-
disabled: busy,
|
|
10199
|
-
children: [
|
|
10200
|
-
busy ? /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-loader-4-line klun-popconfirm__spinner" }) : null,
|
|
10201
|
-
okText
|
|
10202
|
-
]
|
|
10203
|
-
}
|
|
10204
|
-
)
|
|
10205
|
-
] })
|
|
10206
|
-
]
|
|
10207
|
-
}
|
|
10606
|
+
]
|
|
10607
|
+
}
|
|
10608
|
+
),
|
|
10609
|
+
document.body
|
|
10208
10610
|
) : null
|
|
10209
10611
|
] });
|
|
10210
10612
|
});
|
|
@@ -10243,6 +10645,7 @@ exports.CompactSelectForInput = CompactSelectForInput;
|
|
|
10243
10645
|
exports.ConfigProvider = ConfigProvider;
|
|
10244
10646
|
exports.Confirm = Confirm;
|
|
10245
10647
|
exports.ContentLabel = ContentLabel;
|
|
10648
|
+
exports.ContextMenu = ContextMenu2;
|
|
10246
10649
|
exports.CopyButton = CopyButton;
|
|
10247
10650
|
exports.CounterInput = CounterInput;
|
|
10248
10651
|
exports.DatePicker = DatePicker;
|
|
@@ -10265,6 +10668,7 @@ exports.Grid = Grid;
|
|
|
10265
10668
|
exports.Hint = Hint;
|
|
10266
10669
|
exports.HorizontalFilter = HorizontalFilter;
|
|
10267
10670
|
exports.ImageUpload = ImageUpload;
|
|
10671
|
+
exports.ImageViewer = ImageViewer;
|
|
10268
10672
|
exports.InlineInput = InlineInput;
|
|
10269
10673
|
exports.InlineSelect = InlineSelect;
|
|
10270
10674
|
exports.Input = Input;
|
|
@@ -10344,6 +10748,7 @@ exports.toast = toast;
|
|
|
10344
10748
|
exports.trTR = trTR;
|
|
10345
10749
|
exports.useBreakpoint = useBreakpoint;
|
|
10346
10750
|
exports.useConfig = useConfig;
|
|
10751
|
+
exports.useContextMenu = useContextMenu;
|
|
10347
10752
|
exports.useForm = useForm;
|
|
10348
10753
|
exports.useLocale = useLocale;
|
|
10349
10754
|
exports.useMappedSize = useMappedSize;
|
|
@@ -10353,5 +10758,5 @@ exports.useSize = useSize;
|
|
|
10353
10758
|
exports.viVN = viVN;
|
|
10354
10759
|
exports.zhCN = zhCN;
|
|
10355
10760
|
exports.zhTW = zhTW;
|
|
10356
|
-
//# sourceMappingURL=chunk-
|
|
10357
|
-
//# sourceMappingURL=chunk-
|
|
10761
|
+
//# sourceMappingURL=chunk-VFEV4TOL.cjs.map
|
|
10762
|
+
//# sourceMappingURL=chunk-VFEV4TOL.cjs.map
|