geez-input 1.0.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/LICENSE +21 -0
- package/README.md +208 -0
- package/dist/components/geez-input.d.ts +57 -0
- package/dist/components/geez-textarea.d.ts +55 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/engine.d.ts +67 -0
- package/dist/main.d.ts +53 -0
- package/dist/main.js +628 -0
- package/dist/mapping.d.ts +52 -0
- package/dist/types.d.ts +65 -0
- package/dist/use-geez.d.ts +43 -0
- package/dist/vite-env.d.d.ts +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Geez Input Library Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Geez Library
|
|
2
|
+
|
|
3
|
+
A type-safe React library for Geez (Ethiopic) script input with phonetic keyboard support. Write Amharic, Tigrinya, and other Ethiopic languages using Latin characters that automatically transform to Geez script.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-Safe**: Built with TypeScript for full type safety with comprehensive JSDoc documentation
|
|
8
|
+
- **Phonetic Keyboard**: Intuitive Latin-to-Geez transformation following standard conventions
|
|
9
|
+
- **Customizable Components**: Pre-styled input and textarea components with toggle support
|
|
10
|
+
- **React Hook**: `useGeez` hook for custom implementations
|
|
11
|
+
- **Tree-Shakeable**: Optimized bundle size with CSS code splitting
|
|
12
|
+
- **Form Library Support**: Works with React Hook Form, Formik, and other form libraries
|
|
13
|
+
- **Controlled & Uncontrolled**: Full support for both component patterns
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install geez-input
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { GeezInput, GeezTextArea } from 'geez-input'
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
<GeezInput placeholder="Type in Geez..." />
|
|
30
|
+
<GeezTextArea placeholder="Write longer text..." />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Components
|
|
37
|
+
|
|
38
|
+
### GeezInput
|
|
39
|
+
|
|
40
|
+
A styled input component with built-in Geez phonetic keyboard support.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { GeezInput } from 'geez-input'
|
|
44
|
+
import { useState } from 'react'
|
|
45
|
+
|
|
46
|
+
function MyForm() {
|
|
47
|
+
const [name, setName] = useState('')
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<GeezInput
|
|
51
|
+
value={name}
|
|
52
|
+
onChange={(e) => setName(e.target.value)}
|
|
53
|
+
placeholder="Enter your name"
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### GeezTextArea
|
|
60
|
+
|
|
61
|
+
A styled textarea component for longer text input.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { GeezTextArea } from 'geez-input'
|
|
65
|
+
|
|
66
|
+
function MyForm() {
|
|
67
|
+
return (
|
|
68
|
+
<GeezTextArea
|
|
69
|
+
placeholder="Write your story..."
|
|
70
|
+
rows={5}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Hook API
|
|
77
|
+
|
|
78
|
+
### useGeez
|
|
79
|
+
|
|
80
|
+
For custom implementations, use the `useGeez` hook:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { useGeez } from 'geez-input'
|
|
84
|
+
|
|
85
|
+
function CustomInput() {
|
|
86
|
+
const { onKeyDown } = useGeez({
|
|
87
|
+
enabled: true,
|
|
88
|
+
onTransform: (result) => {
|
|
89
|
+
console.log('Transformed:', result)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return <input onKeyDown={onKeyDown} />
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Phonetic Guide
|
|
98
|
+
|
|
99
|
+
The library uses intuitive phonetic mappings:
|
|
100
|
+
|
|
101
|
+
### Consonants
|
|
102
|
+
- `h` → ህ
|
|
103
|
+
- `l` → ል
|
|
104
|
+
- `m` → ም
|
|
105
|
+
- `sh` → ሽ
|
|
106
|
+
- `ch` → ች
|
|
107
|
+
|
|
108
|
+
### Syllables
|
|
109
|
+
Type a consonant followed by a vowel:
|
|
110
|
+
- `ha` → ሀ
|
|
111
|
+
- `lu` → ሉ
|
|
112
|
+
- `mi` → ሚ
|
|
113
|
+
- `sha` → ሻ
|
|
114
|
+
|
|
115
|
+
### Double Vowels
|
|
116
|
+
Type the same vowel twice for alternate forms:
|
|
117
|
+
- `haa` → ሃ
|
|
118
|
+
- `lee` → ሌ
|
|
119
|
+
|
|
120
|
+
### Punctuation
|
|
121
|
+
- `:` → ፡ (word separator)
|
|
122
|
+
- `::` → ። (sentence ending)
|
|
123
|
+
- `,` → ፣
|
|
124
|
+
|
|
125
|
+
## Examples
|
|
126
|
+
|
|
127
|
+
Type phonetically to get Geez text:
|
|
128
|
+
|
|
129
|
+
- `selam` → ስላም (hello)
|
|
130
|
+
- `ethiopia` → ኢትዮጵያ (Ethiopia)
|
|
131
|
+
- `tena yistilign` → ጤና ይስጥልኝ (greetings)
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
### Components
|
|
136
|
+
|
|
137
|
+
#### GeezInput
|
|
138
|
+
|
|
139
|
+
Props:
|
|
140
|
+
- `defaultGeez?: boolean` - Enable Geez mode by default (default: `true`)
|
|
141
|
+
- `className?: string` - Additional CSS classes
|
|
142
|
+
- `...InputHTMLAttributes` - All standard HTML input attributes
|
|
143
|
+
|
|
144
|
+
#### GeezTextArea
|
|
145
|
+
|
|
146
|
+
Props:
|
|
147
|
+
- `defaultGeez?: boolean` - Enable Geez mode by default (default: `true`)
|
|
148
|
+
- `className?: string` - Additional CSS classes
|
|
149
|
+
- `...TextareaHTMLAttributes` - All standard HTML textarea attributes
|
|
150
|
+
|
|
151
|
+
### Hooks
|
|
152
|
+
|
|
153
|
+
#### useGeez(options)
|
|
154
|
+
|
|
155
|
+
Options:
|
|
156
|
+
- `enabled?: boolean` - Enable/disable transformation (default: `true`)
|
|
157
|
+
- `onTransform?: (result: EngineResult) => void` - Callback after each transformation
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
- `onKeyDown: KeyboardEventHandler` - Event handler for keyboard events
|
|
161
|
+
|
|
162
|
+
### Engine
|
|
163
|
+
|
|
164
|
+
#### GeezEngine.transform(textBefore, textAfter, key)
|
|
165
|
+
|
|
166
|
+
Core transformation method:
|
|
167
|
+
- `textBefore: string` - Text before cursor
|
|
168
|
+
- `textAfter: string` - Text after cursor
|
|
169
|
+
- `key: string` - Key pressed
|
|
170
|
+
- Returns: `EngineResult`
|
|
171
|
+
|
|
172
|
+
## TypeScript Support
|
|
173
|
+
|
|
174
|
+
The library is written in TypeScript and provides comprehensive type definitions:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import type {
|
|
178
|
+
GeezOptions,
|
|
179
|
+
EngineResult,
|
|
180
|
+
PhoneticMap,
|
|
181
|
+
SyllableMap
|
|
182
|
+
} from 'geez-input'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Browser Support
|
|
186
|
+
|
|
187
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
188
|
+
- React 18 or 19
|
|
189
|
+
- TypeScript 5+ (optional)
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
198
|
+
|
|
199
|
+
## Links
|
|
200
|
+
|
|
201
|
+
- [GitHub Repository](https://github.com/yourusername/geez-library)
|
|
202
|
+
- [NPM Package](https://www.npmjs.com/package/geez-input)
|
|
203
|
+
- [Documentation Site](https://geez-library.vercel.app)
|
|
204
|
+
- [Issue Tracker](https://github.com/yourusername/geez-library/issues)
|
|
205
|
+
|
|
206
|
+
## Credits
|
|
207
|
+
|
|
208
|
+
Built with TypeScript, React, and Vite.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the GeezInput component
|
|
4
|
+
* Extends all standard HTML input attributes
|
|
5
|
+
*/
|
|
6
|
+
export interface GeezInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
7
|
+
/**
|
|
8
|
+
* Whether Geez input mode is enabled by default
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
defaultGeez?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Additional CSS classes to apply to the input wrapper
|
|
14
|
+
*/
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Styled input component with built-in Geez phonetic keyboard support
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Toggle button to switch between Geez and English input modes
|
|
22
|
+
* - Phonetic transformation (type 'hello' → 'ሀልሎ')
|
|
23
|
+
* - Full support for controlled and uncontrolled component patterns
|
|
24
|
+
* - Forward ref support for form libraries
|
|
25
|
+
* - Styled with a classic paper and ink aesthetic
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* \`\`\`tsx
|
|
29
|
+
* // Basic usage
|
|
30
|
+
* <GeezInput placeholder="Type in Geez..." />
|
|
31
|
+
* \`\`\`
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* \`\`\`tsx
|
|
35
|
+
* // Controlled component
|
|
36
|
+
* function MyForm() {
|
|
37
|
+
* const [name, setName] = useState('')
|
|
38
|
+
* return (
|
|
39
|
+
* <GeezInput
|
|
40
|
+
* value={name}
|
|
41
|
+
* onChange={(e) => setName(e.target.value)}
|
|
42
|
+
* placeholder="Enter your name"
|
|
43
|
+
* />
|
|
44
|
+
* )
|
|
45
|
+
* }
|
|
46
|
+
* \`\`\`
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* \`\`\`tsx
|
|
50
|
+
* // With form library (React Hook Form)
|
|
51
|
+
* function MyForm() {
|
|
52
|
+
* const { register } = useForm()
|
|
53
|
+
* return <GeezInput {...register('name')} />
|
|
54
|
+
* }
|
|
55
|
+
* \`\`\`
|
|
56
|
+
*/
|
|
57
|
+
export declare const GeezInput: React.ForwardRefExoticComponent<GeezInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the GeezTextArea component
|
|
4
|
+
* Extends all standard HTML textarea attributes
|
|
5
|
+
*/
|
|
6
|
+
export interface GeezTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
7
|
+
/**
|
|
8
|
+
* Whether Geez input mode is enabled by default
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
defaultGeez?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Additional CSS classes to apply to the textarea wrapper
|
|
14
|
+
*/
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Styled textarea component with built-in Geez phonetic keyboard support
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Toggle button to switch between Geez and English input modes
|
|
22
|
+
* - Phonetic transformation for longer text
|
|
23
|
+
* - Full support for controlled and uncontrolled component patterns
|
|
24
|
+
* - Forward ref support for form libraries
|
|
25
|
+
* - Styled with a classic paper and ink aesthetic
|
|
26
|
+
* - Minimum height of 150px for comfortable writing
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* \`\`\`tsx
|
|
30
|
+
* // Basic usage
|
|
31
|
+
* <GeezTextArea placeholder="Write your story..." rows={5} />
|
|
32
|
+
* \`\`\`
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* \`\`\`tsx
|
|
36
|
+
* // Controlled component
|
|
37
|
+
* function MyForm() {
|
|
38
|
+
* const [content, setContent] = useState('')
|
|
39
|
+
* return (
|
|
40
|
+
* <GeezTextArea
|
|
41
|
+
* value={content}
|
|
42
|
+
* onChange={(e) => setContent(e.target.value)}
|
|
43
|
+
* placeholder="Enter your text"
|
|
44
|
+
* />
|
|
45
|
+
* )
|
|
46
|
+
* }
|
|
47
|
+
* \`\`\`
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* \`\`\`tsx
|
|
51
|
+
* // Start with English mode
|
|
52
|
+
* <GeezTextArea defaultGeez={false} placeholder="Type here..." />
|
|
53
|
+
* \`\`\`
|
|
54
|
+
*/
|
|
55
|
+
export declare const GeezTextArea: React.ForwardRefExoticComponent<GeezTextAreaProps & React.RefAttributes<HTMLTextAreaElement>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geez Library React Components
|
|
3
|
+
*
|
|
4
|
+
* This module exports pre-styled React components with built-in
|
|
5
|
+
* Geez phonetic keyboard support.
|
|
6
|
+
*
|
|
7
|
+
* @module components
|
|
8
|
+
*/
|
|
9
|
+
export { GeezInput } from './geez-input';
|
|
10
|
+
export { GeezTextArea } from './geez-textarea';
|
|
11
|
+
export type { GeezInputProps } from './geez-input';
|
|
12
|
+
export type { GeezTextAreaProps } from './geez-textarea';
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { EngineResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Core transformation engine for converting Latin phonetic input to Geez script
|
|
4
|
+
*
|
|
5
|
+
* This engine handles the complex rules of Geez syllabic writing system where
|
|
6
|
+
* consonants combine with vowels to form syllables. It processes text character
|
|
7
|
+
* by character, applying transformations based on context and phonetic rules.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* \`\`\`ts
|
|
11
|
+
* const result = GeezEngine.transform('h', '', 'a')
|
|
12
|
+
* // result.transformedValue === 'ሀ'
|
|
13
|
+
* // result.isReplacement === true
|
|
14
|
+
* \`\`\`
|
|
15
|
+
*/
|
|
16
|
+
export declare class GeezEngine {
|
|
17
|
+
/**
|
|
18
|
+
* Transform input text based on a new key press
|
|
19
|
+
*
|
|
20
|
+
* The transformation process follows these rules in order:
|
|
21
|
+
* 1. Check for punctuation combinations (: + : → ።)
|
|
22
|
+
* 2. Check for multi-character consonants (s + h → ሽ)
|
|
23
|
+
* 3. Check for syllable modifications (ህ + a → ሀ)
|
|
24
|
+
* 4. Check for double vowel forms (ሀ + a → ሃ)
|
|
25
|
+
* 5. Check for new consonants (h → ህ)
|
|
26
|
+
* 6. Pass through unrecognized characters
|
|
27
|
+
*
|
|
28
|
+
* @param textBeforeCursor - Text content before the cursor position
|
|
29
|
+
* @param textAfterCursor - Text content after the cursor position
|
|
30
|
+
* @param key - The character key that was pressed
|
|
31
|
+
* @returns Transformation result with new text and cursor position
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* \`\`\`ts
|
|
35
|
+
* // Simple consonant
|
|
36
|
+
* GeezEngine.transform('', '', 'h')
|
|
37
|
+
* // → { transformedValue: 'ህ', newCursorPosition: 1, isReplacement: false }
|
|
38
|
+
*
|
|
39
|
+
* // Syllable formation
|
|
40
|
+
* GeezEngine.transform('ህ', '', 'a')
|
|
41
|
+
* // → { transformedValue: 'ሀ', newCursorPosition: 1, isReplacement: true }
|
|
42
|
+
*
|
|
43
|
+
* // Multi-character consonant
|
|
44
|
+
* GeezEngine.transform('ስ', '', 'h')
|
|
45
|
+
* // → { transformedValue: 'ሽ', newCursorPosition: 1, isReplacement: true }
|
|
46
|
+
* \`\`\`
|
|
47
|
+
*/
|
|
48
|
+
static transform(textBeforeCursor: string, textAfterCursor: string, key: string): EngineResult;
|
|
49
|
+
/**
|
|
50
|
+
* Find the base consonant form (ስድስት/sadis form) for a given character
|
|
51
|
+
*
|
|
52
|
+
* This method searches through the syllable mappings to find which base
|
|
53
|
+
* consonant a character belongs to, even if it's a modified form.
|
|
54
|
+
*
|
|
55
|
+
* @param char - The character to find the base form for
|
|
56
|
+
* @returns The base consonant character, or null if not found
|
|
57
|
+
* @private
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* \`\`\`ts
|
|
61
|
+
* GeezEngine.findSadisBase('ሀ') // returns 'አ' (base for ሀ)
|
|
62
|
+
* GeezEngine.findSadisBase('ላ') // returns 'ል' (base for ላ)
|
|
63
|
+
* GeezEngine.findSadisBase('x') // returns null (not a Geez character)
|
|
64
|
+
* \`\`\`
|
|
65
|
+
*/
|
|
66
|
+
private static findSadisBase;
|
|
67
|
+
}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Geez Input Library - Phonetic keyboard support for Geez/Ethiopic script in React
|
|
4
|
+
*
|
|
5
|
+
* This library provides React components and hooks for typing in Geez (Ethiopic) script
|
|
6
|
+
* using Latin phonetic characters. It supports Amharic, Tigrinya, and other languages
|
|
7
|
+
* written in the Geez script.
|
|
8
|
+
*
|
|
9
|
+
* ## Quick Start
|
|
10
|
+
*
|
|
11
|
+
* \`\`\`tsx
|
|
12
|
+
* import { GeezInput, GeezTextArea, useGeez } from 'geez-input'
|
|
13
|
+
*
|
|
14
|
+
* function App() {
|
|
15
|
+
* return (
|
|
16
|
+
* <div>
|
|
17
|
+
* <GeezInput placeholder="Type your name..." />
|
|
18
|
+
* <GeezTextArea placeholder="Write your story..." />
|
|
19
|
+
* </div>
|
|
20
|
+
* )
|
|
21
|
+
* }
|
|
22
|
+
* \`\`\`
|
|
23
|
+
*
|
|
24
|
+
* ## Main Exports
|
|
25
|
+
*
|
|
26
|
+
* ### Components
|
|
27
|
+
* - {@link GeezInput} - Styled input with Geez keyboard
|
|
28
|
+
* - {@link GeezTextArea} - Styled textarea with Geez keyboard
|
|
29
|
+
*
|
|
30
|
+
* ### Hooks
|
|
31
|
+
* - {@link useGeez} - Hook for custom implementations
|
|
32
|
+
*
|
|
33
|
+
* ### Engine
|
|
34
|
+
* - {@link GeezEngine} - Core transformation engine
|
|
35
|
+
*
|
|
36
|
+
* ### Types
|
|
37
|
+
* - {@link GeezOptions} - Configuration options
|
|
38
|
+
* - {@link EngineResult} - Transformation result
|
|
39
|
+
* - {@link PhoneticMap} - Character mapping type
|
|
40
|
+
* - {@link SyllableMap} - Syllable mapping type
|
|
41
|
+
*
|
|
42
|
+
* ### Mappings
|
|
43
|
+
* - {@link CONSONANTS} - Base consonant mappings
|
|
44
|
+
* - {@link SYLLABLES} - Syllable form mappings
|
|
45
|
+
* - {@link PUNCTUATION} - Punctuation mappings
|
|
46
|
+
* - {@link MULTI_CONSONANTS} - Multi-character consonants
|
|
47
|
+
*/
|
|
48
|
+
export * from './engine';
|
|
49
|
+
export * from './use-geez';
|
|
50
|
+
export * from './types';
|
|
51
|
+
export * from './mapping';
|
|
52
|
+
export * from './components/geez-input';
|
|
53
|
+
export * from './components/geez-textarea';
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import { useCallback as K, forwardRef as A, useState as y } from "react";
|
|
2
|
+
import { jsxs as v, jsx as g } from "react/jsx-runtime";
|
|
3
|
+
const f = {
|
|
4
|
+
h: "ህ",
|
|
5
|
+
l: "ል",
|
|
6
|
+
m: "ም",
|
|
7
|
+
r: "ር",
|
|
8
|
+
s: "ስ",
|
|
9
|
+
rR: "ር",
|
|
10
|
+
sh: "ሽ",
|
|
11
|
+
q: "ቅ",
|
|
12
|
+
b: "ብ",
|
|
13
|
+
t: "ት",
|
|
14
|
+
ch: "ች",
|
|
15
|
+
n: "ን",
|
|
16
|
+
gn: "ኝ",
|
|
17
|
+
N: "ኝ",
|
|
18
|
+
k: "ክ",
|
|
19
|
+
w: "ው",
|
|
20
|
+
z: "ዝ",
|
|
21
|
+
zh: "ዥ",
|
|
22
|
+
y: "ይ",
|
|
23
|
+
d: "ድ",
|
|
24
|
+
j: "ጅ",
|
|
25
|
+
g: "ግ",
|
|
26
|
+
T: "ጥ",
|
|
27
|
+
C: "ጭ",
|
|
28
|
+
P: "ጵ",
|
|
29
|
+
S: "ጽ",
|
|
30
|
+
f: "ፍ",
|
|
31
|
+
p: "ፕ",
|
|
32
|
+
v: "ቭ",
|
|
33
|
+
x: "ኽ",
|
|
34
|
+
H: "ኃ",
|
|
35
|
+
a: "አ",
|
|
36
|
+
u: "ኡ",
|
|
37
|
+
i: "ኢ",
|
|
38
|
+
e: "እ",
|
|
39
|
+
o: "ኦ",
|
|
40
|
+
A: "ኣ",
|
|
41
|
+
E: "ኤ",
|
|
42
|
+
O: "ዖ"
|
|
43
|
+
}, r = {
|
|
44
|
+
አ: {
|
|
45
|
+
a: "አ",
|
|
46
|
+
e: "አ",
|
|
47
|
+
u: "ኡ",
|
|
48
|
+
i: "ኢ",
|
|
49
|
+
aa: "ኣ",
|
|
50
|
+
A: "ኣ",
|
|
51
|
+
ee: "ኤ",
|
|
52
|
+
E: "ኤ",
|
|
53
|
+
o: "ኦ"
|
|
54
|
+
},
|
|
55
|
+
ህ: {
|
|
56
|
+
a: "ሀ",
|
|
57
|
+
e: "ሀ",
|
|
58
|
+
u: "ሁ",
|
|
59
|
+
i: "ሂ",
|
|
60
|
+
aa: "ሃ",
|
|
61
|
+
A: "ሃ",
|
|
62
|
+
ee: "ሄ",
|
|
63
|
+
E: "ሄ",
|
|
64
|
+
o: "ሆ"
|
|
65
|
+
},
|
|
66
|
+
ኃ: {
|
|
67
|
+
a: "ኃ",
|
|
68
|
+
e: "ኃ",
|
|
69
|
+
u: "ኁ",
|
|
70
|
+
i: "ኂ",
|
|
71
|
+
aa: "ኃ",
|
|
72
|
+
A: "ኃ",
|
|
73
|
+
ee: "ኄ",
|
|
74
|
+
E: "ኄ",
|
|
75
|
+
o: "ኆ"
|
|
76
|
+
},
|
|
77
|
+
ል: {
|
|
78
|
+
a: "ለ",
|
|
79
|
+
e: "ለ",
|
|
80
|
+
u: "ሉ",
|
|
81
|
+
i: "ሊ",
|
|
82
|
+
aa: "ላ",
|
|
83
|
+
A: "ላ",
|
|
84
|
+
ee: "ሌ",
|
|
85
|
+
E: "ሌ",
|
|
86
|
+
o: "ሎ",
|
|
87
|
+
ua: "ሏ"
|
|
88
|
+
},
|
|
89
|
+
ም: {
|
|
90
|
+
a: "መ",
|
|
91
|
+
e: "መ",
|
|
92
|
+
u: "ሙ",
|
|
93
|
+
i: "ሚ",
|
|
94
|
+
aa: "ማ",
|
|
95
|
+
A: "ማ",
|
|
96
|
+
ee: "ሜ",
|
|
97
|
+
E: "ሜ",
|
|
98
|
+
o: "ሞ",
|
|
99
|
+
ua: "ሟ"
|
|
100
|
+
},
|
|
101
|
+
ር: {
|
|
102
|
+
a: "ረ",
|
|
103
|
+
e: "ረ",
|
|
104
|
+
u: "ሩ",
|
|
105
|
+
i: "ሪ",
|
|
106
|
+
aa: "ራ",
|
|
107
|
+
A: "ራ",
|
|
108
|
+
ee: "ሬ",
|
|
109
|
+
E: "ሬ",
|
|
110
|
+
o: "ሮ",
|
|
111
|
+
ua: "ሯ"
|
|
112
|
+
},
|
|
113
|
+
ስ: {
|
|
114
|
+
a: "ሰ",
|
|
115
|
+
e: "ሰ",
|
|
116
|
+
u: "ሱ",
|
|
117
|
+
i: "ሲ",
|
|
118
|
+
aa: "ሳ",
|
|
119
|
+
A: "ሳ",
|
|
120
|
+
ee: "ሴ",
|
|
121
|
+
E: "ሴ",
|
|
122
|
+
o: "ሶ",
|
|
123
|
+
ua: "ሷ"
|
|
124
|
+
},
|
|
125
|
+
ሽ: {
|
|
126
|
+
a: "ሸ",
|
|
127
|
+
e: "ሸ",
|
|
128
|
+
u: "ሹ",
|
|
129
|
+
i: "ሺ",
|
|
130
|
+
aa: "ሻ",
|
|
131
|
+
A: "ሻ",
|
|
132
|
+
ee: "ሼ",
|
|
133
|
+
E: "ሼ",
|
|
134
|
+
o: "ሾ",
|
|
135
|
+
ua: "ሿ"
|
|
136
|
+
},
|
|
137
|
+
ቅ: {
|
|
138
|
+
a: "ቀ",
|
|
139
|
+
e: "ቀ",
|
|
140
|
+
u: "ቁ",
|
|
141
|
+
i: "ቂ",
|
|
142
|
+
aa: "ቃ",
|
|
143
|
+
A: "ቃ",
|
|
144
|
+
ee: "ቄ",
|
|
145
|
+
E: "ቄ",
|
|
146
|
+
o: "ቆ",
|
|
147
|
+
ua: "ቋ"
|
|
148
|
+
},
|
|
149
|
+
ብ: {
|
|
150
|
+
a: "በ",
|
|
151
|
+
e: "በ",
|
|
152
|
+
u: "ቡ",
|
|
153
|
+
i: "ቢ",
|
|
154
|
+
aa: "ባ",
|
|
155
|
+
A: "ባ",
|
|
156
|
+
ee: "ቤ",
|
|
157
|
+
E: "ቤ",
|
|
158
|
+
o: "ቦ",
|
|
159
|
+
ua: "ቧ"
|
|
160
|
+
},
|
|
161
|
+
ት: {
|
|
162
|
+
a: "ተ",
|
|
163
|
+
e: "ተ",
|
|
164
|
+
u: "ቱ",
|
|
165
|
+
i: "ቲ",
|
|
166
|
+
aa: "ታ",
|
|
167
|
+
A: "ታ",
|
|
168
|
+
ee: "ቴ",
|
|
169
|
+
E: "ቴ",
|
|
170
|
+
o: "ቶ",
|
|
171
|
+
ua: "ቷ"
|
|
172
|
+
},
|
|
173
|
+
ች: {
|
|
174
|
+
a: "ቸ",
|
|
175
|
+
e: "ቸ",
|
|
176
|
+
u: "ቹ",
|
|
177
|
+
i: "ቺ",
|
|
178
|
+
aa: "ቻ",
|
|
179
|
+
A: "ቻ",
|
|
180
|
+
ee: "ቼ",
|
|
181
|
+
E: "ቼ",
|
|
182
|
+
o: "ቾ",
|
|
183
|
+
ua: "ቿ"
|
|
184
|
+
},
|
|
185
|
+
ን: {
|
|
186
|
+
a: "ነ",
|
|
187
|
+
e: "ነ",
|
|
188
|
+
u: "ኑ",
|
|
189
|
+
i: "ኒ",
|
|
190
|
+
aa: "ና",
|
|
191
|
+
A: "ና",
|
|
192
|
+
ee: "ኔ",
|
|
193
|
+
E: "ኔ",
|
|
194
|
+
o: "ኖ",
|
|
195
|
+
ua: "ኗ"
|
|
196
|
+
},
|
|
197
|
+
ኝ: {
|
|
198
|
+
a: "ኘ",
|
|
199
|
+
e: "ኘ",
|
|
200
|
+
u: "ኙ",
|
|
201
|
+
i: "ኚ",
|
|
202
|
+
aa: "ኛ",
|
|
203
|
+
A: "ኛ",
|
|
204
|
+
ee: "ኜ",
|
|
205
|
+
E: "ኜ",
|
|
206
|
+
o: "ኞ",
|
|
207
|
+
ua: "ኟ"
|
|
208
|
+
},
|
|
209
|
+
ክ: {
|
|
210
|
+
a: "ከ",
|
|
211
|
+
e: "ከ",
|
|
212
|
+
u: "ኩ",
|
|
213
|
+
i: "ኪ",
|
|
214
|
+
aa: "ካ",
|
|
215
|
+
A: "ካ",
|
|
216
|
+
ee: "ኬ",
|
|
217
|
+
E: "ኬ",
|
|
218
|
+
o: "ኮ",
|
|
219
|
+
ua: "ኳ"
|
|
220
|
+
},
|
|
221
|
+
ው: {
|
|
222
|
+
a: "ወ",
|
|
223
|
+
e: "ወ",
|
|
224
|
+
u: "ዉ",
|
|
225
|
+
i: "ዊ",
|
|
226
|
+
aa: "ዋ",
|
|
227
|
+
A: "ዋ",
|
|
228
|
+
ee: "ዌ",
|
|
229
|
+
E: "ዌ",
|
|
230
|
+
o: "ዎ"
|
|
231
|
+
},
|
|
232
|
+
ዝ: {
|
|
233
|
+
a: "ዘ",
|
|
234
|
+
e: "ዘ",
|
|
235
|
+
u: "ዙ",
|
|
236
|
+
i: "ዚ",
|
|
237
|
+
aa: "ዛ",
|
|
238
|
+
A: "ዛ",
|
|
239
|
+
ee: "ዜ",
|
|
240
|
+
E: "ዜ",
|
|
241
|
+
o: "ዞ",
|
|
242
|
+
ua: "ዟ"
|
|
243
|
+
},
|
|
244
|
+
ዥ: {
|
|
245
|
+
a: "ዠ",
|
|
246
|
+
e: "ዠ",
|
|
247
|
+
u: "ዡ",
|
|
248
|
+
i: "ዢ",
|
|
249
|
+
aa: "ዣ",
|
|
250
|
+
A: "ዣ",
|
|
251
|
+
ee: "ዤ",
|
|
252
|
+
E: "ዤ",
|
|
253
|
+
o: "ዦ",
|
|
254
|
+
ua: "ዧ"
|
|
255
|
+
},
|
|
256
|
+
ይ: {
|
|
257
|
+
a: "የ",
|
|
258
|
+
e: "የ",
|
|
259
|
+
u: "ዩ",
|
|
260
|
+
i: "ዪ",
|
|
261
|
+
aa: "ያ",
|
|
262
|
+
A: "ያ",
|
|
263
|
+
ee: "ዬ",
|
|
264
|
+
E: "ዬ",
|
|
265
|
+
o: "ዮ"
|
|
266
|
+
},
|
|
267
|
+
ጅ: {
|
|
268
|
+
a: "ጀ",
|
|
269
|
+
e: "ጀ",
|
|
270
|
+
u: "ጁ",
|
|
271
|
+
i: "ጂ",
|
|
272
|
+
aa: "ጃ",
|
|
273
|
+
A: "ጃ",
|
|
274
|
+
ee: "ጄ",
|
|
275
|
+
E: "ጄ",
|
|
276
|
+
o: "ጆ",
|
|
277
|
+
ua: "ጇ"
|
|
278
|
+
},
|
|
279
|
+
ድ: {
|
|
280
|
+
a: "ደ",
|
|
281
|
+
e: "ደ",
|
|
282
|
+
u: "ዱ",
|
|
283
|
+
i: "ዲ",
|
|
284
|
+
aa: "ዳ",
|
|
285
|
+
A: "ዳ",
|
|
286
|
+
ee: "ዴ",
|
|
287
|
+
E: "ዴ",
|
|
288
|
+
o: "ዶ",
|
|
289
|
+
ua: "ዷ"
|
|
290
|
+
},
|
|
291
|
+
ግ: {
|
|
292
|
+
a: "ገ",
|
|
293
|
+
e: "ገ",
|
|
294
|
+
u: "ጉ",
|
|
295
|
+
i: "ጊ",
|
|
296
|
+
aa: "ጋ",
|
|
297
|
+
A: "ጋ",
|
|
298
|
+
ee: "ጌ",
|
|
299
|
+
E: "ጌ",
|
|
300
|
+
o: "ጎ",
|
|
301
|
+
ua: "ጓ"
|
|
302
|
+
},
|
|
303
|
+
ጥ: {
|
|
304
|
+
a: "ጠ",
|
|
305
|
+
e: "ጠ",
|
|
306
|
+
u: "ጡ",
|
|
307
|
+
i: "ጢ",
|
|
308
|
+
aa: "ጣ",
|
|
309
|
+
A: "ጣ",
|
|
310
|
+
ee: "ጤ",
|
|
311
|
+
E: "ጤ",
|
|
312
|
+
o: "ጦ",
|
|
313
|
+
ua: "ጧ"
|
|
314
|
+
},
|
|
315
|
+
ጭ: {
|
|
316
|
+
a: "ጨ",
|
|
317
|
+
e: "ጨ",
|
|
318
|
+
u: "ጩ",
|
|
319
|
+
i: "ጪ",
|
|
320
|
+
aa: "ጫ",
|
|
321
|
+
A: "ጫ",
|
|
322
|
+
ee: "ጬ",
|
|
323
|
+
E: "ጬ",
|
|
324
|
+
o: "ጮ",
|
|
325
|
+
ua: "ቿ"
|
|
326
|
+
},
|
|
327
|
+
ጵ: {
|
|
328
|
+
a: "ጰ",
|
|
329
|
+
e: "ጰ",
|
|
330
|
+
u: "ጱ",
|
|
331
|
+
i: "ጲ",
|
|
332
|
+
aa: "ጳ",
|
|
333
|
+
A: "ጳ",
|
|
334
|
+
ee: "ጴ",
|
|
335
|
+
E: "ጴ",
|
|
336
|
+
o: "ጶ",
|
|
337
|
+
ua: "ፗ"
|
|
338
|
+
},
|
|
339
|
+
ጽ: {
|
|
340
|
+
a: "ጸ",
|
|
341
|
+
e: "ጸ",
|
|
342
|
+
u: "ጹ",
|
|
343
|
+
i: "ጺ",
|
|
344
|
+
aa: "ጻ",
|
|
345
|
+
A: "ጻ",
|
|
346
|
+
ee: "ጼ",
|
|
347
|
+
E: "ጼ",
|
|
348
|
+
o: "ጾ",
|
|
349
|
+
ua: "ጿ"
|
|
350
|
+
},
|
|
351
|
+
ፍ: {
|
|
352
|
+
a: "ፈ",
|
|
353
|
+
e: "ፈ",
|
|
354
|
+
u: "ፉ",
|
|
355
|
+
i: "ፊ",
|
|
356
|
+
aa: "ፋ",
|
|
357
|
+
A: "ፋ",
|
|
358
|
+
ee: "ፌ",
|
|
359
|
+
E: "ፌ",
|
|
360
|
+
o: "ፎ",
|
|
361
|
+
ua: "ፏ"
|
|
362
|
+
},
|
|
363
|
+
ፕ: {
|
|
364
|
+
a: "ፐ",
|
|
365
|
+
e: "ፐ",
|
|
366
|
+
u: "ፑ",
|
|
367
|
+
i: "ፒ",
|
|
368
|
+
aa: "ፓ",
|
|
369
|
+
A: "ፓ",
|
|
370
|
+
ee: "ፔ",
|
|
371
|
+
E: "ፔ",
|
|
372
|
+
o: "ፖ",
|
|
373
|
+
ua: "ፗ"
|
|
374
|
+
},
|
|
375
|
+
ቭ: {
|
|
376
|
+
a: "ቨ",
|
|
377
|
+
e: "ቨ",
|
|
378
|
+
u: "ቩ",
|
|
379
|
+
i: "ቪ",
|
|
380
|
+
aa: "ቫ",
|
|
381
|
+
A: "ቫ",
|
|
382
|
+
ee: "ቬ",
|
|
383
|
+
E: "ቬ",
|
|
384
|
+
o: "ቮ",
|
|
385
|
+
ua: "ቯ"
|
|
386
|
+
}
|
|
387
|
+
}, w = {
|
|
388
|
+
ስh: "ሽ",
|
|
389
|
+
ችh: "ች",
|
|
390
|
+
ንy: "ኝ",
|
|
391
|
+
ዝh: "ዥ",
|
|
392
|
+
ጽh: "ፁ"
|
|
393
|
+
}, m = {
|
|
394
|
+
":": "፡",
|
|
395
|
+
"፡:": "።",
|
|
396
|
+
"።:": "፡",
|
|
397
|
+
",": "፣",
|
|
398
|
+
"፣,": "፤",
|
|
399
|
+
";": "፤"
|
|
400
|
+
};
|
|
401
|
+
class S {
|
|
402
|
+
/**
|
|
403
|
+
* Transform input text based on a new key press
|
|
404
|
+
*
|
|
405
|
+
* The transformation process follows these rules in order:
|
|
406
|
+
* 1. Check for punctuation combinations (: + : → ።)
|
|
407
|
+
* 2. Check for multi-character consonants (s + h → ሽ)
|
|
408
|
+
* 3. Check for syllable modifications (ህ + a → ሀ)
|
|
409
|
+
* 4. Check for double vowel forms (ሀ + a → ሃ)
|
|
410
|
+
* 5. Check for new consonants (h → ህ)
|
|
411
|
+
* 6. Pass through unrecognized characters
|
|
412
|
+
*
|
|
413
|
+
* @param textBeforeCursor - Text content before the cursor position
|
|
414
|
+
* @param textAfterCursor - Text content after the cursor position
|
|
415
|
+
* @param key - The character key that was pressed
|
|
416
|
+
* @returns Transformation result with new text and cursor position
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* \`\`\`ts
|
|
420
|
+
* // Simple consonant
|
|
421
|
+
* GeezEngine.transform('', '', 'h')
|
|
422
|
+
* // → { transformedValue: 'ህ', newCursorPosition: 1, isReplacement: false }
|
|
423
|
+
*
|
|
424
|
+
* // Syllable formation
|
|
425
|
+
* GeezEngine.transform('ህ', '', 'a')
|
|
426
|
+
* // → { transformedValue: 'ሀ', newCursorPosition: 1, isReplacement: true }
|
|
427
|
+
*
|
|
428
|
+
* // Multi-character consonant
|
|
429
|
+
* GeezEngine.transform('ስ', '', 'h')
|
|
430
|
+
* // → { transformedValue: 'ሽ', newCursorPosition: 1, isReplacement: true }
|
|
431
|
+
* \`\`\`
|
|
432
|
+
*/
|
|
433
|
+
static transform(e, a, t) {
|
|
434
|
+
const n = e.slice(-1), l = n + t;
|
|
435
|
+
if (m[l])
|
|
436
|
+
return {
|
|
437
|
+
transformedValue: e.slice(0, -1) + m[l] + a,
|
|
438
|
+
newCursorPosition: e.length,
|
|
439
|
+
isReplacement: !0
|
|
440
|
+
};
|
|
441
|
+
if (m[t])
|
|
442
|
+
return {
|
|
443
|
+
transformedValue: e + m[t] + a,
|
|
444
|
+
newCursorPosition: e.length + 1,
|
|
445
|
+
isReplacement: !1
|
|
446
|
+
};
|
|
447
|
+
if (w[l])
|
|
448
|
+
return {
|
|
449
|
+
transformedValue: e.slice(0, -1) + w[l] + a,
|
|
450
|
+
newCursorPosition: e.length,
|
|
451
|
+
isReplacement: !0
|
|
452
|
+
};
|
|
453
|
+
if (r[n] && r[n][t])
|
|
454
|
+
return {
|
|
455
|
+
transformedValue: e.slice(0, -1) + r[n][t] + a,
|
|
456
|
+
newCursorPosition: e.length,
|
|
457
|
+
isReplacement: !0
|
|
458
|
+
};
|
|
459
|
+
if (["a", "e", "i"].includes(t)) {
|
|
460
|
+
const o = this.findSadisBase(n);
|
|
461
|
+
if (o && r[o]) {
|
|
462
|
+
const i = t + t;
|
|
463
|
+
if (r[o][i])
|
|
464
|
+
return {
|
|
465
|
+
transformedValue: e.slice(0, -1) + r[o][i] + a,
|
|
466
|
+
newCursorPosition: e.length,
|
|
467
|
+
isReplacement: !0
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return f[t] ? {
|
|
472
|
+
transformedValue: e + f[t] + a,
|
|
473
|
+
newCursorPosition: e.length + 1,
|
|
474
|
+
isReplacement: !1
|
|
475
|
+
} : {
|
|
476
|
+
transformedValue: e + t + a,
|
|
477
|
+
newCursorPosition: e.length + 1,
|
|
478
|
+
isReplacement: !1
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Find the base consonant form (ስድስት/sadis form) for a given character
|
|
483
|
+
*
|
|
484
|
+
* This method searches through the syllable mappings to find which base
|
|
485
|
+
* consonant a character belongs to, even if it's a modified form.
|
|
486
|
+
*
|
|
487
|
+
* @param char - The character to find the base form for
|
|
488
|
+
* @returns The base consonant character, or null if not found
|
|
489
|
+
* @private
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* \`\`\`ts
|
|
493
|
+
* GeezEngine.findSadisBase('ሀ') // returns 'አ' (base for ሀ)
|
|
494
|
+
* GeezEngine.findSadisBase('ላ') // returns 'ል' (base for ላ)
|
|
495
|
+
* GeezEngine.findSadisBase('x') // returns null (not a Geez character)
|
|
496
|
+
* \`\`\`
|
|
497
|
+
*/
|
|
498
|
+
static findSadisBase(e) {
|
|
499
|
+
for (const [a, t] of Object.entries(r))
|
|
500
|
+
if (a === e || Object.values(t).includes(e)) return a;
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const z = (c = {}) => {
|
|
505
|
+
const { enabled: e = !0, onTransform: a } = c;
|
|
506
|
+
return { onKeyDown: K(
|
|
507
|
+
(n) => {
|
|
508
|
+
if (!e || [
|
|
509
|
+
"Backspace",
|
|
510
|
+
"Delete",
|
|
511
|
+
"ArrowLeft",
|
|
512
|
+
"ArrowRight",
|
|
513
|
+
"ArrowUp",
|
|
514
|
+
"ArrowDown",
|
|
515
|
+
"Home",
|
|
516
|
+
"End",
|
|
517
|
+
"Tab",
|
|
518
|
+
"Enter",
|
|
519
|
+
"Escape",
|
|
520
|
+
"PageUp",
|
|
521
|
+
"PageDown"
|
|
522
|
+
].includes(n.key) || n.ctrlKey || n.metaKey || n.key.length !== 1 || n.altKey) return;
|
|
523
|
+
n.preventDefault();
|
|
524
|
+
const o = n.currentTarget, { selectionStart: i, selectionEnd: p, value: d } = o, b = d.substring(0, i || 0), E = d.substring(p || 0), u = S.transform(b, E, n.key), s = Object.getOwnPropertyDescriptor(o.constructor.prototype, "value");
|
|
525
|
+
s && s.set ? s.set.bind(o)(u.transformedValue) : o.value = u.transformedValue;
|
|
526
|
+
let h;
|
|
527
|
+
typeof InputEvent < "u" ? h = new InputEvent("input", {
|
|
528
|
+
bubbles: !0,
|
|
529
|
+
cancelable: !0,
|
|
530
|
+
inputType: "insertText",
|
|
531
|
+
data: n.key
|
|
532
|
+
}) : h = new Event("input", { bubbles: !0, cancelable: !0 }), o.dispatchEvent(h);
|
|
533
|
+
const N = new Event("change", {
|
|
534
|
+
bubbles: !0,
|
|
535
|
+
cancelable: !0
|
|
536
|
+
});
|
|
537
|
+
o.dispatchEvent(N), requestAnimationFrame(() => {
|
|
538
|
+
if (document.activeElement === o)
|
|
539
|
+
try {
|
|
540
|
+
o.setSelectionRange(u.newCursorPosition, u.newCursorPosition);
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}), a && a(u);
|
|
544
|
+
},
|
|
545
|
+
[e, a]
|
|
546
|
+
) };
|
|
547
|
+
}, P = A(
|
|
548
|
+
({ defaultGeez: c = !0, className: e, onChange: a, onKeyDown: t, value: n, ...l }, o) => {
|
|
549
|
+
const [i, p] = y(c), d = {
|
|
550
|
+
...l,
|
|
551
|
+
...n !== void 0 && { value: n }
|
|
552
|
+
}, { onKeyDown: b } = z({ enabled: i });
|
|
553
|
+
return /* @__PURE__ */ v("div", { className: "relative group w-full", children: [
|
|
554
|
+
/* @__PURE__ */ g(
|
|
555
|
+
"input",
|
|
556
|
+
{
|
|
557
|
+
...d,
|
|
558
|
+
ref: o,
|
|
559
|
+
onKeyDown: (s) => {
|
|
560
|
+
b(s), t && t(s);
|
|
561
|
+
},
|
|
562
|
+
onChange: (s) => {
|
|
563
|
+
a && a(s);
|
|
564
|
+
},
|
|
565
|
+
className: `w-full bg-paper border-2 border-gold/20 focus:border-crimson outline-none p-4 font-serif text-lg transition-all ${e || ""}`
|
|
566
|
+
}
|
|
567
|
+
),
|
|
568
|
+
/* @__PURE__ */ g(
|
|
569
|
+
"button",
|
|
570
|
+
{
|
|
571
|
+
type: "button",
|
|
572
|
+
onClick: () => p(!i),
|
|
573
|
+
className: `absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-md transition-all z-10 font-semibold text-sm ${i ? "bg-crimson text-white" : "bg-gold/10 text-gold"}`,
|
|
574
|
+
title: i ? "Switch to English" : "Switch to Ge'ez",
|
|
575
|
+
tabIndex: -1,
|
|
576
|
+
children: i ? "አማ" : "EN"
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
] });
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
P.displayName = "GeezInput";
|
|
583
|
+
const T = A(
|
|
584
|
+
({ defaultGeez: c = !0, className: e, onChange: a, onKeyDown: t, value: n, ...l }, o) => {
|
|
585
|
+
const [i, p] = y(c), d = {
|
|
586
|
+
...l,
|
|
587
|
+
...n !== void 0 && { value: n }
|
|
588
|
+
}, { onKeyDown: b } = z({ enabled: i }), E = (s) => {
|
|
589
|
+
b(s), t && t(s);
|
|
590
|
+
}, u = (s) => {
|
|
591
|
+
a && a(s);
|
|
592
|
+
};
|
|
593
|
+
return /* @__PURE__ */ v("div", { className: `relative group w-full ${e || ""}`, children: [
|
|
594
|
+
/* @__PURE__ */ g(
|
|
595
|
+
"textarea",
|
|
596
|
+
{
|
|
597
|
+
...d,
|
|
598
|
+
ref: o,
|
|
599
|
+
onKeyDown: E,
|
|
600
|
+
onChange: u,
|
|
601
|
+
className: `w-full bg-paper border-2 border-gold/20 focus:border-crimson outline-none p-6 font-serif text-lg leading-relaxed transition-all min-h-[150px] ${e || ""}`
|
|
602
|
+
}
|
|
603
|
+
),
|
|
604
|
+
/* @__PURE__ */ g(
|
|
605
|
+
"button",
|
|
606
|
+
{
|
|
607
|
+
type: "button",
|
|
608
|
+
onClick: () => p(!i),
|
|
609
|
+
className: `absolute right-4 top-4 px-3 py-1.5 rounded-md transition-all z-10 font-semibold text-sm ${i ? "bg-crimson text-white" : "bg-gold/10 text-gold"}`,
|
|
610
|
+
title: i ? "Switch to English" : "Switch to Ge'ez",
|
|
611
|
+
tabIndex: -1,
|
|
612
|
+
children: i ? "አማ" : "EN"
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
] });
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
T.displayName = "GeezTextArea";
|
|
619
|
+
export {
|
|
620
|
+
f as CONSONANTS,
|
|
621
|
+
S as GeezEngine,
|
|
622
|
+
P as GeezInput,
|
|
623
|
+
T as GeezTextArea,
|
|
624
|
+
w as MULTI_CONSONANTS,
|
|
625
|
+
m as PUNCTUATION,
|
|
626
|
+
r as SYLLABLES,
|
|
627
|
+
z as useGeez
|
|
628
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base consonant characters in Geez script
|
|
3
|
+
* Maps Latin phonetic characters to their corresponding Geez consonants (6th form/ስድስት)
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* \`\`\`ts
|
|
7
|
+
* CONSONANTS['h'] // returns 'ህ'
|
|
8
|
+
* CONSONANTS['sh'] // returns 'ሽ'
|
|
9
|
+
* \`\`\`
|
|
10
|
+
*/
|
|
11
|
+
export declare const CONSONANTS: Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Syllable forms for each consonant with vowel modifications
|
|
14
|
+
* Each consonant base has multiple forms based on the following vowel
|
|
15
|
+
*
|
|
16
|
+
* Vowel order (traditional):
|
|
17
|
+
* - a/e (ግዕዝ/Geez) - base form
|
|
18
|
+
* - u (ካዕብ/Kaeb) - u modification
|
|
19
|
+
* - i (ሳልስ/Salis) - i modification
|
|
20
|
+
* - aa/A (ራብዕ/Rabee) - aa modification
|
|
21
|
+
* - ee/E (ኃምስ/Hamis) - ee modification
|
|
22
|
+
* - o (ሳድስ/Sadis) - o modification
|
|
23
|
+
* - ua (ሳብዕ/Sabe) - ua modification (not available for all)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* \`\`\`ts
|
|
27
|
+
* SYLLABLES['ህ']['a'] // returns 'ሀ'
|
|
28
|
+
* SYLLABLES['ል']['ua'] // returns 'ሏ'
|
|
29
|
+
* \`\`\`
|
|
30
|
+
*/
|
|
31
|
+
export declare const SYLLABLES: Record<string, Record<string, string>>;
|
|
32
|
+
/**
|
|
33
|
+
* Multi-character consonant combinations
|
|
34
|
+
* Handles digraphs and special character sequences
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* \`\`\`ts
|
|
38
|
+
* MULTI_CONSONANTS['ስh'] // returns 'ሽ' (sh sound)
|
|
39
|
+
* \`\`\`
|
|
40
|
+
*/
|
|
41
|
+
export declare const MULTI_CONSONANTS: Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* Punctuation mark mappings from Latin to Geez
|
|
44
|
+
* Supports sequential input for different punctuation forms
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* \`\`\`ts
|
|
48
|
+
* PUNCTUATION[':'] // returns '፡' (word separator)
|
|
49
|
+
* PUNCTUATION['፡:'] // returns '።' (sentence ending)
|
|
50
|
+
* \`\`\`
|
|
51
|
+
*/
|
|
52
|
+
export declare const PUNCTUATION: Record<string, string>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phonetic character mapping from Latin to Geez script
|
|
3
|
+
* @example
|
|
4
|
+
* ```ts
|
|
5
|
+
* const mapping: PhoneticMap = { 'h': 'ህ', 'l': 'ል' }
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export type PhoneticMap = Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Syllable mapping with vowel modifications for each consonant
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const syllables: SyllableMap = {
|
|
14
|
+
* 'ህ': { 'a': 'ሀ', 'u': 'ሁ', 'i': 'ሂ' }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export type SyllableMap = Record<string, Record<string, string>>;
|
|
19
|
+
/**
|
|
20
|
+
* Result of a character transformation operation
|
|
21
|
+
*/
|
|
22
|
+
export interface EngineResult {
|
|
23
|
+
/** The complete transformed text value */
|
|
24
|
+
transformedValue: string;
|
|
25
|
+
/** The new cursor position after transformation */
|
|
26
|
+
newCursorPosition: number;
|
|
27
|
+
/** Whether this transformation replaced existing characters */
|
|
28
|
+
isReplacement: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Keyboard configuration settings
|
|
32
|
+
* @deprecated Not currently used in the library
|
|
33
|
+
*/
|
|
34
|
+
export interface KeyboardConfig {
|
|
35
|
+
/** Whether the Geez keyboard is enabled */
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
/** The phonetic standard to use */
|
|
38
|
+
standard: "GFF" | "Custom";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Configuration options for the useGeez hook
|
|
42
|
+
*/
|
|
43
|
+
export interface GeezOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Callback function triggered after each transformation
|
|
46
|
+
* @param result - The result of the transformation
|
|
47
|
+
*/
|
|
48
|
+
onTransform?: (result: EngineResult) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Whether Geez transformation is enabled
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Geez transformation statistics for a single operation
|
|
57
|
+
*/
|
|
58
|
+
export interface TransformStats {
|
|
59
|
+
/** Input character that triggered the transformation */
|
|
60
|
+
inputChar: string;
|
|
61
|
+
/** Output character(s) after transformation */
|
|
62
|
+
outputChar: string;
|
|
63
|
+
/** Type of transformation performed */
|
|
64
|
+
transformType: "consonant" | "syllable" | "punctuation" | "multi-consonant" | "double-vowel" | "passthrough";
|
|
65
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { GeezOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* React hook for adding Geez phonetic keyboard functionality to input elements
|
|
5
|
+
*
|
|
6
|
+
* This hook intercepts keyboard events and transforms Latin characters to Geez script
|
|
7
|
+
* in real-time. It properly handles controlled and uncontrolled React components,
|
|
8
|
+
* cursor positioning, and form events.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Configuration options for the hook
|
|
11
|
+
* @returns Object containing the onKeyDown event handler
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* \`\`\`tsx
|
|
15
|
+
* function MyInput() {
|
|
16
|
+
* const { onKeyDown } = useGeez({
|
|
17
|
+
* enabled: true,
|
|
18
|
+
* onTransform: (result) => console.log('Transformed:', result)
|
|
19
|
+
* })
|
|
20
|
+
* return <input onKeyDown={onKeyDown} />
|
|
21
|
+
* }
|
|
22
|
+
* \`\`\`
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* \`\`\`tsx
|
|
26
|
+
* // With controlled component
|
|
27
|
+
* function ControlledInput() {
|
|
28
|
+
* const [value, setValue] = useState('')
|
|
29
|
+
* const { onKeyDown } = useGeez()
|
|
30
|
+
*
|
|
31
|
+
* return (
|
|
32
|
+
* <input
|
|
33
|
+
* value={value}
|
|
34
|
+
* onChange={(e) => setValue(e.target.value)}
|
|
35
|
+
* onKeyDown={onKeyDown}
|
|
36
|
+
* />
|
|
37
|
+
* )
|
|
38
|
+
* }
|
|
39
|
+
* \`\`\`
|
|
40
|
+
*/
|
|
41
|
+
export declare const useGeez: (options?: GeezOptions) => {
|
|
42
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "geez-input",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Type-safe React library for Geez (Ethiopic) script input with phonetic keyboard support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"geez",
|
|
7
|
+
"ethiopic",
|
|
8
|
+
"amharic",
|
|
9
|
+
"tigrinya",
|
|
10
|
+
"react",
|
|
11
|
+
"typescript",
|
|
12
|
+
"keyboard",
|
|
13
|
+
"input",
|
|
14
|
+
"phonetic"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/yourusername/geez-library#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/yourusername/geez-library/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/yourusername/geez-library.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Your Name",
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"**/*.css"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/main.js",
|
|
31
|
+
"module": "./dist/main.js",
|
|
32
|
+
"types": "./dist/main.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": "./dist/main.js",
|
|
36
|
+
"types": "./dist/main.d.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "npx vite build",
|
|
46
|
+
"dev": "npx vite",
|
|
47
|
+
"preview": "npx vite preview",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22",
|
|
53
|
+
"@types/react": "^19",
|
|
54
|
+
"@types/react-dom": "^19",
|
|
55
|
+
"@vitejs/plugin-react": "5.1.2",
|
|
56
|
+
"react": "19.2.0",
|
|
57
|
+
"react-dom": "19.2.0",
|
|
58
|
+
"typescript": "^5",
|
|
59
|
+
"vite": "7.3.0",
|
|
60
|
+
"vite-plugin-dts": "4.5.4",
|
|
61
|
+
"vite-plugin-lib-inject-css": "2.2.2"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
65
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|