@1771technologies/lytenyte-pro
Version:
1,013 lines (1,012 loc) • 35.5 kB
JavaScript
import * as React from "react";
import { useRef, useState, useEffect, useCallback, useMemo, forwardRef } from "react";
import { u as useDragStore, b as useStore } from "./drag-store-BF4ad65L.js";
import { arrayOverlap, clsx } from "@1771technologies/js-utils";
import { m as mergeProps, E as contains, P as PropTypes } from "./proptypes-BjYr2nFr.js";
import { u as useButton } from "./useButton-DWXzFgcr.js";
import { u as useForkRef, a as useComponentRenderer, c as pressableTriggerOpenStateMapping } from "./InternalBackdrop-C4RACVzs.js";
import { o as ownerDocument, c as useGrid } from "./useScrollLock-D4UY33Sb.js";
import { g as getPseudoElementBounds } from "./getPseudoElementBounds-BSHt6WYm.js";
import { u as useMenuRootContext } from "./column-menu-driver-cG-EZgLa.js";
import { jsx, jsxs } from "react/jsx-runtime";
import { a as Menu } from "./menu-C49unLOk.js";
const toJSON = () => "";
function getVisibleBoundingBox(element) {
if (!element) return null;
let visibleBox = element.getBoundingClientRect();
let current = element.parentElement;
while (current && current !== document.body && current !== document.documentElement) {
const style = getComputedStyle(current);
const isClipping = style.overflow !== "visible";
if (isClipping) {
const parentRect = current.getBoundingClientRect();
visibleBox = {
top: Math.max(visibleBox.top, parentRect.top),
y: Math.max(visibleBox.top, parentRect.top),
right: Math.min(visibleBox.right, parentRect.right),
bottom: Math.min(visibleBox.bottom, parentRect.bottom),
left: Math.max(visibleBox.left, parentRect.left),
x: Math.max(visibleBox.left, parentRect.left),
width: 0,
// Initialized to 0 for now (calculated below)
height: 0,
// Initialized to 0 for now (calculated below)
toJSON
};
visibleBox.width = Math.max(0, visibleBox.right - visibleBox.left);
visibleBox.height = Math.max(0, visibleBox.bottom - visibleBox.top);
if (visibleBox.width === 0 || visibleBox.height === 0) {
return null;
}
}
current = current.parentElement;
}
return visibleBox;
}
function computeActiveDrag(mounted, tags, data, id, x, y) {
const targets = getDropTargets(mounted, x, y, tags);
const sortedRects = targets.sort((left, right) => {
const distanceToRootA = nodesToRoot(left.target);
const distanceToRootB = nodesToRoot(right.target);
return distanceToRootA - distanceToRootB;
});
return {
data,
id,
tags,
over: sortedRects,
x,
y
};
}
function getDropTargets(mounted, x, y, tags) {
const overlapping = mounted.reduce((acc, c) => {
const bb = c.target.getBoundingClientRect();
const overlaps = x >= bb.x && x <= bb.x + bb.width && y >= bb.y && y < bb.y + bb.height;
if (!overlaps) return acc;
const v = getVisibleBoundingBox(c.target);
const visiblyOverlaps = v && x >= v.x && x < v.x + v.width && y >= v.y && y <= v.y + v.height;
if (!visiblyOverlaps) return acc;
acc.push({
id: c.id,
accepted: c.accepted,
target: c.target,
box: bb,
canDrop: c.accepted.some((c2) => tags.includes(c2)),
xHalf: x <= bb.x + bb.width / 2 ? "left" : "right",
yHalf: y <= bb.y + bb.height / 2 ? "top" : "bottom",
data: c.data
});
return acc;
}, []);
return overlapping;
}
function nodesToRoot(c) {
let current = 0;
while (c) {
current++;
c = c.parentElement;
}
return current;
}
function useDraggable({
id,
getTags,
getData,
onDragStart,
onDragMove,
onDragEnd,
onDragCancel
}) {
const hasDragStartedRef = useRef(false);
const [isActive, setIsActive] = useState(false);
const store = useDragStore();
const [drag, setDrag] = useState(null);
useEffect(() => {
if (!drag) return;
const c = new AbortController();
drag.addEventListener(
"touchstart",
(ev) => {
ev.preventDefault();
},
{ passive: false, signal: c.signal }
);
drag.addEventListener(
"pointerdown",
(ev) => {
const startX = ev.clientX;
const startY = ev.clientY;
if (ev.pointerType === "touch") {
hasDragStartedRef.current = true;
const data = getData();
const tags = getTags();
const s = store.getState();
const next = computeActiveDrag(s.mounted, tags, data, id, startX, startY);
store.setState({ active: next });
onDragStart?.(next);
setIsActive(true);
ev.stopPropagation();
ev.preventDefault();
}
if (ev.pointerType === "mouse" && ev.button !== 0) return;
const c2 = new AbortController();
let anim = null;
const clickFix = (ev2) => {
ev2.preventDefault();
ev2.stopPropagation();
ev2.stopImmediatePropagation();
globalThis.removeEventListener("click", clickFix, { capture: true });
};
globalThis.addEventListener("click", clickFix, { capture: true });
document.addEventListener(
"pointermove",
(ev2) => {
if (!hasDragStartedRef.current && !shouldDragStart(ev2, startX, startY)) return;
const x = ev2.clientX;
const y = ev2.clientY;
if (!hasDragStartedRef.current) {
hasDragStartedRef.current = true;
const data = getData();
const tags = getTags();
const s = store.getState();
const next = computeActiveDrag(s.mounted, tags, data, id, x, y);
store.setState({ active: next });
onDragStart?.(next);
setIsActive(true);
}
if (anim) return;
anim = requestAnimationFrame(() => {
const s = store.getState();
const data = s.active.data;
const tags = s.active.tags;
const next = computeActiveDrag(s.mounted, tags, data, id, x, y);
store.setState({ active: next });
onDragMove?.(next);
anim = null;
});
},
{ signal: c2.signal }
);
document.addEventListener(
"pointerup",
(ev2) => {
c2.abort();
setTimeout(() => globalThis.removeEventListener("click", clickFix, { capture: true }));
if (!hasDragStartedRef.current) return;
endDrag(ev2.clientX, ev2.clientY);
},
{ signal: c2.signal, capture: true }
);
document.addEventListener(
"pointercancel",
(ev2) => {
c2.abort();
setTimeout(() => globalThis.removeEventListener("click", clickFix, { capture: true }));
if (!hasDragStartedRef.current) return;
endDrag(ev2.clientX, ev2.clientY, true);
},
{ signal: c2.signal }
);
document.addEventListener(
"keydown",
(ev2) => {
c2.abort();
setTimeout(() => globalThis.removeEventListener("click", clickFix, { capture: true }));
if (!hasDragStartedRef.current) return;
if (ev2.key === "Escape") {
ev2.preventDefault();
ev2.stopPropagation();
const s = store.getState().active;
endDrag(s.x, s.y, true);
}
},
{ signal: c2.signal, capture: true }
);
function endDrag(x, y, isCancel = false) {
const s = store.getState();
setIsActive(false);
const next = computeActiveDrag(
s.mounted,
s.active.tags,
s.active.data,
s.active.id,
x,
y
);
if (isCancel) {
onDragCancel?.(next);
} else {
onDragEnd?.(next);
}
hasDragStartedRef.current = false;
store.setState({ active: null });
}
},
{ passive: false, signal: c.signal }
);
return () => c.abort();
}, [drag, getData, getTags, id, onDragCancel, onDragEnd, onDragMove, onDragStart, store]);
return { setDrag, isActive };
}
function shouldDragStart(ev, startX, startY) {
const deltaY = Math.abs(ev.clientY - startY);
const deltaX = Math.abs(ev.clientX - startX);
return deltaX > 10 || deltaY > 10;
}
function useDroppable({ id, accepted, data, active = true }) {
const [target, ref] = useState(null);
const store = useDragStore();
const over = useStore(store, (s) => s.active?.over.find((c) => c.id === id));
const isNearestOver = useStore(store, (s) => s.active?.over.at(-1) === over);
const canDrop = !!over?.canDrop;
const isOver = !!over;
const isTarget = useStore(store, (s) => arrayOverlap(s.active?.tags ?? [], accepted));
useEffect(() => {
if (!target || !active) return;
store.setState((prev) => {
if (prev.mounted.find((c) => c.id === id)) return prev;
return {
mounted: [...prev.mounted, { accepted, id, target, data }]
};
});
return () => store.setState((prev) => {
return { mounted: prev.mounted.filter((c) => c.id !== id) };
});
}, [accepted, active, data, id, store, target]);
return { ref, isOver, isTarget, canDrop, yHalf: over?.yHalf, xHalf: over?.xHalf, isNearestOver };
}
const useEdgeScroll = ({
isActive,
threshold = 50,
direction = "both",
maxSpeed = 20,
acceleration = 0.5
}) => {
const [container, ref] = useState(null);
const scrollIntervalX = useRef(null);
const scrollIntervalY = useRef(null);
const speedX = useRef(0);
const speedY = useRef(0);
const stopScrollX = useCallback(() => {
if (scrollIntervalX.current) {
clearInterval(scrollIntervalX.current);
scrollIntervalX.current = null;
speedX.current = 0;
}
}, []);
const stopScrollY = useCallback(() => {
if (scrollIntervalY.current) {
clearInterval(scrollIntervalY.current);
scrollIntervalY.current = null;
speedY.current = 0;
}
}, []);
const stopScrolling = useCallback(() => {
stopScrollX();
stopScrollY();
}, [stopScrollX, stopScrollY]);
const startScrollingXLeft = useCallback(() => {
if (!scrollIntervalX.current) {
scrollIntervalX.current = setInterval(() => {
speedX.current = Math.min(speedX.current + acceleration, maxSpeed);
container?.scrollBy({ left: speedX.current * -1 });
}, 16);
}
}, [acceleration, container, maxSpeed]);
const startScrollingXRight = useCallback(() => {
if (!scrollIntervalX.current) {
scrollIntervalX.current = setInterval(() => {
speedX.current = Math.min(speedX.current + acceleration, maxSpeed);
container?.scrollBy({ left: speedX.current });
}, 16);
}
}, [acceleration, container, maxSpeed]);
const startScrollTop = useCallback(() => {
if (!scrollIntervalY.current) {
scrollIntervalY.current = setInterval(() => {
speedY.current = Math.min(speedY.current + acceleration, maxSpeed);
container?.scrollBy({ top: speedY.current * -1 });
}, 16);
}
}, [acceleration, container, maxSpeed]);
const startScrollBottom = useCallback(() => {
if (!scrollIntervalY.current) {
scrollIntervalY.current = setInterval(() => {
speedY.current = Math.min(speedY.current + acceleration, maxSpeed);
container?.scrollBy({ top: speedY.current });
}, 16);
}
}, [acceleration, container, maxSpeed]);
const controllerRef = useRef(null);
useEffect(() => {
if (!isActive) {
controllerRef.current?.abort();
controllerRef.current = null;
stopScrolling();
return;
}
if (!container) return;
const handleMouseMove = (event) => {
const containerRect = container.getBoundingClientRect();
const distanceToTop = Math.abs(event.clientY - containerRect.top);
const distanceToBottom = Math.abs(event.clientY - containerRect.bottom);
const distanceToLeft = Math.abs(event.clientX - containerRect.left);
const distanceToRight = Math.abs(event.clientX - containerRect.right);
if (direction === "both" || direction === "horizontal") {
if (distanceToLeft < threshold) {
startScrollingXLeft();
} else if (distanceToRight < threshold) {
startScrollingXRight();
} else {
stopScrollX();
}
}
if (direction === "both" || direction === "vertical") {
if (distanceToTop < threshold) {
startScrollTop();
} else if (distanceToBottom < threshold) {
startScrollBottom();
} else {
stopScrollY();
}
}
};
const controller = new AbortController();
controllerRef.current = controller;
container.addEventListener("pointermove", handleMouseMove, {
passive: false,
capture: true,
signal: controller.signal
});
container.addEventListener("pointerleave", stopScrolling);
return () => {
controller.abort();
stopScrolling();
};
}, [
isActive,
threshold,
direction,
maxSpeed,
acceleration,
container,
stopScrolling,
startScrollingXLeft,
startScrollingXRight,
stopScrollX,
startScrollTop,
startScrollBottom,
stopScrollY
]);
return ref;
};
function useMenuTrigger(parameters) {
const BOUNDARY_OFFSET = 2;
const {
disabled = false,
rootRef: externalRef,
open,
setOpen,
setTriggerElement,
positionerRef,
allowMouseUpTriggerRef
} = parameters;
const triggerRef = React.useRef(null);
const mergedRef = useForkRef(externalRef, triggerRef);
const allowMouseUpTriggerTimeoutRef = React.useRef(-1);
const {
getButtonProps,
buttonRef
} = useButton({
disabled,
buttonRef: mergedRef
});
const handleRef = useForkRef(buttonRef, setTriggerElement);
React.useEffect(() => {
if (!open) {
allowMouseUpTriggerRef.current = false;
}
}, [allowMouseUpTriggerRef, open]);
const getTriggerProps = React.useCallback((externalProps) => {
return mergeProps({
"aria-haspopup": "menu",
tabIndex: 0,
// this is needed to make the button focused after click in Safari
ref: handleRef,
onMouseDown: (event) => {
if (open) {
return;
}
allowMouseUpTriggerTimeoutRef.current = window.setTimeout(() => {
allowMouseUpTriggerRef.current = true;
}, 200);
const doc = ownerDocument(event.currentTarget);
function handleMouseUp(mouseEvent) {
if (!triggerRef.current) {
return;
}
if (allowMouseUpTriggerTimeoutRef.current !== -1) {
clearTimeout(allowMouseUpTriggerTimeoutRef.current);
allowMouseUpTriggerTimeoutRef.current = -1;
}
allowMouseUpTriggerRef.current = false;
const mouseUpTarget = mouseEvent.target;
if (contains(triggerRef.current, mouseUpTarget) || contains(positionerRef.current, mouseUpTarget) || mouseUpTarget === triggerRef.current) {
return;
}
const bounds = getPseudoElementBounds(triggerRef.current);
if (mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET && mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET) {
return;
}
setOpen(false, mouseEvent);
}
doc.addEventListener("mouseup", handleMouseUp, {
once: true
});
}
}, externalProps, getButtonProps);
}, [getButtonProps, handleRef, open, setOpen, positionerRef, allowMouseUpTriggerRef]);
return React.useMemo(() => ({
getTriggerProps,
triggerRef: handleRef
}), [getTriggerProps, handleRef]);
}
const MenuTrigger = /* @__PURE__ */ React.forwardRef(function MenuTrigger2(props, forwardedRef) {
const {
render,
className,
disabled = false,
...other
} = props;
const {
triggerProps: rootTriggerProps,
disabled: menuDisabled,
setTriggerElement,
open,
mounted,
setOpen,
allowMouseUpTriggerRef,
positionerRef
} = useMenuRootContext();
const {
getTriggerProps
} = useMenuTrigger({
disabled: disabled || menuDisabled,
rootRef: forwardedRef,
setTriggerElement,
open,
setOpen,
allowMouseUpTriggerRef,
positionerRef
});
const state = React.useMemo(() => ({
disabled,
open: mounted
}), [disabled, mounted]);
const propGetter = React.useCallback((externalProps) => mergeProps(rootTriggerProps, externalProps, getTriggerProps), [getTriggerProps, rootTriggerProps]);
const {
renderElement
} = useComponentRenderer({
render: render || "button",
className,
state,
propGetter,
customStyleHookMapping: pressableTriggerOpenStateMapping,
extraProps: other
});
return renderElement();
});
process.env.NODE_ENV !== "production" ? MenuTrigger.propTypes = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* @ignore
*/
children: PropTypes.node,
/**
* CSS class applied to the element, or a function that
* returns a class based on the component’s state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* Whether the component should ignore user interaction.
* @default false
*/
disabled: PropTypes.bool,
/**
* Allows you to replace the component’s HTML element
* with a different tag, or compose it with another component.
*
* Accepts a `ReactElement` or a function that returns the element to render.
*/
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
} : void 0;
const canAgg = (c, base) => {
return Boolean(c.aggFnDefault ?? c.aggFnsAllowed?.length ?? base.aggFnsAllowed?.length);
};
const canMeasure = (c, base) => {
return Boolean(
c.measureFnDefault ?? c.measureFnsAllowed?.length ?? base.measureFnsAllowed?.length
);
};
function useAggregationSource(source) {
const { api, state: sx } = useGrid();
const columns = sx.columns.use();
const aggModel = sx.aggModel.use();
return useMemo(() => {
if (source !== "aggregations") return [];
const entries = Object.entries(aggModel);
const active = [];
for (const [key, m] of entries) {
let column = api.columnById(key) ?? null;
if (column && (api.columnIsPivot(column) || api.columnIsGridGenerated(column))) column = null;
const secondaryLabel = typeof m.fn === "string" ? `(${m.fn})` : "Fn(x)";
const onToggle = () => sx.aggModel.set((prev) => {
const next = { ...prev };
delete next[key];
return next;
});
if (column) {
active.push({
kind: "column",
active: true,
label: column.headerName ?? column.id,
secondaryLabel,
onClick: onToggle,
draggable: false,
dragTags: [],
dropId: `agg-${column.id}`,
column,
isAggregation: true,
dropTags: [],
dropData: {}
});
} else {
active.push({
kind: "column",
active: true,
label: key,
secondaryLabel,
onClick: onToggle,
draggable: false,
dragTags: [],
dropId: `agg-${key}`,
dropTags: [],
dropData: {}
});
}
}
const base = sx.columnBase.peek();
const inactive = columns.filter((c) => {
const agg = c.aggFnDefault ?? c.aggFnsAllowed?.length ?? base.aggFnsAllowed?.length;
const isAgged = !!aggModel[c.id];
return !!agg && !isAgged;
}).map((c) => {
const aggFn = c.aggFnDefault ?? c.aggFnsAllowed?.[0] ?? base.aggFnsAllowed?.at(0);
const onToggle = () => sx.aggModel.set((prev) => aggFn ? { ...prev, [c.id]: { fn: aggFn } } : prev);
const aggName = typeof aggFn === "string" ? `(${aggFn})` : "Fn(x)";
return {
kind: "column",
label: c.headerName ?? c.id,
active: false,
secondaryLabel: aggName,
onClick: onToggle,
draggable: false,
dropId: `agg-${c.id}`,
dropTags: [],
dragTags: [],
dropData: {}
};
});
return [...active, ...inactive];
}, [aggModel, api, columns, source, sx.aggModel, sx.columnBase]);
}
function useMeasuresSource(source) {
const { api, state: sx } = useGrid();
const columns = sx.columns.use();
const measureModel = sx.measureModel.use();
return useMemo(() => {
if (source !== "measures") return [];
const entries = Object.entries(measureModel);
const active = [];
for (const [key, m] of entries) {
let column = api.columnById(key) ?? null;
if (column && (api.columnIsPivot(column) || api.columnIsGridGenerated(column))) column = null;
const secondaryLabel = typeof m.fn === "string" ? `(${m.fn})` : "Fn(x)";
const onToggle = () => sx.measureModel.set((prev) => {
const next = { ...prev };
delete next[key];
return next;
});
if (column) {
active.push({
kind: "column",
active: true,
label: column.headerName ?? column.id,
secondaryLabel,
onClick: onToggle,
draggable: false,
dragTags: [],
dropTags: [],
dropData: {},
column,
isMeasure: true,
dropId: `measure-${key}`
});
} else {
active.push({
kind: "column",
active: true,
label: key,
secondaryLabel,
onClick: onToggle,
draggable: false,
dragTags: [],
dropTags: [],
dropData: {},
dropId: `measure-${key}`
});
}
}
const base = sx.columnBase.peek();
const inactive = columns.filter((c) => {
const measure = c.measureFnDefault ?? c.measureFnsAllowed?.length ?? base.measureFnsAllowed?.length;
const isMeasured = !!measureModel[c.id];
return !!measure && !isMeasured;
}).map((c) => {
const measureFn = c.measureFnDefault ?? c.measureFnsAllowed?.[0] ?? base.measureFnsAllowed?.at(0);
const measureName = typeof measureFn === "string" ? `(${measureFn})` : "Fn(x)";
const onToggle = () => sx.measureModel.set(
(prev) => measureFn ? { ...prev, [c.id]: { fn: measureFn } } : prev
);
return {
kind: "column",
label: c.headerName ?? c.id,
active: false,
secondaryLabel: measureName,
onClick: onToggle,
draggable: false,
dragTags: [],
dropTags: [],
dropData: {},
dropId: `measure-${c.id}`
};
});
return [...active, ...inactive];
}, [api, columns, measureModel, source, sx.columnBase, sx.measureModel]);
}
function useRowGroupsSource(source, dir = "horizontal") {
const { api, state: sx } = useGrid();
const rowModel = sx.rowGroupModel.use();
const columns = sx.columns.use();
const aggModel = sx.aggModel.use();
const measureModel = sx.measureModel.use();
const base = sx.columnBase.use();
return useMemo(() => {
if (source !== "row-groups") return [];
const activeItems = rowModel.map((c) => api.columnById(c)).map((c) => {
const onToggle = () => sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
const dragTags = ["row-group"];
if (!api.columnIsVisible(c, true)) dragTags.push("columns");
if (api.columnIsPivotable(c)) dragTags.push("column-pivot");
if (!aggModel[c.id] && canAgg(c, base)) dragTags.push("aggregations");
if (!measureModel[c.id] && canMeasure(c, base)) dragTags.push("measures");
const dragEnd = (d) => {
const over = d.over.at(-1);
if (!over || !over.canDrop) return;
const isBefore = dir === "horizontal" ? (sx.rtl.peek() ? "right" : "left") === over.xHalf : over.yHalf === "top";
if (over.data.target === "row-group") {
const id = over.data.id;
if (id === c.id) return;
const myIndex = rowModel.indexOf(c.id);
const next = [...rowModel];
next.splice(myIndex, 1);
const targetIndex = next.indexOf(id);
next.splice(targetIndex + (isBefore ? 0 : 1), 0, c.id);
sx.rowGroupModel.set(next);
return;
}
if (over.id === "columns-pills") {
api.columnUpdate(c, { hide: false });
api.columnMoveToVisibleIndex([c.id], 0);
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
return;
}
if (over.data.target === "columns") {
const id = over.data.id;
if (id === c.id) return;
api.columnUpdate(c, { hide: false });
if (isBefore) api.columnMoveBefore([c.id], id);
else api.columnMoveAfter([c.id], id);
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
return;
}
if (over.id === "column-pivots-pills") {
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
sx.columnPivotModel.set((prev) => [...prev, c.id]);
return;
}
if (over.data.target === "column-pivot") {
const id = over.data.id;
if (id === c.id) return;
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
sx.columnPivotModel.set((prev) => {
const next = [...prev];
const index = next.indexOf(id) + (isBefore ? 0 : 1);
next.splice(index, 0, c.id);
return next;
});
}
if (over.id === "aggregations-pills") {
const aggFn = c.aggFnDefault ?? c.aggFnsAllowed?.at(0) ?? base.aggFnsAllowed?.at(0);
if (!aggFn) return;
sx.aggModel.set((prev) => ({ ...prev, [c.id]: { fn: aggFn } }));
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
}
if (over.id === "measures-pills") {
const measureFn = c.measureFnDefault ?? c.measureFnsAllowed?.at(0) ?? base.measureFnsAllowed?.at(0);
if (!measureFn) return;
sx.measureModel.set((prev) => ({ ...prev, [c.id]: { fn: measureFn } }));
sx.rowGroupModel.set((prev) => prev.filter((x) => x !== c.id));
}
};
return {
kind: "row-group",
label: c.headerName ?? c.id,
active: true,
onClick: onToggle,
draggable: true,
isRowGroup: true,
dragEnd,
dragTags,
dropTags: ["row-group"],
dropData: { target: "row-group", id: c.id },
dropId: `row-group-${c.id}`
};
});
const inactiveItems = columns.filter((c) => api.columnIsRowGroupable(c) && !rowModel.includes(c.id)).map((c) => {
const onToggle = () => sx.rowGroupModel.set((prev) => [...prev, c.id]);
return {
kind: "row-group",
label: c.headerName ?? c.id,
active: false,
onClick: onToggle,
draggable: false,
dragTags: [],
dropTags: [],
dropData: {},
dropId: `row-group-${c.id}`
};
});
return [...activeItems, ...inactiveItems];
}, [
aggModel,
api,
base,
columns,
dir,
measureModel,
rowModel,
source,
sx.aggModel,
sx.columnPivotModel,
sx.measureModel,
sx.rowGroupModel,
sx.rtl
]);
}
function useColumnPivotSource(source, dir = "horizontal") {
const { api, state: sx } = useGrid();
const columns = sx.columns.use();
const pivotModel = sx.columnPivotModel.use();
const aggModel = sx.aggModel.use();
const measureModel = sx.measureModel.use();
const base = sx.columnBase.use();
return useMemo(() => {
if (source !== "column-pivots") return [];
const appliedPivots = new Set(pivotModel);
const canBePivoted = columns.filter(
(c) => !appliedPivots.has(c.id) && api.columnIsPivotable(c)
);
const pivotedColumns = pivotModel.map((c) => api.columnById(c));
const activeItems = pivotedColumns.map((c) => {
const onToggle = () => sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
const dragTags = ["column-pivot"];
if (api.columnIsRowGroupable(c)) dragTags.push("row-group");
if (!api.columnIsVisible(c, true)) dragTags.push("columns");
if (!aggModel[c.id] && canAgg(c, base)) dragTags.push("aggregations");
if (!measureModel[c.id] && canMeasure(c, base)) dragTags.push("measures");
const dragEnd = (d) => {
const over = d.over.at(-1);
if (!over || !over.canDrop) return;
const isBefore = dir === "horizontal" ? (sx.rtl.peek() ? "right" : "left") === over.xHalf : over.yHalf === "top";
if (over.data.target === "column-pivot") {
const id = over.data.id;
if (id === c.id) return;
const myIndex = pivotModel.indexOf(c.id);
const next = [...pivotModel];
next.splice(myIndex, 1);
const targetIndex = next.indexOf(id);
next.splice(targetIndex + (isBefore ? 0 : 1), 0, c.id);
sx.columnPivotModel.set(next);
return;
}
if (over.id === "columns-pills") {
api.columnUpdate(c, { hide: false });
api.columnMoveToVisibleIndex([c.id], 0);
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
return;
}
if (over.data.target === "columns") {
const id = over.data.id;
if (id === c.id) return;
api.columnUpdate(c, { hide: false });
if (isBefore) api.columnMoveBefore([c.id], id);
else api.columnMoveAfter([c.id], id);
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
return;
}
if (over.id === "row-groups-pills") {
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
sx.rowGroupModel.set((prev) => [...prev, c.id]);
return;
}
if (over.data.target === "row-group") {
const id = over.data.id;
if (id === c.id) return;
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
sx.rowGroupModel.set((prev) => {
const next = [...prev];
const index = next.indexOf(id) + (isBefore ? 0 : 1);
next.splice(index, 0, c.id);
return next;
});
}
if (over.id === "aggregations-pills") {
const aggFn = c.aggFnDefault ?? c.aggFnsAllowed?.at(0) ?? base.aggFnsAllowed?.at(0);
if (!aggFn) return;
sx.aggModel.set((prev) => ({ ...prev, [c.id]: { fn: aggFn } }));
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
}
if (over.id === "measures-pills") {
const measureFn = c.measureFnDefault ?? c.measureFnsAllowed?.at(0) ?? base.measureFnsAllowed?.at(0);
if (!measureFn) return;
sx.measureModel.set((prev) => ({ ...prev, [c.id]: { fn: measureFn } }));
sx.columnPivotModel.set((prev) => prev.filter((x) => x !== c.id));
}
};
return {
kind: "column-pivot",
label: c.headerName ?? c.id,
active: true,
onClick: onToggle,
dragEnd,
dropId: `pivot-${c.id}`,
draggable: true,
isColumnPivot: true,
dragTags,
dropTags: ["column-pivot"],
dropData: { target: "column-pivot", id: c.id }
};
});
const inactiveItems = canBePivoted.map((c) => {
const onToggle = () => sx.columnPivotModel.set((prev) => [...prev, c.id]);
return {
kind: "column-pivot",
label: c.headerName ?? c.id,
active: false,
onClick: onToggle,
draggable: false,
dropId: `pivot-${c.id}`,
dragTags: [],
dropTags: [],
dropData: {}
};
});
return [...activeItems, ...inactiveItems];
}, [
aggModel,
api,
base,
columns,
dir,
measureModel,
pivotModel,
source,
sx.aggModel,
sx.columnPivotModel,
sx.measureModel,
sx.rowGroupModel,
sx.rtl
]);
}
function PillManagerAggMenu({ column, grid }) {
const base = grid.state.columnBase.use();
const aggModel = grid.state.aggModel.use();
const agg = aggModel[column.id]?.fn;
let allowed = column.aggFnsAllowed ?? base.aggFnsAllowed ?? [];
if (typeof agg === "function") allowed = ["Fn(x)", ...allowed];
else if (typeof agg === "string" && !allowed.includes(agg)) allowed = [agg, ...allowed];
return /* @__PURE__ */ jsx(Menu.Container, { children: /* @__PURE__ */ jsx(
Menu.RadioGroup,
{
value: typeof agg === "function" ? "Fn(x)" : agg ?? "",
onValueChange: (v) => {
if (v === "Fn(x)") return;
const next = { ...aggModel };
next[column.id] = {
...next[column.id],
fn: v
};
grid.state.aggModel.set(next);
},
children: allowed.map((c) => {
return /* @__PURE__ */ jsxs(Menu.Radio, { value: c, closeOnClick: true, children: [
c,
/* @__PURE__ */ jsx(Menu.RadioIndicator, {})
] }, c);
})
}
) });
}
function PillManagerMeasureMenu({ column, grid }) {
const base = grid.state.columnBase.use();
const measureModel = grid.state.measureModel.use();
const agg = measureModel[column.id].fn;
let allowed = column.measureFnsAllowed ?? base.measureFnsAllowed ?? [];
if (typeof agg === "function") allowed = ["Fn(x)", ...allowed];
else if (typeof agg === "string" && !allowed.includes(agg)) allowed = [agg, ...allowed];
return /* @__PURE__ */ jsx(Menu.Container, { children: /* @__PURE__ */ jsx(
Menu.RadioGroup,
{
value: typeof agg === "function" ? "Fn(x)" : agg ?? "",
onValueChange: (v) => {
if (v === "Fn(x)") return;
const next = { ...measureModel };
next[column.id] = {
...next[column.id],
fn: v
};
grid.state.measureModel.set(next);
},
children: allowed.map((c) => {
return /* @__PURE__ */ jsxs(Menu.Radio, { value: c, closeOnClick: true, children: [
c,
/* @__PURE__ */ jsx(Menu.RadioIndicator, {})
] }, c);
})
}
) });
}
const Pill = forwardRef(
function Pill2({ className, interactive, kind = "plain", ...props }, ref) {
return /* @__PURE__ */ jsx(
"div",
{
...props,
className: clsx("lng1771-pill", className),
ref,
"data-pill-interactive": interactive,
"data-pill-kind": kind
}
);
}
);
export {
MenuTrigger as M,
Pill as P,
canMeasure as a,
useMeasuresSource as b,
canAgg as c,
useRowGroupsSource as d,
useColumnPivotSource as e,
useDroppable as f,
useEdgeScroll as g,
useDraggable as h,
PillManagerAggMenu as i,
PillManagerMeasureMenu as j,
useAggregationSource as u
};