@forgeax/app-shell 0.39.1 → 0.40.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
@@ -20,7 +20,25 @@ interface ResizeHandleProps {
20
20
  ariaLabel?: string;
21
21
  title?: string;
22
22
  }
23
+ type AnchoredResizeDirection = 'subtract' | 'add';
24
+ interface AnchoredResizeSession {
25
+ readonly startSize: number;
26
+ readonly direction: AnchoredResizeDirection;
27
+ totalDelta: number;
28
+ }
29
+ /** Capture one unclamped drag origin so overshoot stays pinned until the pointer returns. */
30
+ declare function beginAnchoredResize(startSize: number, direction?: AnchoredResizeDirection): AnchoredResizeSession;
31
+ /** Project incremental pointer travel from the stable drag origin. */
32
+ declare function applyAnchoredResizeDelta(session: AnchoredResizeSession, delta: number): number;
23
33
  declare function ResizeHandle({ orientation, onDrag, onDragStart, onDragEnd, className, ariaLabel, title, }: ResizeHandleProps): react.JSX.Element;
34
+ interface AnchoredResizeHandleProps extends Omit<ResizeHandleProps, 'onDrag' | 'onDragStart' | 'onDragEnd'> {
35
+ readSize: () => number;
36
+ writeSize: (next: number) => void;
37
+ direction?: AnchoredResizeDirection;
38
+ resizingBodyClassName?: string;
39
+ }
40
+ /** Product-neutral anchored resize session over the public pointer carrier. */
41
+ declare function AnchoredResizeHandle({ readSize, writeSize, direction, resizingBodyClassName, ...handleProps }: AnchoredResizeHandleProps): react.JSX.Element;
24
42
 
25
43
  type PanelContentPadding = 'none' | 'sm' | 'md';
26
44
  type PanelContentScroll = 'none' | 'auto';
@@ -163,4 +181,4 @@ interface ShellSlotProps extends HTMLAttributes<HTMLElement> {
163
181
  /** Structural shell marker that preserves the caller's semantic element. */
164
182
  declare function ShellSlot({ as, name, ...props }: ShellSlotProps): ReactElement;
165
183
 
166
- export { 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, ResizeHandle, type ResizeHandleProps, ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, SurfacePlaceholder, type SurfacePlaceholderProps, SurfaceRegion, type SurfaceRegionProps, hashSlotHue, isSlotDebugEnabled, useDockLayoutControlBinding, useLocalSize };
184
+ 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, ResizeHandle, type ResizeHandleProps, ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, SurfacePlaceholder, type SurfacePlaceholderProps, SurfaceRegion, type SurfaceRegionProps, applyAnchoredResizeDelta, beginAnchoredResize, hashSlotHue, isSlotDebugEnabled, useDockLayoutControlBinding, useLocalSize };
package/dist/react.js CHANGED
@@ -624,6 +624,46 @@ function useLocalSize(key, initial, min, max) {
624
624
  };
625
625
  return [value, setValue];
626
626
  }
