@burdenoff/microfe-files 2026.1017.10 → 2026.1023.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ActionMenu.d.ts","sourceRoot":"","sources":["../../../src/files/components/ActionMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAAE,EAMR,MAAM,OAAO,CAAC;AAMf,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA+J1C,CAAC"}
1
+ {"version":3,"file":"ActionMenu.d.ts","sourceRoot":"","sources":["../../../src/files/components/ActionMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAAE,EAOR,MAAM,OAAO,CAAC;AAOf,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAmL1C,CAAC"}
@@ -1,79 +1,96 @@
1
1
  import { tWithFallback as e } from "../utils/i18n.js";
2
- import { useCallback as t, useEffect as n, useEffectEvent as r, useRef as i, useState as a } from "react";
3
- import { MoreVertical as o } from "lucide-react";
4
- import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
5
- import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
- import { createPortal as d } from "react-dom";
2
+ import { actionMenuPosition as t } from "./actionMenuPosition.js";
3
+ import { useCallback as n, useEffect as r, useEffectEvent as i, useLayoutEffect as a, useRef as o, useState as s } from "react";
4
+ import { MoreVertical as c } from "lucide-react";
5
+ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
6
+ import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { createPortal as p } from "react-dom";
7
8
  //#region src/files/components/ActionMenu.tsx
8
- var f = ({ items: f, triggerClassName: p = "", menuClassName: m = "", align: h = "right", isOpen: g, onToggle: _ }) => {
9
- let { t: v } = u(), [y, b] = a(!1), [x, S] = a(null), C = i(null), w = i(null), T = g === void 0 ? y : g, E = t((e) => {
10
- _ ? _(e) : b(e);
11
- }, [_]);
12
- n(() => {
13
- if (T && C.current) {
14
- let e = C.current.getBoundingClientRect(), t = h === "right" ? e.right - 200 : e.left;
15
- t < 10 && (t = 10), t + 200 > window.innerWidth - 10 && (t = window.innerWidth - 200 - 10), S({
16
- top: e.bottom + 4,
17
- left: t
9
+ var m = ({ items: m, triggerClassName: h = "", menuClassName: g = "", align: _ = "right", isOpen: v, onToggle: y }) => {
10
+ let { t: b } = f(), [x, S] = s(!1), [C, w] = s(null), T = o(null), E = o(null), D = v === void 0 ? x : v, O = n((e) => {
11
+ y ? y(e) : S(e);
12
+ }, [y]);
13
+ a(() => {
14
+ if (!D) return;
15
+ let e = T.current, n = E.current;
16
+ if (!e || !n) return;
17
+ let r = () => {
18
+ let r = t({
19
+ trigger: e.getBoundingClientRect(),
20
+ menuWidth: n.offsetWidth,
21
+ menuHeight: n.offsetHeight,
22
+ viewportWidth: window.innerWidth,
23
+ viewportHeight: window.innerHeight,
24
+ align: _
18
25
  });
19
- }
20
- }, [T, h]);
21
- let D = r(() => {
22
- E(!1);
26
+ w((e) => e?.top === r.top && e.left === r.left ? e : r);
27
+ };
28
+ r();
29
+ let i = new ResizeObserver(r);
30
+ return i.observe(n), i.observe(e), window.addEventListener("resize", r), window.addEventListener("scroll", r, !0), () => {
31
+ i.disconnect(), window.removeEventListener("resize", r), window.removeEventListener("scroll", r, !0);
32
+ };
33
+ }, [D, _]);
34
+ let k = i(() => {
35
+ O(!1);
23
36
  });
24
- n(() => {
37
+ r(() => {
25
38
  let e = (e) => {
26
- w.current && !w.current.contains(e.target) && D();
39
+ E.current && !E.current.contains(e.target) && k();
27
40
  };
28
- if (T) return document.addEventListener("mousedown", e), () => {
41
+ if (D) return document.addEventListener("mousedown", e), () => {
29
42
  document.removeEventListener("mousedown", e);
30
43
  };
31
- }, [T]), n(() => {
44
+ }, [D]), r(() => {
32
45
  let e = (e) => {
33
- e.key === "Escape" && D();
46
+ e.key === "Escape" && k();
34
47
  };
35
- if (T) return document.addEventListener("keydown", e), () => {
48
+ if (D) return document.addEventListener("keydown", e), () => {
36
49
  document.removeEventListener("keydown", e);
37
50
  };
38
- }, [T]);
39
- let O = (e) => {
40
- e.onClick(), E(!1);
51
+ }, [D]);
52
+ let A = (e) => {
53
+ e.onClick(), O(!1);
41
54
  };
42
- return /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("button", {
55
+ return /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ u("button", {
43
56
  type: "button",
44
- ref: C,
57
+ ref: T,
45
58
  onClick: (e) => {
46
- e.stopPropagation(), E(!T);
59
+ e.stopPropagation(), O(!D);
47
60
  },
48
- className: `p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors ${p}`,
49
- "aria-label": e(v, "files.actionMenu.moreActions", "More actions"),
50
- title: e(v, "files.actionMenu.moreActions", "More actions"),
51
- "aria-expanded": T,
61
+ className: `p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors ${h}`,
62
+ "aria-label": e(b, "files.actionMenu.moreActions", "More actions"),
63
+ title: e(b, "files.actionMenu.moreActions", "More actions"),
64
+ "aria-expanded": D,
52
65
  "aria-haspopup": "true",
53
- children: /* @__PURE__ */ c(o, { className: "size-4 text-text-primary" })
54
- }), !T || !x ? null : d(/* @__PURE__ */ c("div", {
55
- ref: w,
56
- className: `fixed z-[9999] min-w-[200px] bg-bg-elevated rounded-lg shadow-lg border border-border-default py-1 animate-in fade-in-0 zoom-in-95 duration-150 ${m}`,
66
+ children: /* @__PURE__ */ u(c, { className: "size-4 text-text-primary" })
67
+ }), !D || typeof document > "u" ? null : p(/* @__PURE__ */ u("div", {
68
+ ref: E,
69
+ className: `fixed z-[9999] overflow-y-auto bg-bg-elevated rounded-lg shadow-lg border border-border-default py-1 animate-in fade-in-0 zoom-in-95 transition-[opacity,transform] duration-150 ${g}`,
57
70
  style: {
58
- top: `${x.top}px`,
59
- left: `${x.left}px`
71
+ top: C?.top ?? 0,
72
+ left: C?.left ?? 0,
73
+ visibility: C ? "visible" : "hidden",
74
+ minWidth: "min(200px, calc(100vw - 20px))",
75
+ maxWidth: "calc(100vw - 20px)",
76
+ maxHeight: "calc(100vh - 20px)"
60
77
  },
61
78
  role: "menu",
62
- children: f.map((e, t) => /* @__PURE__ */ l("div", { children: [e.divider && t > 0 && /* @__PURE__ */ c("div", { className: "h-px bg-border-subtle my-1" }), /* @__PURE__ */ l("button", {
79
+ children: m.map((e, t) => /* @__PURE__ */ d("div", { children: [e.divider && t > 0 && /* @__PURE__ */ u("div", { className: "h-px bg-border-subtle my-1" }), /* @__PURE__ */ d("button", {
63
80
  type: "button",
64
81
  onClick: (t) => {
65
- t.stopPropagation(), O(e);
82
+ t.stopPropagation(), A(e);
66
83
  },
67
84
  className: `w-full flex items-center gap-3 px-4 py-2 text-sm transition-all duration-150 ${e.variant === "danger" ? "text-status-error-text hover:bg-status-error-bg-subtle" : "text-text-primary hover:bg-action-ghost-bg-hover"}`,
68
85
  role: "menuitem",
69
- children: [e.icon && /* @__PURE__ */ c("span", {
86
+ children: [e.icon && /* @__PURE__ */ u("span", {
70
87
  className: "flex-shrink-0",
71
88
  children: e.icon
72
- }), /* @__PURE__ */ c("span", { children: e.label })]
89
+ }), /* @__PURE__ */ u("span", { children: e.label })]
73
90
  })] }, e.label))
74
91
  }), document.body)] });
75
92
  };
76
93
  //#endregion
77
- export { f as ActionMenu };
94
+ export { m as ActionMenu };
78
95
 
79
96
  //# sourceMappingURL=ActionMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionMenu.js","names":["_Fragment","ActionMenu","controlledIsOpen","useI18n","internalIsOpen","setInternalIsOpen","useState","menuPosition","setMenuPosition","buttonRef","useRef","menuRef","isOpen","setIsOpen","useCallback","onToggle","open","rect","left","align","closeMenu","useEffectEvent","useEffect","handleClickOutside","event","handleEscape","handleItemClick","item","Fragment","triggerClassName","tWithFallback","t","MoreVertical","createPortal","menuClassName","items","index","e"],"sources":["../../../src/files/components/ActionMenu.tsx"],"sourcesContent":["import {\n type FC,\n useState,\n useRef,\n useEffect,\n useEffectEvent,\n useCallback,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { MoreVertical } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\n\nexport interface ActionMenuItem {\n label: string;\n icon?: React.ReactNode;\n onClick: () => void;\n variant?: \"default\" | \"danger\";\n divider?: boolean;\n}\n\ninterface ActionMenuProps {\n items: ActionMenuItem[];\n triggerClassName?: string;\n menuClassName?: string;\n align?: \"left\" | \"right\";\n isOpen?: boolean;\n onToggle?: (open: boolean) => void;\n}\n\nexport const ActionMenu: FC<ActionMenuProps> = ({\n items,\n triggerClassName = \"\",\n menuClassName = \"\",\n align = \"right\",\n isOpen: controlledIsOpen,\n onToggle,\n}) => {\n const { t } = useI18n();\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const [menuPosition, setMenuPosition] = useState<{\n top: number;\n left: number;\n } | null>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Use controlled state if provided, otherwise use internal state\n const isOpen =\n controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;\n const setIsOpen = useCallback(\n (open: boolean) => {\n if (onToggle) {\n onToggle(open);\n } else {\n setInternalIsOpen(open);\n }\n },\n [onToggle],\n );\n\n // Calculate menu position when opening\n useEffect(() => {\n if (isOpen && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n const menuWidth = 200; // Approximate menu width\n\n let left = align === \"right\" ? rect.right - menuWidth : rect.left;\n\n // Ensure menu doesn't go off-screen\n if (left < 10) left = 10;\n if (left + menuWidth > window.innerWidth - 10) {\n left = window.innerWidth - menuWidth - 10;\n }\n\n setMenuPosition({\n top: rect.bottom + 4, // 4px gap below button\n left: left,\n });\n }\n }, [isOpen, align]);\n\n const closeMenu = useEffectEvent(() => {\n setIsOpen(false);\n });\n\n // Close menu when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n closeMenu();\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }\n }, [isOpen]);\n\n // Close menu on Escape key\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n closeMenu();\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"keydown\", handleEscape);\n return () => {\n document.removeEventListener(\"keydown\", handleEscape);\n };\n }\n }, [isOpen]);\n\n const handleItemClick = (item: ActionMenuItem) => {\n item.onClick();\n setIsOpen(false);\n };\n\n // Render menu dropdown\n const renderMenu = () => {\n if (!isOpen || !menuPosition) return null;\n\n return createPortal(\n <div\n ref={menuRef}\n className={`fixed z-[9999] min-w-[200px] bg-bg-elevated rounded-lg shadow-lg border border-border-default py-1 animate-in fade-in-0 zoom-in-95 duration-150 ${menuClassName}`}\n style={{\n top: `${menuPosition.top}px`,\n left: `${menuPosition.left}px`,\n }}\n role=\"menu\"\n >\n {items.map((item, index) => (\n <div key={item.label}>\n {item.divider && index > 0 && (\n <div className=\"h-px bg-border-subtle my-1\" />\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleItemClick(item);\n }}\n className={`w-full flex items-center gap-3 px-4 py-2 text-sm transition-all duration-150 ${\n item.variant === \"danger\"\n ? \"text-status-error-text hover:bg-status-error-bg-subtle\"\n : \"text-text-primary hover:bg-action-ghost-bg-hover\"\n }`}\n role=\"menuitem\"\n >\n {item.icon && <span className=\"flex-shrink-0\">{item.icon}</span>}\n <span>{item.label}</span>\n </button>\n </div>\n ))}\n </div>,\n document.body,\n );\n };\n\n return (\n <>\n <button\n type=\"button\"\n ref={buttonRef}\n onClick={(e) => {\n e.stopPropagation();\n setIsOpen(!isOpen);\n }}\n className={`p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors ${triggerClassName}`}\n aria-label={tWithFallback(\n t,\n \"files.actionMenu.moreActions\",\n \"More actions\",\n )}\n title={tWithFallback(t, \"files.actionMenu.moreActions\", \"More actions\")}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n >\n <MoreVertical className=\"size-4 text-text-primary\" />\n </button>\n {renderMenu()}\n </>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActionMenu.js","names":["_Fragment","ActionMenu","controlledIsOpen","useI18n","internalIsOpen","setInternalIsOpen","useState","menuPosition","setMenuPosition","buttonRef","useRef","menuRef","isOpen","setIsOpen","useCallback","onToggle","open","button","menu","updatePosition","next","actionMenuPosition","previous","observer","align","closeMenu","useEffectEvent","useEffect","handleClickOutside","event","handleEscape","handleItemClick","item","Fragment","triggerClassName","tWithFallback","t","MoreVertical","createPortal","menuClassName","items","index","e"],"sources":["../../../src/files/components/ActionMenu.tsx"],"sourcesContent":["import {\n type FC,\n useState,\n useRef,\n useEffect,\n useLayoutEffect,\n useEffectEvent,\n useCallback,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { MoreVertical } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport { actionMenuPosition } from \"./actionMenuPosition\";\n\nexport interface ActionMenuItem {\n label: string;\n icon?: React.ReactNode;\n onClick: () => void;\n variant?: \"default\" | \"danger\";\n divider?: boolean;\n}\n\ninterface ActionMenuProps {\n items: ActionMenuItem[];\n triggerClassName?: string;\n menuClassName?: string;\n align?: \"left\" | \"right\";\n isOpen?: boolean;\n onToggle?: (open: boolean) => void;\n}\n\nexport const ActionMenu: FC<ActionMenuProps> = ({\n items,\n triggerClassName = \"\",\n menuClassName = \"\",\n align = \"right\",\n isOpen: controlledIsOpen,\n onToggle,\n}) => {\n const { t } = useI18n();\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const [menuPosition, setMenuPosition] = useState<{\n top: number;\n left: number;\n } | null>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Use controlled state if provided, otherwise use internal state\n const isOpen =\n controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;\n const setIsOpen = useCallback(\n (open: boolean) => {\n if (onToggle) {\n onToggle(open);\n } else {\n setInternalIsOpen(open);\n }\n },\n [onToggle],\n );\n\n // Mount hidden, then measure before paint. Offset sizes exclude the entrance\n // animation's scale, so the final menu also stays inside the viewport.\n useLayoutEffect(() => {\n if (!isOpen) return;\n const button = buttonRef.current;\n const menu = menuRef.current;\n if (!button || !menu) return;\n\n const updatePosition = () => {\n const next = actionMenuPosition({\n trigger: button.getBoundingClientRect(),\n menuWidth: menu.offsetWidth,\n menuHeight: menu.offsetHeight,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n align,\n });\n setMenuPosition((previous) =>\n previous?.top === next.top && previous.left === next.left\n ? previous\n : next,\n );\n };\n updatePosition();\n const observer = new ResizeObserver(updatePosition);\n observer.observe(menu);\n observer.observe(button);\n window.addEventListener(\"resize\", updatePosition);\n // Capture scrolls in nested file lists as well as the window.\n window.addEventListener(\"scroll\", updatePosition, true);\n return () => {\n observer.disconnect();\n window.removeEventListener(\"resize\", updatePosition);\n window.removeEventListener(\"scroll\", updatePosition, true);\n };\n }, [isOpen, align]);\n\n const closeMenu = useEffectEvent(() => {\n setIsOpen(false);\n });\n\n // Close menu when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n closeMenu();\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }\n }, [isOpen]);\n\n // Close menu on Escape key\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n closeMenu();\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"keydown\", handleEscape);\n return () => {\n document.removeEventListener(\"keydown\", handleEscape);\n };\n }\n }, [isOpen]);\n\n const handleItemClick = (item: ActionMenuItem) => {\n item.onClick();\n setIsOpen(false);\n };\n\n // Render menu dropdown\n const renderMenu = () => {\n if (!isOpen || typeof document === \"undefined\") return null;\n\n return createPortal(\n <div\n ref={menuRef}\n className={`fixed z-[9999] overflow-y-auto bg-bg-elevated rounded-lg shadow-lg border border-border-default py-1 animate-in fade-in-0 zoom-in-95 transition-[opacity,transform] duration-150 ${menuClassName}`}\n style={{\n top: menuPosition?.top ?? 0,\n left: menuPosition?.left ?? 0,\n visibility: menuPosition ? \"visible\" : \"hidden\",\n minWidth: \"min(200px, calc(100vw - 20px))\",\n maxWidth: \"calc(100vw - 20px)\",\n maxHeight: \"calc(100vh - 20px)\",\n }}\n role=\"menu\"\n >\n {items.map((item, index) => (\n <div key={item.label}>\n {item.divider && index > 0 && (\n <div className=\"h-px bg-border-subtle my-1\" />\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleItemClick(item);\n }}\n className={`w-full flex items-center gap-3 px-4 py-2 text-sm transition-all duration-150 ${\n item.variant === \"danger\"\n ? \"text-status-error-text hover:bg-status-error-bg-subtle\"\n : \"text-text-primary hover:bg-action-ghost-bg-hover\"\n }`}\n role=\"menuitem\"\n >\n {item.icon && <span className=\"flex-shrink-0\">{item.icon}</span>}\n <span>{item.label}</span>\n </button>\n </div>\n ))}\n </div>,\n document.body,\n );\n };\n\n return (\n <>\n <button\n type=\"button\"\n ref={buttonRef}\n onClick={(e) => {\n e.stopPropagation();\n setIsOpen(!isOpen);\n }}\n className={`p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors ${triggerClassName}`}\n aria-label={tWithFallback(\n t,\n \"files.actionMenu.moreActions\",\n \"More actions\",\n )}\n title={tWithFallback(t, \"files.actionMenu.moreActions\", \"More actions\")}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n >\n <MoreVertical className=\"size-4 text-text-primary\" />\n </button>\n {renderMenu()}\n </>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ interface MenuGeometry {
2
+ trigger: Pick<DOMRect, "top" | "bottom" | "left" | "right">;
3
+ menuWidth: number;
4
+ menuHeight: number;
5
+ viewportWidth: number;
6
+ viewportHeight: number;
7
+ align: "left" | "right";
8
+ }
9
+ /** Fixed-position coordinates for a measured menu, with the same 10px edge inset as its CSS. */
10
+ export declare function actionMenuPosition({ trigger, menuWidth, menuHeight, viewportWidth, viewportHeight, align, }: MenuGeometry): {
11
+ top: number;
12
+ left: number;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=actionMenuPosition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionMenuPosition.d.ts","sourceRoot":"","sources":["../../../src/files/components/actionMenuPosition.ts"],"names":[],"mappings":"AAAA,UAAU,YAAY;IACpB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,SAAS,EACT,UAAU,EACV,aAAa,EACb,cAAc,EACd,KAAK,GACN,EAAE,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAa9C"}
@@ -0,0 +1,12 @@
1
+ //#region src/files/components/actionMenuPosition.ts
2
+ function e({ trigger: e, menuWidth: t, menuHeight: n, viewportWidth: r, viewportHeight: i, align: a }) {
3
+ let o = e.bottom + 4, s = o + n <= i - 10 ? o : e.top - n - 4, c = a === "right" ? e.right - t : e.left;
4
+ return {
5
+ top: Math.max(10, Math.min(s, i - n - 10)),
6
+ left: Math.max(10, Math.min(c, r - t - 10))
7
+ };
8
+ }
9
+ //#endregion
10
+ export { e as actionMenuPosition };
11
+
12
+ //# sourceMappingURL=actionMenuPosition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionMenuPosition.js","names":["actionMenuPosition","below","trigger","top","menuHeight","viewportHeight","left","align","menuWidth","viewportWidth"],"sources":["../../../src/files/components/actionMenuPosition.ts"],"sourcesContent":["interface MenuGeometry {\n trigger: Pick<DOMRect, \"top\" | \"bottom\" | \"left\" | \"right\">;\n menuWidth: number;\n menuHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n align: \"left\" | \"right\";\n}\n\n/** Fixed-position coordinates for a measured menu, with the same 10px edge inset as its CSS. */\nexport function actionMenuPosition({\n trigger,\n menuWidth,\n menuHeight,\n viewportWidth,\n viewportHeight,\n align,\n}: MenuGeometry): { top: number; left: number } {\n const inset = 10;\n const gap = 4;\n const below = trigger.bottom + gap;\n const top =\n below + menuHeight <= viewportHeight - inset\n ? below\n : trigger.top - menuHeight - gap;\n const left = align === \"right\" ? trigger.right - menuWidth : trigger.left;\n return {\n top: Math.max(inset, Math.min(top, viewportHeight - menuHeight - inset)),\n left: Math.max(inset, Math.min(left, viewportWidth - menuWidth - inset)),\n };\n}\n"],"mappings":";AAUA,SAAgBA,EAAmB,EACjC,YACA,cACA,eACA,kBACA,mBACA,YAC8C;CAC9C,IAEMC,IAAQC,EAAQ,SAAS,GACzBC,IACJF,IAAQG,KAAcC,IAAiB,KACnCJ,IACAC,EAAQ,MAAME,IAAa,GAC3BE,IAAOC,MAAU,UAAUL,EAAQ,QAAQM,IAAYN,EAAQ;CACrE,OAAO;EACL,KAAK,KAAK,IAAI,IAAO,KAAK,IAAIC,GAAKE,IAAiBD,IAAa,EAAK,CAAC;EACvE,MAAM,KAAK,IAAI,IAAO,KAAK,IAAIE,GAAMG,IAAgBD,IAAY,EAAK,CAAC;CACzE;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-files",
3
- "version": "2026.1017.10",
3
+ "version": "2026.1023.1",
4
4
  "description": "Burdenoff Files Microfrontend - Standalone file management module",
5
5
  "type": "module",
6
6
  "imports": {