@enigmax/primitives 0.10.0 → 0.11.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.
@@ -172,7 +172,7 @@ function useButton(options = {}) {
172
172
  event.preventDefault();
173
173
  return;
174
174
  }
175
- void instance.press();
175
+ void instance.press(event);
176
176
  },
177
177
  "aria-disabled": !state.available,
178
178
  "aria-busy": state.loading,
@@ -202,11 +202,12 @@ var Button = forwardRef(function Button2({
202
202
  cooldown,
203
203
  shortcut,
204
204
  onPress,
205
+ onClick,
205
206
  onChange,
206
207
  type = "button",
207
208
  ...rest
208
209
  }, ref) {
209
- const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress, onChange });
210
+ const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress: onPress ?? onClick, onChange });
210
211
  const state = button;
211
212
  const Tag = as ?? (state.element === "a" ? getLinkComponent() : "button");
212
213
  const native = Tag === "button" ? { type } : {};
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { Button } from '../chunk-L4KA342C.js';
3
- export { Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../chunk-L4KA342C.js';
2
+ import { Button } from '../chunk-KTW4GO43.js';
3
+ export { Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../chunk-KTW4GO43.js';
4
4
  export { estimatePasswordStrength, generatePassword } from '../chunk-42Y7OOJM.js';
5
5
  import '../chunk-UZFEEFMF.js';
6
6
  import Link from 'next/link';
@@ -18,6 +18,16 @@ export { F as FuseConstructor } from '../search-CsO3L1Lw.js';
18
18
  * ordinary one means writing the same six lines at every call site, which is how they drift.
19
19
  */
20
20
  interface ButtonProps extends ButtonOptions, Omit<ComponentPropsWithoutRef<"button">, "onClick" | "onChange" | "children" | "type"> {
21
+ /**
22
+ * What to run when it is pressed. The same thing as `onPress`, under the name React
23
+ * already uses - write whichever reads better.
24
+ *
25
+ * It is an ALIAS, not a second handler: the component owns the element's real onClick,
26
+ * because that is where a press is refused while the button is loading, disabled or
27
+ * cooling down. Passing one through would replace that and silently take the behaviour
28
+ * with it. Returning a promise drives `loading` for its duration, same as `onPress`.
29
+ */
30
+ onClick?: ButtonOptions["onPress"];
21
31
  /**
22
32
  * Override what to render. You rarely need it: an href already renders whatever
23
33
  * `setLinkComponent` registered - your router's Link - and a button otherwise.
@@ -133,7 +143,7 @@ declare function useSearch<T>(options?: SearchOptions<T>): UseSearchResult<T>;
133
143
  interface UseButtonResult extends ButtonState {
134
144
  /** Spread onto the element named by `element`. */
135
145
  props: {
136
- onClick: (event: {
146
+ onClick: (event: Event | {
137
147
  preventDefault(): void;
138
148
  }) => void;
139
149
  "aria-disabled": boolean;
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- export { Button, Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../chunk-L4KA342C.js';
2
+ export { Button, Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../chunk-KTW4GO43.js';
3
3
  export { estimatePasswordStrength, generatePassword } from '../chunk-42Y7OOJM.js';
4
4
  import '../chunk-UZFEEFMF.js';
@@ -0,0 +1,9 @@
1
+ import * as react from 'react';
2
+ import { ButtonProps } from '../react/index.js';
3
+ export { BreachChecker, BreachState, BreachStatus, FieldAction, Input, InputProps, PasswordStrength, PasswordStrengthProps, UseButtonResult, UseInputResult, UseMarqueeResult, UseSearchResult, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../react/index.js';
4
+ export { ButtonOptions, ButtonState, EstimateOptions, GeneratePasswordOptions, InputAction, InputIcon, InputOptions, MarqueeHover, MarqueeInstance, MarqueeOptions, PasswordScore, PasswordStrengthReport, estimatePasswordStrength, generatePassword } from '../index.js';
5
+ export { F as FuseConstructor, b as SearchMatch, c as SearchOptions } from '../search-CsO3L1Lw.js';
6
+
7
+ declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLElement>>;
8
+
9
+ export { Button, ButtonProps };
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { Button } from '../chunk-KTW4GO43.js';
3
+ export { Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch } from '../chunk-KTW4GO43.js';
4
+ export { estimatePasswordStrength, generatePassword } from '../chunk-42Y7OOJM.js';
5
+ import '../chunk-UZFEEFMF.js';
6
+ import { Link } from 'react-router';
7
+ import { forwardRef, createElement } from 'react';
8
+
9
+ var RouterLink = forwardRef(function RouterLink2({ href = "", ...props }, ref) {
10
+ return createElement(Link, { ...props, to: href, ref });
11
+ });
12
+ var Button2 = forwardRef(function Button3(props, ref) {
13
+ return createElement(Button, { ...props, as: props.as ?? (props.href ? RouterLink : void 0), ref });
14
+ });
15
+
16
+ export { Button2 as Button };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/primitives",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Headless interaction primitives: the behaviour, the timing and the accessibility of components like a draggable infinite marquee, with no styles of their own. Framework-agnostic core plus thin adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,13 +22,17 @@
22
22
  "./next": {
23
23
  "types": "./dist/next/index.d.ts",
24
24
  "import": "./dist/next/index.js"
25
+ },
26
+ "./react-router": {
27
+ "types": "./dist/react-router/index.d.ts",
28
+ "import": "./dist/react-router/index.js"
25
29
  }
26
30
  },
27
31
  "scripts": {
28
32
  "build": "tsup",
29
33
  "typecheck": "tsc --noEmit",
30
34
  "test": "npm run test:fixtures && playwright test",
31
- "test:fixtures": "esbuild test/fixture/react.tsx --bundle --format=esm --jsx=automatic --outfile=test/fixture/build/react.js --tsconfig=tsconfig.json --alias:next/link=./test/fixture/next-link-stub.tsx",
35
+ "test:fixtures": "esbuild test/fixture/react.tsx --bundle --format=esm --jsx=automatic --outfile=test/fixture/build/react.js --tsconfig=tsconfig.json --alias:next/link=./test/fixture/next-link-stub.tsx --alias:react-router=./test/fixture/react-router-stub.tsx",
32
36
  "test:install": "playwright install chromium",
33
37
  "prepublishOnly": "npm run typecheck && npm run build"
34
38
  },
@@ -42,8 +46,9 @@
42
46
  ],
43
47
  "peerDependencies": {
44
48
  "fuse.js": ">=6",
49
+ "next": ">=13",
45
50
  "react": ">=18",
46
- "next": ">=13"
51
+ "react-router": ">=7"
47
52
  },
48
53
  "peerDependenciesMeta": {
49
54
  "react": {
@@ -54,6 +59,9 @@
54
59
  },
55
60
  "next": {
56
61
  "optional": true
62
+ },
63
+ "react-router": {
64
+ "optional": true
57
65
  }
58
66
  },
59
67
  "devDependencies": {
@@ -64,6 +72,7 @@
64
72
  "next": "^15.5.23",
65
73
  "react": "^19.2.0",
66
74
  "react-dom": "^19.2.8",
75
+ "react-router": "^7.18.2",
67
76
  "tsup": "^8.0.0",
68
77
  "typescript": "^5.0.0"
69
78
  },
@@ -20,7 +20,18 @@ import { createElement, forwardRef, type ComponentPropsWithoutRef, type ElementT
20
20
 
21
21
  // `onChange` is omitted from the DOM side because the primitive's own onChange - which
22
22
  // reports the button's state - would otherwise clash with the form event of the same name.
23
+ // `onClick` is omitted and re-declared below, as an alias rather than a second handler.
23
24
  export interface ButtonProps extends ButtonOptions, Omit<ComponentPropsWithoutRef<"button">, "onClick" | "onChange" | "children" | "type"> {
25
+ /**
26
+ * What to run when it is pressed. The same thing as `onPress`, under the name React
27
+ * already uses - write whichever reads better.
28
+ *
29
+ * It is an ALIAS, not a second handler: the component owns the element's real onClick,
30
+ * because that is where a press is refused while the button is loading, disabled or
31
+ * cooling down. Passing one through would replace that and silently take the behaviour
32
+ * with it. Returning a promise drives `loading` for its duration, same as `onPress`.
33
+ */
34
+ onClick?: ButtonOptions["onPress"];
24
35
  /**
25
36
  * Override what to render. You rarely need it: an href already renders whatever
26
37
  * `setLinkComponent` registered - your router's Link - and a button otherwise.
@@ -52,11 +63,12 @@ export const Button = forwardRef<HTMLElement, ButtonProps>(function Button({
52
63
  cooldown,
53
64
  shortcut,
54
65
  onPress,
66
+ onClick,
55
67
  onChange,
56
68
  type = "button",
57
69
  ...rest
58
70
  }, ref) {
59
- const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress, onChange });
71
+ const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress: onPress ?? onClick, onChange });
60
72
  const state: ButtonState = button;
61
73
 
62
74
  // An href renders the registered link - next/link, react-router's, whatever was passed
@@ -4,7 +4,7 @@ import { createButton, type ButtonOptions, type ButtonState } from "@/core/butto
4
4
  export interface UseButtonResult extends ButtonState {
5
5
  /** Spread onto the element named by `element`. */
6
6
  props: {
7
- onClick: (event: { preventDefault(): void; }) => void;
7
+ onClick: (event: Event | { preventDefault(): void; }) => void;
8
8
  "aria-disabled": boolean;
9
9
  "aria-busy": boolean;
10
10
  "data-loading"?: "";
@@ -76,7 +76,9 @@ export function useButton(options: ButtonOptions = {}): UseButtonResult {
76
76
  // An unavailable link still receives clicks - aria-disabled is advisory -
77
77
  // so the press is refused here rather than relying on the attribute.
78
78
  if (!state.available) { event.preventDefault(); return; }
79
- void instance.press();
79
+ // Passed through: a handler that wants to stop propagation or read the
80
+ // target had no way to get at it while press() was called bare.
81
+ void instance.press(event as Event);
80
82
  },
81
83
  "aria-disabled": !state.available,
82
84
  "aria-busy": state.loading,
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { Link } from "react-router";
4
+ import { forwardRef, createElement } from "react";
5
+ import { Button as BaseButton, type ButtonProps } from "@/react/button";
6
+
7
+ /**
8
+ * The React Router entry. Same components, wired to its `Link`.
9
+ *
10
+ * ```tsx
11
+ * import { Button } from "@enigmax/primitives/react-router";
12
+ *
13
+ * <Button href="/settings">Settings</Button> // client navigation
14
+ * ```
15
+ *
16
+ * Same deal as the Next entry: the import path is the configuration, and a subpath is only
17
+ * ever resolved by a project that HAS the router - which is what lets a framework-agnostic
18
+ * package reference one at all.
19
+ *
20
+ * React Router names its target `to`, not `href`, so the prop is translated below. That
21
+ * translation is the whole reason this is a file rather than a line in the docs.
22
+ *
23
+ * React Aria solves the same problem the other way, with a `RouterProvider` taking the
24
+ * router's `navigate` (react-spectrum.adobe.com/react-aria/routing.html). One mechanism
25
+ * covers every router and it always renders a real anchor, but it is still a line of setup
26
+ * in the app. `setLinkComponent` is this package's version of that, for a router with no
27
+ * entry of its own here.
28
+ */
29
+
30
+ /** Bridges the two names, so a call site writes `href` whatever the router underneath is. */
31
+ const RouterLink = forwardRef<HTMLAnchorElement, { href?: string; }>(function RouterLink({ href = "", ...props }, ref) {
32
+ return createElement(Link, { ...props, to: href, ref });
33
+ });
34
+
35
+ export const Button = forwardRef<HTMLElement, ButtonProps>(function Button(props, ref) {
36
+ // Only an href makes it a link; a plain button must not be handed a router component.
37
+ return createElement(BaseButton, { ...props, as: props.as ?? (props.href ? RouterLink : undefined), ref });
38
+ });
39
+
40
+ export * from "@/react/index";