@enigmax/primitives 0.7.0 → 0.9.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/index.d.ts +42 -2
- package/dist/react/index.js +37 -3
- package/package.json +1 -1
- package/recipes/{marquee.css.tsx → marquee/css.tsx} +1 -1
- package/registry.json +42 -18
- package/src/react/button.tsx +79 -0
- package/src/react/index.ts +2 -0
- package/src/react/link.ts +45 -0
- /package/recipes/{marquee.css → marquee/styles.css} +0 -0
- /package/recipes/{marquee.tailwind.tsx → marquee/tailwind.tsx} +0 -0
- /package/recipes/{search.tailwind.tsx → search/tailwind.tsx} +0 -0
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { RefObject,
|
|
2
|
+
import { RefObject, ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
3
3
|
import { MarqueeOptions, InputOptions, ButtonState, ButtonOptions, GeneratePasswordOptions, EstimateOptions, PasswordStrengthReport } from '../index.js';
|
|
4
4
|
export { InputAction, InputIcon, MarqueeHover, MarqueeInstance, PasswordScore, estimatePasswordStrength, generatePassword } from '../index.js';
|
|
5
5
|
import { b as SearchMatch, c as SearchOptions } from '../search-CsO3L1Lw.js';
|
|
@@ -129,6 +129,46 @@ interface UseButtonResult extends ButtonState {
|
|
|
129
129
|
*/
|
|
130
130
|
declare function useButton(options?: ButtonOptions): UseButtonResult;
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* `<Button>` - the component, for the ninety percent.
|
|
134
|
+
*
|
|
135
|
+
* ```tsx
|
|
136
|
+
* <Button onPress={save}>Save</Button>
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* That is the whole thing: disabled while the work runs, `aria-busy`, `data-loading`, and a
|
|
140
|
+
* cooldown afterwards if you asked for one. `useButton` is still there for a button whose
|
|
141
|
+
* markup is nothing like a button - a card, a table row - but reaching for it to render an
|
|
142
|
+
* ordinary one means writing the same six lines at every call site, which is how they drift.
|
|
143
|
+
*/
|
|
144
|
+
interface ButtonProps extends ButtonOptions, Omit<ComponentPropsWithoutRef<"button">, "onClick" | "onChange" | "children" | "type"> {
|
|
145
|
+
/**
|
|
146
|
+
* Override what to render. You rarely need it: an href already renders whatever
|
|
147
|
+
* `setLinkComponent` registered - your router's Link - and a button otherwise.
|
|
148
|
+
*/
|
|
149
|
+
as?: ElementType;
|
|
150
|
+
/**
|
|
151
|
+
* The label, or a function of the state for one that changes with it.
|
|
152
|
+
*
|
|
153
|
+
* ```tsx
|
|
154
|
+
* <Button onPress={send} cooldown={30_000}>
|
|
155
|
+
* {({ loading, cooldown }) => loading ? "Sending" : cooldown ? `Wait ${Math.ceil(cooldown / 1000)}s` : "Resend"}
|
|
156
|
+
* </Button>
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
children?: ReactNode | ((state: ButtonState) => ReactNode);
|
|
160
|
+
/** Replaces the label while async work runs - a spinner, or just different words. */
|
|
161
|
+
pending?: ReactNode;
|
|
162
|
+
/** `type` on a real button. Ignored on a link, which has none. */
|
|
163
|
+
type?: "button" | "submit" | "reset";
|
|
164
|
+
}
|
|
165
|
+
declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLElement>>;
|
|
166
|
+
|
|
167
|
+
/** Register the router's link. Pass null to go back to a plain anchor. */
|
|
168
|
+
declare function setLinkComponent(component: ElementType | null): void;
|
|
169
|
+
/** What an href should render as right now. */
|
|
170
|
+
declare function getLinkComponent(): ElementType;
|
|
171
|
+
|
|
132
172
|
/** An extra button inside the field. The built-ins are the same shape. */
|
|
133
173
|
interface FieldAction {
|
|
134
174
|
/** Stable id. Lands on `data-enigma-input-action`, and replaces a built-in of the same name. */
|
|
@@ -239,4 +279,4 @@ interface PasswordStrengthProps extends Omit<ComponentPropsWithoutRef<"div">, "c
|
|
|
239
279
|
*/
|
|
240
280
|
declare function PasswordStrength({ value, report, userInputs, segments, labels, showWarning, classNames, ...props }: PasswordStrengthProps): ReactNode;
|
|
241
281
|
|
|
242
|
-
export { type BreachChecker, type BreachState, type BreachStatus, ButtonOptions, ButtonState, EstimateOptions, type FieldAction, GeneratePasswordOptions, Input, InputOptions, type InputProps, MarqueeOptions, PasswordStrength, type PasswordStrengthProps, PasswordStrengthReport, SearchMatch, SearchOptions, type UseButtonResult, type UseInputResult, type UseMarqueeResult, type UseSearchResult, useButton, useInput, useMarquee, useSearch };
|
|
282
|
+
export { type BreachChecker, type BreachState, type BreachStatus, Button, ButtonOptions, type ButtonProps, ButtonState, EstimateOptions, type FieldAction, GeneratePasswordOptions, Input, InputOptions, type InputProps, MarqueeOptions, PasswordStrength, type PasswordStrengthProps, PasswordStrengthReport, SearchMatch, SearchOptions, type UseButtonResult, type UseInputResult, type UseMarqueeResult, type UseSearchResult, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch };
|
package/dist/react/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { generatePassword, estimatePasswordStrength, INPUT_ICON_PATHS, createMarquee, createInput
|
|
2
|
+
import { generatePassword, estimatePasswordStrength, INPUT_ICON_PATHS, createButton, createMarquee, createInput } from '../chunk-42Y7OOJM.js';
|
|
3
3
|
export { estimatePasswordStrength, generatePassword } from '../chunk-42Y7OOJM.js';
|
|
4
4
|
import { createSearch } from '../chunk-UZFEEFMF.js';
|
|
5
|
-
import { forwardRef, useRef, useState, useId, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
|
|
5
|
+
import { forwardRef, createElement, useRef, useState, useId, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
@@ -185,6 +185,40 @@ function useButton(options = {}) {
|
|
|
185
185
|
}
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
// src/react/link.ts
|
|
190
|
+
var linkComponent = null;
|
|
191
|
+
function setLinkComponent(component) {
|
|
192
|
+
linkComponent = component;
|
|
193
|
+
}
|
|
194
|
+
function getLinkComponent() {
|
|
195
|
+
return linkComponent ?? "a";
|
|
196
|
+
}
|
|
197
|
+
var Button = forwardRef(function Button2({
|
|
198
|
+
as,
|
|
199
|
+
children,
|
|
200
|
+
pending,
|
|
201
|
+
href,
|
|
202
|
+
disabled,
|
|
203
|
+
loading,
|
|
204
|
+
cooldown,
|
|
205
|
+
shortcut,
|
|
206
|
+
onPress,
|
|
207
|
+
onChange,
|
|
208
|
+
type = "button",
|
|
209
|
+
...rest
|
|
210
|
+
}, ref) {
|
|
211
|
+
const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress, onChange });
|
|
212
|
+
const state = button;
|
|
213
|
+
const Tag = as ?? (state.element === "a" ? getLinkComponent() : "button");
|
|
214
|
+
const native = Tag === "button" ? { type } : {};
|
|
215
|
+
const label = typeof children === "function" ? children(state) : children;
|
|
216
|
+
return createElement(
|
|
217
|
+
Tag,
|
|
218
|
+
{ ref, ...button.props, ...native, ...rest },
|
|
219
|
+
state.loading && pending !== void 0 ? pending : label
|
|
220
|
+
);
|
|
221
|
+
});
|
|
188
222
|
var ICON_PROPS = {
|
|
189
223
|
viewBox: "0 0 24 24",
|
|
190
224
|
width: "1em",
|
|
@@ -431,4 +465,4 @@ function PasswordStrength({
|
|
|
431
465
|
);
|
|
432
466
|
}
|
|
433
467
|
|
|
434
|
-
export { Input, PasswordStrength, useButton, useInput, useMarquee, useSearch };
|
|
468
|
+
export { Button, Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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",
|
package/registry.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
{
|
|
33
33
|
"path": "src/core/marquee.ts",
|
|
34
|
-
"dest": "marquee.ts",
|
|
34
|
+
"dest": "marquee/marquee.ts",
|
|
35
35
|
"targets": [
|
|
36
36
|
"vanilla",
|
|
37
37
|
"astro",
|
|
@@ -40,33 +40,34 @@
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"path": "src/react/use-marquee.ts",
|
|
43
|
-
"dest": "use-marquee.ts",
|
|
43
|
+
"dest": "marquee/use-marquee.ts",
|
|
44
44
|
"targets": [
|
|
45
45
|
"react"
|
|
46
46
|
],
|
|
47
47
|
"rewrite": {
|
|
48
48
|
"@/core/marquee": "./marquee"
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"main": true
|
|
50
51
|
},
|
|
51
52
|
{
|
|
52
|
-
"path": "recipes/marquee
|
|
53
|
-
"dest": "Marquee.tsx",
|
|
53
|
+
"path": "recipes/marquee/tailwind.tsx",
|
|
54
|
+
"dest": "marquee/Marquee.tsx",
|
|
54
55
|
"targets": [
|
|
55
56
|
"react"
|
|
56
57
|
],
|
|
57
58
|
"style": "tailwind"
|
|
58
59
|
},
|
|
59
60
|
{
|
|
60
|
-
"path": "recipes/marquee
|
|
61
|
-
"dest": "Marquee.tsx",
|
|
61
|
+
"path": "recipes/marquee/css.tsx",
|
|
62
|
+
"dest": "marquee/Marquee.tsx",
|
|
62
63
|
"targets": [
|
|
63
64
|
"react"
|
|
64
65
|
],
|
|
65
66
|
"style": "css"
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
|
-
"path": "recipes/marquee.css",
|
|
69
|
-
"dest": "marquee.css",
|
|
69
|
+
"path": "recipes/marquee/styles.css",
|
|
70
|
+
"dest": "marquee/styles.css",
|
|
70
71
|
"targets": [
|
|
71
72
|
"react",
|
|
72
73
|
"vanilla",
|
|
@@ -235,7 +236,7 @@
|
|
|
235
236
|
"files": [
|
|
236
237
|
{
|
|
237
238
|
"path": "src/core/search.ts",
|
|
238
|
-
"dest": "search.ts",
|
|
239
|
+
"dest": "search/search.ts",
|
|
239
240
|
"targets": [
|
|
240
241
|
"vanilla",
|
|
241
242
|
"astro",
|
|
@@ -244,17 +245,18 @@
|
|
|
244
245
|
},
|
|
245
246
|
{
|
|
246
247
|
"path": "src/react/use-search.ts",
|
|
247
|
-
"dest": "use-search.ts",
|
|
248
|
+
"dest": "search/use-search.ts",
|
|
248
249
|
"targets": [
|
|
249
250
|
"react"
|
|
250
251
|
],
|
|
251
252
|
"rewrite": {
|
|
252
253
|
"@/core/search": "./search"
|
|
253
|
-
}
|
|
254
|
+
},
|
|
255
|
+
"main": true
|
|
254
256
|
},
|
|
255
257
|
{
|
|
256
|
-
"path": "recipes/search
|
|
257
|
-
"dest": "Search.tsx",
|
|
258
|
+
"path": "recipes/search/tailwind.tsx",
|
|
259
|
+
"dest": "search/Search.tsx",
|
|
258
260
|
"targets": [
|
|
259
261
|
"react"
|
|
260
262
|
],
|
|
@@ -278,8 +280,8 @@
|
|
|
278
280
|
},
|
|
279
281
|
{
|
|
280
282
|
"name": "button",
|
|
281
|
-
"title": "Button
|
|
282
|
-
"description": "Disabled, loading, a keyboard shortcut and a cooldown that can outlive a reload
|
|
283
|
+
"title": "Button",
|
|
284
|
+
"description": "A button you pass props to. Disabled, loading, a keyboard shortcut and a cooldown that can outlive a reload collapse into one `available`; an href reports an anchor, so the framework's own Link stays your choice.",
|
|
283
285
|
"targets": [
|
|
284
286
|
"vanilla",
|
|
285
287
|
"astro",
|
|
@@ -298,13 +300,15 @@
|
|
|
298
300
|
"createButton"
|
|
299
301
|
],
|
|
300
302
|
"react": [
|
|
303
|
+
"Button",
|
|
304
|
+
"setLinkComponent",
|
|
301
305
|
"useButton"
|
|
302
306
|
]
|
|
303
307
|
},
|
|
304
308
|
"files": [
|
|
305
309
|
{
|
|
306
310
|
"path": "src/core/button.ts",
|
|
307
|
-
"dest": "button.ts",
|
|
311
|
+
"dest": "button/button.ts",
|
|
308
312
|
"targets": [
|
|
309
313
|
"vanilla",
|
|
310
314
|
"astro",
|
|
@@ -313,13 +317,33 @@
|
|
|
313
317
|
},
|
|
314
318
|
{
|
|
315
319
|
"path": "src/react/use-button.ts",
|
|
316
|
-
"dest": "use-button.ts",
|
|
320
|
+
"dest": "button/use-button.ts",
|
|
317
321
|
"targets": [
|
|
318
322
|
"react"
|
|
319
323
|
],
|
|
320
324
|
"rewrite": {
|
|
321
325
|
"@/core/button": "./button"
|
|
322
326
|
}
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"path": "src/react/link.ts",
|
|
330
|
+
"dest": "button/link.ts",
|
|
331
|
+
"targets": [
|
|
332
|
+
"react"
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"path": "src/react/button.tsx",
|
|
337
|
+
"dest": "button/index.tsx",
|
|
338
|
+
"main": true,
|
|
339
|
+
"targets": [
|
|
340
|
+
"react"
|
|
341
|
+
],
|
|
342
|
+
"rewrite": {
|
|
343
|
+
"@/core/button": "./button",
|
|
344
|
+
"@/react/use-button": "./use-button",
|
|
345
|
+
"@/react/link": "./link"
|
|
346
|
+
}
|
|
323
347
|
}
|
|
324
348
|
],
|
|
325
349
|
"styles": false,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useButton } from "@/react/use-button";
|
|
4
|
+
import { getLinkComponent } from "@/react/link";
|
|
5
|
+
import type { ButtonOptions, ButtonState } from "@/core/button";
|
|
6
|
+
import { createElement, forwardRef, type ComponentPropsWithoutRef, type ElementType, type ReactNode } from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `<Button>` - the component, for the ninety percent.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Button onPress={save}>Save</Button>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* That is the whole thing: disabled while the work runs, `aria-busy`, `data-loading`, and a
|
|
16
|
+
* cooldown afterwards if you asked for one. `useButton` is still there for a button whose
|
|
17
|
+
* markup is nothing like a button - a card, a table row - but reaching for it to render an
|
|
18
|
+
* ordinary one means writing the same six lines at every call site, which is how they drift.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// `onChange` is omitted from the DOM side because the primitive's own onChange - which
|
|
22
|
+
// reports the button's state - would otherwise clash with the form event of the same name.
|
|
23
|
+
export interface ButtonProps extends ButtonOptions, Omit<ComponentPropsWithoutRef<"button">, "onClick" | "onChange" | "children" | "type"> {
|
|
24
|
+
/**
|
|
25
|
+
* Override what to render. You rarely need it: an href already renders whatever
|
|
26
|
+
* `setLinkComponent` registered - your router's Link - and a button otherwise.
|
|
27
|
+
*/
|
|
28
|
+
as?: ElementType;
|
|
29
|
+
/**
|
|
30
|
+
* The label, or a function of the state for one that changes with it.
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <Button onPress={send} cooldown={30_000}>
|
|
34
|
+
* {({ loading, cooldown }) => loading ? "Sending" : cooldown ? `Wait ${Math.ceil(cooldown / 1000)}s` : "Resend"}
|
|
35
|
+
* </Button>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
children?: ReactNode | ((state: ButtonState) => ReactNode);
|
|
39
|
+
/** Replaces the label while async work runs - a spinner, or just different words. */
|
|
40
|
+
pending?: ReactNode;
|
|
41
|
+
/** `type` on a real button. Ignored on a link, which has none. */
|
|
42
|
+
type?: "button" | "submit" | "reset";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Button = forwardRef<HTMLElement, ButtonProps>(function Button({
|
|
46
|
+
as,
|
|
47
|
+
children,
|
|
48
|
+
pending,
|
|
49
|
+
href,
|
|
50
|
+
disabled,
|
|
51
|
+
loading,
|
|
52
|
+
cooldown,
|
|
53
|
+
shortcut,
|
|
54
|
+
onPress,
|
|
55
|
+
onChange,
|
|
56
|
+
type = "button",
|
|
57
|
+
...rest
|
|
58
|
+
}, ref) {
|
|
59
|
+
const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress, onChange });
|
|
60
|
+
const state: ButtonState = button;
|
|
61
|
+
|
|
62
|
+
// An href renders the registered link - next/link, react-router's, whatever was passed
|
|
63
|
+
// to setLinkComponent - and a plain <a> when nothing was. The package cannot import a
|
|
64
|
+
// router itself, so registering one is what turns every href into a client navigation
|
|
65
|
+
// without a word at the call site.
|
|
66
|
+
const Tag: ElementType = as ?? (state.element === "a" ? getLinkComponent() : "button");
|
|
67
|
+
|
|
68
|
+
// `type="button"` is the default on purpose. A bare <button> inside a form submits it,
|
|
69
|
+
// so an action button that forgot it posts the form instead of doing its job.
|
|
70
|
+
const native = Tag === "button" ? { type } : {};
|
|
71
|
+
|
|
72
|
+
const label = typeof children === "function" ? children(state) : children;
|
|
73
|
+
|
|
74
|
+
return createElement(
|
|
75
|
+
Tag,
|
|
76
|
+
{ ref, ...button.props, ...native, ...rest },
|
|
77
|
+
state.loading && pending !== undefined ? pending : label
|
|
78
|
+
);
|
|
79
|
+
});
|
package/src/react/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { useSearch, type UseSearchResult } from "@/react/use-search";
|
|
|
7
7
|
export { type InputOptions, type InputAction, type InputIcon } from "@/core/input";
|
|
8
8
|
export { type SearchOptions, type SearchMatch, type FuseConstructor } from "@/core/search";
|
|
9
9
|
export { useButton, type UseButtonResult } from "@/react/use-button";
|
|
10
|
+
export { Button, type ButtonProps } from "@/react/button";
|
|
11
|
+
export { setLinkComponent, getLinkComponent } from "@/react/link";
|
|
10
12
|
export { type ButtonOptions, type ButtonState } from "@/core/button";
|
|
11
13
|
export {
|
|
12
14
|
Input,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ElementType } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The component every `href` in this package renders through.
|
|
7
|
+
*
|
|
8
|
+
* The package cannot `import Link from "next/link"`. It is framework-agnostic and Next is
|
|
9
|
+
* not a dependency, so that import would fail to resolve for every consumer who is not on
|
|
10
|
+
* Next - a hard build error in Vite, Astro, Remix and plain React.
|
|
11
|
+
*
|
|
12
|
+
* So it is registered instead, ONCE, and after that an href is a router link everywhere:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // app/providers.tsx, or wherever your app starts
|
|
16
|
+
* import Link from "next/link";
|
|
17
|
+
* import { setLinkComponent } from "@enigmax/primitives/react";
|
|
18
|
+
*
|
|
19
|
+
* setLinkComponent(Link);
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // and from then on, every call site
|
|
24
|
+
* <Button href="/settings">Settings</Button>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Without it an href renders a plain `<a>`, which is correct everywhere and merely means a
|
|
28
|
+
* full page load under a router. Any router works the same way - react-router's `Link`,
|
|
29
|
+
* TanStack Router's, Astro has none to register.
|
|
30
|
+
*
|
|
31
|
+
* A module-level value rather than a context: it is one component reference for the whole
|
|
32
|
+
* app, identical on every request, so threading a provider through the tree would buy
|
|
33
|
+
* nothing and cost every consumer a wrapper.
|
|
34
|
+
*/
|
|
35
|
+
let linkComponent: ElementType | null = null;
|
|
36
|
+
|
|
37
|
+
/** Register the router's link. Pass null to go back to a plain anchor. */
|
|
38
|
+
export function setLinkComponent(component: ElementType | null): void {
|
|
39
|
+
linkComponent = component;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What an href should render as right now. */
|
|
43
|
+
export function getLinkComponent(): ElementType {
|
|
44
|
+
return linkComponent ?? "a";
|
|
45
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|