627
+ var bodyClassLeases = /* @__PURE__ */ new WeakMap();
628
+ function acquireBodyClassLease(body, tokens) {
629
+ const uniqueTokens = [...new Set(tokens)];
630
+ let records = bodyClassLeases.get(body);
631
+ if (!records) {
632
+ records = /* @__PURE__ */ new Map();
633
+ bodyClassLeases.set(body, records);
634
+ }
635
+ for (const token of uniqueTokens) {
636
+ const existing = records.get(token);
637
+ if (existing) {
638
+ existing.count += 1;
639
+ continue;
640
+ }
641
+ const owned = !body.classList.contains(token);
642
+ if (owned) body.classList.add(token);
643
+ records.set(token, { count: 1, owned });
644
+ }
645
+ let active = true;
646
+ return () => {
647
+ if (!active) return;
648
+ active = false;
649
+ for (const token of uniqueTokens) {
650
+ const record = records?.get(token);
651
+ if (!record) continue;
652
+ record.count -= 1;
653
+ if (record.count > 0) continue;
654
+ records?.delete(token);
655
+ if (record.owned) body.classList.remove(token);
656
+ }
657
+ if (records?.size === 0) bodyClassLeases.delete(body);
658
+ };
659
+ }
660
+ function beginAnchoredResize(startSize, direction = "subtract") {
661
+ return { startSize, direction, totalDelta: 0 };
662
+ }
663
+ function applyAnchoredResizeDelta(session, delta) {
664
+ session.totalDelta += delta;
665
+ return session.startSize + (session.direction === "subtract" ? -session.totalDelta : session.totalDelta);
666
+ }
627
667
  function ResizeHandle({
628
668
  orientation,
629
669
  onDrag,
@@ -644,24 +684,47 @@ function ResizeHandle({
644
684
  if (!startRef.current) return;
645
685
  startRef.current = null;
646
686
  resetGlobalDragStyles();
647
- onDragEndRef.current?.();
687
+ try {
688
+ onDragEndRef.current?.();
689
+ } catch {
690
+ }
648
691
  };
649
692
  useEffect2(() => finishDrag, []);
650
693
  const onPointerDown = (event) => {
651
694
  if (startRef.current) return;
652
695
  event.preventDefault();
653
- event.currentTarget.setPointerCapture(event.pointerId);
696
+ try {
697
+ event.currentTarget.setPointerCapture(event.pointerId);
698
+ } catch {
699
+ return;
700
+ }
654
701
  startRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };
655
702
  document.body.style.cursor = orientation === "col" ? "col-resize" : "row-resize";
656
703
  document.body.style.userSelect = "none";
657
- onDragStart?.();
704
+ try {
705
+ onDragStart?.();
706
+ } catch {
707
+ try {
708
+ event.currentTarget.releasePointerCapture(event.pointerId);
709
+ } catch {
710
+ }
711
+ finishDrag();
712
+ }
658
713
  };
659
714
  const onPointerMove = (event) => {
660
715
  if (!startRef.current || startRef.current.pointerId !== event.pointerId) return;
661
716
  const deltaX = event.clientX - startRef.current.x;
662
717
  const deltaY = event.clientY - startRef.current.y;
663
718
  startRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };
664
- onDrag(orientation === "col" ? deltaX : deltaY);
719
+ try {
720
+ onDrag(orientation === "col" ? deltaX : deltaY);
721
+ } catch {
722
+ try {
723
+ event.currentTarget.releasePointerCapture(event.pointerId);
724
+ } catch {
725
+ }
726
+ finishDrag();
727
+ }
665
728
  };
666
729
  const finish = (event) => {
667
730
  if (!startRef.current || startRef.current.pointerId !== event.pointerId) return;
@@ -686,6 +749,58 @@ function ResizeHandle({
686
749
  }
687
750
  );
688
751
  }
752
+ function AnchoredResizeHandle({
753
+ readSize,
754
+ writeSize,
755
+ direction = "subtract",
756
+ resizingBodyClassName,
757
+ ...handleProps
758
+ }) {
759
+ const readSizeRef = useRef(readSize);
760
+ const writeSizeRef = useRef(writeSize);
761
+ const directionRef = useRef(direction);
762
+ const bodyClassNameRef = useRef(resizingBodyClassName);
763
+ readSizeRef.current = readSize;
764
+ writeSizeRef.current = writeSize;
765
+ directionRef.current = direction;
766
+ bodyClassNameRef.current = resizingBodyClassName;
767
+ const sessionRef = useRef(null);
768
+ const finishSession = () => {
769
+ const active = sessionRef.current;
770
+ if (!active) return;
771
+ sessionRef.current = null;
772
+ try {
773
+ active.releaseBodyClass();
774
+ } catch {
775
+ }
776
+ };
777
+ useEffect2(() => finishSession, []);
778
+ const startSession = () => {
779
+ finishSession();
780
+ const bodyClassTokens = bodyClassNameRef.current?.trim().split(/\s+/).filter(Boolean) ?? [];
781
+ const resize = beginAnchoredResize(readSizeRef.current(), directionRef.current);
782
+ const releaseBodyClass = typeof document !== "undefined" && bodyClassTokens.length > 0 ? acquireBodyClassLease(document.body, bodyClassTokens) : () => {
783
+ };
784
+ sessionRef.current = {
785
+ resize,
786
+ releaseBodyClass
787
+ };
788
+ };
789
+ const applyDelta = (delta) => {
790
+ const active = sessionRef.current;
791
+ if (!active) return;
792
+ writeSizeRef.current(applyAnchoredResizeDelta(active.resize, delta));
793
+ };
794
+ return /* @__PURE__ */ jsx2(
795
+ ResizeHandle,
796
+ {
797
+ ...handleProps,
798
+ onDrag: applyDelta,
799
+ onDragStart: startSession,
800
+ onDragEnd: finishSession
801
+ }
802
+ );
803
+ }
689
804
 
690
805
  // src/panel.tsx
691
806
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
@@ -1037,6 +1152,7 @@ function ShellSlot({
1037
1152
  return createElement(as, { ...props, "data-fx-slot": name });
1038
1153
  }
1039
1154
  export {
1155
+ AnchoredResizeHandle,
1040
1156
  DetachedPanelBoundary,
1041
1157
  DetachedSurfaceFrame,
1042
1158
  DetachedSurfaceStatus,
@@ -1049,6 +1165,8 @@ export {
1049
1165
  SlotDebugOverlay,
1050
1166
  SurfacePlaceholder,
1051
1167
  SurfaceRegion,
1168
+ applyAnchoredResizeDelta,
1169
+ beginAnchoredResize,
1052
1170
  hashSlotHue,
1053
1171
  isSlotDebugEnabled,
1054
1172
  useDockLayoutControlBinding,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/app-shell",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
4
4
  "description": "Generic Dock, Panel, Window, and Slot composition primitives",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",