You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/morfo/components/password-field.ts

196 lines
5.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { Morfo } from '../types';
import { v } from '../types';
/**
* PasswordField — text input with masked value, visibility toggle and
* CapsLock awareness. APG textbox pattern + ARIA Toggle Button for the
* visibility trigger.
*
* Surface (consensus across Ark UI / Chakra v3 / Mantine / PrimeReact /
* Ant Design — all dedicate a component for this):
*
* <PasswordField bind:value bind:visible>
* <PasswordField.Input />
* <PasswordField.VisibilityTrigger />
* <PasswordField.CapsLockIndicator />
* </PasswordField>
*
* Distinctive UIX contribution: the `CapsLockIndicator` part fills a gap
* present in 10/10 libraries surveyed — none warn the user when caps
* lock is on while typing a password. Soma listens to keydown events,
* reads `KeyboardEvent.getModifierState('CapsLock')`, and exposes the
* boolean via `data-caps-active`.
*/
export const passwordFieldMorfo = {
name: 'PasswordField',
kebab: 'password-field',
scope: ['soma', 'sema'],
texts: {
label: '#?components.password-field.label|Password',
show: '#?components.password-field.show|Show password',
hide: '#?components.password-field.hide|Hide password',
'caps-warning': '#?components.password-field.caps-warning|Caps Lock is on'
},
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/textbox/',
events: [
{
// Value committed (every keystroke).
name: 'commit-input',
semantic: {
family: 'commit',
verb: 'input',
target: v.partRef('input'),
intent: 'neutral',
sequence: 'post'
}
},
{
// User pressed Enter — consumer-defined submit hook.
name: 'commit-submit',
semantic: {
family: 'commit',
verb: 'submit',
target: v.partRef('input'),
intent: 'neutral',
sequence: 'post'
}
},
{
// Visibility toggled (mask ↔ plaintext).
name: 'shift-toggle-visibility',
semantic: {
family: 'shift',
verb: 'toggle',
target: v.partRef('visibility-trigger'),
sequence: 'post'
}
},
{
// Caps Lock state changed while the input is focused.
name: 'shift-caps-state',
semantic: {
family: 'shift',
verb: 'navigate',
target: v.partRef('caps-lock-indicator'),
sequence: 'post'
}
}
],
parts: [
{
name: 'Provider',
kebab: 'provider',
archetype: 'provider',
kind: 'public',
defaultElement: 'div',
optional: false,
data: [
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
{ attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' },
{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' },
{ attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' },
{ attr: 'data-focused', value: v.propRef('focused'), severity: 'optional' },
{ attr: 'data-empty', value: v.propRef('empty'), severity: 'optional' },
{ attr: 'data-visible', value: v.propRef('visible'), severity: 'optional' },
{ attr: 'data-caps-active', value: v.propRef('capsActive'), severity: 'optional' }
],
aria: []
},
{
name: 'Input',
kebab: 'input',
archetype: 'input',
kind: 'public',
defaultElement: 'input',
role: 'textbox',
optional: false,
data: [
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
{ attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' },
{ attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }
],
aria: [
{
attr: 'aria-label',
value: v.translationRef('#?components.password-field.label|Password'),
severity: 'recommended'
},
{
attr: 'aria-invalid',
value: v.literal('true'),
severity: 'optional',
condition: { when: 'prop-truthy', prop: 'invalid' }
},
{
attr: 'aria-required',
value: v.literal('true'),
severity: 'optional',
condition: { when: 'prop-truthy', prop: 'required' }
}
],
keyboard: [{ key: 'Enter', action: 'submit' }]
},
{
name: 'VisibilityTrigger',
kebab: 'visibility-trigger',
archetype: 'trigger',
kind: 'public',
defaultElement: 'button',
role: 'button',
optional: true,
data: [
{ attr: 'data-visible', value: v.propRef('visible'), severity: 'optional' },
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }
],
aria: [
{ attr: 'type', value: v.literal('button') },
{
// Toggle button pattern — pressed when password is visible.
attr: 'aria-pressed',
value: v.mapRef(v.propRef('visible'), { true: 'true', false: 'false' }),
severity: 'recommended'
},
{
// Label flips between "Show" and "Hide" based on state.
attr: 'aria-label',
value: v.translationRef('#?components.password-field.show|Show password'),
severity: 'recommended'
}
]
},
{
// Live region that announces Caps Lock state while the input has
// focus. `aria-live="polite"` so it doesn't interrupt typing flow.
name: 'CapsLockIndicator',
kebab: 'caps-lock-indicator',
archetype: 'indicator',
kind: 'public',
defaultElement: 'span',
role: 'status',
optional: true,
data: [
{ attr: 'data-active', value: v.propRef('capsActive'), severity: 'optional' }
],
aria: [{ attr: 'aria-live', value: v.literal('polite') }]
},
{
// Composable strength meter — consumer provides a 0–4 score.
// UIX does not bundle a strength algorithm (zxcvbn etc.) to stay
// dependency-free; consumer wires whichever library they trust.
name: 'StrengthMeter',
kebab: 'strength-meter',
archetype: 'indicator',
kind: 'public',
defaultElement: 'div',
role: 'meter',
optional: true,
data: [{ attr: 'data-strength', value: v.propRef('strength'), severity: 'optional' }],
aria: [
{ attr: 'aria-valuemin', value: v.literal('0') },
{ attr: 'aria-valuemax', value: v.literal('4') },
{ attr: 'aria-valuenow', value: v.propRef('strength') }
]
}
]
} as const satisfies Morfo;

Powered by TurnKey Linux.