kennzeichen 0.1.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/README.md +234 -0
- package/dist/chunk-WNVAHMV3.js +910 -0
- package/dist/core/index.d.ts +99 -0
- package/dist/core/index.js +22 -0
- package/dist/react/index.d.ts +143 -0
- package/dist/react/index.js +171 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# kennzeichen
|
|
2
|
+
|
|
3
|
+
German license plate parsing and validation. Framework-agnostic core with React adapter.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install kennzeichen
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Parse German license plates with ambiguity detection
|
|
14
|
+
- 726 valid location codes (Unterscheidungszeichen)
|
|
15
|
+
- Handles E (electric) and H (historic) suffixes
|
|
16
|
+
- Framework-agnostic core
|
|
17
|
+
- React hook + headless component
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Core (any environment)
|
|
22
|
+
|
|
23
|
+
The core module works in any JavaScript environment (Node.js, browser, Deno, etc.).
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
parseLicensePlate,
|
|
28
|
+
formatParsedPlate,
|
|
29
|
+
isValidLicensePlate,
|
|
30
|
+
sanitizeLicensePlate,
|
|
31
|
+
LOCATION,
|
|
32
|
+
} from "kennzeichen";
|
|
33
|
+
|
|
34
|
+
// Parse a license plate
|
|
35
|
+
const result = parseLicensePlate("M-AB 1234");
|
|
36
|
+
// { type: 'unambiguous', plate: { part1: 'M', part2: 'AB', part3: '1234' } }
|
|
37
|
+
|
|
38
|
+
// Handle ambiguous input (no separator)
|
|
39
|
+
const ambiguous = parseLicensePlate("CEE1234");
|
|
40
|
+
// { type: 'ambiguous', options: [
|
|
41
|
+
// { part1: 'C', part2: 'EE', part3: '1234' },
|
|
42
|
+
// { part1: 'CE', part2: 'E', part3: '1234' }
|
|
43
|
+
// ] }
|
|
44
|
+
|
|
45
|
+
// Format a parsed plate
|
|
46
|
+
formatParsedPlate({ part1: "M", part2: "AB", part3: "1234" });
|
|
47
|
+
// 'M-AB 1234'
|
|
48
|
+
|
|
49
|
+
// Validate
|
|
50
|
+
isValidLicensePlate("M-AB 1234"); // true
|
|
51
|
+
isValidLicensePlate("XX-AB 1234"); // false (invalid location)
|
|
52
|
+
|
|
53
|
+
// Sanitize (remove separators)
|
|
54
|
+
sanitizeLicensePlate("M-AB 1234"); // 'MAB1234'
|
|
55
|
+
|
|
56
|
+
// Check if a location code exists
|
|
57
|
+
LOCATION.includes("M"); // true
|
|
58
|
+
LOCATION.includes("XX"); // false
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Parse Result Types
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
type ParseResult =
|
|
65
|
+
| { type: "unambiguous"; plate: ParsedPlate } // Single valid interpretation
|
|
66
|
+
| { type: "ambiguous"; options: ParsedPlate[] } // Multiple valid interpretations
|
|
67
|
+
| { type: "partial"; plate: Partial<ParsedPlate> } // Incomplete but valid so far
|
|
68
|
+
| { type: "invalid" }; // Cannot be parsed
|
|
69
|
+
|
|
70
|
+
type ParsedPlate = {
|
|
71
|
+
part1: string; // Location code (e.g., 'M', 'HH', 'BGL')
|
|
72
|
+
part2: string; // Letters (e.g., 'AB', 'X')
|
|
73
|
+
part3: string; // Numbers + optional suffix (e.g., '1234', '99E', '42H')
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### React Hook
|
|
78
|
+
|
|
79
|
+
The hook provides full control over the input state and disambiguation logic.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useLicensePlate, formatParsedPlate } from "kennzeichen/react";
|
|
83
|
+
|
|
84
|
+
function MyLicensePlateInput({ value, onChange }) {
|
|
85
|
+
const lp = useLicensePlate({ value, onChange });
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="relative">
|
|
89
|
+
<input
|
|
90
|
+
value={lp.inputValue}
|
|
91
|
+
onChange={lp.handleChange}
|
|
92
|
+
onKeyDown={lp.handleKeyDown}
|
|
93
|
+
onBlur={lp.handleBlur}
|
|
94
|
+
placeholder="z.B. M-AB 1234"
|
|
95
|
+
/>
|
|
96
|
+
{lp.isDropdownOpen && lp.options && (
|
|
97
|
+
<ul className="absolute mt-1 border rounded bg-white shadow-lg">
|
|
98
|
+
{lp.options.map((opt, i) => (
|
|
99
|
+
<li
|
|
100
|
+
key={i}
|
|
101
|
+
className={`px-3 py-2 cursor-pointer ${
|
|
102
|
+
i === lp.activeIndex ? "bg-blue-100" : "hover:bg-gray-100"
|
|
103
|
+
}`}
|
|
104
|
+
onMouseEnter={() => lp.setActiveIndex(i)}
|
|
105
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
106
|
+
onClick={() => lp.selectOption(opt)}
|
|
107
|
+
>
|
|
108
|
+
{formatParsedPlate(opt)}
|
|
109
|
+
</li>
|
|
110
|
+
))}
|
|
111
|
+
</ul>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### React Headless Component
|
|
119
|
+
|
|
120
|
+
The headless component uses render props to reduce boilerplate while still giving you full control over the UI.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { LicensePlateInput } from "kennzeichen/react";
|
|
124
|
+
|
|
125
|
+
function MyLicensePlateInput({ value, onChange }) {
|
|
126
|
+
return (
|
|
127
|
+
<LicensePlateInput value={value} onChange={onChange}>
|
|
128
|
+
{({ inputProps, isOpen, options }) => (
|
|
129
|
+
<div className="relative">
|
|
130
|
+
<input
|
|
131
|
+
{...inputProps}
|
|
132
|
+
className="border rounded px-3 py-2 w-full"
|
|
133
|
+
placeholder="z.B. M-AB 1234"
|
|
134
|
+
/>
|
|
135
|
+
{isOpen && options && (
|
|
136
|
+
<ul className="absolute mt-1 border rounded bg-white shadow-lg w-full">
|
|
137
|
+
{options.map((opt) => (
|
|
138
|
+
<li
|
|
139
|
+
key={opt.formatted}
|
|
140
|
+
className={`px-3 py-2 cursor-pointer ${
|
|
141
|
+
opt.isActive ? "bg-blue-100" : "hover:bg-gray-100"
|
|
142
|
+
}`}
|
|
143
|
+
{...opt.getProps()}
|
|
144
|
+
>
|
|
145
|
+
{opt.formatted}
|
|
146
|
+
</li>
|
|
147
|
+
))}
|
|
148
|
+
</ul>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
</LicensePlateInput>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## API Reference
|
|
158
|
+
|
|
159
|
+
### Core
|
|
160
|
+
|
|
161
|
+
#### `parseLicensePlate(input: string): ParseResult`
|
|
162
|
+
|
|
163
|
+
Parses a license plate input string into its component parts. Handles various input formats:
|
|
164
|
+
|
|
165
|
+
- With hyphen: `"M-AB 1234"`
|
|
166
|
+
- With spaces: `"M AB 1234"`
|
|
167
|
+
- Without separators: `"MAB1234"`
|
|
168
|
+
|
|
169
|
+
#### `formatParsedPlate(plate: Partial<ParsedPlate>): string`
|
|
170
|
+
|
|
171
|
+
Formats a parsed plate into the standard display format: `"M-AB 1234"`.
|
|
172
|
+
|
|
173
|
+
#### `isValidLicensePlate(input: string): boolean`
|
|
174
|
+
|
|
175
|
+
Returns `true` if the input can be unambiguously parsed as a valid German license plate.
|
|
176
|
+
|
|
177
|
+
#### `sanitizeLicensePlate(plate: string): string`
|
|
178
|
+
|
|
179
|
+
Removes all spaces and hyphens from a license plate string.
|
|
180
|
+
|
|
181
|
+
#### `LOCATION: string[]`
|
|
182
|
+
|
|
183
|
+
Array of all 726 valid German location codes.
|
|
184
|
+
|
|
185
|
+
#### `LOCATION_SET: Set<string>`
|
|
186
|
+
|
|
187
|
+
Set of all valid location codes for O(1) lookup.
|
|
188
|
+
|
|
189
|
+
### React
|
|
190
|
+
|
|
191
|
+
#### `useLicensePlate(options): UseLicensePlateReturn`
|
|
192
|
+
|
|
193
|
+
React hook for managing license plate input state.
|
|
194
|
+
|
|
195
|
+
**Options:**
|
|
196
|
+
|
|
197
|
+
- `value?: string` - Controlled value
|
|
198
|
+
- `onChange?: (value: string) => void` - Called when value changes
|
|
199
|
+
- `onBlur?: () => void` - Called when input loses focus
|
|
200
|
+
|
|
201
|
+
**Returns:**
|
|
202
|
+
|
|
203
|
+
- `inputValue: string` - Current input value
|
|
204
|
+
- `parseResult: ParseResult` - Current parse result
|
|
205
|
+
- `handleChange` - Input onChange handler
|
|
206
|
+
- `handleKeyDown` - Input onKeyDown handler (for dropdown navigation)
|
|
207
|
+
- `handleBlur` - Input onBlur handler
|
|
208
|
+
- `isDropdownOpen: boolean` - Whether dropdown should be visible
|
|
209
|
+
- `options: ParsedPlate[] | null` - Disambiguation options
|
|
210
|
+
- `activeIndex: number` - Currently highlighted option
|
|
211
|
+
- `selectOption(option)` - Select a disambiguation option
|
|
212
|
+
- `setActiveIndex(index)` - Set highlighted option
|
|
213
|
+
|
|
214
|
+
#### `<LicensePlateInput>` (Headless Component)
|
|
215
|
+
|
|
216
|
+
Render props component for license plate input.
|
|
217
|
+
|
|
218
|
+
**Props:**
|
|
219
|
+
|
|
220
|
+
- `value?: string` - Controlled value
|
|
221
|
+
- `onChange?: (value: string) => void` - Called when value changes
|
|
222
|
+
- `onBlur?: () => void` - Called when input loses focus
|
|
223
|
+
- `children: (props: RenderProps) => ReactNode` - Render function
|
|
224
|
+
|
|
225
|
+
**RenderProps:**
|
|
226
|
+
|
|
227
|
+
- `inputProps` - Props to spread on input element
|
|
228
|
+
- `isOpen: boolean` - Whether dropdown should be visible
|
|
229
|
+
- `options: OptionItem[] | null` - Disambiguation options with helper methods
|
|
230
|
+
- `parseResult: ParseResult` - Current parse result
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|