@deadragdoll/reactnu 0.1.30 → 0.1.49

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.
@@ -16,6 +16,8 @@ export type NuIconDefinition = {
16
16
  onContextMenu?: MouseEventHandler<HTMLButtonElement>;
17
17
  onDoubleClick?: MouseEventHandler<HTMLButtonElement>;
18
18
  onPositionChange?: (position: NuIconPosition, icon: NuIconInfo) => void;
19
+ /** Application-defined data preserved when the icon moves to another grid. */
20
+ payload?: unknown;
19
21
  position?: NuIconPosition;
20
22
  };
21
23
  export type NuIconInfo = Omit<NuIconDefinition, "id" | "position"> & {
@@ -33,3 +35,10 @@ export type NuIconManager = {
33
35
  updateIcon: (id: string, patch: Partial<Omit<NuIconDefinition, "id">>) => void;
34
36
  };
35
37
  export type NuIconContextMenuItems = MainMenuNode[] | ((manager: NuIconManager) => MainMenuNode[]);
38
+ export type NuIconDropContext = {
39
+ position: NuIconPosition;
40
+ source: NuIconManager;
41
+ sourceGrid: HTMLElement;
42
+ target: NuIconManager;
43
+ targetGrid: HTMLElement;
44
+ };
@@ -1,7 +1,15 @@
1
1
  import { HTMLAttributes } from "react";
2
- import { NuIconContextMenuItems, NuIconArrangeMode } from "./IconGrid.types";
2
+ import { NuIconContextMenuItems, NuIconArrangeMode, NuIconDropContext, NuIconInfo } from "./IconGrid.types";
3
3
  export type NuIconGridProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
4
+ /** Filters icons accepted from another grid before `onIconDrop` is called. */
5
+ accepts?: (icon: NuIconInfo) => boolean;
4
6
  contextMenuItems?: NuIconContextMenuItems;
5
7
  defaultArrangeMode?: NuIconArrangeMode;
8
+ /** Enables hit testing for a non-interactive grid surface behind other content. */
9
+ dropTarget?: boolean;
10
+ /** Called by the target grid before an icon is transferred. Return false to reject the drop. */
11
+ onIconDrop?: (icon: NuIconInfo, context: NuIconDropContext) => boolean | void;
12
+ /** Called by the source grid after its icon was transferred to another grid. */
13
+ onIconMoveOut?: (icon: NuIconInfo, context: NuIconDropContext) => void;
6
14
  };
