@tensor_1/react 2.45.1 → 2.46.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/README.md CHANGED
@@ -256,6 +256,24 @@ Radial mode still shortens long labels to fit its ring, but every node carries i
256
256
 
257
257
  Ships **ESM + CJS + types**, with per-file `"use client"` so it's safe under React Server Components. Part of the **[UIx v2 styleguide](https://github.com/COx-ORG-H/UIx)**.
258
258
 
259
+ ### Motion: reduced motion, instant dialogs
260
+
261
+ The motion contract lives on the docs Foundations → Motion page and is checked by `check-motion.mjs`. Under
262
+ `prefers-reduced-motion: reduce` the stylesheets drop movement and keep fades, and the loading indicators pulse
263
+ instead of freezing. Motion that your JavaScript drives (a swipe, a scripted scroll, a pan or zoom) reads the same
264
+ preference with `useReducedMotion()` (or `prefersReducedMotion()` in an event handler) and lands on the end state.
265
+
266
+ `Modal instant` opens and closes with no animation, for a keyboard-initiated or many-times-a-day dialog.
267
+ `CommandPalette` marks the dialog or popover it sits in itself, so a palette in a plain `Modal` is instant without
268
+ `data-uix-cmdk-dialog`. `Meter` and `Progress` move on `transform` (they set `--val`, 0–1).
269
+
270
+ ```tsx
271
+ const reduce = useReducedMotion();
272
+ graph.fitView({ animate: !reduce });
273
+
274
+ <Modal open={open} onClose={close} aria-label="Command palette"><CommandPalette>…</CommandPalette></Modal>
275
+ ```
276
+
259
277
  ### Drawer closes on a backdrop click
260
278
 
261
279
  `Drawer` closes on a click on the dimmed backdrop, as well as on Escape and its close button, and calls the same
@@ -18,6 +18,10 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
18
18
  * Modal/Popover and wire search + keyboard selection in the consumer. The input is
19
19
  * a combobox over the listbox of `CommandItem`s; spreads override every default, so
20
20
  * consumers already passing their own roles/labels keep working.
21
+ *
22
+ * ⌘K is keyboard-initiated and used many times a day, so the palette marks the dialog or
23
+ * popover it sits in `[data-uix-instant]`: it opens and closes with no animation, without the
24
+ * consumer knowing the attribute (or the older `data-uix-cmdk-dialog`).
21
25
  */
22
26
  declare function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }: CommandPaletteProps): react.JSX.Element;
23
27
  interface CommandGroupProps {
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { fillLabel } from "../fill-label.js";
6
6
  import { useUixLabels } from "../labels-context.js";
7
7
  const CmdkContext = createContext(null);
8
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
8
9
  function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }) {
9
10
  const uixLabels = useUixLabels();
10
11
  const inputLabel = inputLabelProp ?? uixLabels.commandPalette?.input ?? "Command palette";
@@ -14,6 +15,13 @@ function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resu
14
15
  const resultsTextRef = useRef(resultsText);
15
16
  resultsTextRef.current = resultsText;
16
17
  const listId = useId();
18
+ const rootRef = useRef(null);
19
+ useIsomorphicLayoutEffect(() => {
20
+ const overlay = rootRef.current?.closest("dialog, [popover]");
21
+ if (!overlay || overlay.hasAttribute("data-uix-instant")) return;
22
+ overlay.setAttribute("data-uix-instant", "");
23
+ return () => overlay.removeAttribute("data-uix-instant");
24
+ }, []);
17
25
  const [activeId, setActiveId] = useState(null);
18
26
  const [count, setCount] = useState(0);
19
27
  const [announced, setAnnounced] = useState("");
@@ -29,7 +37,7 @@ function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resu
29
37
  return () => clearTimeout(t);
30
38
  }, [count]);
31
39
  const ctx = useMemo(() => ({ listId, setActive, registerItem }), [listId, setActive, registerItem]);
