@connextar/house 0.11.0 → 0.13.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 +581 -19
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/second-factor-index.d.ts +7 -0
- package/dist/auth/second-factor-index.d.ts.map +1 -0
- package/dist/auth/second-factor-index.js +7 -0
- package/dist/auth/second-factor-index.js.map +1 -0
- package/dist/auth/second-factor-next.d.ts +39 -0
- package/dist/auth/second-factor-next.d.ts.map +1 -0
- package/dist/auth/second-factor-next.js +131 -0
- package/dist/auth/second-factor-next.js.map +1 -0
- package/dist/auth/second-factor-routes.d.ts +29 -0
- package/dist/auth/second-factor-routes.d.ts.map +1 -0
- package/dist/auth/second-factor-routes.js +35 -0
- package/dist/auth/second-factor-routes.js.map +1 -0
- package/dist/auth/second-factor.d.ts +117 -0
- package/dist/auth/second-factor.d.ts.map +1 -0
- package/dist/auth/second-factor.js +127 -0
- package/dist/auth/second-factor.js.map +1 -0
- package/dist/auth/two-factor-policy.d.ts +30 -1
- package/dist/auth/two-factor-policy.d.ts.map +1 -1
- package/dist/auth/two-factor-policy.js +39 -1
- package/dist/auth/two-factor-policy.js.map +1 -1
- package/dist/jobs/health.d.ts +17 -4
- package/dist/jobs/health.d.ts.map +1 -1
- package/dist/jobs/health.js +10 -7
- package/dist/jobs/health.js.map +1 -1
- package/dist/jobs/index.d.ts +4 -3
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +3 -2
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/job-health-list.d.ts +15 -3
- package/dist/jobs/job-health-list.d.ts.map +1 -1
- package/dist/jobs/job-health-list.js +11 -7
- package/dist/jobs/job-health-list.js.map +1 -1
- package/dist/jobs/prune.d.ts +45 -0
- package/dist/jobs/prune.d.ts.map +1 -0
- package/dist/jobs/prune.js +58 -0
- package/dist/jobs/prune.js.map +1 -0
- package/dist/jobs/registry.d.ts +19 -4
- package/dist/jobs/registry.d.ts.map +1 -1
- package/dist/jobs/registry.js +16 -1
- package/dist/jobs/registry.js.map +1 -1
- package/dist/jobs/scheduler.d.ts +12 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +24 -9
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/jobs/store.d.ts +26 -1
- package/dist/jobs/store.d.ts.map +1 -1
- package/dist/jobs/store.js +35 -4
- package/dist/jobs/store.js.map +1 -1
- package/dist/password/common.d.ts +2 -0
- package/dist/password/common.d.ts.map +1 -0
- package/dist/password/common.js +58 -0
- package/dist/password/common.js.map +1 -0
- package/dist/password/estimate-index.d.ts +10 -0
- package/dist/password/estimate-index.d.ts.map +1 -0
- package/dist/password/estimate-index.js +10 -0
- package/dist/password/estimate-index.js.map +1 -0
- package/dist/password/estimate.d.ts +65 -0
- package/dist/password/estimate.d.ts.map +1 -0
- package/dist/password/estimate.js +612 -0
- package/dist/password/estimate.js.map +1 -0
- package/dist/password/index.d.ts +22 -0
- package/dist/password/index.d.ts.map +1 -0
- package/dist/password/index.js +22 -0
- package/dist/password/index.js.map +1 -0
- package/dist/password/password-input.d.ts +55 -0
- package/dist/password/password-input.d.ts.map +1 -0
- package/dist/password/password-input.js +54 -0
- package/dist/password/password-input.js.map +1 -0
- package/dist/password/password-strength.d.ts +59 -0
- package/dist/password/password-strength.d.ts.map +1 -0
- package/dist/password/password-strength.js +74 -0
- package/dist/password/password-strength.js.map +1 -0
- package/dist/password/reveal.d.ts +56 -0
- package/dist/password/reveal.d.ts.map +1 -0
- package/dist/password/reveal.js +109 -0
- package/dist/password/reveal.js.map +1 -0
- package/package.json +19 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/password` — the password field every form uses, and the
|
|
3
|
+
* strength meter for the ones where a password is being chosen.
|
|
4
|
+
*
|
|
5
|
+
* Ten apps had password fields and not one could show what had been typed.
|
|
6
|
+
* One showed a strength meter — a hand-rolled checklist, with an unused copy in
|
|
7
|
+
* a second app — and "Password1!" ticks every box on it. This is one toggle and
|
|
8
|
+
* one honest estimate, so a person meets the same field and the same judgement
|
|
9
|
+
* in every app.
|
|
10
|
+
*
|
|
11
|
+
* React is the only peer it needs. Nothing here imports a form library — the
|
|
12
|
+
* input forwards its ref and every prop, so react-hook-form's `register`, a
|
|
13
|
+
* `Controller` and plain state all fit — and nothing imports a design system:
|
|
14
|
+
* the look is theme-token classes replaced through `classNames`, as in
|
|
15
|
+
* `wizard` and `markdown`. The estimator alone, for a server, is
|
|
16
|
+
* `@connextar/house/password/estimate`.
|
|
17
|
+
*/
|
|
18
|
+
export { estimatePasswordStrength, MAX_ANALYSED_LENGTH, PASSWORD_STRENGTH_MESSAGES, type EstimatePasswordStrengthOptions, type PasswordScore, type PasswordStrengthEstimate, type PasswordStrengthMessages, type PasswordWeakness, } from "./estimate.js";
|
|
19
|
+
export { PASSWORD_INPUT_CLASS_NAMES, PasswordInput, type PasswordInputClassNames, type PasswordInputProps, } from "./password-input.js";
|
|
20
|
+
export { PASSWORD_STRENGTH_CLASS_NAMES, PasswordStrength, type PasswordStrengthClassNames, type PasswordStrengthProps, } from "./password-strength.js";
|
|
21
|
+
export { usePasswordReveal, type PasswordReveal, type PasswordRevealOptions } from "./reveal.js";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/password/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,+BAA+B,EACpC,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/password` — the password field every form uses, and the
|
|
3
|
+
* strength meter for the ones where a password is being chosen.
|
|
4
|
+
*
|
|
5
|
+
* Ten apps had password fields and not one could show what had been typed.
|
|
6
|
+
* One showed a strength meter — a hand-rolled checklist, with an unused copy in
|
|
7
|
+
* a second app — and "Password1!" ticks every box on it. This is one toggle and
|
|
8
|
+
* one honest estimate, so a person meets the same field and the same judgement
|
|
9
|
+
* in every app.
|
|
10
|
+
*
|
|
11
|
+
* React is the only peer it needs. Nothing here imports a form library — the
|
|
12
|
+
* input forwards its ref and every prop, so react-hook-form's `register`, a
|
|
13
|
+
* `Controller` and plain state all fit — and nothing imports a design system:
|
|
14
|
+
* the look is theme-token classes replaced through `classNames`, as in
|
|
15
|
+
* `wizard` and `markdown`. The estimator alone, for a server, is
|
|
16
|
+
* `@connextar/house/password/estimate`.
|
|
17
|
+
*/
|
|
18
|
+
export { estimatePasswordStrength, MAX_ANALYSED_LENGTH, PASSWORD_STRENGTH_MESSAGES, } from "./estimate.js";
|
|
19
|
+
export { PASSWORD_INPUT_CLASS_NAMES, PasswordInput, } from "./password-input.js";
|
|
20
|
+
export { PASSWORD_STRENGTH_CLASS_NAMES, PasswordStrength, } from "./password-strength.js";
|
|
21
|
+
export { usePasswordReveal } from "./reveal.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/password/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,GAM3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,0BAA0B,EAC1B,aAAa,GAGd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAmD,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Every element the input draws. Each value replaces the default for that part. */
|
|
3
|
+
export interface PasswordInputClassNames {
|
|
4
|
+
wrapper: string;
|
|
5
|
+
input: string;
|
|
6
|
+
toggle: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The house look: the shadcn input on the shared theme tokens, with room on the
|
|
11
|
+
* right for the toggle. An app whose own `Input` differs passes its classes as
|
|
12
|
+
* `classNames.input`; one without theme tokens replaces the parts it needs.
|
|
13
|
+
*
|
|
14
|
+
* `[&::-ms-reveal]:hidden` removes the eye Edge draws in every password field,
|
|
15
|
+
* which would otherwise sit beside ours.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PASSWORD_INPUT_CLASS_NAMES: PasswordInputClassNames;
|
|
18
|
+
export interface PasswordInputProps extends Omit<React.ComponentPropsWithoutRef<"input">, "type"> {
|
|
19
|
+
/**
|
|
20
|
+
* What the field is for, which decides `autoComplete`: `"current"` for
|
|
21
|
+
* signing in and for confirming who you are (two-factor, deleting an
|
|
22
|
+
* account); `"new"` wherever a password is being chosen, confirm fields
|
|
23
|
+
* included. An explicit `autoComplete` wins.
|
|
24
|
+
*/
|
|
25
|
+
purpose?: "current" | "new";
|
|
26
|
+
/** Controlled reveal — one toggle shared by a new and a confirm field, say. */
|
|
27
|
+
revealed?: boolean;
|
|
28
|
+
defaultRevealed?: boolean;
|
|
29
|
+
onRevealedChange?: (revealed: boolean) => void;
|
|
30
|
+
/** The toggle's accessible name. Constant: the pressed state says whether it is on. */
|
|
31
|
+
toggleLabel?: string;
|
|
32
|
+
/** Appended to the input's classes, as with the app's own `Input`. */
|
|
33
|
+
className?: string;
|
|
34
|
+
classNames?: Partial<PasswordInputClassNames>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A password field with a show/hide toggle — the one every form in every app
|
|
38
|
+
* uses, whatever it is asking for.
|
|
39
|
+
*
|
|
40
|
+
* A drop-in for `<Input type="password">`: every input prop and the ref pass
|
|
41
|
+
* straight through, so `{...register("password")}`, a `Controller`'s `field`
|
|
42
|
+
* and plain `value`/`onChange` state all work unchanged.
|
|
43
|
+
*
|
|
44
|
+
* The toggle is a real `<button type="button">` inside the field, so it never
|
|
45
|
+
* submits the form and is reached with Tab. Its accessible name stays
|
|
46
|
+
* "Show password" and `aria-pressed` carries the state — a toggle whose label
|
|
47
|
+
* changed as well would be read as "Hide password, pressed", which says the
|
|
48
|
+
* opposite of what is on screen. The eye becomes a struck-through eye for
|
|
49
|
+
* everyone else.
|
|
50
|
+
*
|
|
51
|
+
* See `usePasswordReveal` for what else the toggle takes care of: focus and
|
|
52
|
+
* caret kept, hidden again on submit, no spellcheck.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PasswordInput: React.ForwardRefExoticComponent<PasswordInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
55
|
+
//# sourceMappingURL=password-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-input.d.ts","sourceRoot":"","sources":["../../src/password/password-input.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,oFAAoF;AACpF,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,uBAOxC,CAAC;AAEF,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC/F;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,6FA6CxB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Eye, EyeOff } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cx } from "../internal/cx.js";
|
|
6
|
+
import { mergeRefs, usePasswordReveal } from "./reveal.js";
|
|
7
|
+
/**
|
|
8
|
+
* The house look: the shadcn input on the shared theme tokens, with room on the
|
|
9
|
+
* right for the toggle. An app whose own `Input` differs passes its classes as
|
|
10
|
+
* `classNames.input`; one without theme tokens replaces the parts it needs.
|
|
11
|
+
*
|
|
12
|
+
* `[&::-ms-reveal]:hidden` removes the eye Edge draws in every password field,
|
|
13
|
+
* which would otherwise sit beside ours.
|
|
14
|
+
*/
|
|
15
|
+
export const PASSWORD_INPUT_CLASS_NAMES = {
|
|
16
|
+
wrapper: "relative w-full",
|
|
17
|
+
input: "flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent py-1 pr-10 pl-3 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive md:text-sm [&::-ms-reveal]:hidden",
|
|
18
|
+
toggle: "absolute inset-y-0 right-0 flex w-9 items-center justify-center rounded-r-md text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
19
|
+
icon: "size-4",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A password field with a show/hide toggle — the one every form in every app
|
|
23
|
+
* uses, whatever it is asking for.
|
|
24
|
+
*
|
|
25
|
+
* A drop-in for `<Input type="password">`: every input prop and the ref pass
|
|
26
|
+
* straight through, so `{...register("password")}`, a `Controller`'s `field`
|
|
27
|
+
* and plain `value`/`onChange` state all work unchanged.
|
|
28
|
+
*
|
|
29
|
+
* The toggle is a real `<button type="button">` inside the field, so it never
|
|
30
|
+
* submits the form and is reached with Tab. Its accessible name stays
|
|
31
|
+
* "Show password" and `aria-pressed` carries the state — a toggle whose label
|
|
32
|
+
* changed as well would be read as "Hide password, pressed", which says the
|
|
33
|
+
* opposite of what is on screen. The eye becomes a struck-through eye for
|
|
34
|
+
* everyone else.
|
|
35
|
+
*
|
|
36
|
+
* See `usePasswordReveal` for what else the toggle takes care of: focus and
|
|
37
|
+
* caret kept, hidden again on submit, no spellcheck.
|
|
38
|
+
*/
|
|
39
|
+
export const PasswordInput = React.forwardRef(function PasswordInput({ purpose = "current", revealed: revealedProp, defaultRevealed, onRevealedChange, toggleLabel = "Show password", className, classNames, id: idProp, autoComplete, disabled, ...props }, ref) {
|
|
40
|
+
const c = { ...PASSWORD_INPUT_CLASS_NAMES, ...classNames };
|
|
41
|
+
const generatedId = React.useId();
|
|
42
|
+
const id = idProp ?? generatedId;
|
|
43
|
+
const reveal = usePasswordReveal({
|
|
44
|
+
revealed: revealedProp,
|
|
45
|
+
defaultRevealed,
|
|
46
|
+
onRevealedChange,
|
|
47
|
+
inputId: id,
|
|
48
|
+
disabled,
|
|
49
|
+
});
|
|
50
|
+
const inputRef = React.useMemo(() => mergeRefs(ref, reveal.inputRef), [ref, reveal.inputRef]);
|
|
51
|
+
const Icon = reveal.revealed ? EyeOff : Eye;
|
|
52
|
+
return (_jsxs("div", { className: c.wrapper, children: [_jsx("input", { ...props, ...reveal.inputProps, ref: inputRef, id: id, disabled: disabled, autoComplete: autoComplete ?? (purpose === "new" ? "new-password" : "current-password"), className: cx(c.input, className) }), _jsx("button", { ...reveal.toggleProps, "aria-label": toggleLabel, className: c.toggle, children: _jsx(Icon, { "aria-hidden": "true", className: c.icon }) })] }));
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=password-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-input.js","sourceRoot":"","sources":["../../src/password/password-input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAU3D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAA4B;IACjE,OAAO,EAAE,iBAAiB;IAC1B,KAAK,EACH,wXAAwX;IAC1X,MAAM,EACJ,qQAAqQ;IACvQ,IAAI,EAAE,QAAQ;CACf,CAAC;AAqBF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,UAAU,CAAuC,SAAS,aAAa,CACxG,EACE,OAAO,GAAG,SAAS,EACnB,QAAQ,EAAE,YAAY,EACtB,eAAe,EACf,gBAAgB,EAChB,WAAW,GAAG,eAAe,EAC7B,SAAS,EACT,UAAU,EACV,EAAE,EAAE,MAAM,EACV,YAAY,EACZ,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG;IAEH,MAAM,CAAC,GAA4B,EAAE,GAAG,0BAA0B,EAAE,GAAG,UAAU,EAAE,CAAC;IACpF,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IAClC,MAAM,EAAE,GAAG,MAAM,IAAI,WAAW,CAAC;IACjC,MAAM,MAAM,GAAG,iBAAiB,CAAC;QAC/B,QAAQ,EAAE,YAAY;QACtB,eAAe;QACf,gBAAgB;QAChB,OAAO,EAAE,EAAE;QACX,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9F,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;IAE5C,OAAO,CACL,eAAK,SAAS,EAAE,CAAC,CAAC,OAAO,aACvB,mBACM,KAAK,KACL,MAAM,CAAC,UAAU,EACrB,GAAG,EAAE,QAAQ,EACb,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,YAAY,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC,EACvF,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,GACjC,EACF,oBAAY,MAAM,CAAC,WAAW,gBAAc,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,YAC1E,KAAC,IAAI,mBAAa,MAAM,EAAC,SAAS,EAAE,CAAC,CAAC,IAAI,GAAI,GACvC,IACL,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type PasswordStrengthMessages } from "./estimate.js";
|
|
3
|
+
/** Every element the meter draws. Each value replaces the default for that part. */
|
|
4
|
+
export interface PasswordStrengthClassNames {
|
|
5
|
+
root: string;
|
|
6
|
+
bar: string;
|
|
7
|
+
segment: string;
|
|
8
|
+
/** A filled segment, by score. */
|
|
9
|
+
score0: string;
|
|
10
|
+
score1: string;
|
|
11
|
+
score2: string;
|
|
12
|
+
score3: string;
|
|
13
|
+
score4: string;
|
|
14
|
+
text: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The house look on the shared theme tokens, with amber and green for the two
|
|
19
|
+
* scores no theme token names. The root also carries `data-score`, for an app
|
|
20
|
+
* that styles the meter from its own stylesheet instead.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PASSWORD_STRENGTH_CLASS_NAMES: PasswordStrengthClassNames;
|
|
23
|
+
export interface PasswordStrengthProps {
|
|
24
|
+
/** The password being typed — the field's current value. */
|
|
25
|
+
password: string;
|
|
26
|
+
/** The person's email, name, username, organisation, the app's name. */
|
|
27
|
+
userInputs?: ReadonlyArray<string | null | undefined>;
|
|
28
|
+
/** The app's own minimum, so the sentence can say it while the password is shorter. */
|
|
29
|
+
minLength?: number;
|
|
30
|
+
messages?: Partial<PasswordStrengthMessages>;
|
|
31
|
+
/**
|
|
32
|
+
* Put on the sentence, for the field's `aria-describedby`, so the strength is
|
|
33
|
+
* read with the field when someone returns to it.
|
|
34
|
+
*/
|
|
35
|
+
id?: string;
|
|
36
|
+
/** How long typing must pause before a screen reader hears the new strength. */
|
|
37
|
+
announceDelay?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
classNames?: Partial<PasswordStrengthClassNames>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* How guessable a new password is: four bars and one plain sentence.
|
|
43
|
+
*
|
|
44
|
+
* **Only where a password is being chosen** — sign-up, reset, set, change, and a
|
|
45
|
+
* person setting one for someone else. Never on sign-in or a "confirm it's you"
|
|
46
|
+
* field: rating a password someone already has tells them nothing they can act
|
|
47
|
+
* on, and tells anyone watching the screen something about it.
|
|
48
|
+
*
|
|
49
|
+
* **It refuses nothing.** The meter is advice; whether a password is allowed is
|
|
50
|
+
* the app's rule — its minimum length, and a server check with
|
|
51
|
+
* `estimatePasswordStrength` if it chooses to refuse the weakest. So there is no
|
|
52
|
+
* `valid` here to wire a submit button to.
|
|
53
|
+
*
|
|
54
|
+
* Nothing shows for an empty field. Screen readers hear the strength once typing
|
|
55
|
+
* pauses and only when it has changed, through a polite live region — never a
|
|
56
|
+
* word per keystroke.
|
|
57
|
+
*/
|
|
58
|
+
export declare function PasswordStrength({ password, userInputs, minLength, messages, id, announceDelay, className, classNames, }: PasswordStrengthProps): React.JSX.Element;
|
|
59
|
+
//# sourceMappingURL=password-strength.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-strength.d.ts","sourceRoot":"","sources":["../../src/password/password-strength.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,eAAe,CAAC;AAEvB,oFAAoF;AACpF,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,0BAW3C,CAAC;AAeF,MAAM,WAAW,qBAAqB;IACpC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC7C;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,EACR,EAAE,EACF,aAAoB,EACpB,SAAS,EACT,UAAU,GACX,EAAE,qBAAqB,qBA8CvB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cx } from "../internal/cx.js";
|
|
5
|
+
import { estimatePasswordStrength, } from "./estimate.js";
|
|
6
|
+
/**
|
|
7
|
+
* The house look on the shared theme tokens, with amber and green for the two
|
|
8
|
+
* scores no theme token names. The root also carries `data-score`, for an app
|
|
9
|
+
* that styles the meter from its own stylesheet instead.
|
|
10
|
+
*/
|
|
11
|
+
export const PASSWORD_STRENGTH_CLASS_NAMES = {
|
|
12
|
+
root: "mt-2 space-y-1.5",
|
|
13
|
+
bar: "grid grid-cols-4 gap-1",
|
|
14
|
+
segment: "h-1 rounded-full bg-muted transition-colors",
|
|
15
|
+
score0: "bg-destructive",
|
|
16
|
+
score1: "bg-destructive",
|
|
17
|
+
score2: "bg-amber-500",
|
|
18
|
+
score3: "bg-emerald-500",
|
|
19
|
+
score4: "bg-emerald-600",
|
|
20
|
+
text: "text-xs text-muted-foreground",
|
|
21
|
+
label: "font-medium text-foreground",
|
|
22
|
+
};
|
|
23
|
+
/** Visually hidden, from inline style so it cannot depend on the app's CSS. */
|
|
24
|
+
const VISUALLY_HIDDEN = {
|
|
25
|
+
position: "absolute",
|
|
26
|
+
width: 1,
|
|
27
|
+
height: 1,
|
|
28
|
+
padding: 0,
|
|
29
|
+
margin: -1,
|
|
30
|
+
overflow: "hidden",
|
|
31
|
+
clip: "rect(0, 0, 0, 0)",
|
|
32
|
+
whiteSpace: "nowrap",
|
|
33
|
+
border: 0,
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* How guessable a new password is: four bars and one plain sentence.
|
|
37
|
+
*
|
|
38
|
+
* **Only where a password is being chosen** — sign-up, reset, set, change, and a
|
|
39
|
+
* person setting one for someone else. Never on sign-in or a "confirm it's you"
|
|
40
|
+
* field: rating a password someone already has tells them nothing they can act
|
|
41
|
+
* on, and tells anyone watching the screen something about it.
|
|
42
|
+
*
|
|
43
|
+
* **It refuses nothing.** The meter is advice; whether a password is allowed is
|
|
44
|
+
* the app's rule — its minimum length, and a server check with
|
|
45
|
+
* `estimatePasswordStrength` if it chooses to refuse the weakest. So there is no
|
|
46
|
+
* `valid` here to wire a submit button to.
|
|
47
|
+
*
|
|
48
|
+
* Nothing shows for an empty field. Screen readers hear the strength once typing
|
|
49
|
+
* pauses and only when it has changed, through a polite live region — never a
|
|
50
|
+
* word per keystroke.
|
|
51
|
+
*/
|
|
52
|
+
export function PasswordStrength({ password, userInputs, minLength, messages, id, announceDelay = 1000, className, classNames, }) {
|
|
53
|
+
const c = { ...PASSWORD_STRENGTH_CLASS_NAMES, ...classNames };
|
|
54
|
+
const inputsKey = (userInputs ?? []).join("\u0000");
|
|
55
|
+
const estimate = React.useMemo(() => estimatePasswordStrength(password, {
|
|
56
|
+
userInputs: inputsKey ? inputsKey.split("\u0000") : [],
|
|
57
|
+
minLength,
|
|
58
|
+
messages,
|
|
59
|
+
}), [password, inputsKey, minLength, messages]);
|
|
60
|
+
const message = password ? `${estimate.label}. ${estimate.sentence}` : "";
|
|
61
|
+
const [announced, setAnnounced] = React.useState("");
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
if (!message) {
|
|
64
|
+
setAnnounced("");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const timer = setTimeout(() => setAnnounced(message), announceDelay);
|
|
68
|
+
return () => clearTimeout(timer);
|
|
69
|
+
}, [message, announceDelay]);
|
|
70
|
+
const filled = Math.max(estimate.score, 1);
|
|
71
|
+
const tone = c[`score${estimate.score}`];
|
|
72
|
+
return (_jsxs("div", { className: cx(c.root, className), "data-score": password ? estimate.score : undefined, hidden: !password, children: [password ? (_jsxs(_Fragment, { children: [_jsx("div", { className: c.bar, "aria-hidden": "true", children: [0, 1, 2, 3].map((segment) => (_jsx("span", { className: cx(c.segment, segment < filled && tone) }, segment))) }), _jsxs("p", { id: id, className: c.text, children: [_jsxs("span", { className: c.label, children: [estimate.label, "."] }), " ", estimate.sentence] })] })) : null, _jsx("span", { role: "status", "aria-live": "polite", style: VISUALLY_HIDDEN, children: announced })] }));
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=password-strength.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-strength.js","sourceRoot":"","sources":["../../src/password/password-strength.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface PasswordRevealOptions {
|
|
3
|
+
/** Controlled: whether the password is shown. Pair with `onRevealedChange`. */
|
|
4
|
+
revealed?: boolean;
|
|
5
|
+
/** Uncontrolled starting state. Hidden unless said otherwise. */
|
|
6
|
+
defaultRevealed?: boolean;
|
|
7
|
+
onRevealedChange?: (revealed: boolean) => void;
|
|
8
|
+
/** The input's id, for the toggle's `aria-controls`. */
|
|
9
|
+
inputId: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface PasswordReveal {
|
|
13
|
+
revealed: boolean;
|
|
14
|
+
setRevealed: (revealed: boolean) => void;
|
|
15
|
+
toggle: () => void;
|
|
16
|
+
/** Attach to the password input, merged with any ref of the host's own. */
|
|
17
|
+
inputRef: React.RefCallback<HTMLInputElement>;
|
|
18
|
+
/** Spread onto the input, after the host's own props. */
|
|
19
|
+
inputProps: {
|
|
20
|
+
type: "password" | "text";
|
|
21
|
+
spellCheck: false;
|
|
22
|
+
autoCapitalize: "off";
|
|
23
|
+
autoCorrect: "off";
|
|
24
|
+
"data-revealed": boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Spread onto the toggle button. It supplies no label: give it one. */
|
|
27
|
+
toggleProps: {
|
|
28
|
+
type: "button";
|
|
29
|
+
"aria-pressed": boolean;
|
|
30
|
+
"aria-controls": string;
|
|
31
|
+
disabled: boolean | undefined;
|
|
32
|
+
onClick: () => void;
|
|
33
|
+
onMouseDown: (event: React.MouseEvent) => void;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Show and hide a password, with everything a bare `type` switch gets wrong.
|
|
38
|
+
* `PasswordInput` is built on this; an app whose field is not a plain input —
|
|
39
|
+
* an MUI `TextField`, a floating-label row — spreads the two prop sets itself
|
|
40
|
+
* and keeps the same behaviour.
|
|
41
|
+
*
|
|
42
|
+
* - **The field keeps focus and the caret stays put.** Clicking the toggle
|
|
43
|
+
* would move focus to it, and switching `type` resets the selection in some
|
|
44
|
+
* browsers; so a pointer press never takes focus, and the caret is put back
|
|
45
|
+
* where it was. A keyboard user who tabbed to the toggle stays on it.
|
|
46
|
+
* - **It is hidden again the moment its form submits**, synchronously, before
|
|
47
|
+
* the browser reads the form. A password submitted from a text field is a
|
|
48
|
+
* value the browser may keep in its ordinary autofill history and offer in
|
|
49
|
+
* any other text box — the odd autocomplete a reveal must never cause.
|
|
50
|
+
* - **Spellcheck and autocorrect are off, always.** A revealed field is a text
|
|
51
|
+
* field, and enhanced spellcheck sends the text of text fields to a server.
|
|
52
|
+
*/
|
|
53
|
+
export declare function usePasswordReveal({ revealed: controlled, defaultRevealed, onRevealedChange, inputId, disabled, }: PasswordRevealOptions): PasswordReveal;
|
|
54
|
+
/** One ref from several — the host's (react-hook-form's `register`, say) and ours. */
|
|
55
|
+
export declare function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>): React.RefCallback<T>;
|
|
56
|
+
//# sourceMappingURL=reveal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal.d.ts","sourceRoot":"","sources":["../../src/password/reveal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,yDAAyD;IACzD,UAAU,EAAE;QACV,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;QAC1B,UAAU,EAAE,KAAK,CAAC;QAClB,cAAc,EAAE,KAAK,CAAC;QACtB,WAAW,EAAE,KAAK,CAAC;QACnB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,wEAAwE;IACxE,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;QAC9B,OAAO,EAAE,MAAM,IAAI,CAAC;QACpB,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;KAChD,CAAC;CACH;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EAAE,UAAU,EACpB,eAAuB,EACvB,gBAAgB,EAChB,OAAO,EACP,QAAQ,GACT,EAAE,qBAAqB,GAAG,cAAc,CA6ExC;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAO3F"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Show and hide a password, with everything a bare `type` switch gets wrong.
|
|
5
|
+
* `PasswordInput` is built on this; an app whose field is not a plain input —
|
|
6
|
+
* an MUI `TextField`, a floating-label row — spreads the two prop sets itself
|
|
7
|
+
* and keeps the same behaviour.
|
|
8
|
+
*
|
|
9
|
+
* - **The field keeps focus and the caret stays put.** Clicking the toggle
|
|
10
|
+
* would move focus to it, and switching `type` resets the selection in some
|
|
11
|
+
* browsers; so a pointer press never takes focus, and the caret is put back
|
|
12
|
+
* where it was. A keyboard user who tabbed to the toggle stays on it.
|
|
13
|
+
* - **It is hidden again the moment its form submits**, synchronously, before
|
|
14
|
+
* the browser reads the form. A password submitted from a text field is a
|
|
15
|
+
* value the browser may keep in its ordinary autofill history and offer in
|
|
16
|
+
* any other text box — the odd autocomplete a reveal must never cause.
|
|
17
|
+
* - **Spellcheck and autocorrect are off, always.** A revealed field is a text
|
|
18
|
+
* field, and enhanced spellcheck sends the text of text fields to a server.
|
|
19
|
+
*/
|
|
20
|
+
export function usePasswordReveal({ revealed: controlled, defaultRevealed = false, onRevealedChange, inputId, disabled, }) {
|
|
21
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultRevealed);
|
|
22
|
+
const revealed = controlled ?? uncontrolled;
|
|
23
|
+
const input = React.useRef(null);
|
|
24
|
+
const selection = React.useRef(null);
|
|
25
|
+
const latest = React.useRef({ controlled, onRevealedChange });
|
|
26
|
+
latest.current = { controlled, onRevealedChange };
|
|
27
|
+
const setRevealed = React.useCallback((next) => {
|
|
28
|
+
if (latest.current.controlled === undefined)
|
|
29
|
+
setUncontrolled(next);
|
|
30
|
+
latest.current.onRevealedChange?.(next);
|
|
31
|
+
}, []);
|
|
32
|
+
const toggle = React.useCallback(() => {
|
|
33
|
+
const node = input.current;
|
|
34
|
+
selection.current =
|
|
35
|
+
node && node.ownerDocument.activeElement === node ? [node.selectionStart, node.selectionEnd] : null;
|
|
36
|
+
setRevealed(!revealed);
|
|
37
|
+
}, [revealed, setRevealed]);
|
|
38
|
+
// Put the caret back after the type switch, while the field still has focus.
|
|
39
|
+
React.useLayoutEffect(() => {
|
|
40
|
+
const node = input.current;
|
|
41
|
+
const kept = selection.current;
|
|
42
|
+
selection.current = null;
|
|
43
|
+
if (!node || !kept || node.ownerDocument.activeElement !== node)
|
|
44
|
+
return;
|
|
45
|
+
const [start, end] = kept;
|
|
46
|
+
if (start !== null && end !== null)
|
|
47
|
+
node.setSelectionRange(start, end);
|
|
48
|
+
}, [revealed]);
|
|
49
|
+
const hideOnSubmit = React.useRef(null);
|
|
50
|
+
hideOnSubmit.current = () => {
|
|
51
|
+
// Before React re-renders: the browser reads the form as soon as this returns.
|
|
52
|
+
if (input.current)
|
|
53
|
+
input.current.type = "password";
|
|
54
|
+
if (revealed)
|
|
55
|
+
setRevealed(false);
|
|
56
|
+
};
|
|
57
|
+
const [form, setForm] = React.useState(null);
|
|
58
|
+
const inputRef = React.useCallback((node) => {
|
|
59
|
+
input.current = node;
|
|
60
|
+
// Only on attach: a ref detached and re-attached between renders must not churn state.
|
|
61
|
+
if (node)
|
|
62
|
+
setForm(node.form);
|
|
63
|
+
}, []);
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (!form)
|
|
66
|
+
return;
|
|
67
|
+
const hide = () => hideOnSubmit.current?.();
|
|
68
|
+
form.addEventListener("submit", hide);
|
|
69
|
+
form.addEventListener("reset", hide);
|
|
70
|
+
return () => {
|
|
71
|
+
form.removeEventListener("submit", hide);
|
|
72
|
+
form.removeEventListener("reset", hide);
|
|
73
|
+
};
|
|
74
|
+
}, [form]);
|
|
75
|
+
return {
|
|
76
|
+
revealed,
|
|
77
|
+
setRevealed,
|
|
78
|
+
toggle,
|
|
79
|
+
inputRef,
|
|
80
|
+
inputProps: {
|
|
81
|
+
type: revealed ? "text" : "password",
|
|
82
|
+
spellCheck: false,
|
|
83
|
+
autoCapitalize: "off",
|
|
84
|
+
autoCorrect: "off",
|
|
85
|
+
"data-revealed": revealed,
|
|
86
|
+
},
|
|
87
|
+
toggleProps: {
|
|
88
|
+
type: "button",
|
|
89
|
+
"aria-pressed": revealed,
|
|
90
|
+
"aria-controls": inputId,
|
|
91
|
+
disabled,
|
|
92
|
+
onClick: toggle,
|
|
93
|
+
// A pointer press must not take focus from the field.
|
|
94
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** One ref from several — the host's (react-hook-form's `register`, say) and ours. */
|
|
99
|
+
export function mergeRefs(...refs) {
|
|
100
|
+
return (node) => {
|
|
101
|
+
for (const ref of refs) {
|
|
102
|
+
if (typeof ref === "function")
|
|
103
|
+
ref(node);
|
|
104
|
+
else if (ref)
|
|
105
|
+
ref.current = node;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=reveal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal.js","sourceRoot":"","sources":["../../src/password/reveal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connextar/house",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "The pieces every app we build needs and none of them should own a copy of.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./dist/auth/index.d.ts",
|
|
31
31
|
"default": "./dist/auth/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./auth/second-factor": {
|
|
34
|
+
"types": "./dist/auth/second-factor-index.d.ts",
|
|
35
|
+
"default": "./dist/auth/second-factor-index.js"
|
|
36
|
+
},
|
|
33
37
|
"./account": {
|
|
34
38
|
"types": "./dist/account/index.d.ts",
|
|
35
39
|
"default": "./dist/account/index.js"
|
|
@@ -98,6 +102,14 @@
|
|
|
98
102
|
"types": "./dist/editor/emoji-index.d.ts",
|
|
99
103
|
"default": "./dist/editor/emoji-index.js"
|
|
100
104
|
},
|
|
105
|
+
"./password": {
|
|
106
|
+
"types": "./dist/password/index.d.ts",
|
|
107
|
+
"default": "./dist/password/index.js"
|
|
108
|
+
},
|
|
109
|
+
"./password/estimate": {
|
|
110
|
+
"types": "./dist/password/estimate-index.d.ts",
|
|
111
|
+
"default": "./dist/password/estimate-index.js"
|
|
112
|
+
},
|
|
101
113
|
"./house.css": {
|
|
102
114
|
"style": "./dist/house.css",
|
|
103
115
|
"default": "./dist/house.css"
|
|
@@ -120,6 +132,7 @@
|
|
|
120
132
|
},
|
|
121
133
|
"peerDependencies": {
|
|
122
134
|
"@anthropic-ai/sdk": ">=0.122.0",
|
|
135
|
+
"better-auth": ">=1.6.33 <1.8.0",
|
|
123
136
|
"next": "^16.0.0",
|
|
124
137
|
"openai": ">=7.10.0",
|
|
125
138
|
"react": "^19.0.0",
|
|
@@ -142,6 +155,9 @@
|
|
|
142
155
|
},
|
|
143
156
|
"zod": {
|
|
144
157
|
"optional": true
|
|
158
|
+
},
|
|
159
|
+
"better-auth": {
|
|
160
|
+
"optional": true
|
|
145
161
|
}
|
|
146
162
|
},
|
|
147
163
|
"devDependencies": {
|
|
@@ -153,6 +169,8 @@
|
|
|
153
169
|
"@types/react-dom": "^19.3.0",
|
|
154
170
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
155
171
|
"@typescript-eslint/parser": "^8.0.0",
|
|
172
|
+
"better-auth": "1.7.2",
|
|
173
|
+
"better-auth-1-6": "npm:better-auth@1.6.33",
|
|
156
174
|
"eslint": "^9.0.0",
|
|
157
175
|
"jsdom": "^27.4.0",
|
|
158
176
|
"next": "^16.3.3",
|