@enigmax/primitives 0.22.0 → 0.24.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/{chunk-QQFNAKMY.js → chunk-45UHLZYT.js} +1 -1
- package/dist/chunk-4VUHQFAT.js +130 -0
- package/dist/{chunk-D5A2ZMAG.js → chunk-4ZPMP47J.js} +6 -1
- package/dist/chunk-DZMHY3SU.js +777 -0
- package/dist/{chunk-FWVWX67R.js → chunk-FGBIZDV2.js} +3 -3
- package/dist/chunk-N6PDHMAX.js +213 -0
- package/dist/chunk-NONGREXC.js +248 -0
- package/dist/{chunk-R4ZAEE7V.js → chunk-QJJ34E4G.js} +34 -10
- package/dist/chunk-S2FPN5ID.js +115 -0
- package/dist/chunk-S7EE57YB.js +9 -0
- package/dist/chunk-VKL3DEIQ.js +804 -0
- package/dist/chunk-WSQC3PCC.js +466 -0
- package/dist/chunk-Z3VDE7OA.js +532 -0
- package/dist/clipboard-menu-B_ouitfS.d.ts +264 -0
- package/dist/color-D_rZ83Oc.d.ts +152 -0
- package/dist/color-OPV3BJV6.js +452 -0
- package/dist/{index-BHpOZncw.d.ts → index-ZPvlf9vs.d.ts} +53 -5
- package/dist/index.d.ts +6 -2
- package/dist/index.js +9 -4
- package/dist/keys-D2zJs1uB.d.ts +100 -0
- package/dist/next/index.d.ts +10 -3
- package/dist/next/index.js +22 -13
- package/dist/react/context-menu.d.ts +219 -0
- package/dist/react/context-menu.js +6 -0
- package/dist/react/index.d.ts +12 -5
- package/dist/react/index.js +21 -12
- package/dist/react/input.d.ts +3 -3
- package/dist/react/input.js +2 -1
- package/dist/react/palette.d.ts +1 -1
- package/dist/react/palette.js +3 -3
- package/dist/react/search.d.ts +1 -1
- package/dist/react/search.js +2 -2
- package/dist/react/select.d.ts +217 -0
- package/dist/react/select.js +7 -0
- package/dist/react/selection.d.ts +104 -0
- package/dist/react/selection.js +4 -0
- package/dist/react-router/index.d.ts +10 -3
- package/dist/react-router/index.js +22 -13
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.js +1 -1
- package/dist/{search-BD9-5O5U.d.ts → search-DYgqRp37.d.ts} +13 -1
- package/dist/{search-UQEXAPQB.js → search-PBZORZ7P.js} +1 -1
- package/dist/select-ClSy-J1f.d.ts +100 -0
- package/dist/selection-B_pmzHpy.d.ts +150 -0
- package/package.json +23 -2
- package/recipes/color/styles.css +143 -0
- package/recipes/context-menu/styles.css +185 -0
- package/recipes/input/styles.css +9 -0
- package/recipes/palette/styles.css +3 -0
- package/recipes/search/tailwind.tsx +3 -2
- package/recipes/select/styles.css +230 -0
- package/recipes/toast/styles.css +1 -1
- package/registry.json +432 -5
- package/src/core/clipboard-menu.ts +270 -0
- package/src/core/color.ts +248 -0
- package/src/core/context-menu.ts +694 -0
- package/src/core/keys.ts +264 -0
- package/src/core/search.ts +19 -0
- package/src/core/select.ts +404 -0
- package/src/core/selection.ts +648 -0
- package/src/index.ts +92 -1
- package/src/react/context-menu/context.ts +64 -0
- package/src/react/context-menu/index.tsx +108 -0
- package/src/react/context-menu/root.tsx +970 -0
- package/src/react/context-menu/styles.ts +194 -0
- package/src/react/index.ts +100 -1
- package/src/react/input/color-styles.ts +156 -0
- package/src/react/input/color.tsx +466 -0
- package/src/react/input/index.tsx +54 -6
- package/src/react/input/types.ts +59 -3
- package/src/react/palette/root.tsx +2 -2
- package/src/react/select/context.ts +56 -0
- package/src/react/select/index.tsx +96 -0
- package/src/react/select/root.tsx +839 -0
- package/src/react/select/styles.ts +238 -0
- package/src/react/selection/index.tsx +115 -0
- package/src/react/selection/use-selection.ts +260 -0
- package/dist/password-C8lG4Zm9.d.ts +0 -71
- /package/dist/{chunk-U3V4EHOB.js → chunk-MSOCCQGH.js} +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { F as FuseConstructor, c as SearchOptions } from './search-DYgqRp37.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The parts of a context menu that are not rendering: which branch is open, where the
|
|
5
|
+
* highlight is inside it, what a submenu's rows are once they have been fetched, and which
|
|
6
|
+
* of those rows survived the filter.
|
|
7
|
+
*
|
|
8
|
+
* Framework-agnostic like every other core here, and for the same reason: the arithmetic of
|
|
9
|
+
* a menu is not React. What IS specific to this one is that a menu is a TREE and a select is
|
|
10
|
+
* a list, so everything below is keyed by a PATH - the list of item ids from the root down to
|
|
11
|
+
* the open submenu - rather than by an index.
|
|
12
|
+
*
|
|
13
|
+
* The behaviour it copies is the Windows one, deliberately: a right-click opens it at the
|
|
14
|
+
* pointer, a submenu opens after a beat of hovering and closes after a beat of leaving,
|
|
15
|
+
* arrows walk the rows and skip everything that cannot be chosen, Right opens a submenu and
|
|
16
|
+
* Left goes back, and typing jumps to a row.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A row that does something. The default kind, so `type` can be left off. */
|
|
20
|
+
interface ContextMenuAction {
|
|
21
|
+
type?: "item";
|
|
22
|
+
/** Unique among its SIBLINGS. The path down the tree is built from these. */
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
/** A second line under the label, for a row whose consequence is not obvious. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Anything the renderer can draw - a node, a URL, a name. `unknown` because a core cannot know. */
|
|
28
|
+
icon?: unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Printed on the right, the way every desktop menu prints it: `"Mod+C"`, `"F2"`,
|
|
31
|
+
* `"Delete"`. Written by `shortcutTokens` for the platform it is rendered on, so one
|
|
32
|
+
* spec is `⌘C` on a Mac and `Ctrl+C` elsewhere.
|
|
33
|
+
*/
|
|
34
|
+
shortcut?: string;
|
|
35
|
+
/** Listed, announced as unavailable, never highlighted and never invoked. */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Deletes something. Rendered in the destructive colour and reported as such. */
|
|
38
|
+
destructive?: boolean;
|
|
39
|
+
/** A checkable row: a tick, or a radio dot when `group` is set. */
|
|
40
|
+
checked?: boolean;
|
|
41
|
+
/** Rows carrying the same group render together under its name, and check as a radio set. */
|
|
42
|
+
group?: string;
|
|
43
|
+
/** Extra words the filter should match. */
|
|
44
|
+
keywords?: string[];
|
|
45
|
+
/** A heading over this row's submenu. See `ContextMenuOptions.title` for what it is for. */
|
|
46
|
+
title?: string;
|
|
47
|
+
/** A submenu, known up front. An empty list is not a submenu: the row keeps no arrow. */
|
|
48
|
+
items?: readonly ContextMenuEntry[];
|
|
49
|
+
/**
|
|
50
|
+
* A submenu fetched on demand, once. What it returns is cached under this row's path for
|
|
51
|
+
* `cacheMs`, so reopening the branch shows the rows immediately instead of spinning
|
|
52
|
+
* again - which is the whole reason a slow submenu is bearable.
|
|
53
|
+
*/
|
|
54
|
+
loadItems?: () => Promise<readonly ContextMenuEntry[]>;
|
|
55
|
+
/** Put a filter in this submenu. `"auto"` (the default) adds one from `SEARCHABLE_FROM` rows. */
|
|
56
|
+
searchable?: boolean | "auto";
|
|
57
|
+
/** Anything the caller wants back in `onSelect`. */
|
|
58
|
+
data?: unknown;
|
|
59
|
+
}
|
|
60
|
+
/** A rule between two blocks of rows. Never highlighted, never counted by the keyboard. */
|
|
61
|
+
interface ContextMenuSeparator {
|
|
62
|
+
type: "separator";
|
|
63
|
+
id?: string;
|
|
64
|
+
}
|
|
65
|
+
/** A caption over a block of rows. Same rules as a separator: visible, unreachable. */
|
|
66
|
+
interface ContextMenuLabel {
|
|
67
|
+
type: "label";
|
|
68
|
+
id?: string;
|
|
69
|
+
label: string;
|
|
70
|
+
}
|
|
71
|
+
type ContextMenuEntry = ContextMenuAction | ContextMenuSeparator | ContextMenuLabel;
|
|
72
|
+
/** Whether an entry is a row the keyboard and the pointer can land on. */
|
|
73
|
+
declare function isAction(entry: ContextMenuEntry): entry is ContextMenuAction;
|
|
74
|
+
/** The fields the filter reads when the caller does not say. */
|
|
75
|
+
declare const CONTEXT_MENU_SEARCH_KEYS: string[];
|
|
76
|
+
/** Where a menu was opened. Viewport coordinates, which is what a pointer event reports. */
|
|
77
|
+
interface ContextMenuPoint {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
}
|
|
81
|
+
/** One open level: the root, then one per submenu below it. */
|
|
82
|
+
interface ContextMenuLevel {
|
|
83
|
+
/** Ids from the root down to the item that opened this level. Empty for the root. */
|
|
84
|
+
path: string[];
|
|
85
|
+
/** A heading over the rows, or null. Also the panel's accessible name. */
|
|
86
|
+
title: string | null;
|
|
87
|
+
/** Every entry at this level, separators and labels included. */
|
|
88
|
+
entries: ContextMenuEntry[];
|
|
89
|
+
/** What the panel shows: the entries, or what survived the filter. */
|
|
90
|
+
visible: ContextMenuEntry[];
|
|
91
|
+
/** Index into `visible`. -1 when nothing is highlighted. */
|
|
92
|
+
active: number;
|
|
93
|
+
query: string;
|
|
94
|
+
/** Whether this level draws a filter field. */
|
|
95
|
+
searchable: boolean;
|
|
96
|
+
/** An awaited submenu that has not answered yet. */
|
|
97
|
+
loading: boolean;
|
|
98
|
+
/** What the load rejected with, so the level can say so instead of staying empty. */
|
|
99
|
+
error: Error | null;
|
|
100
|
+
}
|
|
101
|
+
interface ContextMenuState {
|
|
102
|
+
open: boolean;
|
|
103
|
+
/** Where it was opened, in viewport coordinates. Null while closed. */
|
|
104
|
+
point: ContextMenuPoint | null;
|
|
105
|
+
/** The root level first, then one per open submenu. */
|
|
106
|
+
levels: ContextMenuLevel[];
|
|
107
|
+
}
|
|
108
|
+
interface ContextMenuOptions {
|
|
109
|
+
items?: readonly ContextMenuEntry[];
|
|
110
|
+
/**
|
|
111
|
+
* A heading over the rows, naming what the menu is acting ON - the file that was
|
|
112
|
+
* right-clicked, or "12 items selected". A menu opened at the pointer is the one surface
|
|
113
|
+
* with no context around it, so without this the rows are the only clue about what they
|
|
114
|
+
* will happen to.
|
|
115
|
+
*/
|
|
116
|
+
title?: string;
|
|
117
|
+
/** Fuse.js's constructor, for fuzzy filtering. Omit it for the built-in matcher. */
|
|
118
|
+
fuse?: FuseConstructor;
|
|
119
|
+
fuseOptions?: Record<string, unknown>;
|
|
120
|
+
matcher?: SearchOptions<ContextMenuEntry>["matcher"];
|
|
121
|
+
searchKeys?: string[];
|
|
122
|
+
/** How long a fetched submenu stays cached. 0 refetches every time. Default 5 minutes. */
|
|
123
|
+
cacheMs?: number;
|
|
124
|
+
/** Every state change. */
|
|
125
|
+
onChange?: (state: ContextMenuState) => void;
|
|
126
|
+
/** A row was invoked. The menu has already closed unless the row keeps it open. */
|
|
127
|
+
onSelect?: (item: ContextMenuAction, path: string[]) => void;
|
|
128
|
+
onOpenChange?: (open: boolean) => void;
|
|
129
|
+
}
|
|
130
|
+
interface ContextMenuInstance {
|
|
131
|
+
readonly state: ContextMenuState;
|
|
132
|
+
/**
|
|
133
|
+
* Open at a point, or reopen somewhere else - a second right-click MOVES the menu.
|
|
134
|
+
*
|
|
135
|
+
* Returns whether it opened. A menu with no rows to show does NOT open: an empty box at
|
|
136
|
+
* the pointer says nothing, and refusing here is what lets the trigger leave the press
|
|
137
|
+
* alone so the browser's own menu appears instead of nothing at all.
|
|
138
|
+
*/
|
|
139
|
+
open(point: ContextMenuPoint): boolean;
|
|
140
|
+
close(): void;
|
|
141
|
+
/** Move the highlight inside the deepest open level, skipping what cannot be chosen. */
|
|
142
|
+
move(key: ContextMenuMoveKey): void;
|
|
143
|
+
setActive(level: number, index: number): void;
|
|
144
|
+
setQuery(level: number, query: string): void;
|
|
145
|
+
/**
|
|
146
|
+
* Open the submenu of the row at `index` in `level`, closing any deeper branch.
|
|
147
|
+
*
|
|
148
|
+
* `focus` highlights its first row, which is what the KEYBOARD needs and what a pointer
|
|
149
|
+
* must not do: a row that looks hovered before the pointer arrives reads as the menu
|
|
150
|
+
* having chosen for you. Default false, so hovering is the plain case.
|
|
151
|
+
*/
|
|
152
|
+
openSubmenu(level: number, index: number, focus?: boolean): void;
|
|
153
|
+
/** Close every level below `level`. */
|
|
154
|
+
closeBelow(level: number): void;
|
|
155
|
+
/** Invoke a row: reports it, and closes unless it is disabled or opens a submenu. */
|
|
156
|
+
select(level: number, index: number): void;
|
|
157
|
+
/** Invoke whatever is highlighted in the deepest open level. */
|
|
158
|
+
selectActive(): void;
|
|
159
|
+
/** Right on a row with a submenu opens it; on any other row it does nothing. */
|
|
160
|
+
enterSubmenu(): void;
|
|
161
|
+
/** Left closes the deepest submenu and puts the highlight back on the row that opened it. */
|
|
162
|
+
leaveSubmenu(): void;
|
|
163
|
+
/** Jump to the row starting with what was typed, the way a desktop menu does. */
|
|
164
|
+
typeahead(character: string): void;
|
|
165
|
+
/** Drop what a fetched submenu returned, so the next open asks again. */
|
|
166
|
+
invalidate(path?: string[]): void;
|
|
167
|
+
update(options: Partial<ContextMenuOptions>): void;
|
|
168
|
+
subscribe(listener: (state: ContextMenuState) => void): () => void;
|
|
169
|
+
destroy(): void;
|
|
170
|
+
}
|
|
171
|
+
type ContextMenuMoveKey = "ArrowDown" | "ArrowUp" | "Home" | "End" | "PageDown" | "PageUp";
|
|
172
|
+
declare function createContextMenu(options?: ContextMenuOptions): ContextMenuInstance;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The rows every context menu on a desktop already has: Copy, Cut and Paste.
|
|
176
|
+
*
|
|
177
|
+
* A menu opened over a selection or over a field is expected to offer them - the browser's own
|
|
178
|
+
* does, and replacing that menu with a custom one takes them away without saying so. Which is
|
|
179
|
+
* why they are ON by default here and turned off with a prop, rather than the other way round:
|
|
180
|
+
* the defect is silent, so the default has to be the safe one.
|
|
181
|
+
*
|
|
182
|
+
* WHAT DECIDES THEY APPEAR is what was right-clicked, and it is read at the moment the menu
|
|
183
|
+
* opens, not from React state - by then the selection is settled (a `contextmenu` event fires
|
|
184
|
+
* after the press has adjusted it) and the caret is still where the visitor left it.
|
|
185
|
+
*
|
|
186
|
+
* - **Copy**, when there is selected text under the pointer.
|
|
187
|
+
* - **Cut**, when that selection is also in something writable.
|
|
188
|
+
* - **Paste**, in anything writable - and disabled when the clipboard is known to be empty.
|
|
189
|
+
*
|
|
190
|
+
* Not a React module: what a selection is, whether a field takes writes, and how text is put
|
|
191
|
+
* back into one are DOM questions, so a menu drawn by anything else gets the same rows.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/** The ids these rows carry. Namespaced, so they can never collide with a caller's own. */
|
|
195
|
+
declare const CLIPBOARD_PREFIX = "enigma:clipboard:";
|
|
196
|
+
type ClipboardAction = "copy" | "cut" | "paste";
|
|
197
|
+
/** What the menu was opened over, as far as the clipboard is concerned. */
|
|
198
|
+
interface ClipboardTarget {
|
|
199
|
+
/** The field or contenteditable under the pointer, or null when it is neither. */
|
|
200
|
+
editable: HTMLElement | null;
|
|
201
|
+
/** Whether that element takes writes: not disabled, not read-only. */
|
|
202
|
+
writable: boolean;
|
|
203
|
+
/** The selected text, from the field's own selection or the document's. */
|
|
204
|
+
selection: string;
|
|
205
|
+
/**
|
|
206
|
+
* Whether that text may be put on the clipboard.
|
|
207
|
+
*
|
|
208
|
+
* False for a password field. The clipboard is shared with every other application on the
|
|
209
|
+
* machine and is not cleared, so a menu that copies a password out of a masked field
|
|
210
|
+
* leaks it somewhere the visitor cannot see - and the browser's own menu refuses too.
|
|
211
|
+
*/
|
|
212
|
+
copyable: boolean;
|
|
213
|
+
/** Where the selection was, so it can be put back after the menu has taken focus. */
|
|
214
|
+
range: {
|
|
215
|
+
start: number;
|
|
216
|
+
end: number;
|
|
217
|
+
} | null;
|
|
218
|
+
}
|
|
219
|
+
interface ClipboardMenuLabels {
|
|
220
|
+
copy?: string;
|
|
221
|
+
cut?: string;
|
|
222
|
+
paste?: string;
|
|
223
|
+
}
|
|
224
|
+
interface ClipboardMenuOptions {
|
|
225
|
+
copy?: boolean;
|
|
226
|
+
cut?: boolean;
|
|
227
|
+
paste?: boolean;
|
|
228
|
+
labels?: ClipboardMenuLabels;
|
|
229
|
+
/** Whatever the renderer draws icons with. `unknown`, because a core cannot know. */
|
|
230
|
+
icons?: {
|
|
231
|
+
copy?: unknown;
|
|
232
|
+
cut?: unknown;
|
|
233
|
+
paste?: unknown;
|
|
234
|
+
};
|
|
235
|
+
/** The clipboard is known to be empty, so Paste is listed and greyed rather than missing. */
|
|
236
|
+
clipboardEmpty?: boolean;
|
|
237
|
+
}
|
|
238
|
+
/** What was right-clicked, read the moment the menu opens. */
|
|
239
|
+
declare function inspectClipboardTarget(node: EventTarget | null): ClipboardTarget;
|
|
240
|
+
/**
|
|
241
|
+
* Whether the clipboard has text in it, or null when that cannot be known.
|
|
242
|
+
*
|
|
243
|
+
* Null is the common answer and not a failure: reading the clipboard needs permission, and
|
|
244
|
+
* asking for it puts a browser prompt on screen just to decide whether to grey out a row -
|
|
245
|
+
* which is a worse trade than showing an enabled Paste that turns out to do nothing. So the
|
|
246
|
+
* permission is only READ, never requested, and the clipboard is only opened where it has
|
|
247
|
+
* already been granted.
|
|
248
|
+
*/
|
|
249
|
+
declare function clipboardHasText(): Promise<boolean | null>;
|
|
250
|
+
/** The rows for this target, in the order every desktop menu puts them. */
|
|
251
|
+
declare function clipboardEntries(target: ClipboardTarget, options?: ClipboardMenuOptions): ContextMenuEntry[];
|
|
252
|
+
/** Which clipboard row an id belongs to, or null for anything that is not one of ours. */
|
|
253
|
+
declare function clipboardAction(id: string): ClipboardAction | null;
|
|
254
|
+
/**
|
|
255
|
+
* Do what the row says.
|
|
256
|
+
*
|
|
257
|
+
* Called straight from the press that chose it, so the browser still counts it as a user
|
|
258
|
+
* gesture - which is what the clipboard API requires and what makes a paste possible at all.
|
|
259
|
+
* Returns whether the action happened: a refused permission and an empty clipboard are both
|
|
260
|
+
* "no", and neither is worth an exception the caller has to catch.
|
|
261
|
+
*/
|
|
262
|
+
declare function performClipboardAction(action: ClipboardAction, target: ClipboardTarget): Promise<boolean>;
|
|
263
|
+
|
|
264
|
+
export { CLIPBOARD_PREFIX as C, CONTEXT_MENU_SEARCH_KEYS as a, type ClipboardAction as b, type ClipboardMenuLabels as c, type ClipboardMenuOptions as d, type ClipboardTarget as e, type ContextMenuAction as f, type ContextMenuEntry as g, type ContextMenuInstance as h, type ContextMenuLabel as i, type ContextMenuLevel as j, type ContextMenuMoveKey as k, type ContextMenuOptions as l, type ContextMenuPoint as m, type ContextMenuSeparator as n, type ContextMenuState as o, clipboardAction as p, clipboardEntries as q, clipboardHasText as r, createContextMenu as s, inspectClipboardTarget as t, isAction as u, performClipboardAction as v };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Making a password, and judging one.
|
|
3
|
+
*
|
|
4
|
+
* Both are opt-in. A sign-in form wants neither: offering to generate a password where one
|
|
5
|
+
* already exists is noise, and scoring one the visitor cannot change is worse. They belong
|
|
6
|
+
* on a registration form and a change-password form, which is where an agent should switch
|
|
7
|
+
* them on.
|
|
8
|
+
*/
|
|
9
|
+
/** Character classes a generated password can draw from. */
|
|
10
|
+
interface PasswordAlphabet {
|
|
11
|
+
lowercase?: boolean;
|
|
12
|
+
uppercase?: boolean;
|
|
13
|
+
digits?: boolean;
|
|
14
|
+
symbols?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface GeneratePasswordOptions extends PasswordAlphabet {
|
|
17
|
+
/** Default 20. Long beats clever: length is the only term that scales. */
|
|
18
|
+
length?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Drop the characters that are read wrong off a screen or off paper - I l 1 O 0.
|
|
21
|
+
* Worth it when the password will be typed by hand, not worth the entropy otherwise.
|
|
22
|
+
*/
|
|
23
|
+
excludeAmbiguous?: boolean;
|
|
24
|
+
/** Characters to remove from every class, e.g. ones your backend rejects. */
|
|
25
|
+
exclude?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Guarantee at least one character from every class asked for. Most password policies
|
|
28
|
+
* demand it; it costs a little entropy, because it removes every password that happens
|
|
29
|
+
* to lack one.
|
|
30
|
+
*/
|
|
31
|
+
requireEachClass?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A random password from the classes asked for.
|
|
35
|
+
*
|
|
36
|
+
* @throws when the runtime has no CSPRNG, or when the options ask for something impossible
|
|
37
|
+
* (every class excluded, or a length too short to hold one of each).
|
|
38
|
+
*/
|
|
39
|
+
declare function generatePassword(options?: GeneratePasswordOptions): string;
|
|
40
|
+
type PasswordScore = 0 | 1 | 2 | 3 | 4;
|
|
41
|
+
interface PasswordStrengthReport {
|
|
42
|
+
/** 0 worst, 4 best. What the bars under the field render. */
|
|
43
|
+
score: PasswordScore;
|
|
44
|
+
/** Estimated bits of entropy after the penalties below. */
|
|
45
|
+
bits: number;
|
|
46
|
+
/**
|
|
47
|
+
* Why it scored what it scored, worst first. Show the first one; showing all of them
|
|
48
|
+
* turns a hint into a lecture.
|
|
49
|
+
*/
|
|
50
|
+
warnings: string[];
|
|
51
|
+
/** Empty field. Render nothing rather than a zero score, which reads as a failure. */
|
|
52
|
+
empty: boolean;
|
|
53
|
+
}
|
|
54
|
+
interface EstimateOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Values the visitor has already typed elsewhere - email, name, company. A password
|
|
57
|
+
* containing one of them is guessable by anyone who has the sign-up form in front of
|
|
58
|
+
* them, and no character-class rule catches it.
|
|
59
|
+
*/
|
|
60
|
+
userInputs?: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Score a password.
|
|
64
|
+
*
|
|
65
|
+
* The bits are an estimate and the bands are a convention, not a measurement - they exist
|
|
66
|
+
* to move a bar, not to certify anything. Swap this out for zxcvbn where the number has to
|
|
67
|
+
* mean something, and check the breach corpus for the cases no estimator can see.
|
|
68
|
+
*/
|
|
69
|
+
declare function estimatePasswordStrength(password: string, options?: EstimateOptions): PasswordStrengthReport;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Reading a colour, writing one, and converting between the two models a picker needs.
|
|
73
|
+
*
|
|
74
|
+
* Arithmetic only, like every other core here: no DOM, no framework. The picker drags a
|
|
75
|
+
* saturation/value square and a hue rail, which are HSV; a form field holds `#3b82f6`,
|
|
76
|
+
* `rgb(59, 130, 246)` or an `hsl()`, which is what a stylesheet and a database take. This
|
|
77
|
+
* module is the translation between them.
|
|
78
|
+
*
|
|
79
|
+
* Two things it does NOT do, both on purpose:
|
|
80
|
+
*
|
|
81
|
+
* - **Named colours.** `red`, `rebeccapurple` and the other 146 are a table nobody needs in
|
|
82
|
+
* a bundle to drag a square, and the DOM already resolves them for free (assign the name
|
|
83
|
+
* to `style.color` and read it back). The picker's canonical value is a hex string.
|
|
84
|
+
* - **Colour spaces past sRGB.** `oklch()` and `color()` describe colours a hex cannot, so
|
|
85
|
+
* accepting one here and handing back `#rrggbb` would silently clip it. Parsing returns
|
|
86
|
+
* null instead, which the field reports as unparseable rather than as a different colour.
|
|
87
|
+
*/
|
|
88
|
+
/** sRGB, 0-255 per channel, with alpha 0-1. The transport shape everything converts through. */
|
|
89
|
+
interface Rgb {
|
|
90
|
+
r: number;
|
|
91
|
+
g: number;
|
|
92
|
+
b: number;
|
|
93
|
+
a: number;
|
|
94
|
+
}
|
|
95
|
+
/** Hue 0-360, saturation and value 0-1, alpha 0-1. What the picker's two controls move. */
|
|
96
|
+
interface Hsv {
|
|
97
|
+
h: number;
|
|
98
|
+
s: number;
|
|
99
|
+
v: number;
|
|
100
|
+
a: number;
|
|
101
|
+
}
|
|
102
|
+
/** Hue 0-360, saturation and lightness 0-1, alpha 0-1. Only used by `hsl()` in and out. */
|
|
103
|
+
interface Hsl {
|
|
104
|
+
h: number;
|
|
105
|
+
s: number;
|
|
106
|
+
l: number;
|
|
107
|
+
a: number;
|
|
108
|
+
}
|
|
109
|
+
/** How a colour is written back into the field. */
|
|
110
|
+
type ColorFormat = "hex" | "rgb" | "hsl";
|
|
111
|
+
interface FormatColorOptions {
|
|
112
|
+
/** Write the alpha channel. Off drops it, so a half-transparent colour becomes opaque. */
|
|
113
|
+
alpha?: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* A colour string to sRGB, or null when it is not one.
|
|
117
|
+
*
|
|
118
|
+
* Null is the whole point of the return type: a field is unparseable for as long as someone
|
|
119
|
+
* is halfway through typing `#3b8`, and a picker that guesses at that moment fights the
|
|
120
|
+
* caret. Everything here is tolerant of what a person types - a missing `#`, upper case,
|
|
121
|
+
* stray spaces - and intolerant of what would be a guess.
|
|
122
|
+
*/
|
|
123
|
+
declare function parseColor(input: string): Rgb | null;
|
|
124
|
+
/** `#rrggbb`, or `#rrggbbaa` when alpha is asked for and the colour is not opaque. */
|
|
125
|
+
declare function toHex(color: Rgb, options?: FormatColorOptions): string;
|
|
126
|
+
/**
|
|
127
|
+
* A colour back to a string.
|
|
128
|
+
*
|
|
129
|
+
* The legacy comma syntax for `rgb()` and `hsl()`, deliberately: this string is going into a
|
|
130
|
+
* field someone will paste into a stylesheet, a spreadsheet or an older toolchain, and the
|
|
131
|
+
* space-and-slash form is the one those still refuse.
|
|
132
|
+
*/
|
|
133
|
+
declare function formatColor(color: Rgb, format?: ColorFormat, options?: FormatColorOptions): string;
|
|
134
|
+
/**
|
|
135
|
+
* sRGB to HSV, keeping a hue the arithmetic cannot see.
|
|
136
|
+
*
|
|
137
|
+
* THE colour picker bug, and it is in almost every hand-rolled one: hue is undefined at
|
|
138
|
+
* black, at white and at every grey, because those have no dominant channel. Recomputing it
|
|
139
|
+
* from the RGB therefore returns 0 - red - so dragging the square into its bottom-left
|
|
140
|
+
* corner and back out again resets a hue the visitor picked, and the rail jumps under their
|
|
141
|
+
* finger. `fallbackHue` is the hue they last chose, and it is what the picker keeps its own
|
|
142
|
+
* state for.
|
|
143
|
+
*/
|
|
144
|
+
declare function rgbToHsv(color: Rgb, fallbackHue?: number): Hsv;
|
|
145
|
+
declare function hsvToRgb(color: Hsv): Rgb;
|
|
146
|
+
/** sRGB to HSL. Same undefined hue at the greys, same reason to pass the one being kept. */
|
|
147
|
+
declare function rgbToHsl(color: Rgb, fallbackHue?: number): Hsl;
|
|
148
|
+
declare function hslToRgb(color: Hsl): Rgb;
|
|
149
|
+
/** Same colour, to the byte. Used to tell a value the picker wrote from one someone typed. */
|
|
150
|
+
declare function colorEquals(a: Rgb | null, b: Rgb | null): boolean;
|
|
151
|
+
|
|
152
|
+
export { type ColorFormat as C, type EstimateOptions as E, type FormatColorOptions as F, type GeneratePasswordOptions as G, type Hsl as H, type PasswordAlphabet as P, type Rgb as R, type Hsv as a, type PasswordScore as b, type PasswordStrengthReport as c, colorEquals as d, estimatePasswordStrength as e, formatColor as f, generatePassword as g, hslToRgb as h, hsvToRgb as i, rgbToHsv as j, parseColor as p, rgbToHsl as r, toHex as t };
|