@forgeax/app-shell 0.42.0 → 0.44.0

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/dist/react.d.ts CHANGED
@@ -51,6 +51,25 @@ interface PointerDragSessionOptions<Session> {
51
51
  readonly stopPropagation?: boolean;
52
52
  readonly draggingClassName?: string;
53
53
  }
54
+ type ThresholdPointerDragFinishReason = 'pointerup' | 'pointercancel' | 'cancel' | 'dispose' | 'error';
55
+ interface ThresholdPointerDragSessionOptions<Session> {
56
+ readonly target: Window;
57
+ readonly pointerId: number;
58
+ readonly startX: number;
59
+ readonly startY: number;
60
+ readonly threshold: number;
61
+ readonly begin: (event: globalThis.PointerEvent) => Session | null;
62
+ readonly move: (session: Session, event: globalThis.PointerEvent) => void;
63
+ readonly end?: (session: Session, reason: ThresholdPointerDragFinishReason) => void;
64
+ readonly onSettled?: (reason: ThresholdPointerDragFinishReason, started: boolean) => void;
65
+ readonly registerCancel?: (cancel: () => void) => void | (() => void);
66
+ }
67
+ interface ThresholdPointerDragSession {
68
+ cancel(): void;
69
+ dispose(): void;
70
+ }
71
+ /** Own one post-pointerdown threshold arm and its global listener lifecycle. */
72
+ declare function installThresholdPointerDragSession<Session>(options: ThresholdPointerDragSessionOptions<Session>): ThresholdPointerDragSession;
54
73
  /** Own one imperative pointer-capture drag session and its balanced teardown. */
55
74
  declare function installPointerDragSession<Session>(options: PointerDragSessionOptions<Session>): () => void;
56
75
  type AnchoredResizeDirection = 'subtract' | 'add';
@@ -73,6 +92,23 @@ interface AnchoredResizeHandleProps extends Omit<ResizeHandleProps, 'onDrag' | '
73
92
  /** Product-neutral anchored resize session over the public pointer carrier. */
74
93
  declare function AnchoredResizeHandle({ readSize, writeSize, direction, resizingBodyClassName, ...handleProps }: AnchoredResizeHandleProps): react.JSX.Element;
75
94
 
95
+ type PointerReorderFinishReason = 'pointerup' | 'pointercancel' | 'buttons-released' | 'replacement' | 'cancel' | 'dispose' | 'error';
96
+ interface PointerReorderSessionOptions<Item> {
97
+ readonly element: HTMLElement;
98
+ readonly target: Window;
99
+ readonly resolveItem: (target: EventTarget | null) => Item | null;
100
+ readonly canStart?: (event: globalThis.PointerEvent, item: Item) => boolean;
101
+ readonly reorder: (dragged: Item, over: Item, event: globalThis.PointerEvent) => void;
102
+ readonly onDraggingChange?: (item: Item | null, reason?: PointerReorderFinishReason) => void;
103
+ readonly equals?: (left: Item, right: Item) => boolean;
104
+ }
105
+ interface PointerReorderSession {
106
+ cancel(): void;
107
+ dispose(): void;
108
+ }
109
+ /** Own one delegated pointer reorder lifecycle without owning item identity or mutation policy. */
110
+ declare function installPointerReorderSession<Item>(options: PointerReorderSessionOptions<Item>): PointerReorderSession;
111
+
76
112
  type PanelContentPadding = 'none' | 'sm' | 'md';
77
113
  type PanelContentScroll = 'none' | 'auto';
78
114
  type PanelContentTone = 'default' | 'surface' | 'tool';
