@forgeax/app-shell 0.52.0 → 0.54.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
@@ -197,6 +197,41 @@ interface DockTabStatusSummaryProps extends HTMLAttributes<HTMLSpanElement> {
197
197
  */
198
198
  declare const DockTabStatusSummary: react.ForwardRefExoticComponent<DockTabStatusSummaryProps & react.RefAttributes<HTMLSpanElement>>;
199
199
 
200
+ /** Stable opt-in hook for portalled controls that belong to an interaction. */
201
+ declare const OWNED_INTERACTION_SURFACE_SELECTOR = "[data-fx-interaction-scope]";
202
+ /**
203
+ * Report whether a pointer/focus target belongs to an owned or caller-exempt
204
+ * interaction surface. Each caller selector is isolated so one malformed host
205
+ * selector cannot suppress a later valid match or escape the dismissal guard.
206
+ */
207
+ declare function isDismissExemptInteractionTarget(target: Element | null, additionalSelectors?: readonly string[]): boolean;
208
+
209
+ type OutsideDismissReason = 'pointer' | 'focus' | 'iframe-focus';
210
+ interface OutsideDismissEventTarget {
211
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
212
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
213
+ }
214
+ interface OutsideDismissLifecycleOptions {
215
+ readonly isOpen: () => boolean;
216
+ readonly isPinned: () => boolean;
217
+ readonly containsTarget: (target: Element) => boolean;
218
+ readonly isDismissExempt: (target: Element | null) => boolean;
219
+ readonly onContainedPointerDown?: (target: Element) => void;
220
+ readonly dismiss: (reason: OutsideDismissReason) => void;
221
+ readonly documentTarget?: OutsideDismissEventTarget;
222
+ readonly windowTarget?: OutsideDismissEventTarget;
223
+ readonly getActiveElement?: () => Element | null;
224
+ readonly requestFrame?: (callback: FrameRequestCallback) => number;
225
+ readonly cancelFrame?: (handle: number) => void;
226
+ }
227
+ /**
228
+ * Own the browser listener lifecycle for outside pointer/focus dismissal.
229
+ * Products inject live state, containment, exemptions and the close operation;
230
+ * App Shell only balances listeners, coalesces deferred iframe focus, and fences
231
+ * callbacks after disposal.
232
+ */
233
+ declare function installOutsideDismissLifecycle(options: OutsideDismissLifecycleOptions): () => void;
234
+
200
235
  type PanelContentPadding = 'none' | 'sm' | 'md';
201
236
  type PanelContentScroll = 'none' | 'auto';
202
237
  type PanelContentTone = 'default' | 'surface' | 'tool';
@@ -338,4 +373,4 @@ interface ShellSlotProps extends HTMLAttributes<HTMLElement> {
338
373
  /** Structural shell marker that preserves the caller's semantic element. */
339
374
  declare function ShellSlot({ as, name, ...props }: ShellSlotProps): ReactElement;
340
375
 
341
- 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, DockTabAction, type DockTabActionProps, DockTabFrame, type DockTabFrameProps, DockTabIcon, type DockTabIconProps, DockTabStatusSummary, type DockTabStatusSummaryItem, type DockTabStatusSummaryProps, type DockTabStatusTone, DockTabTitle, type DockTabTitleProps, FloatingMenu, type FloatingMenuAnchor, type FloatingMenuPoint, type FloatingMenuProps, type LiveTabPlacement, type LiveTabPlacementSource, type LiveTabTitleSource, 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 TabCloseInteractions, type TabCloseInteractionsOptions, type TabPinnedStateSource, type ThresholdPointerDragFinishReason, type ThresholdPointerDragSession, type ThresholdPointerDragSessionOptions, applyAnchoredResizeDelta, beginAnchoredResize, createPersistentSizeStore, hashSlotHue, installPointerDragSession, installPointerReorderSession, installThresholdPointerDragSession, isSlotDebugEnabled, useDockLayoutControlBinding, useLiveTabPlacement, useLiveTabTitle, useLocalSize, usePersistentSizeStore, useTabCloseInteractions, useTabPinnedState };
376
+ 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, DockTabAction, type DockTabActionProps, DockTabFrame, type DockTabFrameProps, DockTabIcon, type DockTabIconProps, DockTabStatusSummary, type DockTabStatusSummaryItem, type DockTabStatusSummaryProps, type DockTabStatusTone, DockTabTitle, type DockTabTitleProps, FloatingMenu, type FloatingMenuAnchor, type FloatingMenuPoint, type FloatingMenuProps, type LiveTabPlacement, type LiveTabPlacementSource, type LiveTabTitleSource, OWNED_INTERACTION_SURFACE_SELECTOR, type OutsideDismissEventTarget, type OutsideDismissLifecycleOptions, type OutsideDismissReason, 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 TabCloseInteractions, type TabCloseInteractionsOptions, type TabPinnedStateSource, type ThresholdPointerDragFinishReason, type ThresholdPointerDragSession, type ThresholdPointerDragSessionOptions, applyAnchoredResizeDelta, beginAnchoredResize, createPersistentSizeStore, hashSlotHue, installOutsideDismissLifecycle, installPointerDragSession, installPointerReorderSession, installThresholdPointerDragSession, isDismissExemptInteractionTarget, isSlotDebugEnabled, useDockLayoutControlBinding, useLiveTabPlacement, useLiveTabTitle, useLocalSize, usePersistentSizeStore, useTabCloseInteractions, useTabPinnedState };
package/dist/react.js CHANGED
@@ -1544,6 +1544,170 @@ var DockTabStatusSummary = forwardRef2(function DockTabStatusSummary2({ classNam
1544
1544
  });
1545
1545
  DockTabStatusSummary.displayName = "DockTabStatusSummary";
1546
1546
 
1547
+ // src/dismiss-exemption.ts
1548
+ var OWNED_INTERACTION_SURFACE_SELECTOR = "[data-fx-interaction-scope]";
1549
+ function isDismissExemptInteractionTarget(target, additionalSelectors = []) {
1550
+ if (!target) return false;
1551
+ for (const selector of [OWNED_INTERACTION_SURFACE_SELECTOR, ...additionalSelectors]) {
1552
+ try {
1553
+ if (target.closest(selector) !== null) return true;
1554
+ } catch {
1555
+ }
1556
+ }
1557
+ return false;
1558
+ }
1559
+
1560
+ // src/outside-dismiss.ts
1561
+ function installOutsideDismissLifecycle(options) {
1562
+ const documentTarget = options.documentTarget ?? (typeof document === "undefined" ? void 0 : document);
1563
+ const windowTarget = options.windowTarget ?? (typeof window === "undefined" ? void 0 : window);
1564
+ const getActiveElement = options.getActiveElement ?? (() => typeof document === "undefined" ? null : document.activeElement);
1565
+ const requestFrame = options.requestFrame ?? (typeof requestAnimationFrame === "undefined" ? void 0 : requestAnimationFrame.bind(globalThis));
1566
+ const cancelFrame = options.cancelFrame ?? (typeof cancelAnimationFrame === "undefined" ? void 0 : cancelAnimationFrame.bind(globalThis));
1567
+ if (!documentTarget || !windowTarget) return () => {
1568
+ };
1569
+ let disposed = false;
1570
+ let dismissRequested = false;
1571
+ let frameHandle = null;
1572
+ let frameGeneration = 0;
1573
+ const removeListener = (target, type, listener, options2) => {
1574
+ try {
1575
+ target.removeEventListener(type, listener, options2);
1576
+ } catch {
1577
+ }
1578
+ };
1579
+ const disposition = (target) => {
1580
+ if (disposed) return "inactive";
1581
+ let open;
1582
+ try {
1583
+ open = options.isOpen();
1584
+ } catch {
1585
+ return "inactive";
1586
+ }
1587
+ if (!open) {
1588
+ dismissRequested = false;
1589
+ return "inactive";
1590
+ }
1591
+ if (dismissRequested) return "inactive";
1592
+ try {
1593
+ if (options.isPinned()) return "inactive";
1594
+ } catch {
1595
+ return "inactive";
1596
+ }
1597
+ if (target) {
1598
+ try {
1599
+ if (options.containsTarget(target)) return "contained";
1600
+ } catch {
1601
+ return "inactive";
1602
+ }
1603
+ }
1604
+ try {
1605
+ if (options.isDismissExempt(target)) return "exempt";
1606
+ } catch {
1607
+ return "inactive";
1608
+ }
1609
+ return "outside";
1610
+ };
1611
+ const requestDismiss = (reason) => {
1612
+ if (dismissRequested || disposed) return;
1613
+ dismissRequested = true;
1614
+ try {
1615
+ options.dismiss(reason);
1616
+ } catch {
1617
+ dismissRequested = false;
1618
+ }
1619
+ };
1620
+ const onPointerDown = (event) => {
1621
+ const target = event.target;
1622
+ const result = disposition(target);
1623
+ if (result === "contained" && target) {
1624
+ try {
1625
+ options.onContainedPointerDown?.(target);
1626
+ } catch {
1627
+ }
1628
+ return;
1629
+ }
1630
+ if (result === "outside") requestDismiss("pointer");
1631
+ };
1632
+ const onFocusIn = (event) => {
1633
+ if (disposition(event.target) === "outside") {
1634
+ requestDismiss("focus");
1635
+ }
1636
+ };
1637
+ const onWindowBlur = () => {
1638
+ if (disposed || frameHandle !== null || !requestFrame) return;
1639
+ let open;
1640
+ try {
1641
+ open = options.isOpen();
1642
+ } catch {
1643
+ return;
1644
+ }
1645
+ if (!open) {
1646
+ dismissRequested = false;
1647
+ return;
1648
+ }
1649
+ const generation = ++frameGeneration;
1650
+ let callbackRan = false;
1651
+ let handle;
1652
+ try {
1653
+ handle = requestFrame(() => {
1654
+ callbackRan = true;
1655
+ if (disposed || generation !== frameGeneration) return;
1656
+ frameHandle = null;
1657
+ let active;
1658
+ try {
1659
+ active = getActiveElement();
1660
+ } catch {
1661
+ return;
1662
+ }
1663
+ if (!active || active.tagName !== "IFRAME") return;
1664
+ if (disposition(active) === "outside") requestDismiss("iframe-focus");
1665
+ });
1666
+ } catch {
1667
+ return;
1668
+ }
1669
+ if (callbackRan || disposed || generation !== frameGeneration) {
1670
+ try {
1671
+ cancelFrame?.(handle);
1672
+ } catch {
1673
+ }
1674
+ return;
1675
+ }
1676
+ frameHandle = handle;
1677
+ };
1678
+ let pointerRegistered = false;
1679
+ let focusRegistered = false;
1680
+ let blurRegistered = false;
1681
+ try {
1682
+ documentTarget.addEventListener("pointerdown", onPointerDown, true);
1683
+ pointerRegistered = true;
1684
+ documentTarget.addEventListener("focusin", onFocusIn);
1685
+ focusRegistered = true;
1686
+ windowTarget.addEventListener("blur", onWindowBlur);
1687
+ blurRegistered = true;
1688
+ } catch {
1689
+ if (pointerRegistered) removeListener(documentTarget, "pointerdown", onPointerDown, true);
1690
+ if (focusRegistered) removeListener(documentTarget, "focusin", onFocusIn);
1691
+ if (blurRegistered) removeListener(windowTarget, "blur", onWindowBlur);
1692
+ disposed = true;
1693
+ }
1694
+ return () => {
1695
+ if (disposed) return;
1696
+ disposed = true;
1697
+ frameGeneration++;
1698
+ if (frameHandle !== null) {
1699
+ try {
1700
+ cancelFrame?.(frameHandle);
1701
+ } catch {
1702
+ }
1703
+ frameHandle = null;
1704
+ }
1705
+ if (pointerRegistered) removeListener(documentTarget, "pointerdown", onPointerDown, true);
1706
+ if (focusRegistered) removeListener(documentTarget, "focusin", onFocusIn);
1707
+ if (blurRegistered) removeListener(windowTarget, "blur", onWindowBlur);
1708
+ };
1709
+ }
1710
+
1547
1711
  // src/panel.tsx
1548
1712
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1549
1713
  function PanelEmptyState({
@@ -1905,6 +2069,7 @@ export {
1905
2069
  DockTabStatusSummary,
1906
2070
  DockTabTitle,
1907
2071
  FloatingMenu,
2072
+ OWNED_INTERACTION_SURFACE_SELECTOR,
1908
2073
  PanelEmptyState,
1909
2074
  PanelSurface,
1910
2075
  ResizeHandle,
@@ -1916,9 +2081,11 @@ export {
1916
2081
  beginAnchoredResize,
1917
2082
  createPersistentSizeStore,
1918
2083
  hashSlotHue,
2084
+ installOutsideDismissLifecycle,
1919
2085
  installPointerDragSession,
1920
2086
  installPointerReorderSession,
1921
2087
  installThresholdPointerDragSession,
2088
+ isDismissExemptInteractionTarget,
1922
2089
  isSlotDebugEnabled,
1923
2090
  useDockLayoutControlBinding,
1924
2091
  useLiveTabPlacement,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/app-shell",
3
- "version": "0.52.0",
3
+ "version": "0.54.0",
4
4
  "description": "Generic Dock, Panel, Window, and Slot composition primitives",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",