@ai-matrx/design-system 0.37.1 → 0.38.1

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/index.d.cts CHANGED
@@ -792,6 +792,20 @@ declare function CreatablePicker({ value, options, onSelect, placeholder, search
792
792
  * `aria-describedby` is dropped when there is no description rather than
793
793
  * pointing at nothing.
794
794
  *
795
+ * 6. ON DESKTOP A DIALOG IS A WINDOW, NOT A WALL (0.38.0, Arman 2026-09-23:
796
+ * "none of the built-in page ai integrations are accessible because the
797
+ * page is totally blocked"). Unless the caller passes `modal`, a desktop
798
+ * dialog does not block the page: no overlay, the page, the Agents menu,
799
+ * the assist dock and right-click AI stay usable beside it. It behaves like
800
+ * a window: a click outside never closes it (`dismissOnOutsideClick` opts a
801
+ * quick picker back in), Escape closes it only while focus is inside it
802
+ * (Escape in the page belongs to the page), and its `DialogHeader` drags
803
+ * it, clamped on screen, double-click to recenter. On a phone it stays the
804
+ * blocking bottom sheet — nothing useful sits behind a sheet. Confirmations
805
+ * are `AlertDialog`, a different component, and stay blocking. This is a
806
+ * LIGHT window: the host's heavy window manager (tray, persistence) is a
807
+ * separate system and must not be pulled into every dialog.
808
+ *
795
809
  * SEAM INVERSIONS. The host original resolved its portal target through
796
810
  * app-shaped hooks (a popped-out window-panel body). Here that is the
797
811
  * package's `PortalContainerProvider` seam, with an explicit `container` prop
@@ -828,6 +842,13 @@ declare const DialogOverlay: React.ForwardRefExoticComponent<Omit<DialogPrimitiv
828
842
  */
829
843
  declare const DialogContentPrimitive: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
830
844
  interface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
845
+ /**
846
+ * Windowed (non-blocking) dialogs never close from a click outside them —
847
+ * the person is reaching for the page or an AI control beside the window.
848
+ * Pass `true` for a quick picker that should close when the person clicks
849
+ * away. Ignored on a blocking dialog, which keeps Radix's behaviour.
850
+ */
851
+ dismissOnOutsideClick?: boolean;
831
852
  /** Render as a bottom sheet below the mobile breakpoint. Default `true`. */
832
853
  mobileSheet?: boolean;
833
854
  /** Render the built-in close control. Default `true`. */
package/dist/index.d.ts CHANGED
@@ -792,6 +792,20 @@ declare function CreatablePicker({ value, options, onSelect, placeholder, search
792
792
  * `aria-describedby` is dropped when there is no description rather than
793
793
  * pointing at nothing.
794
794
  *
795
+ * 6. ON DESKTOP A DIALOG IS A WINDOW, NOT A WALL (0.38.0, Arman 2026-09-23:
796
+ * "none of the built-in page ai integrations are accessible because the
797
+ * page is totally blocked"). Unless the caller passes `modal`, a desktop
798
+ * dialog does not block the page: no overlay, the page, the Agents menu,
799
+ * the assist dock and right-click AI stay usable beside it. It behaves like
800
+ * a window: a click outside never closes it (`dismissOnOutsideClick` opts a
801
+ * quick picker back in), Escape closes it only while focus is inside it
802
+ * (Escape in the page belongs to the page), and its `DialogHeader` drags
803
+ * it, clamped on screen, double-click to recenter. On a phone it stays the
804
+ * blocking bottom sheet — nothing useful sits behind a sheet. Confirmations
805
+ * are `AlertDialog`, a different component, and stay blocking. This is a
806
+ * LIGHT window: the host's heavy window manager (tray, persistence) is a
807
+ * separate system and must not be pulled into every dialog.
808
+ *
795
809
  * SEAM INVERSIONS. The host original resolved its portal target through
796
810
  * app-shaped hooks (a popped-out window-panel body). Here that is the
797
811
  * package's `PortalContainerProvider` seam, with an explicit `container` prop
@@ -828,6 +842,13 @@ declare const DialogOverlay: React.ForwardRefExoticComponent<Omit<DialogPrimitiv
828
842
  */
829
843
  declare const DialogContentPrimitive: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
830
844
  interface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
845
+ /**
846
+ * Windowed (non-blocking) dialogs never close from a click outside them —
847
+ * the person is reaching for the page or an AI control beside the window.
848
+ * Pass `true` for a quick picker that should close when the person clicks
849
+ * away. Ignored on a blocking dialog, which keeps Radix's behaviour.
850
+ */
851
+ dismissOnOutsideClick?: boolean;
831
852
  /** Render as a bottom sheet below the mobile breakpoint. Default `true`. */
832
853
  mobileSheet?: boolean;
833
854
  /** Render the built-in close control. Default `true`. */
package/dist/index.js CHANGED
@@ -1873,12 +1873,14 @@ import * as VisuallyHidden2 from "@radix-ui/react-visually-hidden";
1873
1873
  import { treeContainsComponent as treeContainsComponent3 } from "@ai-matrx/kit/react-tree";
1874
1874
  import * as React17 from "react";
1875
1875
  import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
1876
+ var DialogWindowedByDefaultContext = React17.createContext(false);
1876
1877
  var Dialog = ({
1877
1878
  children,
1878
1879
  ...props
1879
1880
  }) => {
1880
- const modal = props.modal ?? true;
1881
- return /* @__PURE__ */ jsx21(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ jsx21(DialogPrimitive2.Root, { ...props, modal, children }) });
1881
+ const isMobile = useIsMobile();
1882
+ const modal = props.modal ?? isMobile;
1883
+ return /* @__PURE__ */ jsx21(DialogWindowedByDefaultContext.Provider, { value: props.modal === void 0, children: /* @__PURE__ */ jsx21(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ jsx21(DialogPrimitive2.Root, { ...props, modal, children }) }) });
1882
1884
  };
1883
1885
  Dialog.displayName = "Dialog";
1884
1886
  var DialogTrigger = DialogPrimitive2.Trigger;
@@ -1926,6 +1928,11 @@ DialogContentPrimitive.displayName = "DialogContentPrimitive";
1926
1928
  var DIALOG_DESKTOP_CLASSES2 = "matrx-dialog-card fixed inset-0 z-[10000] m-auto grid h-fit min-w-0 w-full max-w-lg gap-4 max-h-[85dvh] overflow-y-auto overscroll-contain [--dialog-pad:1.5rem] overflow-x-clip border bg-background p-6 shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full sm:rounded-lg";
1927
1929
  var DIALOG_MOBILE_SHEET_CLASSES2 = "matrx-mobile-sheet matrx-dialog-card fixed inset-x-0 bottom-0 left-0 right-0 top-auto z-[10000] flex min-w-0 flex-col w-full max-w-full max-h-[90dvh] gap-4 [--dialog-pad:1rem] overflow-x-clip border-t bg-background p-4 pb-safe shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full rounded-t-2xl rounded-b-none overflow-y-auto overscroll-contain";
1928
1930
  var DIALOG_MOBILE_SHEET_OVERRIDE2 = "inset-x-0 bottom-0 left-0 right-0 top-auto translate-none w-full max-w-full max-h-[90dvh] rounded-b-none rounded-t-2xl overflow-y-auto";
1931
+ var DialogWindowHandleContext = React17.createContext(null);
1932
+ var WINDOW_KEEP_VISIBLE = 80;
1933
+ function isInteractiveTarget(target) {
1934
+ return target instanceof Element && Boolean(target.closest("button, a, input, textarea, select, [role='button'], [contenteditable='true']"));
1935
+ }
1929
1936
  var DialogContent = React17.forwardRef(
1930
1937
  ({
1931
1938
  className,
@@ -1934,15 +1941,51 @@ var DialogContent = React17.forwardRef(
1934
1941
  animated = true,
1935
1942
  showCloseButton = true,
1936
1943
  container,
1944
+ dismissOnOutsideClick = false,
1945
+ onInteractOutside,
1946
+ onEscapeKeyDown,
1947
+ style,
1937
1948
  ...props
1938
1949
  }, ref) => {
1939
1950
  const isMobile = useIsMobile();
1940
1951
  const isModal = useRadixDialogModal();
1941
1952
  const injectedContainer = usePortalContainer(container);
1942
1953
  const asSheet = mobileSheet && isMobile;
1954
+ const windowed = !isModal && !asSheet;
1955
+ const windowedByDefault = React17.useContext(DialogWindowedByDefaultContext);
1943
1956
  const [containerEl, setContainerEl] = React17.useState(
1944
1957
  null
1945
1958
  );
1959
+ const [offset, setOffset] = React17.useState({ x: 0, y: 0 });
1960
+ const dragRef = React17.useRef(null);
1961
+ const handle = React17.useMemo(() => {
1962
+ if (!windowed) return null;
1963
+ const clamp = (value, limit) => Math.max(-limit, Math.min(limit, value));
1964
+ return {
1965
+ onPointerDown: (event) => {
1966
+ if (event.button !== 0 || isInteractiveTarget(event.target)) return;
1967
+ dragRef.current = { x: event.clientX, y: event.clientY, baseX: offset.x, baseY: offset.y };
1968
+ event.currentTarget.setPointerCapture?.(event.pointerId);
1969
+ },
1970
+ onPointerMove: (event) => {
1971
+ const start = dragRef.current;
1972
+ if (!start) return;
1973
+ setOffset({
1974
+ x: clamp(start.baseX + event.clientX - start.x, window.innerWidth / 2 - WINDOW_KEEP_VISIBLE / 2),
1975
+ y: clamp(start.baseY + event.clientY - start.y, window.innerHeight / 2 - WINDOW_KEEP_VISIBLE / 2)
1976
+ });
1977
+ },
1978
+ onPointerUp: (event) => {
1979
+ if (!dragRef.current) return;
1980
+ dragRef.current = null;
1981
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
1982
+ },
1983
+ onPointerCancel: () => {
1984
+ dragRef.current = null;
1985
+ },
1986
+ onDoubleClick: () => setOffset({ x: 0, y: 0 })
1987
+ };
1988
+ }, [windowed, offset.x, offset.y]);
1946
1989
  const mergedRef = React17.useCallback(
1947
1990
  (node) => {
1948
1991
  setContainerEl(node);
@@ -1970,19 +2013,33 @@ var DialogContent = React17.forwardRef(
1970
2013
  // can sit beside a host's floating window manager (which starts at
1971
2014
  // z=1000); staying below that boundary makes a newly focused window
1972
2015
  // usable without coupling the two systems.
1973
- !isModal && "z-[900]"
2016
+ !isModal && !windowedByDefault && "z-[900]",
2017
+ // Without an overlay the card needs its own separation from the page.
2018
+ windowed && "shadow-2xl ring-1 ring-border/60"
1974
2019
  ),
2020
+ style: windowed && (offset.x !== 0 || offset.y !== 0) ? { ...style, translate: `${offset.x}px ${offset.y}px` } : style,
2021
+ onInteractOutside: (event) => {
2022
+ onInteractOutside?.(event);
2023
+ if (windowed && !dismissOnOutsideClick) event.preventDefault();
2024
+ },
2025
+ onEscapeKeyDown: (event) => {
2026
+ onEscapeKeyDown?.(event);
2027
+ const pressedIn = event.target instanceof Node && event.target !== document.body ? event.target : document.activeElement;
2028
+ if (windowed && containerEl && !(pressedIn && containerEl.contains(pressedIn))) {
2029
+ event.preventDefault();
2030
+ }
2031
+ },
1975
2032
  ...hasDescription ? {} : { "aria-describedby": void 0 },
1976
2033
  ...props,
1977
2034
  children: [
1978
2035
  hasTitle ? null : /* @__PURE__ */ jsx21(VisuallyHidden2.Root, { children: /* @__PURE__ */ jsx21(DialogPrimitive2.Title, { children: "Dialog" }) }),
1979
- /* @__PURE__ */ jsx21(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ jsx21(
2036
+ /* @__PURE__ */ jsx21(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ jsx21(DialogWindowHandleContext.Provider, { value: handle, children: /* @__PURE__ */ jsx21(
1980
2037
  PortalContainerProvider,
1981
2038
  {
1982
2039
  container: containerEl ?? injectedContainer ?? null,
1983
2040
  children
1984
2041
  }
1985
- ) }),
2042
+ ) }) }),
1986
2043
  showCloseButton ? /* @__PURE__ */ jsxs10(
1987
2044
  DialogPrimitive2.Close,
1988
2045
  {
@@ -2005,19 +2062,28 @@ DialogContent.displayName = DialogPrimitive2.Content.displayName;
2005
2062
  var DialogHeader = ({
2006
2063
  className,
2007
2064
  ...props
2008
- }) => /* @__PURE__ */ jsx21(
2009
- "div",
2010
- {
2011
- "data-slot": "dialog-header",
2012
- className: cn(
2013
- // DialogContent owns an absolute close control. Reserve its hit area in
2014
- // every header so trailing actions never render underneath it.
2015
- "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
2016
- className
2017
- ),
2018
- ...props
2019
- }
2020
- );
2065
+ }) => {
2066
+ const handle = React17.useContext(DialogWindowHandleContext);
2067
+ return /* @__PURE__ */ jsx21(
2068
+ "div",
2069
+ {
2070
+ "data-slot": "dialog-header",
2071
+ ...handle ? {
2072
+ ...handle,
2073
+ "data-window-handle": "",
2074
+ title: "Drag to move \xB7 double-click to recenter"
2075
+ } : {},
2076
+ className: cn(
2077
+ // DialogContent owns an absolute close control. Reserve its hit area in
2078
+ // every header so trailing actions never render underneath it.
2079
+ "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
2080
+ handle && "cursor-grab select-none active:cursor-grabbing",
2081
+ className
2082
+ ),
2083
+ ...props
2084
+ }
2085
+ );
2086
+ };
2021
2087
  DialogHeader.displayName = "DialogHeader";
2022
2088
  var DialogFooter = ({
2023
2089
  className,
@@ -2207,7 +2273,7 @@ var DropdownMenuShortcut = ({
2207
2273
  DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
2208
2274
 
2209
2275
  // src/editable-label.tsx
2210
- import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState4 } from "react";
2276
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
2211
2277
  import { jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
2212
2278
  function EditableLabel({
2213
2279
  value,
@@ -2233,7 +2299,7 @@ function EditableLabel({
2233
2299
  const [draft, setDraft] = useState4(value);
2234
2300
  const [error, setError] = useState4(null);
2235
2301
  const [busy, setBusy] = useState4(false);
2236
- const inputRef = useRef2(null);
2302
+ const inputRef = useRef3(null);
2237
2303
  useEffect2(() => {
2238
2304
  if (!editing) setDraft(value);
2239
2305
  }, [value, editing]);
@@ -2514,7 +2580,7 @@ var Label3 = React20.forwardRef(({ className, ...props }, ref) => /* @__PURE__ *
2514
2580
  Label3.displayName = LabelPrimitive.Root.displayName;
2515
2581
 
2516
2582
  // src/overflow-toolbar.tsx
2517
- import { useLayoutEffect, useMemo, useRef as useRef3, useState as useState5 } from "react";
2583
+ import { useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
2518
2584
  import { Fragment, jsx as jsx26, jsxs as jsxs14 } from "react/jsx-runtime";
2519
2585
  var GAP_PX = 6;
2520
2586
  var TONE = {
@@ -2596,13 +2662,13 @@ function OverflowToolbar({
2596
2662
  renderOverflowMenu,
2597
2663
  renderTooltip
2598
2664
  }) {
2599
- const visibleActions = useMemo(
2665
+ const visibleActions = useMemo2(
2600
2666
  () => actions.filter((a) => !a.hidden),
2601
2667
  [actions]
2602
2668
  );
2603
- const containerRef = useRef3(null);
2604
- const ghostRef = useRef3(null);
2605
- const leadingRef = useRef3(null);
2669
+ const containerRef = useRef4(null);
2670
+ const ghostRef = useRef4(null);
2671
+ const leadingRef = useRef4(null);
2606
2672
  const [visibleCount, setVisibleCount] = useState5(visibleActions.length);
2607
2673
  const signature = visibleActions.map(
2608
2674
  (a) => `${a.id}:${a.hideLabel ? 1 : 0}:${a.running ? 1 : 0}:${a.runningLabel ?? ""}:${a.label}`
@@ -4005,15 +4071,15 @@ var TextareaWithPrefix = React32.forwardRef(
4005
4071
  TextareaWithPrefix.displayName = "TextareaWithPrefix";
4006
4072
 
4007
4073
  // src/use-scroll-fade.ts
4008
- import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState8 } from "react";
4074
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef6, useState as useState8 } from "react";
4009
4075
  var EPSILON = 2;
4010
4076
  function useScrollFade() {
4011
4077
  const [state, setState] = useState8({
4012
4078
  top: false,
4013
4079
  bottom: false
4014
4080
  });
4015
- const nodeRef = useRef5(null);
4016
- const cleanupRef = useRef5(null);
4081
+ const nodeRef = useRef6(null);
4082
+ const cleanupRef = useRef6(null);
4017
4083
  const measure = useCallback5(() => {
4018
4084
  const el = nodeRef.current;
4019
4085
  if (!el) return;