@net-advantage/jo-ui-input
v2.1.0
Published
Publishable input primitives package.
Readme
@net-advantage/jo-ui-input
Publishable input primitives package.
The input follows a strict MVVM split. The model owns kind behavior, parsing, and formatting. The view-model owns editing state. The view renders the view-model. Semantic wrappers only set kind and numeric defaults.
MVVM
flowchart LR
Props["InputProps"] --> ViewModel["useInputViewModel"]
Model["input.model.ts<br/>kind, parse, format"] --> ViewModel
ViewModel --> View["Input.tsx"]
Metadata["jo-ui-field-metadata"] --> View
View -->|"focus, change, blur"| ViewModel
ViewModel -->|"onValueChange"| Host["Host application"]
ViewModel -->|"onParsedValueChange"| Host
Wrappers["EmailInput and numeric wrappers"] --> View| Layer | File | Responsibility |
| --- | --- | --- |
| Contract | input.types.ts | Kind, parsed value, props, and view-model |
| Model | input.model.ts | Resolve kind behavior, parse text, and format on blur |
| Model | textarea.model.ts | Textarea classes and controlRows |
| View-model | input.view-model.ts | Framework-free input observable; own the draft while editing |
| View | input.use-view-model.ts | useInputViewModel reads that observable and syncs a controlled value |
| View-model | date-field.view-model.ts | Framework-free date-field observable |
| View | date-field.use-view-model.ts | useDateFieldViewModel reads that observable |
| View-model | textarea.view-model.ts | Framework-free textarea observable |
| View | textarea.use-view-model.ts | useTextareaViewModel reads that observable |
| View | Input.tsx | Render the field from the view-model |
| View | DateField.tsx | Date input with the same height, radius, and label wiring |
| View | Textarea.tsx | Multiline field with the same control metrics |
| Shared metadata | @net-advantage/jo-ui-field-metadata | Lay out the label, helper text, required marker, and nested input |
| Wrappers | semantic-inputs.tsx | Set kind for email, integer, decimal, percentage, and currency |
Models
flowchart TD
Kind["InputKind"] --> Behavior["KindBehavior"]
Kind --> Parsed["ParsedInputValue"]
Behavior --> Presentation["NumericPresentation"]
Props["InputProps"] --> ViewModel["InputViewModel"]
Parsed --> ViewModel
Presentation --> ViewModelInputKindistext,email,password,search,tel,url,integer,decimal,percentage, orcurrency.kindis the only behavior prop.InputFielddoes not accepttype. The DOM input type is derived fromkindand is not a public prop.ParsedInputValueis a string, a number, ornull.- A controlled value is shown until the field is focused. While editing, the view-model keeps a draft. Blur formats that draft and leaves edit mode.
parseandformatoverride the kind parser and formatter. Omit them and the model uses the kind defaults.InputField,DateField, andTextarearequirelabelorariaLabel. A visiblelabelis preferred and is referenced witharia-labelledby.ariaLabelis the control'saria-labelwhen the field should not show a label. Passing neither is a compile error. The same contract applies toEmailInput,IntegerInput,DecimalInput,PercentageInput, andCurrencyInput.labelmay be any React node.labelPlacementacceptstoporleft;descriptionPlacementacceptsbelow-labelorbelow-control.- Validation errors remain alerts below the input while ordinary helper text is rendered by
FieldMetadata. fullWidthstretches a field. InsideFormLayout, fields are full width unlessfullWidthisfalse.DateFieldandTextareause the same control height and--jo-radius-controlas the text input.
Semantic input strategy
Use one base control (InputField) and configure behavior with kind:
text,email,password,search,tel,urlinteger,decimal,percentage,currency
InputField has no type prop. resolveKindBehavior derives the DOM type from kind. email, password, search, tel, and url keep that HTML type. text, integer, decimal, percentage, and currency use type="text".
Helpful props for semantic kinds:
onParsedValueChange: emits parsed values (number,string, ornull)parse: custom parser overrideformat: custom formatter overridelocale,currency,minimumFractionDigits,maximumFractionDigits
Convenience wrappers are also exported:
EmailInputIntegerInputDecimalInputPercentageInputCurrencyInput
Locale and currency
Resolved locale and currency follow this order:
- The resolved locale is the
localeprop when it is a non-empty string. Otherwise it isIntl.DateTimeFormat().resolvedOptions().locale. - The currency is the
currencyprop when it is a non-empty string. An explicit currency always wins. Otherwise the currency is the ISO 4217 code for that locale's region. - The region is
Intl.Locale(locale).region. When that is missing, the region isnew Intl.Locale(locale).maximize().region. - Regions map to currencies as follows: US USD, GB GBP, DE/FR/ES/IT/NL/IE/AT/BE/PT/FI EUR, JP JPY, CN CNY, CA CAD, AU AUD, NZ NZD, CH CHF, SE SEK, NO NOK, DK DKK, IN INR, BR BRL, MX MXN, KR KRW, SG SGD, HK HKD, ZA ZAR.
- When the region is absent or unmapped, the currency symbol stays empty. The number is still formatted.
CurrencyInput does not default the currency to USD. Omit currency to use the locale currency.
Structure
- src: runtime component, model, view-model, and types.
- stories: Storybook stories for documentation and visual testing.
Publish footprint
This package publishes only:
- dist
- README.md
Stories and tests are excluded from the published artifact by design.