7
- export declare function NuIconGrid({ className, contextMenuItems: contextMenuItemsSource, defaultArrangeMode, onContextMenu, onPointerDown, ...props }: NuIconGridProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function NuIconGrid({ accepts, className, contextMenuItems: contextMenuItemsSource, defaultArrangeMode, dropTarget, onIconDrop, onIconMoveOut, onContextMenu, onPointerDown, ...props }: NuIconGridProps): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { ForwardedRef, HTMLAttributes, RefAttributes } from "react";
1
+ import { ForwardedRef, HTMLAttributes, MouseEvent, RefAttributes } from "react";
2
2
  import { ListBoxGroup, ListBoxItem } from "./internals/types";
3
3
  export type { ListBoxCategory, ListBoxGroup, ListBoxItem, ListBoxLabel } from "./internals/types";
4
4
  export type ListBoxHandle = {
@@ -11,6 +11,7 @@ export type ListBoxHandle = {
11
11
  export type ListBoxProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
12
12
  data: ListBoxGroup[];
13
13
  checkedIds?: string[];
14
+ onPopupMenu?: (event: MouseEvent<HTMLDivElement>, item: ListBoxItem, group: ListBoxGroup) => void;
14
15
  emptyText?: string;
15
16
  onItemCheckChange?: (item: ListBoxItem, group: ListBoxGroup, checked: boolean) => void;
16
17
  onItemDoubleClick?: (item: ListBoxItem, group: ListBoxGroup) => void;
@@ -19,5 +20,5 @@ export type ListBoxProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
19
20
  uncheckedShape?: "box" | "none";
20
21
  onItemSelect?: (item: ListBoxItem, group: ListBoxGroup) => void;
21
22
  };
22
- declare function ListBoxInner({ className, data, checkedIds, emptyText, onItemCheckChange, onItemDoubleClick, onItemSelect, rightCheckBox, selectedId, uncheckedShape, ...props }: ListBoxProps, ref: ForwardedRef<ListBoxHandle>): import("react/jsx-runtime").JSX.Element;
23
+ declare function ListBoxInner({ className, data, checkedIds, emptyText, onPopupMenu, onItemCheckChange, onItemDoubleClick, onItemSelect, rightCheckBox, selectedId, uncheckedShape, ...props }: ListBoxProps, ref: ForwardedRef<ListBoxHandle>): import("react/jsx-runtime").JSX.Element;
23
24
  export declare const ListBox: (props: ListBoxProps & RefAttributes<ListBoxHandle>) => ReturnType<typeof ListBoxInner>;
@@ -1,3 +1,4 @@
1
+ import { MouseEvent } from "react";
1
2
  import { ListBoxGroup, ListBoxItem } from "./types";
2
3
  type ListBoxGroupViewProps = {
3
4
  group: ListBoxGroup;
@@ -6,6 +7,7 @@ type ListBoxGroupViewProps = {
6
7
  listboxId: string;
7
8
  onActivateItem: (item: ListBoxItem, group: ListBoxGroup, itemId: string) => void;
8
9
  onDoubleClickItem?: (item: ListBoxItem, group: ListBoxGroup) => void;
10
+ onPopupMenuItem?: (event: MouseEvent<HTMLDivElement>, item: ListBoxItem, group: ListBoxGroup, itemId: string) => void;
9
11
  onToggleItemCheck: (itemId: string) => void;
10
12
  registerItemRef: (itemId: string, node: HTMLDivElement | null) => void;
11
13
  resolvedActiveId: string | null;
@@ -13,5 +15,5 @@ type ListBoxGroupViewProps = {
13
15
  selectedId?: string;
14
16
  uncheckedShape: "box" | "none";
15
17
  };
16
- export declare function ListBoxGroupView({ group, groupIndex, isItemChecked, listboxId, onActivateItem, onDoubleClickItem, onToggleItemCheck, registerItemRef, resolvedActiveId, rightCheckBox, selectedId, uncheckedShape }: ListBoxGroupViewProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function ListBoxGroupView({ group, groupIndex, isItemChecked, listboxId, onActivateItem, onDoubleClickItem, onPopupMenuItem, onToggleItemCheck, registerItemRef, resolvedActiveId, rightCheckBox, selectedId, uncheckedShape }: ListBoxGroupViewProps): import("react/jsx-runtime").JSX.Element;
17
19
  export {};
@@ -1,3 +1,4 @@
1
+ import { MouseEvent } from "react";
1
2
  import { ListBoxGroup, ListBoxItem } from "./types";
2
3
  type ListBoxItemViewProps = {
3
4
  group: ListBoxGroup;
@@ -8,11 +9,12 @@ type ListBoxItemViewProps = {
8
9
  itemId: string;
9
10
  onActivate: (item: ListBoxItem, group: ListBoxGroup, itemId: string) => void;
10
11
  onDoubleClick?: (item: ListBoxItem, group: ListBoxGroup) => void;
12
+ onPopupMenu?: (event: MouseEvent<HTMLDivElement>, item: ListBoxItem, group: ListBoxGroup, itemId: string) => void;
11
13
  onToggleCheck: (itemId: string) => void;
12
14
  registerItemRef: (itemId: string, node: HTMLDivElement | null) => void;
13
15
  rightCheckBox: boolean;
14
16
  uncheckedShape: "box" | "none";
15
17
  };
16
- declare function ListBoxItemViewInner({ group, isActive, isChecked, isSelected, item, itemId, onActivate, onDoubleClick, onToggleCheck, registerItemRef, rightCheckBox, uncheckedShape }: ListBoxItemViewProps): import("react/jsx-runtime").JSX.Element;
18
+ declare function ListBoxItemViewInner({ group, isActive, isChecked, isSelected, item, itemId, onActivate, onPopupMenu, onDoubleClick, onToggleCheck, registerItemRef, rightCheckBox, uncheckedShape }: ListBoxItemViewProps): import("react/jsx-runtime").JSX.Element;
17
19
  export declare const ListBoxItemView: import("react").MemoExoticComponent<typeof ListBoxItemViewInner>;
18
20
  export {};
@@ -1,5 +1,5 @@
1
- import { ForwardedRef, HTMLAttributes, RefAttributes } from "react";
2
- import { TreeListColumn, TreeListGetCellContent, TreeListItemBase } from "./internals/types";
1
+ import { ForwardedRef, HTMLAttributes, MouseEvent, RefAttributes } from "react";
2
+ import { TreeListColumn, TreeListCellContext, TreeListGetCellContent, TreeListItemBase } from "./internals/types";
3
3
  export type { TreeListCellContext, TreeListColumn, TreeListGetCellContent, TreeListItemBase } from "./internals/types";
4
4
  export type TreeListViewHandle = {
5
5
  activateItem: (itemId: string) => void;
@@ -13,6 +13,7 @@ export type TreeListViewHandle = {
13
13
  export type TreeListViewProps<T extends TreeListItemBase<T>> = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
14
14
  activeItemId?: string;
15
15
  checkedIds?: string[];
16
+ onPopupMenu?: (event: MouseEvent<HTMLDivElement>, item: T, context: TreeListCellContext<T>) => void;
16
17
  columns: TreeListColumn<T>[];
17
18
  data: T[];
18
19
  dataVersion?: number;
@@ -29,5 +30,5 @@ export type TreeListViewProps<T extends TreeListItemBase<T>> = Omit<HTMLAttribut
29
30
  selectedId?: string;
30
31
  uncheckedShape?: "box" | "none";
31
32
  };
32
- declare function TreeListViewInner<T extends TreeListItemBase<T>>({ activeItemId: activeItemIdProp, checkedIds, className, columns, data, dataVersion, defaultActiveItemId, defaultExpandedIds, emptyText, expandedIds, getCellContent, onActiveItemChange, onExpandedIdsChange, onItemCheckChange, onItemDoubleClick, onItemSelect, selectedId, uncheckedShape, ...props }: TreeListViewProps<T>, ref: ForwardedRef<TreeListViewHandle>): import("react/jsx-runtime").JSX.Element;
33
+ declare function TreeListViewInner<T extends TreeListItemBase<T>>({ activeItemId: activeItemIdProp, checkedIds, className, columns, onPopupMenu, data, dataVersion, defaultActiveItemId, defaultExpandedIds, emptyText, expandedIds, getCellContent, onActiveItemChange, onExpandedIdsChange, onItemCheckChange, onItemDoubleClick, onItemSelect, selectedId, uncheckedShape, ...props }: TreeListViewProps<T>, ref: ForwardedRef<TreeListViewHandle>): import("react/jsx-runtime").JSX.Element;
33
34
  export declare const TreeListView: <T extends TreeListItemBase<T>>(props: TreeListViewProps<T> & RefAttributes<TreeListViewHandle>) => ReturnType<typeof TreeListViewInner>;
@@ -1,4 +1,5 @@
1
- import { TreeListColumn, TreeListGetCellContent, TreeListItemBase } from "./types";
1
+ import { MouseEvent } from "react";
2
+ import { TreeListCellContext, TreeListColumn, TreeListGetCellContent, TreeListItemBase } from "./types";
2
3
  type TreeListViewRowProps<T extends TreeListItemBase<T>> = {
3
4
  activeItemId: string | null;
4
5
  checkedIds?: string[];
@@ -12,6 +13,7 @@ type TreeListViewRowProps<T extends TreeListItemBase<T>> = {
12
13
  item: T;
13
14
  onActivateItem: (item: T, itemId: string) => void;
14
15
  onDoubleClickItem?: (item: T) => void;
16
+ onPopupMenuItem?: (event: MouseEvent<HTMLDivElement>, item: T, context: TreeListCellContext<T>) => void;
15
17
  onToggleItemCheck?: (item: T, checked: boolean) => void;
16
18
  onToggleItemExpanded: (item: T, expanded: boolean) => void;
17
19
  originOffset: number;
@@ -23,6 +25,6 @@ type TreeListViewRowProps<T extends TreeListItemBase<T>> = {
23
25
  treeId: string;
24
26
  uncheckedShape: "box" | "none";
25
27
  };
26
- declare function TreeListViewRowInner<T extends TreeListItemBase<T>>({ activeItemId, checkedIds, columns, depth, expandedIdSet, getCellContent, guideMask, guideOffsets, hasNextSibling, item, onActivateItem, onDoubleClickItem, onToggleItemCheck, onToggleItemExpanded, originOffset, registerItemRef, rowIndexMap, selectedItemId, templateColumns, treeColumnId, treeId, uncheckedShape }: TreeListViewRowProps<T>): import("react/jsx-runtime").JSX.Element;
28
+ declare function TreeListViewRowInner<T extends TreeListItemBase<T>>({ activeItemId, checkedIds, columns, depth, expandedIdSet, getCellContent, guideMask, guideOffsets, hasNextSibling, item, onActivateItem, onDoubleClickItem, onPopupMenuItem, onToggleItemCheck, onToggleItemExpanded, originOffset, registerItemRef, rowIndexMap, selectedItemId, templateColumns, treeColumnId, treeId, uncheckedShape }: TreeListViewRowProps<T>): import("react/jsx-runtime").JSX.Element;
27
29
  export declare const TreeListViewRow: typeof TreeListViewRowInner;
28
30
  export {};
package/dist/index.cjs CHANGED
@@ -1024,6 +1024,7 @@ function ListBoxItemViewInner({
1024
1024
  item,
1025
1025
  itemId,
1026
1026
  onActivate,
1027
+ onPopupMenu,
1027
1028
  onDoubleClick,
1028
1029
  onToggleCheck,
1029
1030
  registerItemRef,
@@ -1042,6 +1043,9 @@ function ListBoxItemViewInner({
1042
1043
  onDoubleClick?.(item, group);
1043
1044
  }
1044
1045
  }
1046
+ function handleContextMenu(event) {
1047
+ onPopupMenu?.(event, item, group, itemId);
1048
+ }
1045
1049
  function handleToggleCheck() {
1046
1050
  onToggleCheck(itemId);
1047
1051
  }
@@ -1070,6 +1074,7 @@ function ListBoxItemViewInner({
1070
1074
  ].filter(Boolean).join(" "),
1071
1075
  id: itemId,
1072
1076
  onClick: handleActivate,
1077
+ onContextMenu: onPopupMenu ? handleContextMenu : void 0,
1073
1078
  onDoubleClick: handleDoubleClick,
1074
1079
  ref: (node) => registerItemRef(itemId, node),
1075
1080
  role: "option",
@@ -1095,6 +1100,7 @@ function ListBoxGroupView({
1095
1100
  listboxId,
1096
1101
  onActivateItem,
1097
1102
  onDoubleClickItem,
1103
+ onPopupMenuItem,
1098
1104
  onToggleItemCheck,
1099
1105
  registerItemRef,
1100
1106
  resolvedActiveId,
@@ -1126,6 +1132,7 @@ function ListBoxGroupView({
1126
1132
  itemId,
1127
1133
  onActivate: onActivateItem,
1128
1134
  onDoubleClick: onDoubleClickItem,
1135
+ onPopupMenu: onPopupMenuItem,
1129
1136
  onToggleCheck: onToggleItemCheck,
1130
1137
  registerItemRef,
1131
1138
  rightCheckBox,
@@ -1144,6 +1151,7 @@ function ListBoxInner({
1144
1151
  data,
1145
1152
  checkedIds,
1146
1153
  emptyText = "No items",
1154
+ onPopupMenu,
1147
1155
  onItemCheckChange,
1148
1156
  onItemDoubleClick,
1149
1157
  onItemSelect,
@@ -1203,6 +1211,18 @@ function ListBoxInner({
1203
1211
  },
1204
1212
  [activateItem, selectableItems]
1205
1213
  );
1214
+ const handleItemPopupMenu = (0, import_react5.useCallback)(
1215
+ (event, item, group, itemId) => {
1216
+ if (item.disabled || !onPopupMenu) {
1217
+ return;
1218
+ }
1219
+ event.preventDefault();
1220
+ event.stopPropagation();
1221
+ activateItem(item, group, itemId);
1222
+ onPopupMenu(event, item, group);
1223
+ },
1224
+ [activateItem, onPopupMenu]
1225
+ );
1206
1226
  function moveActive(direction) {
1207
1227
  if (selectableItems.length === 0) {
1208
1228
  return;
@@ -1323,6 +1343,7 @@ function ListBoxInner({
1323
1343
  listboxId,
1324
1344
  onActivateItem: activateItem,
1325
1345
  onDoubleClickItem: onItemDoubleClick,
1346
+ onPopupMenuItem: handleItemPopupMenu,
1326
1347
  onToggleItemCheck: toggleItemCheck,
1327
1348
  registerItemRef,
1328
1349
  resolvedActiveId,
@@ -1998,8 +2019,6 @@ function WindowInner({
1998
2019
  return;
1999
2020
  }
2000
2021
  onActivate?.();
2001
- event.preventDefault();
2002
- event.stopPropagation();
2003
2022
  }
2004
2023
  function handleTitlePointerDown(event) {
2005
2024
  onActivate?.();
@@ -4583,6 +4602,7 @@ function useNuIconGridContext() {
4583
4602
  // src/components/IconGrid/NuIconGrid.tsx
4584
4603
  var import_jsx_runtime35 = require("react/jsx-runtime");
4585
4604
  var DRAG_THRESHOLD = 3;
4605
+ var gridRegistrations = /* @__PURE__ */ new Map();
4586
4606
  function clamp2(value, minimum, maximum) {
4587
4607
  return Math.min(Math.max(value, minimum), maximum);
4588
4608
  }
@@ -4592,11 +4612,85 @@ function resolveIconContextMenuItems(source, icon) {
4592
4612
  function resolveGridContextMenuItems(source, manager) {
4593
4613
  return typeof source === "function" ? source(manager) : source ?? [];
4594
4614
  }
4595
- function NuIconGridItem({ gridElement, icon }) {
4615
+ function findGridRegistrationAtPoint(clientX, clientY) {
4616
+ const target = document.elementFromPoint(clientX, clientY);
4617
+ const gridElement = target?.closest(".nu-icon-grid");
4618
+ if (gridElement) {
4619
+ return gridRegistrations.get(gridElement);
4620
+ }
4621
+ if (target?.closest(".nu-window")) {
4622
+ return void 0;
4623
+ }
4624
+ return Array.from(gridRegistrations.values()).reverse().find((registration) => {
4625
+ if (!registration.dropTarget) {
4626
+ return false;
4627
+ }
4628
+ const rect = registration.element.getBoundingClientRect();
4629
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
4630
+ });
4631
+ }
4632
+ function getTransferredPosition(targetGrid, iconElement, clientX, clientY) {
4633
+ const targetRect = targetGrid.getBoundingClientRect();
4634
+ const iconRect = iconElement.getBoundingClientRect();
4635
+ return {
4636
+ x: Math.round(
4637
+ clamp2(
4638
+ clientX - targetRect.left - iconRect.width / 2,
4639
+ 0,
4640
+ Math.max(0, targetRect.width - iconRect.width)
4641
+ )
4642
+ ),
4643
+ y: Math.round(
4644
+ clamp2(
4645
+ clientY - targetRect.top - iconRect.height / 2,
4646
+ 0,
4647
+ Math.max(0, targetRect.height - iconRect.height)
4648
+ )
4649
+ )
4650
+ };
4651
+ }
4652
+ function createDragPreview(iconElement) {
4653
+ const rect = iconElement.getBoundingClientRect();
4654
+ const preview = iconElement.cloneNode(true);
4655
+ preview.removeAttribute("id");
4656
+ preview.setAttribute("aria-hidden", "true");
4657
+ preview.setAttribute("disabled", "");
4658
+ preview.setAttribute("tabindex", "-1");
4659
+ Object.assign(preview.style, {
4660
+ height: `${rect.height}px`,
4661
+ left: `${rect.left}px`,
4662
+ margin: "0",
4663
+ opacity: window.getComputedStyle(iconElement).getPropertyValue("--nu-icon-grid-icon-drag-preview-opacity").trim() || "0.85",
4664
+ pointerEvents: "none",
4665
+ position: "fixed",
4666
+ top: `${rect.top}px`,
4667
+ width: `${rect.width}px`,
4668
+ zIndex: "2147483647"
4669
+ });
4670
+ const themeStyle = getThemePortalStyle(iconElement);
4671
+ Object.entries(themeStyle ?? {}).forEach(([property, value]) => {
4672
+ if (value !== void 0) {
4673
+ preview.style.setProperty(property, String(value));
4674
+ }
4675
+ });
4676
+ document.body.append(preview);
4677
+ return preview;
4678
+ }
4679
+ function moveDragPreview(preview, iconElement, clientX, clientY) {
4680
+ const rect = iconElement.getBoundingClientRect();
4681
+ preview.style.left = `${Math.round(clientX - rect.width / 2)}px`;
4682
+ preview.style.top = `${Math.round(clientY - rect.height / 2)}px`;
4683
+ }
4684
+ function NuIconGridItem({
4685
+ gridElement,
4686
+ icon,
4687
+ onIconMoveOut
4688
+ }) {
4596
4689
  const manager = useNuIconGridContext();
4597
4690
  const contextMenu = usePopupMenu();
4598
4691
  const dragStartRef = (0, import_react25.useRef)(void 0);
4599
4692
  const isDraggingRef = (0, import_react25.useRef)(false);
4693
+ const dragPreviewRef = (0, import_react25.useRef)(null);
4600
4694
  const [isDragging, setIsDragging] = (0, import_react25.useState)(false);
4601
4695
  const suppressClickRef = (0, import_react25.useRef)(false);
4602
4696
  const latestPositionRef = (0, import_react25.useRef)(icon.position);
@@ -4604,6 +4698,11 @@ function NuIconGridItem({ gridElement, icon }) {
4604
4698
  icon.contextMenuItems,
4605
4699
  icon
4606
4700
  );
4701
+ function removeDragPreview() {
4702
+ dragPreviewRef.current?.remove();
4703
+ dragPreviewRef.current = null;
4704
+ }
4705
+ (0, import_react25.useEffect)(() => removeDragPreview, []);
4607
4706
  function handlePointerDown(event) {
4608
4707
  if (event.button !== 0 || icon.disabled) {
4609
4708
  return;
@@ -4629,8 +4728,19 @@ function NuIconGridItem({ gridElement, icon }) {
4629
4728
  if (!isDraggingRef.current && Math.max(Math.abs(deltaX), Math.abs(deltaY)) < DRAG_THRESHOLD) {
4630
4729
  return;
4631
4730
  }
4632
- isDraggingRef.current = true;
4633
- setIsDragging(true);
4731
+ if (!isDraggingRef.current) {
4732
+ isDraggingRef.current = true;
4733
+ setIsDragging(true);
4734
+ dragPreviewRef.current = createDragPreview(event.currentTarget);
4735
+ }
4736
+ if (dragPreviewRef.current) {
4737
+ moveDragPreview(
4738
+ dragPreviewRef.current,
4739
+ event.currentTarget,
4740
+ event.clientX,
4741
+ event.clientY
4742
+ );
4743
+ }
4634
4744
  const gridRect = gridElement.getBoundingClientRect();
4635
4745
  const iconRect = event.currentTarget.getBoundingClientRect();
4636
4746
  const position = {
@@ -4650,7 +4760,6 @@ function NuIconGridItem({ gridElement, icon }) {
4650
4760
  )
4651
4761
  };
4652
4762
  latestPositionRef.current = position;
4653
- manager.moveIcon(icon.id, position);
4654
4763
  }
4655
4764
  function finishDragging(event) {
4656
4765
  const dragStart = dragStartRef.current;
@@ -4661,16 +4770,56 @@ function NuIconGridItem({ gridElement, icon }) {
4661
4770
  event.currentTarget.releasePointerCapture(event.pointerId);
4662
4771
  }
4663
4772
  dragStartRef.current = void 0;
4773
+ removeDragPreview();
4664
4774
  if (!isDraggingRef.current) {
4665
4775
  return;
4666
4776
  }
4667
4777
  suppressClickRef.current = true;
4668
4778
  isDraggingRef.current = false;
4669
4779
  setIsDragging(false);
4670
- icon.onPositionChange?.(latestPositionRef.current, {
4671
- ...icon,
4672
- position: latestPositionRef.current
4673
- });
4780
+ if (event.type === "pointercancel" || !gridElement) {
4781
+ return;
4782
+ }
4783
+ const targetRegistration = findGridRegistrationAtPoint(
4784
+ event.clientX,
4785
+ event.clientY
4786
+ );
4787
+ if (!targetRegistration) {
4788
+ return;
4789
+ }
4790
+ if (targetRegistration.element === gridElement) {
4791
+ manager.moveIcon(icon.id, latestPositionRef.current);
4792
+ icon.onPositionChange?.(latestPositionRef.current, {
4793
+ ...icon,
4794
+ position: latestPositionRef.current
4795
+ });
4796
+ return;
4797
+ }
4798
+ if (!targetRegistration.manager.icons.some(
4799
+ (targetIcon) => targetIcon.id === icon.id
4800
+ )) {
4801
+ const position = getTransferredPosition(
4802
+ targetRegistration.element,
4803
+ event.currentTarget,
4804
+ event.clientX,
4805
+ event.clientY
4806
+ );
4807
+ const transferredIcon = { ...icon, position };
4808
+ const context = {
4809
+ position,
4810
+ source: manager,
4811
+ sourceGrid: gridElement,
4812
+ target: targetRegistration.manager,
4813
+ targetGrid: targetRegistration.element
4814
+ };
4815
+ if (targetRegistration.accepts?.(transferredIcon) !== false && targetRegistration.onIconDrop?.(transferredIcon, context) !== false) {
4816
+ targetRegistration.manager.addIcon(transferredIcon);
4817
+ targetRegistration.manager.selectIcon(transferredIcon.id);
4818
+ manager.removeIcon(icon.id);
4819
+ onIconMoveOut?.(transferredIcon, context);
4820
+ return;
4821
+ }
4822
+ }
4674
4823
  }
4675
4824
  function handleClick(event) {
4676
4825
  if (suppressClickRef.current) {
@@ -4736,9 +4885,13 @@ function NuIconGridItem({ gridElement, icon }) {
4736
4885
  ] });
4737
4886
  }
4738
4887
  function NuIconGrid({
4888
+ accepts,
4739
4889
  className,
4740
4890
  contextMenuItems: contextMenuItemsSource,
4741
4891
  defaultArrangeMode,
4892
+ dropTarget = false,
4893
+ onIconDrop,
4894
+ onIconMoveOut,
4742
4895
  onContextMenu,
4743
4896
  onPointerDown,
4744
4897
  ...props
@@ -4774,6 +4927,21 @@ function NuIconGrid({
4774
4927
  resizeObserver.observe(gridElement);
4775
4928
  return () => resizeObserver.disconnect();
4776
4929
  }, [arrangeIcons, defaultArrangeMode, gridElement, setGridSize]);
4930
+ (0, import_react25.useLayoutEffect)(() => {
4931
+ if (!gridElement) {
4932
+ return;
4933
+ }
4934
+ gridRegistrations.set(gridElement, {
4935
+ accepts,
4936
+ dropTarget,
4937
+ element: gridElement,
4938
+ manager,
4939
+ onIconDrop
4940
+ });
4941
+ return () => {
4942
+ gridRegistrations.delete(gridElement);
4943
+ };
4944
+ }, [accepts, dropTarget, gridElement, manager, onIconDrop]);
4777
4945
  function handleContextMenu(event) {
4778
4946
  onContextMenu?.(event);
4779
4947
  if (event.defaultPrevented || contextMenuItems.length === 0) {
@@ -4802,7 +4970,15 @@ function NuIconGrid({
4802
4970
  ref: setGridElement,
4803
4971
  role: "group",
4804
4972
  children: [
4805
- manager.icons.map((icon) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NuIconGridItem, { gridElement, icon }, icon.id)),
4973
+ manager.icons.map((icon) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4974
+ NuIconGridItem,
4975
+ {
4976
+ gridElement,
4977
+ icon,
4978
+ onIconMoveOut
4979
+ },
4980
+ icon.id
4981
+ )),
4806
4982
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4807
4983
  PopupMenu,
4808
4984
  {
@@ -9004,6 +9180,7 @@ function TreeListViewRowInner({
9004
9180
  item,
9005
9181
  onActivateItem,
9006
9182
  onDoubleClickItem,
9183
+ onPopupMenuItem,
9007
9184
  onToggleItemCheck,
9008
9185
  onToggleItemExpanded,
9009
9186
  originOffset,
@@ -9042,6 +9219,17 @@ function TreeListViewRowInner({
9042
9219
  }
9043
9220
  onDoubleClickItem?.(item);
9044
9221
  }
9222
+ function handleContextMenu(event) {
9223
+ if (item.disabled) {
9224
+ return;
9225
+ }
9226
+ onPopupMenuItem?.(event, item, {
9227
+ depth,
9228
+ isLeaf: !hasChildren,
9229
+ item,
9230
+ rowIndex
9231
+ });
9232
+ }
9045
9233
  function handleToggleExpanded(event) {
9046
9234
  event.stopPropagation();
9047
9235
  if (item.disabled || !hasChildren) {
@@ -9075,6 +9263,7 @@ function TreeListViewRowInner({
9075
9263
  ].filter(Boolean).join(" "),
9076
9264
  id: `${treeId}-${itemId}`,
9077
9265
  onClick: handleActivate,
9266
+ onContextMenu: onPopupMenuItem ? handleContextMenu : void 0,
9078
9267
  onDoubleClick: handleDoubleClick,
9079
9268
  ref: (node) => registerItemRef(itemId, node),
9080
9269
  role: "row",
@@ -9223,6 +9412,7 @@ function TreeListViewRowInner({
9223
9412
  item: child,
9224
9413
  onActivateItem,
9225
9414
  onDoubleClickItem,
9415
+ onPopupMenuItem,
9226
9416
  onToggleItemCheck,
9227
9417
  onToggleItemExpanded,
9228
9418
  originOffset: originOffset + leadOffset,
@@ -9244,7 +9434,7 @@ function areTreeListViewRowPropsEqual(previousProps, nextProps) {
9244
9434
  return !didActivePathChange && !didSelectedPathChange && previousProps.checkedIds === nextProps.checkedIds && previousProps.columns === nextProps.columns && previousProps.depth === nextProps.depth && previousProps.expandedIdSet === nextProps.expandedIdSet && previousProps.getCellContent === nextProps.getCellContent && areTreeListGuideArraysEqual(previousProps.guideMask, nextProps.guideMask) && areTreeListGuideArraysEqual(
9245
9435
  previousProps.guideOffsets,
9246
9436
  nextProps.guideOffsets
9247
- ) && previousProps.hasNextSibling === nextProps.hasNextSibling && previousProps.item === nextProps.item && previousProps.onActivateItem === nextProps.onActivateItem && previousProps.onDoubleClickItem === nextProps.onDoubleClickItem && previousProps.onToggleItemCheck === nextProps.onToggleItemCheck && previousProps.onToggleItemExpanded === nextProps.onToggleItemExpanded && previousProps.originOffset === nextProps.originOffset && previousProps.registerItemRef === nextProps.registerItemRef && previousProps.rowIndexMap === nextProps.rowIndexMap && previousProps.templateColumns === nextProps.templateColumns && previousProps.treeColumnId === nextProps.treeColumnId && previousProps.treeId === nextProps.treeId && previousProps.uncheckedShape === nextProps.uncheckedShape;
9437
+ ) && previousProps.hasNextSibling === nextProps.hasNextSibling && previousProps.item === nextProps.item && previousProps.onActivateItem === nextProps.onActivateItem && previousProps.onDoubleClickItem === nextProps.onDoubleClickItem && previousProps.onPopupMenuItem === nextProps.onPopupMenuItem && previousProps.onToggleItemCheck === nextProps.onToggleItemCheck && previousProps.onToggleItemExpanded === nextProps.onToggleItemExpanded && previousProps.originOffset === nextProps.originOffset && previousProps.registerItemRef === nextProps.registerItemRef && previousProps.rowIndexMap === nextProps.rowIndexMap && previousProps.templateColumns === nextProps.templateColumns && previousProps.treeColumnId === nextProps.treeColumnId && previousProps.treeId === nextProps.treeId && previousProps.uncheckedShape === nextProps.uncheckedShape;
9248
9438
  }
9249
9439
  var TreeListViewRow = (0, import_react44.memo)(
9250
9440
  TreeListViewRowInner,
@@ -9258,6 +9448,7 @@ function TreeListViewInner({
9258
9448
  checkedIds,
9259
9449
  className,
9260
9450
  columns,
9451
+ onPopupMenu,
9261
9452
  data,
9262
9453
  dataVersion,
9263
9454
  defaultActiveItemId,
@@ -9427,6 +9618,18 @@ function TreeListViewInner({
9427
9618
  },
9428
9619
  [onItemSelect, selectedId, updateActiveItem]
9429
9620
  );
9621
+ const handleItemPopupMenu = (0, import_react45.useCallback)(
9622
+ (event, item, context) => {
9623
+ if (item.disabled || !onPopupMenu) {
9624
+ return;
9625
+ }
9626
+ event.preventDefault();
9627
+ event.stopPropagation();
9628
+ activateEntry(item, item.id);
9629
+ onPopupMenu(event, item, context);
9630
+ },
9631
+ [activateEntry, onPopupMenu]
9632
+ );
9430
9633
  const activateResolvedItem = (0, import_react45.useCallback)(
9431
9634
  (itemId) => {
9432
9635
  if (!itemId) {
@@ -9732,6 +9935,7 @@ function TreeListViewInner({
9732
9935
  item,
9733
9936
  onActivateItem: activateEntry,
9734
9937
  onDoubleClickItem: onItemDoubleClick ? handleItemDoubleClick : void 0,
9938
+ onPopupMenuItem: handleItemPopupMenu,
9735
9939
  onToggleItemCheck: handleItemCheckChange,
9736
9940
  onToggleItemExpanded: setExpandedState,
9737
9941
  originOffset: 0,
@@ -9768,10 +9972,12 @@ var classicTheme = {
9768
9972
  panelBackground: "#6262e3",
9769
9973
  panelInsetBackground: "#6262e3",
9770
9974
  titleBackground: "#ffffff",
9975
+ menuBackground: "#ffffff",
9771
9976
  titleText: "#1f36bc",
9772
9977
  textPrimary: "#ffffff",
9773
9978
  textMuted: "#dfe3ff",
9774
9979
  textInverse: "#000000",
9980
+ mainMenuText: "#000000",
9775
9981
  textAccent: "#ffff55",
9776
9982
  textHotkey: "#ff5555",
9777
9983
  buttonFace: "#ffffff",
@@ -9804,10 +10010,12 @@ var amberTheme = {
9804
10010
  panelBackground: "#4b2b00",
9805
10011
  panelInsetBackground: "#382000",
9806
10012
  titleBackground: "#ffd28f",
10013
+ menuBackground: "#ffd28f",
9807
10014
  titleText: "#4b2b00",
9808
10015
  textPrimary: "#ffd28f",
9809
10016
  textMuted: "#d6b57a",
9810
10017
  textInverse: "#201000",
10018
+ mainMenuText: "#201000",
9811
10019
  textAccent: "#fff27a",
9812
10020
  textHotkey: "#ff5f00",
9813
10021
  buttonFace: "#ffd28f",
@@ -9840,10 +10048,12 @@ var phosphorTheme = {
9840
10048
  panelBackground: "#11351a",
9841
10049
  panelInsetBackground: "#0d2813",
9842
10050
  titleBackground: "#b7ffbf",
10051
+ menuBackground: "#b7ffbf",
9843
10052
  titleText: "#0b2310",
9844
10053
  textPrimary: "#b7ffbf",
9845
10054
  textMuted: "#8ed39a",
9846
10055
  textInverse: "#061208",
10056
+ mainMenuText: "#061208",
9847
10057
  textAccent: "#f7ff7a",
9848
10058
  textHotkey: "#ff6f6f",
9849
10059
  buttonFace: "#b7ffbf",
@@ -9876,10 +10086,12 @@ var midnightTheme = {
9876
10086
  panelBackground: "#17273b",
9877
10087
  panelInsetBackground: "#111e2e",
9878
10088
  titleBackground: "#d4dde8",
10089
+ menuBackground: "#d4dde8",
9879
10090
  titleText: "#14233a",
9880
10091
  textPrimary: "#e6edf7",
9881
10092
  textMuted: "#aebdcd",
9882
10093
  textInverse: "#0b1220",
10094
+ mainMenuText: "#0b1220",
9883
10095
  textAccent: "#ffd166",
9884
10096
  textHotkey: "#ff7171",
9885
10097
  buttonFace: "#d4dde8",
@@ -9912,10 +10124,12 @@ var grayscaleTheme = {
9912
10124
  panelBackground: "#3f3f3f",
9913
10125
  panelInsetBackground: "#343434",
9914
10126
  titleBackground: "#262626",
10127
+ menuBackground: "#262626",
9915
10128
  titleText: "#d0d0d0",
9916
10129
  textPrimary: "#c8c8c8",
9917
10130
  textMuted: "#969696",
9918
10131
  textInverse: "#d0d0d0",
10132
+ mainMenuText: "#d0d0d0",
9919
10133
  textAccent: "#e0e0e0",
9920
10134
  textHotkey: "#ffffff",
9921
10135
  buttonFace: "#5b5b5b",
@@ -9959,10 +10173,12 @@ function getNuThemeStyle(theme) {
9959
10173
  "--nu-color-panel": theme.tokens.panelBackground,
9960
10174
  "--nu-color-panel-inset": theme.tokens.panelInsetBackground,
9961
10175
  "--nu-color-title-bg": theme.tokens.titleBackground,
10176
+ "--nu-color-menu": theme.tokens.menuBackground,
9962
10177
  "--nu-color-title-text": theme.tokens.titleText,
9963
10178
  "--nu-text-primary": theme.tokens.textPrimary,
9964
10179
  "--nu-text-muted": theme.tokens.textMuted,
9965
10180
  "--nu-text-inverse": theme.tokens.textInverse,
10181
+ "--nu-main-menu-text": theme.tokens.mainMenuText,
9966
10182
  "--nu-text-accent": theme.tokens.textAccent,
9967
10183
  "--nu-text-hotkey": theme.tokens.textHotkey,
9968
10184
  "--nu-color-button-face": theme.tokens.buttonFace,
@@ -10046,6 +10262,7 @@ function NuThemeProvider({
10046
10262
  onFontFamilyChange,
10047
10263
  onFontSizeChange,
10048
10264
  onThemeChange,
10265
+ style,
10049
10266
  theme
10050
10267
  }) {
10051
10268
  const generatedId = (0, import_react47.useId)();
@@ -10131,7 +10348,8 @@ function NuThemeProvider({
10131
10348
  ...getNuThemeStyle(resolvedTheme),
10132
10349
  "--nu-font-body": resolvedFontFamily,
10133
10350
  fontFamily: resolvedFontFamily,
10134
- fontSize: `${resolvedFontSize}px`
10351
+ fontSize: `${resolvedFontSize}px`,
10352
+ ...style
10135
10353
  },
10136
10354
  children: [
10137
10355
  crtGlitch ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(NuCrtGlitch, { ...typeof crtGlitch === "object" ? crtGlitch : {} }) : null,