32
- return /* @__PURE__ */ jsx(CmdkContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cx("uix-cmdk", className), ...props, children: [
40
+ return /* @__PURE__ */ jsx(CmdkContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cx("uix-cmdk", className), ...props, children: [
33
41
  /* @__PURE__ */ jsx(
34
42
  "input",
35
43
  {
@@ -25,7 +25,7 @@ function Meter({ value = 0, tone, label, toneLabels, className, ...props }) {
25
25
  "div",
26
26
  {
27
27
  className: "uix-meter__fill",
28
- style: { width: `${pct}%` },
28
+ style: { "--val": pct / 100 },
29
29
  ...tone && tone !== "success" ? { "data-tone": tone } : {}
30
30
  }
31
31
  )
@@ -28,7 +28,12 @@ interface ModalProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
28
28
  /** Accessible name for the close button (default "Close dialog"). */
29
29
  closeLabel?: string;
30
30
  role?: 'dialog' | 'alertdialog';
31
+ /**
32
+ * Open and close with no animation (`[data-uix-instant]`). For a keyboard-initiated or
33
+ * many-times-a-day dialog. A `CommandPalette` inside sets this on its own.
34
+ */
35
+ instant?: boolean;
31
36
  }
32
- declare function Modal({ open, onClose, dirty, pending, discardLabels, title, children, footer, className, closeLabel: closeLabelProp, role, onKeyDown, ...rest }: ModalProps): react.JSX.Element;
37
+ declare function Modal({ open, onClose, dirty, pending, discardLabels, title, children, footer, className, closeLabel: closeLabelProp, role, instant, onKeyDown, ...rest }: ModalProps): react.JSX.Element;
33
38
 
34
39
  export { Modal, type ModalProps };
@@ -17,6 +17,7 @@ function Modal({
17
17
  className,
18
18
  closeLabel: closeLabelProp,
19
19
  role = "dialog",
20
+ instant = false,
20
21
  onKeyDown,
21
22
  ...rest
22
23
  }) {
@@ -32,6 +33,7 @@ function Modal({
32
33
  className: cx("uix-dialog", className),
33
34
  role,
34
35
  "aria-labelledby": title != null ? titleId : void 0,
36
+ "data-uix-instant": instant ? "" : void 0,
35
37
  ...rest,
36
38
  onKeyDown: (event) => {
37
39
  onKeyDown?.(event);
@@ -13,7 +13,7 @@ function Progress({ value = 0, max = 100, indeterminate, label, className, ...pr
13
13
  "aria-label": label,
14
14
  className: cx("uix-progress", indeterminate && "uix-progress--indeterminate", className),
15
15
  ...props,
16
- children: /* @__PURE__ */ jsx("div", { className: "uix-progress__bar", style: indeterminate ? void 0 : { width: `${pct}%` } })
16
+ children: /* @__PURE__ */ jsx("div", { className: "uix-progress__bar", style: indeterminate ? void 0 : { "--val": pct / 100 } })
17
17
  }
18
18
  );
19
19
  }
@@ -0,0 +1,6 @@
1
+ /** Non-hook read for event handlers and effects. `false` where there is no `matchMedia` (SSR, tests). */
2
+ declare function prefersReducedMotion(): boolean;
3
+ /** True while the user asks for reduced motion; re-renders when the setting changes. `false` on the server. */
4
+ declare function useReducedMotion(): boolean;
5
+
6
+ export { prefersReducedMotion, useReducedMotion };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { useSyncExternalStore } from "react";
3
+ const QUERY = "(prefers-reduced-motion: reduce)";
4
+ function subscribe(onChange) {
5
+ if (typeof matchMedia === "undefined") return () => {
6
+ };
7
+ const mql = matchMedia(QUERY);
8
+ mql.addEventListener("change", onChange);
9
+ return () => mql.removeEventListener("change", onChange);
10
+ }
11
+ function prefersReducedMotion() {
12
+ return typeof matchMedia !== "undefined" && matchMedia(QUERY).matches;
13
+ }
14
+ function useReducedMotion() {
15
+ return useSyncExternalStore(subscribe, prefersReducedMotion, () => false);
16
+ }
17
+ export {
18
+ prefersReducedMotion,
19
+ useReducedMotion
20
+ };