@@ -214,4 +250,4 @@ interface ShellSlotProps extends HTMLAttributes<HTMLElement> {
214
250
  /** Structural shell marker that preserves the caller's semantic element. */
215
251
  declare function ShellSlot({ as, name, ...props }: ShellSlotProps): ReactElement;
216
252
 
217
- export { type AnchoredResizeDirection, AnchoredResizeHandle, type AnchoredResizeHandleProps, type AnchoredResizeSession, DetachedPanelBoundary, type DetachedPanelBoundaryProps, DetachedSurfaceFrame, type DetachedSurfaceFrameProps, DetachedSurfaceStatus, type DetachedSurfaceStatusProps, type DetachedSurfaceStatusTone, type DockLayoutControlBinding, type DockLayoutControlBindingOptions, DockLayoutMenu, type DockLayoutMenuPanel, type DockLayoutMenuProps, FloatingMenu, type FloatingMenuAnchor, type FloatingMenuPoint, type FloatingMenuProps, type PanelContentPadding, type PanelContentPolicy, type PanelContentScroll, type PanelContentTone, PanelEmptyState, type PanelEmptyStateProps, PanelSurface, type PanelSurfaceProps, type PersistentSizeStorage, type PersistentSizeStore, type PersistentSizeStoreOptions, type PersistentSizeUpdate, type PointerDragFinishReason, type PointerDragSessionOptions, ResizeHandle, type ResizeHandleProps, ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, SurfacePlaceholder, type SurfacePlaceholderProps, SurfaceRegion, type SurfaceRegionProps, applyAnchoredResizeDelta, beginAnchoredResize, createPersistentSizeStore, hashSlotHue, installPointerDragSession, isSlotDebugEnabled, useDockLayoutControlBinding, useLocalSize, usePersistentSizeStore };
253
+ export { type AnchoredResizeDirection, AnchoredResizeHandle, type AnchoredResizeHandleProps, type AnchoredResizeSession, DetachedPanelBoundary, type DetachedPanelBoundaryProps, DetachedSurfaceFrame, type DetachedSurfaceFrameProps, DetachedSurfaceStatus, type DetachedSurfaceStatusProps, type DetachedSurfaceStatusTone, type DockLayoutControlBinding, type DockLayoutControlBindingOptions, DockLayoutMenu, type DockLayoutMenuPanel, type DockLayoutMenuProps, FloatingMenu, type FloatingMenuAnchor, type FloatingMenuPoint, type FloatingMenuProps, type PanelContentPadding, type PanelContentPolicy, type PanelContentScroll, type PanelContentTone, PanelEmptyState, type PanelEmptyStateProps, PanelSurface, type PanelSurfaceProps, type PersistentSizeStorage, type PersistentSizeStore, type PersistentSizeStoreOptions, type PersistentSizeUpdate, type PointerDragFinishReason, type PointerDragSessionOptions, type PointerReorderFinishReason, type PointerReorderSession, type PointerReorderSessionOptions, ResizeHandle, type ResizeHandleProps, ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, SurfacePlaceholder, type SurfacePlaceholderProps, SurfaceRegion, type SurfaceRegionProps, type ThresholdPointerDragFinishReason, type ThresholdPointerDragSession, type ThresholdPointerDragSessionOptions, applyAnchoredResizeDelta, beginAnchoredResize, createPersistentSizeStore, hashSlotHue, installPointerDragSession, installPointerReorderSession, installThresholdPointerDragSession, isSlotDebugEnabled, useDockLayoutControlBinding, useLocalSize, usePersistentSizeStore };
package/dist/react.js CHANGED
@@ -723,6 +723,117 @@ function acquireElementClassLease(element, tokens) {
723
723
  if (records?.size === 0) elementClassLeases.delete(element);
724
724
  };
725
725
  }
