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 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';
@@ -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>;
@@ -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
+ }