PasswordGenerator
Generates from a policy with a cryptographic source and no modulo bias, as random, pronounceable or a passphrase, shows true entropy, and refuses an impossible policy.
Demo
Password generator
129.0 bits Very strong
A passphrase
52.0 bits Fair
on_change has been called 0 times.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/password_generator.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/password_generator.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/password_generator.qwe
component PasswordGeneratorDemoLive() { state n = 0 <div.demo-stack> <PasswordGenerator id="password-generator-demo-phrase" title="A passphrase" mode="passphrase" count={5} on_change={(p) => { n += 1 }}/> <p.demo-note data-said="true">{`on_change has been called ${n} time${n == 1 ? "" : "s"}.`}</p> </div>}view PasswordGeneratorDemo() = <div.demo-stack> <PasswordGenerator id="password-generator-demo" length={20}/> <PasswordGeneratorDemoLive/> </div>Source
ui/password_generator.qwe
// PasswordGenerator: makes a password from a policy, in the browser, with the browser's// cryptographic source. Random from the classes chosen, pronounceable (consonants and// vowels in turn) or a passphrase of words; look-alikes and any other characters can// be left out. Each pick is free of modulo bias (a draw past the last whole round of// the alphabet is thrown away), and every class asked for appears because a password// without one is drawn again, not patched, which keeps all allowed passwords equally// likely, so the entropy shown is the true one, exact to the policy. A policy that// cannot be met is refused, saying why, and shows no password.//// import ui/password_generator.qwe use PasswordGenerator//// <PasswordGenerator length={20} symbols={false}/>// <PasswordGenerator id="phrase" mode="passphrase" count={6} separator="." on_change={(p) => { password = p }}/>//// mode: "random" (the default), "pronounceable" or "passphrase". length: the characters// (16; 1 to 256). lower, upper, digits, symbols: the classes (all true). lookalikes:// false leaves out characters easily mistaken (i l 1 L o 0 O; true keeps them). exclude:// other characters to leave out. count: the words of a passphrase (5; 1 to 24).// separator: between them ("-"). words: your own word list, for a passphrase (1,368// common English words by default). id: a prefix for its ids ("password-generator"),// unique on the page. name: the password field's name ("password"). title: the heading.// class: more classes for its outer element.// on_change(password): inside one of your components, called with each new password,// "" when the policy is refused.//// A pronounceable password takes one digit and one symbol at its end when those are// on, and capitals anywhere when both cases are. It is easy to say because it has// far fewer choices: its entropy shows that.//// For a program's own use: password_generator_check(policy) gives the reasons a policy// cannot be met, password_generator_bits(policy) its entropy, and// password_generator_make(policy, draw) a password, with draw(n) a fair number below n.// In a browser password_generator_draw() makes that draw; on a server, use `crypto`.import ui/theme.qwe use ui_clslet password_generator_css = css`.ui-pwgen { display: grid; gap: 14px; min-width: 0; max-width: 560px; padding: 20px 22px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.45 var(--ui-font); }.ui-pwgen .ui-pwgen-title { margin: 0; font-size: 16px; font-weight: 650; }.ui-pwgen-out { display: flex; flex-wrap: wrap; gap: 6px; }.ui-pwgen-value { box-sizing: border-box; flex: 1 1 220px; min-width: 0; height: 40px; padding: 0 10px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-subtle); color: var(--ui-fg); font: 14px var(--ui-mono); }.ui-pwgen-value:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-pwgen-btn { height: 40px; padding: 0 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 500 13px/1 var(--ui-font); cursor: pointer; }.ui-pwgen-btn:hover:not(:disabled) { border-color: var(--ui-strong); background: var(--ui-subtle); }.ui-pwgen-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-pwgen-btn:disabled { opacity: .5; cursor: not-allowed; }.ui-pwgen-strength { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-pwgen-strength b { color: var(--ui-fg); font-variant-numeric: tabular-nums; }.ui-pwgen-said { color: var(--ui-muted); font-size: 13px; }.ui-pwgen-reasons { margin: 0; padding: 10px 12px 10px 28px; border-radius: var(--ui-radius-sm); background: var(--ui-danger-soft); color: var(--ui-danger); font-size: 13px; }.ui-pwgen-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }.ui-pwgen-field { display: grid; gap: 5px; min-width: 0; }.ui-pwgen-field label { font-size: 13px; font-weight: 500; }.ui-pwgen-input { box-sizing: border-box; width: 100%; height: 36px; padding: 0 10px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); }.ui-pwgen-input:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-pwgen-input[aria-invalid="true"] { border-color: var(--ui-danger); }.ui-pwgen-ticks { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0; padding: 0; border: 0; }.ui-pwgen-ticks legend { padding: 0 0 4px; font-size: 13px; font-weight: 500; }.ui-pwgen-tick { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }.ui-pwgen-tick input { margin: 0; accent-color: var(--ui-accent); }.ui-pwgen-tick input:focus-visible { outline: none; box-shadow: var(--ui-focus); }`let password_generator_lower = "abcdefghijklmnopqrstuvwxyz"let password_generator_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"let password_generator_digits = "0123456789"let password_generator_symbols = "!@#$%^&*()-_=+[]{};:,.<>?/"let password_generator_similar = "il1Lo0O"let password_generator_vowels = "aeiou"let password_generator_consonants = "bcdfghjklmnprstvwz"let password_generator_most_tries = 20000// Short common words, none rude and none much alike, for a passphrase to be typed.let password_generator_word_text = "able about above acid acorn actor adapt adult after again agent agree ahead aim air alarm album alert alike alive alley allow alone along alpha alter amber among ample angle ankle apple apply april apron arena argue arise armor army arrow art ash aside asked aunt autumn avoid awake award aware axis bacon badge bagel baker balance ball bamboo banana band bank barn basic basin basket batch bath beach beam bean bear beard beat bed bee beef begin bell belt bench berry best bike bird birth black blade blank blaze blend bless blind block bloom blue board boat body bold bolt bond bone bonus book boost boot border boss both bottle bowl box brain branch brand brass brave bread break brick bridge brief bright bring broad brook brown brush bubble bucket build bulb bunch bus bush butter button cabin cable cactus cage cake calf call calm camel camp canal candle candy canoe canvas canyon cape car card care cargo carpet carry cart case cash castle cat catch cause cave cedar ceiling cell cement center chain chair chalk champ chance change chapter charm chart chase cheap check cheese chef cherry chess chest chief child chime choice chord circle city civil claim clam class clay clean clear clerk click cliff climb clock close cloth cloud clover club coach coast coat cocoa coconut code coffee coin cold color comb comet comic common cook cool copper copy coral cork corn corner cotton couch count country couple cousin cover cow crab craft crane crate cream creek crew cricket crisp crop cross crow crowd crown cube curl curve cycle daily dairy daisy dance dark dart data date dawn day deal dear debate decade deck deep deer delta denim depth desert desk detail dial diary diesel dinner direct dish ditch dive dock doctor dog dollar dolphin domain donkey door double dough dove down dozen draft dragon drama draw dream dress drift drill drink drive drop drum dry duck dune dusk dust eagle early earth easel east easy echo edge eight elbow elder elect elm ember empty end engine enjoy enter entry equal error even event ever every exact exit extra fable face fact factor fair fairy false family fan farm fast feast feather fence fern ferry fever fiber field fifth fifty fig film final finch find fine finger finish fire firm first fish five fix flag flame flash flat flavor fleet flight flint float flock floor flour flow flower fluid flute fly foam focus fog fold folk food foot force forest forge fork form fort forty forum fossil four fox frame free fresh friend frog front frost fruit fuel full fun fund funny fur future gain galaxy game garden garlic gas gate gather gauge gear gem gentle giant gift ginger giraffe give glad glass globe glove glow glue goal goat gold golf good goose grain grand grape graph grass gravel gray great green greet grid grill grin grip ground group grove grow guard guess guest guide guitar gulf habit hair half hall hammer hand happy harbor hard harvest hat have hawk hay head health hear heart heat heavy hedge help hen herb herd hero hidden high hill hint hip hire hobby hockey hold hole holiday home honest honey hood hook hope horn horse hotel hour house hub human humor hundred hunt hurry hut ice icon idea image index inch indoor ink inner input insect inside invite iron island item ivory ivy jacket jade jam jar jazz jeans jelly jet jewel job join joke jolly journal joy judge juice jump jungle junior just kayak keep kettle key kick kid kind king kiosk kit kite kitten kiwi knee knife knock knot know koala label lace ladder lake lamb lamp land lane large laser last late laugh lava lawn layer lead leaf lean learn leather left lemon lens lesson letter level lever lift light like lily lime line linen link lion list little live lizard load loaf lobby local lock lodge logic long look loop lotus loud love low lucky lunar lunch machine magic magnet maid mail main major make mango manor map maple marble march market mask mass master mat match math matter maybe meadow meal medal media melody melon member memory menu merge merit metal meter middle mild milk mill mind mineral mint minute mirror mist mix model modern moment money monkey month moon moose morning moss mother motor mount mouse mouth move movie much mud mug mule music name nap narrow nation nature navy near neat neck need needle nest net never new next nice night nine noble noise noodle noon north nose note notice novel now number nurse nut oak oar oasis ocean octave odd offer office often oil old olive omega onion only open opera option orange orbit orchid order organ other otter ounce outer oval oven over owl owner oxygen oyster pace pack paddle page paint pair palace palm pan panda panel paper parade parcel park parrot part party pass past pasta patch path pause paw peace peach peak peanut pear pearl pebble pedal pen pencil penny people pepper perfect permit person pet phone photo piano pick picnic picture pie piece pig pigeon pike pillow pilot pin pine pink pipe pistachio pitch pizza place plain plan planet plant plate play plaza plenty plot plum plus pocket poem poet point polar pole policy polish pond pony pool poppy porch port post pot potato pouch powder power praise prairie press price pride prime print prize proof proud prune pump pupil puppy purple purse push puzzle quail quart queen quest quick quiet quill quilt quite quiz quote rabbit raccoon race rack radar radio raft rail rain rainbow raise rake ranch range rapid rare rate raven raw reach read ready real reason rebel recipe record red reef region relax relay remote rent repair reply report rest result return ribbon rice rich ride ridge right ring ripe rise river road robin robot rock rocket roll roof room root rope rose rough round route row royal rubber ruby rug rule ruler run rural sack sad saddle safe sage sail salad salmon salon salt same sample sand satin sauce save saw scale scarf scene school science scoop scope score scout screen sea seal search season seat second secret seed seek self sell send sense series serve seven shade shadow shake shape share shark sharp shawl sheep sheet shelf shell shield shift shine ship shirt shoe shop shore short shout show shrimp side sign silk silver simple sing sink sister six size skate sketch ski skill skirt sky slate sled sleep slice slide slim slope slow small smart smile smoke smooth snail snake snow soap soccer sock sofa soft soil solar solid some song soon sound soup source south space spark speak speed spell spice spider spin spoon sport spot spray spring spruce square squid stable stack staff stage stair stamp stand star start state station stay steam steel stem step stick still stock stone stool stop store storm story stove strap straw stream street stripe strong student study style sugar suit summer summit sun sunny super sure surf swan sweet swift swim swing switch table tackle tail take tale talk tall tank tape target task taste taxi tea teach team tell ten tennis tent term test text thank theme thick thin think third three thumb thunder ticket tide tiger tile time tiny tip tire title toast today toe token tomato tone tool tooth top topic torch total touch tour towel tower town toy trace track trade trail train transit tray treat tree trend trial tribe trick trim trip trout truck true trumpet trunk trust truth try tube tulip tuna tune tunnel turkey turn turtle tutor twelve twenty twice twin two type uncle under union unit until upper urban use usual valley value valve van vanilla vapor vase vault velvet verse very vest vessel video view village vine violet violin visit vista vital voice volume vote voyage wagon wait walk wall walnut wander warm wash watch water wave wax way wealth wear weather weave web week weight well west wet whale wheat wheel when white whole wide wild will willow win wind window wine wing winter wire wise wish wolf wood wool word work world worth wrap wren write yard yarn year yellow yes yogurt young zebra zero zinc zone"fn password_generator_wordlist() -> list = split(password_generator_word_text, " ")// What is left of an alphabet after what the policy leaves out.fn password_generator_keep(alphabet: str, p: map) -> list { let out = (p.lookalikes == false ? password_generator_similar : "") + (p.exclude ?? "") unique(split(alphabet, "")) | filter((c) => !contains(out, c))}// The classes on, and what is left of each: [{ name, chars }].fn password_generator_classes(p: map) -> list = [{ on: p.lower, name: "Lower case", text: password_generator_lower }, { on: p.upper, name: "Capitals", text: password_generator_upper }, { on: p.digits, name: "Digits", text: password_generator_digits }, { on: p.symbols, name: "Symbols", text: password_generator_symbols }] | filter((c) => c.on == true) | map((c) => { name: c.name, chars: password_generator_keep(c.text, p) })// The letters of a pronounceable password, by place: [consonants, vowels].fn password_generator_sounds(p: map) -> list { let both = p.lower == true && p.upper == true let cased = (s: str) => both ? s + upper(s) : (p.upper == true ? upper(s) : s) [password_generator_keep(cased(password_generator_consonants), p), password_generator_keep(cased(password_generator_vowels), p)]}// How many characters a pronounceable password spends on its digit and symbol.fn password_generator_tail(p: map) -> int = (p.digits == true ? 1 : 0) + (p.symbols == true ? 1 : 0)// The word list in use, each once.fn password_generator_list_of(p: map) -> list { let given = type_of(p.words) == "list" ? p.words | filter((w) => type_of(w) == "str" && trim(w) != "") | map(trim) : password_generator_wordlist() unique(given)}// Why a policy cannot be met: a list of sentences, empty when it can.fn password_generator_check(p: map) -> list { if p.mode == "passphrase" { let n = p.count let list_size = len(password_generator_list_of(p)) let out = [] if type_of(n) != "int" { out = push(out, "The number of words must be a whole number.") } if type_of(n) == "int" && (n < 1 || n > 24) { out = push(out, "A passphrase has 1 to 24 words.") } if list_size < 2 { out = push(out, `The word list needs at least 2 different words; it has ${list_size}.`) } return out } let n = p.length if type_of(n) != "int" { return ["The length must be a whole number."] } if n < 1 || n > 256 { return ["The length must be from 1 to 256."] } if p.mode == "pronounceable" { let sounds = password_generator_sounds(p) let room = n - password_generator_tail(p) let out = [] if p.lower != true && p.upper != true { out = push(out, "A pronounceable password needs letters: turn on lower case or capitals.") } else if len(sounds[0]) == 0 { out = push(out, "Every consonant is left out, so nothing can be said.") } else if len(sounds[1]) == 0 { out = push(out, "Every vowel is left out, so nothing can be said.") } if room < 2 { out = push(out, `A length of ${n} leaves ${max([room, 0])} letter${room == 1 ? "" : "s"} after the digit and symbol; pronounceable needs at least 2.`) } if p.digits == true && len(password_generator_keep(password_generator_digits, p)) == 0 { out = push(out, "Digits are on, but every digit is left out.") } if p.symbols == true && len(password_generator_keep(password_generator_symbols, p)) == 0 { out = push(out, "Symbols are on, but every symbol is left out.") } return out } let classes = password_generator_classes(p) if len(classes) == 0 { return ["Choose at least one kind of character."] } let empty = classes | filter((c) => len(c.chars) == 0) | map((c) => `${c.name} are on, but every one of them is left out.`) if len(empty) > 0 { return empty } if n < len(classes) { return [`A length of ${n} cannot hold one each of ${len(classes)} kinds of character: make it at least ${len(classes)}, or turn one off.`] } []}// The entropy of a policy that can be met, in bits. In random mode it is exact to the// rule that each class appears: of the N^L strings, the share that have every class,// by inclusion and exclusion over the classes.fn password_generator_bits(p: map) -> float { if p.mode == "passphrase" { return p.count * math.log2(len(password_generator_list_of(p))) } if p.mode == "pronounceable" { let sounds = password_generator_sounds(p) let room = p.length - password_generator_tail(p) let vowels = floor(room / 2) let digit = p.digits == true ? math.log2(len(password_generator_keep(password_generator_digits, p))) : 0 let symbol = p.symbols == true ? math.log2(len(password_generator_keep(password_generator_symbols, p))) : 0 return (room - vowels) * math.log2(len(sounds[0])) + vowels * math.log2(len(sounds[1])) + digit + symbol } let sizes = password_generator_classes(p) | map((c) => len(c.chars)) let total = sum(sizes) let subsets = range(pow(2, len(sizes))) let share = sum(subsets | map((mask) => { let picked = enumerate(sizes) | filter((e) => bit.and(bit.shr(mask, e[0]), 1) == 1) let gone = sum(picked | map((e) => e[1])) (len(picked) % 2 == 0 ? 1 : -1) * pow((total - gone) / total, p.length) })) p.length * math.log2(total) + (share > 0 ? math.log2(share) : 0)}// A fair number below n from a source of 32-bit numbers: draws at or past the last// whole round of n are thrown away, which is what keeps `%` from favouring low numbers.fn password_generator_below(n: int, next32) -> int { let span = 4294967296 let limit = span - span % n let v = next32() while v >= limit { v = next32() } v % n}// draw(n) on the browser's cryptographic source, 64 numbers at a time.fn password_generator_draw() { let buffer = [] let at = 0 let next32 = () => { if at >= len(buffer) { buffer = js("Array.from", js("crypto.getRandomValues", js_new("Uint32Array", 64))) at = 0 } at += 1 buffer[at - 1] } (n) => password_generator_below(n, next32)}fn password_generator_pick(items: list, draw) = items[draw(len(items))]// A password for a policy that can be met.fn password_generator_make(p: map, draw) -> str { if p.mode == "passphrase" { let words = password_generator_list_of(p) return join(range(p.count) | map((_) => password_generator_pick(words, draw)), p.separator ?? "-") } if p.mode == "pronounceable" { let sounds = password_generator_sounds(p) let room = p.length - password_generator_tail(p) let letters = range(room) | map((i) => password_generator_pick(sounds[i % 2], draw)) let digit = p.digits == true ? [password_generator_pick(password_generator_keep(password_generator_digits, p), draw)] : [] let symbol = p.symbols == true ? [password_generator_pick(password_generator_keep(password_generator_symbols, p), draw)] : [] return join(letters + digit + symbol, "") } let classes = password_generator_classes(p) let every = flatten(classes | map((c) => c.chars)) let tries = 0 let made = [] let whole = false while !whole && tries < password_generator_most_tries { made = range(p.length) | map((_) => password_generator_pick(every, draw)) whole = all(classes, (c) => any(made, (x) => contains(c.chars, x))) tries += 1 } join(made, "")}// What a number of bits comes to, for people.fn password_generator_rating(bits) -> str = bits < 40 ? "Weak" : (bits < 60 ? "Fair" : (bits < 80 ? "Strong" : "Very strong"))// Bits cut to one decimal: never more than is there.fn password_generator_bits_text(bits) -> str { let tenths = floor(bits * 10) `${floor(tenths / 10)}.${tenths % 10}`}// A count from a field, or the text when it is no whole number.fn password_generator_count(text) = re.test(trim(str(text)), "^\\d{1,6}$") ? int(trim(str(text))) : text// A box as a view: its props are live, so ticking it keeps it, and the focus on it.view PasswordGeneratorTick(label: str, on: bool, on_set = null) = <label.ui-pwgen-tick><input type="checkbox" checked={on} @change={(e) => { on_set(e.checked) }}/>{label}</label>component PasswordGenerator(mode: str = "random", length: int = 16, lower: bool = true, upper: bool = true, digits: bool = true, symbols: bool = true, lookalikes: bool = true, exclude: str = "", count: int = 5, separator: str = "-", words: list? = null, id: str = "password-generator", name: str = "password", title: str = "Password generator", on_change = null, class: str? = null) { style global password_generator_css state how = mode state length_text = str(length) state count_text = str(count) state use_lower = lower state use_upper = upper state use_digits = digits state use_symbols = symbols state keep_similar = lookalikes state left_out = exclude state joiner = separator state password = "" state said = "" let policy = { mode: how, length: password_generator_count(length_text), count: password_generator_count(count_text), lower: use_lower, upper: use_upper, digits: use_digits, symbols: use_symbols, lookalikes: keep_similar, exclude: left_out, separator: joiner, words } let reasons = password_generator_check(policy) let fine = len(reasons) == 0 let bits = fine ? password_generator_bits(policy) : 0 let phrase = how == "passphrase" let fresh = () => { let p = policy let can = len(password_generator_check(p)) == 0 password = can ? password_generator_make(p, password_generator_draw()) : "" said = "" on_change && on_change(password) } let copy = () => { if js("isSecureContext") != true { said = "Could not copy: select it instead" } else { js("navigator.clipboard.writeText", password).then((_ = null) => { said = "Copied" }, (_ = null) => { said = "Could not copy: select it instead" }) } } <section class={ui_cls(["ui-pwgen", class])} id={id} aria-labelledby={`${id}-title`} @mount={(el) => { fresh() }}> <h3.ui-pwgen-title id={`${id}-title`}>{title}</h3> <div.ui-pwgen-out> <input.ui-pwgen-value type="text" id={`${id}-value`} name={name} value={password} readonly="true" autocomplete="off" spellcheck="false" aria-label="The password"/> <button.ui-pwgen-btn type="button" data-copy="true" disabled={password == ""} @click={copy}>Copy</button> <button.ui-pwgen-btn type="button" data-new="true" disabled={!fine} @click={fresh}>New</button> </div> {!fine && <ul.ui-pwgen-reasons role="alert">{for [i, r] in enumerate(reasons) => <li key={i}>{r}</li>}</ul>} {fine && <p.ui-pwgen-strength aria-live="polite"><b>{`${password_generator_bits_text(bits)} bits`}</b> <span>{password_generator_rating(bits)}</span></p>} <span.ui-pwgen-said role="status">{said}</span> <div.ui-pwgen-form> <div.ui-pwgen-field> <label for={`${id}-mode`}>Kind</label> <select.ui-pwgen-input id={`${id}-mode`} @change={(e) => { how = e.value; fresh() }}> <option value="random" selected={how == "random"}>Random</option> <option value="pronounceable" selected={how == "pronounceable"}>Pronounceable</option> <option value="passphrase" selected={how == "passphrase"}>Passphrase</option> </select> </div> {!phrase && <div.ui-pwgen-field> <label for={`${id}-length`}>Length</label> <input.ui-pwgen-input type="text" inputmode="numeric" id={`${id}-length`} value={length_text} autocomplete="off" @input={(e) => { length_text = e.value; fresh() }}/> </div>} {phrase && <div.ui-pwgen-field> <label for={`${id}-count`}>Words</label> <input.ui-pwgen-input type="text" inputmode="numeric" id={`${id}-count`} value={count_text} autocomplete="off" @input={(e) => { count_text = e.value; fresh() }}/> </div>} {phrase && <div.ui-pwgen-field> <label for={`${id}-separator`}>Between words</label> <input.ui-pwgen-input type="text" id={`${id}-separator`} value={joiner} autocomplete="off" @input={(e) => { joiner = e.value; fresh() }}/> </div>} {!phrase && <fieldset.ui-pwgen-ticks> <legend>Use</legend> <PasswordGeneratorTick label="a to z" on={use_lower} on_set={(v) => { use_lower = v; fresh() }}/> <PasswordGeneratorTick label="A to Z" on={use_upper} on_set={(v) => { use_upper = v; fresh() }}/> <PasswordGeneratorTick label="0 to 9" on={use_digits} on_set={(v) => { use_digits = v; fresh() }}/> <PasswordGeneratorTick label="Symbols" on={use_symbols} on_set={(v) => { use_symbols = v; fresh() }}/> <PasswordGeneratorTick label="Allow look-alikes (i l 1 L o 0 O)" on={keep_similar} on_set={(v) => { keep_similar = v; fresh() }}/> </fieldset>} {!phrase && <div.ui-pwgen-field> <label for={`${id}-exclude`}>Leave out these</label> <input.ui-pwgen-input type="text" id={`${id}-exclude`} value={left_out} autocomplete="off" spellcheck="false" @input={(e) => { left_out = e.value; fresh() }}/> </div>} </div> </section>}Related
AmountInputReads 1.5k, 2m, 1,234.50, 1.234,50, $12 and EUR 40, decides which mark groups, shows the normalised amount, flags what could go two ways, and sends a real decimal.3 views · 8 downloadsAuthFormSign in, sign up, forgot and reset password in one form that switches in place, each posting to its own route.7 views · 4 downloadsAutosaveFormKeeps a draft in the browser as it is typed, offers to restore or discard it, says when saving failed, and works with storage blocked.3 views · 5 downloadsBarcodeInputTells EAN-13, EAN-8, UPC-A, ISBN-10 and ISBN-13 apart by shape, checks the check digit, says what the right one would be, and converts ISBN-10 to ISBN-13.2 views · 5 downloadsBranchingSurveyOne question at a time with jumps by answer: back keeps answers, progress is counted from the longest path left, and unreachable or looping questions are reported.3 views · 6 downloadsColorContrastChecks two colours against WCAG 2 for normal and large text, and finds the nearest colour that passes with the same hue and saturation.3 views · 5 downloads
Thread
Thread 0 posts
On PasswordGenerator: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.