726
+ function installThresholdPointerDragSession(options) {
727
+ const threshold = Number.isFinite(options.threshold) ? Math.max(0, options.threshold) : 0;
728
+ let disposed = false;
729
+ let settled = false;
730
+ let settledReason = null;
731
+ let starting = false;
732
+ let active = null;
733
+ let unregisterCancel = null;
734
+ const removeListeners = () => {
735
+ options.target.removeEventListener("pointermove", onPointerMove, true);
736
+ options.target.removeEventListener("pointerup", onPointerFinish, true);
737
+ options.target.removeEventListener("pointercancel", onPointerFinish, true);
738
+ };
739
+ const releaseCancelRegistration = () => {
740
+ const unregister = unregisterCancel;
741
+ unregisterCancel = null;
742
+ try {
743
+ unregister?.();
744
+ } catch {
745
+ }
746
+ };
747
+ const settle = (reason) => {
748
+ if (settled) return;
749
+ settled = true;
750
+ settledReason = reason;
751
+ removeListeners();
752
+ releaseCancelRegistration();
753
+ const current = active;
754
+ active = null;
755
+ if (current !== null) {
756
+ try {
757
+ options.end?.(current, reason);
758
+ } catch {
759
+ }
760
+ }
761
+ try {
762
+ options.onSettled?.(reason, current !== null);
763
+ } catch {
764
+ }
765
+ };
766
+ function onPointerMove(event) {
767
+ if (settled || event.pointerId !== options.pointerId) return;
768
+ if (active === null) {
769
+ if (starting) return;
770
+ if (Math.hypot(event.clientX - options.startX, event.clientY - options.startY) < threshold) return;
771
+ starting = true;
772
+ let candidate;
773
+ try {
774
+ candidate = options.begin(event);
775
+ } catch {
776
+ starting = false;
777
+ settle("error");
778
+ return;
779
+ }
780
+ starting = false;
781
+ if (candidate === null) {
782
+ settle("cancel");
783
+ return;
784
+ }
785
+ if (settled) {
786
+ try {
787
+ options.end?.(candidate, settledReason ?? "dispose");
788
+ } catch {
789
+ }
790
+ return;
791
+ }
792
+ active = candidate;
793
+ }
794
+ const current = active;
795
+ if (current === null) return;
796
+ try {
797
+ options.move(current, event);
798
+ } catch {
799
+ settle("error");
800
+ }
801
+ }
802
+ function onPointerFinish(event) {
803
+ if (settled || event.pointerId !== options.pointerId) return;
804
+ settle(event.type === "pointercancel" ? "pointercancel" : "pointerup");
805
+ }
806
+ options.target.addEventListener("pointermove", onPointerMove, true);
807
+ options.target.addEventListener("pointerup", onPointerFinish, true);
808
+ options.target.addEventListener("pointercancel", onPointerFinish, true);
809
+ if (options.registerCancel) {
810
+ let registered;
811
+ try {
812
+ registered = options.registerCancel(() => settle("cancel"));
813
+ } catch {
814
+ settle("error");
815
+ registered = void 0;
816
+ }
817
+ if (registered) {
818
+ if (settled) {
819
+ try {
820
+ registered();
821
+ } catch {
822
+ }
823
+ } else {
824
+ unregisterCancel = registered;
825
+ }
826
+ }
827
+ }
828
+ return {
829
+ cancel: () => settle("cancel"),
830
+ dispose: () => {
831
+ if (disposed) return;
832
+ disposed = true;
833
+ settle("dispose");
834
+ }
835
+ };
836
+ }
726
837
  function installPointerDragSession(options) {
727
838
  const { element } = options;
728
839
  let disposed = false;
@@ -956,6 +1067,114 @@ function AnchoredResizeHandle({
956
1067
  );
957
1068
  }
958
1069
 
1070
+ // src/pointer-reorder.ts
1071
+ function installPointerReorderSession(options) {
1072
+ const equals = options.equals ?? Object.is;
1073
+ let disposed = false;
1074
+ let generation = 0;
1075
+ let active = null;
1076
+ const publishDragging = (item, reason) => {
1077
+ try {
1078
+ options.onDraggingChange?.(item, reason);
1079
+ } catch {
1080
+ }
1081
+ };
1082
+ const settle = (reason) => {
1083
+ const current = active;
1084
+ if (current === null) return;
1085
+ active = null;
1086
+ if (current.dragging) publishDragging(null, reason);
1087
+ };
1088
+ const onPointerDown = (event) => {
1089
+ if (disposed || event.button !== 0 || event.isPrimary === false) return;
1090
+ let item;
1091
+ try {
1092
+ item = options.resolveItem(event.target);
1093
+ if (item === null || options.canStart?.(event, item) === false) return;
1094
+ } catch {
1095
+ return;
1096
+ }
1097
+ const nextGeneration = ++generation;
1098
+ settle("replacement");
1099
+ if (disposed || generation !== nextGeneration) return;
1100
+ active = {
1101
+ generation: nextGeneration,
1102
+ pointerId: event.pointerId,
1103
+ item,
1104
+ dragging: false,
1105
+ lastOver: null
1106
+ };
1107
+ };
1108
+ const onPointerMove = (event) => {
1109
+ const current = active;
1110
+ if (disposed || current === null || event.pointerId !== current.pointerId) return;
1111
+ if ((event.buttons & 1) !== 1) {
1112
+ settle("buttons-released");
1113
+ return;
1114
+ }
1115
+ let over;
1116
+ try {
1117
+ over = options.resolveItem(event.target);
1118
+ } catch {
1119
+ settle("error");
1120
+ return;
1121
+ }
1122
+ if (active !== current || current.generation !== generation) return;
1123
+ if (over === null) return;
1124
+ let sameItem;
1125
+ try {
1126
+ sameItem = equals(over, current.item);
1127
+ } catch {
1128
+ if (active === current) settle("error");
1129
+ return;
1130
+ }
1131
+ if (active !== current || current.generation !== generation || sameItem) return;
1132
+ if (current.lastOver !== null) {
1133
+ let sameTarget;
1134
+ try {
1135
+ sameTarget = equals(over, current.lastOver);
1136
+ } catch {
1137
+ if (active === current) settle("error");
1138
+ return;
1139
+ }
1140
+ if (active !== current || current.generation !== generation || sameTarget) return;
1141
+ }
1142
+ current.lastOver = over;
1143
+ if (!current.dragging) {
1144
+ current.dragging = true;
1145
+ publishDragging(current.item);
1146
+ if (active !== current || current.generation !== generation) return;
1147
+ }
1148
+ try {
1149
+ options.reorder(current.item, over, event);
1150
+ } catch {
1151
+ settle("error");
1152
+ }
1153
+ };
1154
+ const onPointerFinish = (event) => {
1155
+ const current = active;
1156
+ if (disposed || current === null || event.pointerId !== current.pointerId) return;
1157
+ settle(event.type === "pointercancel" ? "pointercancel" : "pointerup");
1158
+ };
1159
+ options.element.addEventListener("pointerdown", onPointerDown);
1160
+ options.element.addEventListener("pointermove", onPointerMove);
1161
+ options.target.addEventListener("pointerup", onPointerFinish, true);
1162
+ options.target.addEventListener("pointercancel", onPointerFinish, true);
1163
+ return {
1164
+ cancel: () => settle("cancel"),
1165
+ dispose: () => {
1166
+ if (disposed) return;
1167
+ disposed = true;
1168
+ generation += 1;
1169
+ options.element.removeEventListener("pointerdown", onPointerDown);
1170
+ options.element.removeEventListener("pointermove", onPointerMove);
1171
+ options.target.removeEventListener("pointerup", onPointerFinish, true);
1172
+ options.target.removeEventListener("pointercancel", onPointerFinish, true);
1173
+ settle("dispose");
1174
+ }
1175
+ };
1176
+ }
1177
+
959
1178
  // src/panel.tsx
960
1179
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
961
1180
  function PanelEmptyState({
@@ -1324,6 +1543,8 @@ export {
1324
1543
  createPersistentSizeStore,
1325
1544
  hashSlotHue,
1326
1545
  installPointerDragSession,
1546
+ installPointerReorderSession,
1547
+ installThresholdPointerDragSession,
1327
1548
  isSlotDebugEnabled,
1328
1549
  useDockLayoutControlBinding,
1329
1550
  useLocalSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/app-shell",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "description": "Generic Dock, Panel, Window, and Slot composition primitives",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",