@enigmax/primitives 0.17.0 → 0.18.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.
Files changed (89) hide show
  1. package/dist/button-CaXaqG_K.d.ts +63 -0
  2. package/dist/chunk-2QFTRNAZ.js +88 -0
  3. package/dist/chunk-6BGBYUSZ.js +114 -0
  4. package/dist/chunk-AU3H5WIY.js +107 -0
  5. package/dist/chunk-F25CGNQC.js +19 -0
  6. package/dist/chunk-HC2ME5PU.js +168 -0
  7. package/dist/chunk-HS3X3XCW.js +43 -0
  8. package/dist/chunk-IIT6U7LM.js +249 -0
  9. package/dist/chunk-IXVMRVD4.js +96 -0
  10. package/dist/chunk-KJINGUQN.js +188 -0
  11. package/dist/chunk-LKA2UG4P.js +542 -0
  12. package/dist/chunk-MMQPZGSU.js +161 -0
  13. package/dist/chunk-OCMI7R6H.js +79 -0
  14. package/dist/chunk-QYMUIW5I.js +28 -0
  15. package/dist/chunk-S653GLSF.js +17 -0
  16. package/dist/chunk-SNYUBXWQ.js +149 -0
  17. package/dist/chunk-U3V4EHOB.js +41 -0
  18. package/dist/chunk-UOSSNUSC.js +309 -0
  19. package/dist/chunk-WSB655JB.js +47 -0
  20. package/dist/chunk-XNNQRA35.js +31 -0
  21. package/dist/chunk-XQHCZAPJ.js +102 -0
  22. package/dist/chunk-ZCUFYBPB.js +154 -0
  23. package/dist/chunk-ZWR2EXHQ.js +55 -0
  24. package/dist/flags-BBJc9unY.d.ts +133 -0
  25. package/dist/index-D-ARvWpQ.d.ts +306 -0
  26. package/dist/index-DNXPtY9X.d.ts +144 -0
  27. package/dist/index.d.ts +16 -602
  28. package/dist/index.js +9 -1
  29. package/dist/input-BwXjFenq.d.ts +77 -0
  30. package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
  31. package/dist/network-D2LsBG_k.d.ts +39 -0
  32. package/dist/next/index.d.ts +20 -3
  33. package/dist/next/index.js +23 -3
  34. package/dist/notifications-BpVV6sel.d.ts +70 -0
  35. package/dist/password-3DRQYAYQ.js +2 -0
  36. package/dist/password-C8lG4Zm9.d.ts +71 -0
  37. package/dist/password-FB2CUEKJ.js +1 -0
  38. package/dist/react/button.d.ts +75 -0
  39. package/dist/react/button.js +4 -0
  40. package/dist/react/flag.d.ts +37 -0
  41. package/dist/react/flag.js +3 -0
  42. package/dist/react/index.d.ts +28 -312
  43. package/dist/react/index.js +22 -2
  44. package/dist/react/input.d.ts +4 -0
  45. package/dist/react/input.js +3 -0
  46. package/dist/react/marquee.d.ts +44 -0
  47. package/dist/react/marquee.js +3 -0
  48. package/dist/react/network.d.ts +20 -0
  49. package/dist/react/network.js +3 -0
  50. package/dist/react/notifications.d.ts +17 -0
  51. package/dist/react/notifications.js +3 -0
  52. package/dist/react/palette.d.ts +3 -0
  53. package/dist/react/palette.js +4 -0
  54. package/dist/react/relative-time.d.ts +21 -0
  55. package/dist/react/relative-time.js +3 -0
  56. package/dist/react/search.d.ts +30 -0
  57. package/dist/react/search.js +3 -0
  58. package/dist/react/slot.d.ts +47 -0
  59. package/dist/react/slot.js +2 -0
  60. package/dist/react/toast.d.ts +40 -0
  61. package/dist/react/toast.js +4 -0
  62. package/dist/react-router/index.d.ts +20 -3
  63. package/dist/react-router/index.js +23 -3
  64. package/dist/relative-time-YpRTG7YH.d.ts +106 -0
  65. package/dist/search-P4OHCRXD.js +50 -0
  66. package/package.json +52 -2
  67. package/registry.json +175 -18
  68. package/src/core/flags.ts +88 -42
  69. package/src/core/input-icons.ts +34 -0
  70. package/src/core/input.ts +5 -21
  71. package/src/core/palette.ts +0 -0
  72. package/src/index.ts +2 -2
  73. package/src/react/button.tsx +70 -4
  74. package/src/react/flag.tsx +18 -6
  75. package/src/react/index.ts +40 -5
  76. package/src/react/input/icon.tsx +17 -0
  77. package/src/react/input/index.tsx +307 -0
  78. package/src/react/input/password.tsx +174 -0
  79. package/src/react/input/search.tsx +82 -0
  80. package/src/react/input/types.ts +146 -0
  81. package/src/react/input/write-value.ts +18 -0
  82. package/src/react/palette/context.ts +60 -0
  83. package/src/react/palette/index.tsx +66 -0
  84. package/src/react/palette/root.tsx +621 -0
  85. package/src/react/slot.tsx +91 -0
  86. package/src/react/use-button.ts +3 -1
  87. package/dist/chunk-4GLYRF4B.js +0 -1259
  88. package/dist/chunk-JFNND6P4.js +0 -749
  89. package/src/react/input.tsx +0 -429
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The prop shapes for `<Input>`, split from the component so the per-type chunks can import
3
+ * them without importing the component - and so every type in here is erased at build,
4
+ * which is what keeps a `type="text"` field from pulling the password estimator or a search
5
+ * engine into the bundle.
6
+ *
7
+ * The API is ONE component keyed on `type`, the way HTML itself is: `type` is an attribute
8
+ * of one element, not a different element. Radix splits Select, Checkbox and Slider into
9
+ * their own primitives because those are composed widgets rather than an `<input>` - that
10
+ * split is about the DOM, not about the props. What varies here is which props EXIST, and
11
+ * that is a discriminated union: `strength` on a text field and `items` on a password field
12
+ * are compile errors rather than props that quietly do nothing.
13
+ */
14
+
15
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
16
+ import type { SearchMatch, FuseConstructor, SearchOptions } from "@/core/search";
17
+ import type { GeneratePasswordOptions, EstimateOptions, PasswordStrengthReport } from "@/core/password";
18
+
19
+ /** An extra button inside the field. The built-ins are the same shape. */
20
+ export interface FieldAction {
21
+ /** Stable id. Lands on `data-enigma-input-action`, and replaces a built-in of the same name. */
22
+ name: string;
23
+ /** Accessible name. Becomes `aria-label` and `title`. */
24
+ label: string;
25
+ icon: ReactNode;
26
+ onSelect: () => void;
27
+ /** Renders `aria-pressed`. Omit for actions that are not toggles. */
28
+ pressed?: boolean;
29
+ /** Default true. A false action is not rendered at all. */
30
+ visible?: boolean;
31
+ }
32
+
33
+ export type BreachStatus = "idle" | "checking" | "safe" | "breached" | "error";
34
+
35
+ export interface BreachState {
36
+ status: BreachStatus;
37
+ /** How many breaches the password appears in. 0 unless `status` is "breached". */
38
+ count: number;
39
+ /** Whatever the checker threw. The form decides whether that is worth showing. */
40
+ error: unknown;
41
+ }
42
+
43
+ export type BreachChecker = (password: string, options: { signal: AbortSignal; }) => Promise<{ breached: boolean; count: number; }>;
44
+
45
+ /** Every `type` a single `<input>` element takes, plus the two this component teaches. */
46
+ export type InputType =
47
+ | "text" | "email" | "password" | "search" | "tel" | "url" | "number"
48
+ | "date" | "datetime-local" | "month" | "week" | "time"
49
+ | "color" | "file" | "range" | "hidden" | "checkbox" | "radio";
50
+
51
+ export interface PasswordStrengthClassNames {
52
+ track?: string;
53
+ segment?: string;
54
+ label?: string;
55
+ warning?: string;
56
+ }
57
+
58
+ /** What every type has, whatever it is. */
59
+ export interface InputBaseProps extends Omit<ComponentPropsWithoutRef<"input">, "children" | "type"> {
60
+ /** The reveal toggle. Defaults to on for `type="password"` and off for everything else. */
61
+ reveal?: boolean;
62
+ revealLabels?: { show?: string; hide?: string; };
63
+ /** Extra buttons, or a replacement for a built-in by name. */
64
+ actions?: FieldAction[];
65
+ /** Which end the buttons sit at. Position them yourself; this only orders the markup. */
66
+ position?: "start" | "end";
67
+ /** Props for the element wrapping the field, its buttons and anything under them. */
68
+ wrapperProps?: ComponentPropsWithoutRef<"div">;
69
+ /** Props for the row holding the field and its buttons - this is what you position. */
70
+ fieldProps?: ComponentPropsWithoutRef<"div">;
71
+ /**
72
+ * Classes for the parts you cannot reach with a ref, which is what Tailwind needs.
73
+ * `className` still goes to the `<input>` itself, where you would expect it.
74
+ */
75
+ classNames?: {
76
+ actions?: string;
77
+ action?: string;
78
+ strength?: PasswordStrengthClassNames;
79
+ results?: string;
80
+ result?: string;
81
+ };
82
+ /** Rendered inside the wrapper, after everything the type adds. Your error goes here. */
83
+ children?: ReactNode;
84
+ }
85
+
86
+ /** The half only a password field has. */
87
+ export interface PasswordOnlyProps {
88
+ type: "password";
89
+ /** Offer to generate a password. `true` for the defaults, or the generator's options. */
90
+ generate?: boolean | GeneratePasswordOptions;
91
+ generateLabel?: string;
92
+ /** Show what was generated. On by default - a password nobody can read is not usable. */
93
+ revealOnGenerate?: boolean;
94
+ /**
95
+ * Also copy it to the clipboard. OFF by default and worth leaving off: the clipboard is
96
+ * shared with every other app on the machine and is not cleared.
97
+ */
98
+ copyOnGenerate?: boolean;
99
+ onGenerate?: (password: string) => void;
100
+ /** Score the password as it is typed, and render the meter under the field. */
101
+ strength?: boolean | EstimateOptions;
102
+ onStrengthChange?: (report: PasswordStrengthReport) => void;
103
+ /**
104
+ * Check the password against a breach corpus - pass `checkPasswordBreach` from
105
+ * @enigmax/utils, or your own. It is a prop rather than a built-in because it makes a
106
+ * network request, and that is not a decision a field should take on its own.
107
+ */
108
+ breach?: BreachChecker;
109
+ /** Quiet time before a check fires, in ms. Default 500. */
110
+ breachDelay?: number;
111
+ onBreachChange?: (state: BreachState) => void;
112
+ }
113
+
114
+ /** The half only a search field has. The palette is its own component, not a prop. */
115
+ export interface SearchOnlyProps<Item = unknown> {
116
+ type: "search";
117
+ /** What to search. Leave it out for a field that only reports the query. */
118
+ items?: Item[];
119
+ /** Which fields to read. `["title", "body"]`, or leave it out for every string field. */
120
+ keys?: SearchOptions<Item>["keys"];
121
+ /** Quiet time before a search runs, in ms. Default 150. */
122
+ delay?: number;
123
+ /** Cap the result list. Applies to the empty query too. */
124
+ limit?: number;
125
+ /** Fuse.js's constructor, for fuzzy matching. Without it, a substring matcher is used. */
126
+ fuse?: FuseConstructor;
127
+ fuseOptions?: SearchOptions<Item>["fuseOptions"];
128
+ /** Replaces the engine outright, and wins over `fuse`. */
129
+ matcher?: SearchOptions<Item>["matcher"];
130
+ onResults?: (matches: SearchMatch<Item>[], query: string) => void;
131
+ /** Render the results under the field. Without it, the field only reports them. */
132
+ renderResults?: (matches: SearchMatch<Item>[], query: string) => ReactNode;
133
+ /** Clear button. On by default for a search field, because the platform's own is not. */
134
+ clearable?: boolean;
135
+ clearLabel?: string;
136
+ }
137
+
138
+ /** Everything else: one `<input>`, its native props, and no extras to bundle. */
139
+ export interface PlainOnlyProps {
140
+ type?: Exclude<InputType, "password" | "search">;
141
+ }
142
+
143
+ export type InputProps<Item = unknown> = InputBaseProps & (PasswordOnlyProps | SearchOnlyProps<Item> | PlainOnlyProps);
144
+
145
+ /** Everything the implementation reads, after the union has done its job at the call site. */
146
+ export type AnyInputProps<Item = unknown> = InputBaseProps & Partial<Omit<PasswordOnlyProps, "type">> & Partial<Omit<SearchOnlyProps<Item>, "type">> & { type?: InputType; };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Write a value the way a keystroke would.
3
+ *
4
+ * Assigning `input.value` directly is invisible to React: it tracks the last value it
5
+ * rendered and skips the change event when the DOM disagrees with it. Going through the
6
+ * prototype's setter and dispatching a bubbling `input` event makes a generated password or
7
+ * a cleared search behave exactly like typing - which is what makes it work with a
8
+ * controlled field, an uncontrolled one, and a form library, without knowing which it is in.
9
+ *
10
+ * Its own module because both the field and the per-type chunks write values, and a helper
11
+ * imported from the field would tie a chunk back to the module that lazily loads it.
12
+ */
13
+ export function writeValue(input: HTMLInputElement, next: string): void {
14
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
15
+ if (setter) setter.call(input, next);
16
+ else input.value = next;
17
+ input.dispatchEvent(new Event("input", { bubbles: true }));
18
+ }
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ import type { SearchMatch } from "@/core/search";
4
+ import { createContext, useContext } from "react";
5
+ import type { RecentEntry } from "@/core/palette";
6
+
7
+ /** One line in the palette, whatever it came from. */
8
+ export interface PaletteRow<Item> {
9
+ /** Stable within a render. Used for React keys and for `aria-activedescendant`. */
10
+ id: string;
11
+ /** Which group it appears under. */
12
+ group: string;
13
+ /** `item` for a result, `recent` for a memory, `action` for a row the app declared. */
14
+ kind: "item" | "recent" | "action";
15
+ item?: Item;
16
+ recent?: RecentEntry;
17
+ /** Set for a result that came from the engine, so a renderer can show the score. */
18
+ match?: SearchMatch<Item>;
19
+ /** What running this row does. The palette closes afterwards unless this returns false. */
20
+ onSelect?: () => void | boolean;
21
+ label: string;
22
+ description?: string;
23
+ }
24
+
25
+ export interface PaletteContextValue<Item = unknown> {
26
+ open: boolean;
27
+ setOpen: (open: boolean) => void;
28
+ query: string;
29
+ setQuery: (query: string) => void;
30
+ rows: PaletteRow<Item>[];
31
+ active: number;
32
+ setActive: (index: number) => void;
33
+ /** Run a row: its own action, or the palette's `onSelect`, then remember it. */
34
+ select: (row: PaletteRow<Item> | undefined) => void;
35
+ /** Wipe the remembered searches. */
36
+ clearRecents: () => void;
37
+ forgetRecent: (entry: RecentEntry) => void;
38
+ /** True while the engine is loading or an async source is in flight. */
39
+ busy: boolean;
40
+ /** ids the parts need to point at each other. */
41
+ ids: { field: string; list: string; title: string; };
42
+ /** `Ctrl K` or `⌘ K`, whichever this platform uses. */
43
+ shortcutLabel: string;
44
+ triggerRef: { current: HTMLElement | null; };
45
+ fieldRef: { current: HTMLInputElement | null; };
46
+ rowId: (row: PaletteRow<Item>) => string;
47
+ }
48
+
49
+ /**
50
+ * Null rather than a default value: a part rendered outside its Root is a mistake with a
51
+ * clear fix, and a silent default would leave it half-working - a field that types into
52
+ * nothing, an item that never highlights.
53
+ */
54
+ export const PaletteContext = createContext<PaletteContextValue<never> | null>(null);
55
+
56
+ export function usePaletteContext<Item = unknown>(part: string): PaletteContextValue<Item> {
57
+ const value = useContext(PaletteContext);
58
+ if (!value) throw new Error(`<${part}> must be rendered inside <SearchPalette.Root>.`);
59
+ return value as unknown as PaletteContextValue<Item>;
60
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import type { PaletteRow } from "@/react/palette/context";
5
+ import {
6
+ PaletteRoot, PaletteTrigger, PaletteContent, PaletteField, PaletteList, PaletteItem, PaletteFooter,
7
+ type PaletteRootProps, type PaletteListProps
8
+ } from "@/react/palette/root";
9
+
10
+ export interface SearchPaletteProps<Item> extends PaletteRootProps<Item> {
11
+ placeholder?: string;
12
+ /** The trigger's own content. Pass null for a palette that only opens on the shortcut. */
13
+ trigger?: ReactNode | null;
14
+ /** Render one row, same signature as `SearchPalette.List`. */
15
+ renderItem?: PaletteListProps<Item>["children"];
16
+ empty?: ReactNode;
17
+ footer?: ReactNode;
18
+ }
19
+
20
+ /**
21
+ * The palette, assembled.
22
+ *
23
+ * ```tsx
24
+ * <SearchPalette items={docs} keys={["title", "body"]} onSelect={(doc) => go(doc.href)} />
25
+ * ```
26
+ *
27
+ * Everything it renders is a part you can render yourself instead - see `SearchPalette.Root`
28
+ * for the anatomy. This is the composition that covers the common case, not a different
29
+ * component: it is those parts, in the order they belong in.
30
+ */
31
+ export function SearchPalette<Item>({
32
+ placeholder = "Search",
33
+ trigger,
34
+ renderItem,
35
+ empty,
36
+ footer,
37
+ children,
38
+ ...root
39
+ }: SearchPaletteProps<Item>): ReactNode {
40
+ return (
41
+ <PaletteRoot<Item> {...root}>
42
+ {trigger !== null && <PaletteTrigger>{trigger}</PaletteTrigger>}
43
+ <PaletteContent>
44
+ <PaletteField placeholder={placeholder} aria-label={placeholder} />
45
+ <PaletteList<Item> empty={empty}>{renderItem}</PaletteList>
46
+ {footer === null ? null : <PaletteFooter>{footer}</PaletteFooter>}
47
+ </PaletteContent>
48
+ {children}
49
+ </PaletteRoot>
50
+ );
51
+ }
52
+
53
+ SearchPalette.Root = PaletteRoot;
54
+ SearchPalette.Trigger = PaletteTrigger;
55
+ SearchPalette.Content = PaletteContent;
56
+ SearchPalette.Field = PaletteField;
57
+ SearchPalette.List = PaletteList;
58
+ SearchPalette.Item = PaletteItem;
59
+ SearchPalette.Footer = PaletteFooter;
60
+
61
+ export {
62
+ PaletteRoot, PaletteTrigger, PaletteContent, PaletteField, PaletteList, PaletteItem, PaletteFooter,
63
+ type PaletteRootProps, type PaletteListProps, type PaletteSection
64
+ } from "@/react/palette/root";
65
+ export { usePaletteContext, type PaletteRow, type PaletteContextValue } from "@/react/palette/context";
66
+ export type { PaletteRow as SearchPaletteRow };