@siyabasa/singlish 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,111 @@
1
+ # Remeinium Open Source License (ROSL)
2
+
3
+ **Version 1.0 — February 2026**
4
+
5
+ ## 1. Background
6
+
7
+ This Remeinium Open Source License (“License”) governs the use of software source code, documentation, and related build artifacts released by **Remeinium Corp** (“Remeinium”) through **Remeinium Siyabasa Labs**, including but not limited to the **@siyabasa/singlish** library.
8
+
9
+ By downloading, accessing, using, modifying, or distributing the Software, you agree to the terms of this License.
10
+
11
+ ---
12
+
13
+ ## 2. License Grant
14
+
15
+ Remeinium grants you a **worldwide, royalty-free, non-exclusive, perpetual license** to:
16
+
17
+ * Use the Software for **research, educational, and commercial purposes**
18
+ * Reproduce and distribute the Software, in original or modified form
19
+ * Fine-tune, adapt, or integrate the Software into applications, systems, or services
20
+ * Distribute binaries or compiled versions of the Software without restriction
21
+
22
+ ---
23
+
24
+ ## 3. Attribution Requirements
25
+
26
+ If you distribute the Software or use it in a public or commercial product, you must provide reasonable attribution:
27
+
28
+ > “This product uses software developed by Remeinium Siyabasa Labs / Remeinium Corp.”
29
+
30
+ Attribution may be provided in documentation, an “About” page, or similar materials.
31
+
32
+ ---
33
+
34
+ ## 4. Restrictions
35
+
36
+ You may **not**:
37
+
38
+ * Misrepresent the Software or derivatives as being authored by anyone other than Remeinium
39
+ * Use Remeinium trademarks, logos, or branding without explicit permission
40
+ * Falsely imply endorsement by Remeinium
41
+
42
+ This License **does not** restrict commercial use, resale, or deployment of applications built using the Software.
43
+
44
+ ---
45
+
46
+ ## 5. Ownership
47
+
48
+ Remeinium retains ownership of the original Software and source code.
49
+ You retain ownership of:
50
+
51
+ * Your applications, services, or research
52
+ * Your modified versions of the Software
53
+ * The output generated by the Software
54
+
55
+ No rights are granted to Remeinium over your downstream work.
56
+
57
+ ---
58
+
59
+ ## 6. Responsible Use
60
+
61
+ The Software is provided as a general-purpose language technology tool.
62
+ You are solely responsible for ensuring compliance with applicable laws, regulations, and ethical norms in your use of the Software.
63
+
64
+ Remeinium Siyabasa Labs encourages responsible, non-harmful use aligned with human well-being and the advancement of language technology.
65
+
66
+ ---
67
+
68
+ ## 7. Disclaimer of Warranty
69
+
70
+ THE SOFTWARE IS PROVIDED **“AS IS”**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
71
+
72
+ ---
73
+
74
+ ## 8. Limitation of Liability
75
+
76
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, REMEINIUM SHALL NOT BE LIABLE FOR ANY DAMAGES ARISING FROM THE USE, MISUSE, OR INABILITY TO USE THE SOFTWARE, INCLUDING INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES.
77
+
78
+ ---
79
+
80
+ ## 9. Termination
81
+
82
+ This License terminates automatically if you violate its terms.
83
+ Upon termination, you must cease use and distribution of the Software.
84
+
85
+ ---
86
+
87
+ ## 10. Governing Law
88
+
89
+ This License shall be governed by the laws of **Sri Lanka**, without regard to conflict-of-law principles.
90
+
91
+ ---
92
+
93
+ ## 11. Entire Agreement
94
+
95
+ This License constitutes the entire agreement concerning the Software and supersedes any prior statements or understandings.
96
+
97
+ ---
98
+
99
+ ## 12. Attribution of Origin
100
+
101
+ **@siyabasa/singlish** is released by **Remeinium Siyabasa Labs** in service of advancing Sinhala language technology and open human knowledge.
102
+
103
+ ---
104
+
105
+ ## 13. Contact Us
106
+
107
+ If you have questions, concerns, or requests regarding this License, you may contact us at:
108
+
109
+ Email: support@remeinium.com
110
+ Organization: Remeinium Corp.
111
+ Web: https://labs.remeinium.com/siyabasa
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @siyabasa/singlish
2
+
3
+ A deterministic, high-performance transliteration engine for Singlish (Romanized Sinhala) with phonetic precision and zero-latency IME support. Built for the modern web by **Remeinium Siyabasa Labs**.
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/@siyabasa/singlish.svg)](https://www.npmjs.com/package/@siyabasa/singlish)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@siyabasa/singlish)](https://bundlephobia.com/package/@siyabasa/singlish)
7
+ [![License](https://img.shields.io/npm/l/@siyabasa/singlish.svg)](https://github.com/remeinium/singlish/blob/main/LICENSE)
8
+
9
+ ## Design Philosophy
10
+
11
+ The Singlish Input Method Editor (IME) landscape has long been fragmented by ad-hoc regex replacements and non-standard transliteration schemes. **@siyabasa/singlish** introduces a rigourous, computer-science first approach to Sinhala transliteration:
12
+
13
+ - **Deterministic Phoneme Tokenization**: Unlike simple string replacement, our engine tokenizes Roman input into phonetic units before rendering, ensuring 100% accuracy for complex conjuncts (*yansaya*, *rakaransaya*) and vowel modifiers.
14
+ - **Zero-Latency Architecture**: Optimized for the critical rendering path. The core conversion engine operates in O(n) time complexity using a prefix-trie based lookahead parser.
15
+ - **Universal Runtime**: Isomorphic design that runs seamlessly on the Edge, Node.js, and in the Browser.
16
+
17
+ ## Architecture
18
+
19
+ The engine uses a two-stage compilation process:
20
+ 1. **Lexical Analysis**: Input text is scanned and tokenized into phonemes using a greedy matching algorithm against a compiled trie of 400+ phoneme patterns.
21
+ 2. **Contextual Rendering**: A state-machine renderer processes the token stream to handle inherent vowels, *hal-kirima*, and context-dependent glyph shaping (e.g., standard *ra* vs *rakaransaya* forms).
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @siyabasa/singlish
27
+ # or
28
+ yarn add @siyabasa/singlish
29
+ # or
30
+ pnpm add @siyabasa/singlish
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### Core Transliteration
36
+
37
+ The core API is stateless and synchronous, designed for high-throughput server-side rendering or bulk text processing.
38
+
39
+ ```typescript
40
+ import { convertSinglishToSinhala } from '@siyabasa/singlish';
41
+
42
+ const output = convertSinglishToSinhala('aayuboowan');
43
+ // Output: "ආයුබෝවන්"
44
+ ```
45
+
46
+ ### React IME Hook
47
+
48
+ For building rich text editors or chat interfaces, use the `useSinglishConverter` hook. It manages cursor position, input history, and IME state automatically.
49
+
50
+ ```tsx
51
+ import { useSinglishConverter } from '@siyabasa/singlish';
52
+
53
+ export function Editor() {
54
+ const { inputProps, enabled, toggle } = useSinglishConverter({ enabled: true });
55
+
56
+ return (
57
+ <div className="flex flex-col gap-2">
58
+ <div className="flex justify-between items-center">
59
+ <span className="text-sm font-medium">Input Method</span>
60
+ <button
61
+ onClick={toggle}
62
+ className="px-3 py-1 text-xs rounded-full bg-gray-100 hover:bg-gray-200 transition-colors"
63
+ >
64
+ {enabled ? '🇱🇰 Siyabasa IME' : '🇺🇸 English'}
65
+ </button>
66
+ </div>
67
+ <textarea
68
+ {...inputProps}
69
+ className="w-full h-32 p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
70
+ placeholder="Type in Singlish (e.g., 'kohomada')..."
71
+ />
72
+ </div>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ### Auto-Attach (Zero Config)
78
+
79
+ For legacy applications or rapid prototyping, the Auto-Attach module uses a `MutationObserver` to automatically hydrate all input fields in the DOM with Singlish capabilities.
80
+
81
+ ```typescript
82
+ import { createAutoAttach, createUIToggle } from '@siyabasa/singlish';
83
+
84
+ // Automatically attaches to all input[type="text"] and textarea elements
85
+ const autoAttach = createAutoAttach({
86
+ exclude: '[data-no-ime]' // Optional exclusion selector
87
+ });
88
+
89
+ // Mounts a floating toggle widget
90
+ const toggle = createUIToggle({
91
+ position: 'bottom-right',
92
+ theme: 'auto'
93
+ });
94
+
95
+ toggle.mount();
96
+ ```
97
+
98
+ ## API Reference
99
+
100
+ ### `convertSinglishToSinhala(text: string, options?: ConversionOptions): string`
101
+ Pure function to transpile Singlish text to Sinhala Unicode.
102
+
103
+ ### `convertWithMetadata(text: string): ConversionResult`
104
+ Extended version of the core converter that provides tokenization details and conversion metrics.
105
+
106
+ ### `useSinglishConverter(options?: HookOptions)`
107
+ React hook that returns spreadable input props (`value`, `onChange`, `onKeyDown`, etc.) for seamless IME integration.
108
+
109
+ ## License
110
+
111
+ ROSL 1.0 © [Remeinium Siyabasa Labs](https://labs.remeinium.com)
112
+
113
+ ---
114
+
115
+ <div align="center">
116
+ <p>Built with ❤️ for the Sri Lankan Developer Community</p>
117
+ </div>
package/dist/index.cjs ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";var J=Object.defineProperty;var we=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var Ke=(t,e,n)=>e in t?J(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var Ue=(t,e)=>{for(var n in e)J(t,n,{get:e[n],enumerable:!0})},Be=(t,e,n,u)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of De(e))!Pe.call(t,o)&&o!==n&&J(t,o,{get:()=>e[o],enumerable:!(u=we(e,o))||u.enumerable});return t};var Fe=t=>Be(J({},"__esModule",{value:!0}),t);var de=(t,e,n)=>Ke(t,typeof e!="symbol"?e+"":e,n);var st={};Ue(st,{CONSONANTS:()=>ye,HAL_CHAR:()=>ve,SPECIAL:()=>Re,SinglishIME:()=>D,VOWELS:()=>be,VOWEL_MODIFIERS_MAP:()=>Le,ZWJ_CHAR:()=>Oe,containsSinhala:()=>_e,convertLastWord:()=>Se,convertSinglishToSinhala:()=>H,convertWithMetadata:()=>me,createAutoAttach:()=>Ve,createUIToggle:()=>ke,isSinhalaChar:()=>Q,resolveBuffer:()=>oe,segmentText:()=>Te,useSinglishConverter:()=>xe});module.exports=Fe(st);var ze=[["zdha","SANYAKA_DHA"],["chh","ASPIRATED_CH"],["thh","ASPIRATED_TH"],["dhh","ASPIRATED_DH"],["zga","SANYAKA_GA"],["zja","SANYAKA_JA"],["zda","SANYAKA_DA"],["zqa","SANYAKA_DHA"],["zka","SANYAKA_KA"],["zha","SANYAKA_HA"],["aa","V_AA"],["Aa","V_AE_LONG"],["AA","V_AE_LONG"],["ai","V_AI"],["au","V_AU"],["ou","V_AU"],["ii","V_II"],["uu","V_UU"],["ee","V_EE"],["oo","V_OO"],["Ru","V_RU_LONG"],["Lu","SPECIAL_LU"],["kh","KH"],["gh","GH"],["ch","CH"],["ph","PH"],["bh","BH"],["th","TH"],["dh","DH"],["Sh","RETROFLEX_S"],["sh","SH"],["ng","N_G"],["Th","RETROFLEX_TH"],["Dh","RETROFLEX_DH"],["Ba","SANYAKA_BA"],["a","V_A"],["A","V_AE"],["i","V_I"],["u","V_U"],["U","V_UU"],["e","V_E"],["E","V_EE"],["o","V_O"],["O","V_OO"],["R","V_RU"],["k","K"],["g","G"],["j","J"],["t","RETROFLEX_T"],["d","RETROFLEX_D"],["T","RETROFLEX_TH_SINGLE"],["D","RETROFLEX_DH_SINGLE"],["n","N"],["N","RETROFLEX_N"],["p","P"],["b","B_LOWER"],["B","SANYAKA_B"],["m","M"],["y","Y"],["r","R_CONS"],["l","L_CONS"],["L","RETROFLEX_L"],["v","V_CONS"],["w","V_CONS"],["s","S_CONS"],["S","RETROFLEX_S"],["h","H_CONS"],["f","F"],["q","DH"],["x","ANUSVARA"],["X","MAHAPRANAANUSVARA"],["H","VISARGA"],[" "," "],[`
2
+ `,`
3
+ `],[" "," "],[":",":"],[";",";"],[".","."],["-","-"],[",",","],["/","/"],["?","?"],["!","!"],["(","("],[")",")"],["[","["],["]","]"],['"','"'],["'","'"],["0","0"],["1","1"],["2","2"],["3","3"],["4","4"],["5","5"],["6","6"],["7","7"],["8","8"],["9","9"]],W={K:"\u0D9A",KH:"\u0D9B",G:"\u0D9C",GH:"\u0D9D",CH:"\u0DA0",ASPIRATED_CH:"\u0DA1",J:"\u0DA2",RETROFLEX_T:"\u0DA7",RETROFLEX_TH:"\u0DA8",RETROFLEX_TH_SINGLE:"\u0DA8",RETROFLEX_D:"\u0DA9",RETROFLEX_DH:"\u0DAA",RETROFLEX_DH_SINGLE:"\u0DAA",RETROFLEX_N:"\u0DAB",TH:"\u0DAD",ASPIRATED_TH:"\u0DAE",DH:"\u0DAF",ASPIRATED_DH:"\u0DB0",N:"\u0DB1",P:"\u0DB4",PH:"\u0DB5",B_LOWER:"\u0DB6",BH:"\u0DB7",M:"\u0DB8",Y:"\u0DBA",R_CONS:"\u0DBB",L_CONS:"\u0DBD",RETROFLEX_L:"\u0DC5",V_CONS:"\u0DC0",SH:"\u0DC1",RETROFLEX_S:"\u0DC2",S_CONS:"\u0DC3",H_CONS:"\u0DC4",F:"\u0DC6"},pe=new Set(Object.keys(W)),Ae={SANYAKA_GA:"\u0D9F",SANYAKA_JA:"\u0DA6",SANYAKA_DA:"\u0DAC",SANYAKA_DHA:"\u0DB3",SANYAKA_KA:"\u0DA4",SANYAKA_HA:"\u0DA5",SANYAKA_B:"\u0DB9",SANYAKA_BA:"\u0DB9"},We=new Set(Object.keys(Ae)),re={V_A:"\u0D85",V_AA:"\u0D86",V_AE:"\u0D87",V_AE_LONG:"\u0D88",V_I:"\u0D89",V_II:"\u0D8A",V_U:"\u0D8B",V_UU:"\u0D8C",V_RU:"\u0D8D",V_RU_LONG:"\u0D8E",V_E:"\u0D91",V_EE:"\u0D92",V_AI:"\u0D93",V_O:"\u0D94",V_OO:"\u0D95",V_AU:"\u0D96"},Y={V_AA:"\u0DCF",V_AE:"\u0DD0",V_AE_LONG:"\u0DD1",V_I:"\u0DD2",V_II:"\u0DD3",V_U:"\u0DD4",V_UU:"\u0DD6",V_E:"\u0DD9",V_EE:"\u0DDA",V_AI:"\u0DDB",V_O:"\u0DDC",V_OO:"\u0DDD",V_AU:"\u0DDE"},Ye=new Set(Object.keys(re)),Ee=new Set(Object.keys(Y)),te={ANUSVARA:"\u0D82",MAHAPRANAANUSVARA:"\u0D9E",VISARGA:"\u0D83",SPECIAL_LU:"\u0DC5\u0DD4"},ne="N_G";function B(t){return pe.has(t)}function Z(t){return We.has(t)}function Ge(t){return Ye.has(t)}function q(t){return t==="V_A"}function $(t){return Ee.has(t)}function Xe(t){return t===void 0?!0:[" ",".",",","!","?",";",":",`
4
+ `," ","-","/","(",")","[","]",'"',"'"].includes(t)}function je(t){return t==="Y"||t==="R_CONS"}function Je(t){let e=[],n=0;for(;n<t.length;){let u=!1;for(let[o,s]of ze)if(t.substring(n,n+o.length)===o){e.push(s),n+=o.length,u=!0;break}u||(e.push(t[n]),n++)}return e}function Ze(t){let e=[],n=0;for(;n<t.length;){let u=t[n],o=t[n+1];if(te[u]!==void 0){e.push(te[u]),n++;continue}if(u===ne){let s=W.N,i=W.G;o!==void 0&&$(o)?(e.push(s+"\u0DCA"),e.push(i),e.push(Y[o]),n+=2):o!==void 0&&q(o)?(e.push(s+"\u0DCA"),e.push(i),n+=2):(o!==void 0&&B(o),e.push(s+"\u0DCA"),e.push(i+"\u0DCA"),n++);continue}if(Ge(u)&&!B(u)){e.push(re[u]),n++;continue}if(Z(u)){let s=Ae[u];u==="SANYAKA_B"?o!==void 0&&$(o)?(e.push(s),e.push(Y[o]),n+=2):o!==void 0&&q(o)?(e.push(s),n+=2):(o!==void 0&&(B(o)||Z(o)),e.push(s+"\u0DCA"),n++):(e.push(s),n++);continue}if(B(u)){let s=W[u];if(o!==void 0&&je(o)){let i=t[n+2],g=W[o];if(o==="R_CONS"&&i==="V_U"){e.push(s+"\u0DD8"),n+=3;continue}if(o==="R_CONS"&&i==="V_UU"){e.push(s+"\u0DF2"),n+=3;continue}if(i!==void 0&&$(i)){e.push(s+"\u0DCA\u200D"+g),e.push(Y[i]),n+=3;continue}else if(i!==void 0&&q(i)){e.push(s+"\u0DCA\u200D"+g),n+=3;continue}else if(i!==void 0&&(B(i)||Z(i)||i===ne)){e.push(s+"\u0DCA\u200D"+g+"\u0DCA"),n+=2;continue}else{e.push(s+"\u0DCA\u200D"+g+"\u0DCA"),n+=2;continue}}if(o!==void 0&&$(o)){e.push(s),e.push(Y[o]),n+=2;continue}if(o!==void 0&&q(o)){e.push(s),n+=2;continue}if(o!==void 0&&(B(o)||Z(o)||o===ne)){e.push(s+"\u0DCA"),n++;continue}if(Xe(o)||o===void 0){e.push(s+"\u0DCA"),n++;continue}if(o!==void 0&&te[o]!==void 0){e.push(s),n++;continue}e.push(s),n++;continue}e.push(u),n++}return e.join("")}function H(t,e){if(!t)return t;let n=Je(t);return Ze(n)}function Se(t,e){if(!t)return t;let n=-1;for(let s=t.length-1;s>=0;s--)if(t[s]===" "||t[s]===`
5
+ `){n=s;break}if(n===-1)return H(t,e);let u=t.substring(0,n+1),o=t.substring(n+1);return o?u+H(o,e):t}function me(t,e){let n=t,u=H(t,e),o=0;for(let s=0;s<Math.min(n.length,u.length);s++)n[s]!==u[s]&&o++;return{text:u,original:n,conversions:o}}function Q(t){let e=t.charCodeAt(0);return e>=3456&&e<=3583}function _e(t){for(let e of t)if(Q(e))return!0;return!1}function Te(t){let e=[],n="",u=null;for(let o of t){let s=Q(o);u===null&&(u=s),s===u?n+=o:(n&&e.push({text:n,isSinhala:u}),n=o,u=s)}return n&&u!==null&&e.push({text:n,isSinhala:u}),e}var be=re,Le=Ee,ye=pe,Re={x:"\u0D82",H:"\u0D83"},ve="\u0DCA",Oe="\u200D";var c=require("react");function Ne(){return{children:new Map,isTerminal:!1}}var qe=["zdha","chh","thh","dhh","zga","zja","zda","zqa","zka","zha","aa","Aa","AA","ai","au","ou","ii","uu","ee","oo","Ru","Lu","kh","gh","ch","ph","bh","th","dh","Sh","sh","ng","Th","Dh","Ba","a","A","i","u","U","e","E","o","O","R","k","g","j","t","d","T","D","n","N","p","b","B","m","y","r","l","L","v","w","s","S","h","f","q","x","X","H"],se=$e(qe);function $e(t){let e=Ne();for(let n of t){let u=e;for(let o of n){let s=u.children.get(o);s||(s=Ne(),u.children.set(o,s)),u=s}u.isTerminal=!0}return e}function Ce(t){if(t.length===0)return!0;let e=se;for(let n of t){let u=e.children.get(n);if(!u)return!1;e=u}return e.children.size>0}function Qe(t){if(t.length===0)return!0;let e=se;for(let n of t){let u=e.children.get(n);if(!u)return!1;e=u}return!0}function et(t,e){let n=se,u=0;for(let o=e;o<t.length;o++){let s=n.children.get(t[o]);if(!s)break;n=s,n.isTerminal&&(u=o-e+1)}return u}var He=new Set(["k","kh","g","gh","ch","chh","j","t","Th","d","Dh","T","D","th","thh","dh","dhh","n","N","p","ph","b","bh","B","Ba","m","y","r","l","L","v","w","sh","Sh","S","s","h","f","q","ng","zga","zja","zda","zdha","zqa","zka","zha"]),tt=new Set(["a","aa","A","Aa","AA","i","ii","u","uu","U","e","ee","E","o","oo","O","ai","au","ou","R","Ru"]);function oe(t){if(!t)return{toCommit:"",remaining:""};let e=[],n=0,u=-1;for(;n<t.length;){let m=et(t,n);if(m>0)e.push({start:n,length:m,pattern:t.slice(n,n+m),isPassthrough:!1}),n+=m;else if(Qe(t[n])){u=n;break}else e.push({start:n,length:1,pattern:t[n],isPassthrough:!0}),n+=1}if(e.length===0)return{toCommit:"",remaining:t};if(u>=0){let m=e.length;for(;m>0;){let a=e[m-1];if(!a.isPassthrough&&He.has(a.pattern)){m--;continue}break}let A=m>0?e[m-1].start+e[m-1].length:0;return A===0?{toCommit:"",remaining:t}:{toCommit:H(t.slice(0,A)),remaining:t.slice(A)}}let o=e.length-1,s=e[o],i=s.start+s.length;if(e.length===1)return s.isPassthrough?{toCommit:s.pattern,remaining:""}:{toCommit:"",remaining:t};let g=o;for(i===t.length&&Ce(s.pattern)&&(g=o);g>0;){let m=e[g],A=e[g-1];if(!A.isPassthrough&&He.has(A.pattern)&&!m.isPassthrough&&(tt.has(m.pattern)||Ce(m.pattern))){g--;continue}break}let _=e[g].start;if(_===0)return{toCommit:"",remaining:t};let k=t.slice(0,_),V=t.slice(_);return{toCommit:H(k),remaining:V}}var D=class{constructor(){de(this,"buffer","")}getBuffer(){return this.buffer}getSpeculativeDisplay(){return this.buffer?H(this.buffer):""}processKey(e){this.buffer+=e;let n=oe(this.buffer);return this.buffer=n.remaining,n}backspace(){return this.buffer.length>0?(this.buffer=this.buffer.slice(0,-1),!0):!1}flush(){if(!this.buffer)return"";let e=H(this.buffer);return this.buffer="",e}hasPending(){return this.buffer.length>0}reset(){this.buffer=""}};function ie(t){return t.length===1&&/[a-zA-Z]/.test(t)}function xe(t){let{enabled:e=!1,onConvert:n,options:u,initialValue:o="",mobileSupport:s=!1}=t||{},[i,g]=(0,c.useState)(o),[_,k]=(0,c.useState)(e),[V,m]=(0,c.useState)(""),A=(0,c.useRef)(null),a=(0,c.useRef)(new D),N=(0,c.useRef)(0),x=(0,c.useRef)(-1),E=(0,c.useRef)(null),v=(0,c.useRef)(null),l=(0,c.useCallback)(()=>{let r=a.current.getBuffer(),f=a.current.getSpeculativeDisplay();N.current=f.length,m(r)},[]),h=(0,c.useCallback)((r,f,S)=>{let O=N.current,C=Math.max(0,f-O),I=r.slice(0,C),d=r.slice(f);return{newValue:I+S+d,newCursor:I.length+S.length}},[]),b=(0,c.useCallback)((r,f)=>{x.current=f,v.current=r,g(r)},[]),M=(0,c.useCallback)(()=>{a.current.reset(),N.current=0,l(),x.current=-1,E.current=null,v.current=null},[l]),w=(0,c.useCallback)((r,f)=>{if(!a.current.hasPending())return{value:f,cursor:r};let S=a.current.flush(),O=h(f,r,S);return l(),{value:O.newValue,cursor:O.newCursor}},[h,l]),ee=(0,c.useCallback)(r=>{M(),g(r)},[M]),y=(0,c.useCallback)(()=>{A.current?.focus()},[]),R=(0,c.useCallback)(()=>{M(),g("")},[M]),F=(0,c.useCallback)(()=>{if(!i)return;let r=i,f=H(r,u);M(),v.current=f,g(f),n&&f!==r&&n(r,f)},[i,u,n,M]),G=(0,c.useCallback)(()=>{if(!a.current.hasPending())return i;let r=A.current?.selectionStart??i.length,f=w(r,i);return b(f.value,f.cursor),f.value},[i,w,b]),K=(0,c.useCallback)(r=>{if(!r&&a.current.hasPending()){let f=A.current?.selectionStart??i.length,S=a.current.flush(),O=h(i,f,S);l(),g(O.newValue)}a.current.reset(),l(),k(r)},[i,h,l]),X=(0,c.useCallback)(()=>{K(!_)},[_,K]),z=(0,c.useCallback)(r=>{A.current=r},[]),ue=(0,c.useCallback)(r=>{if(A.current=r.currentTarget,!_||!s)return;let f=r.nativeEvent;if(f.inputType!=="insertText")return;let S=f.data??"";if(S.length!==1)return;if(E.current===S){r.preventDefault(),E.current=null;return}E.current=null;let O=r.currentTarget,C=O.selectionStart,I=O.selectionEnd,d=C,p=C!==I;if(ie(S)){r.preventDefault();let T=i,L=d;if(p){if(a.current.hasPending()){let Me=a.current.flush();T=h(T,L,Me).newValue,N.current=0}let j=Math.min(C,I),ge=Math.max(C,I);T=T.slice(0,j)+T.slice(ge),L=j,a.current.reset(),N.current=0}let P=a.current.processKey(S),U=h(T,L,P.toCommit+a.current.getSpeculativeDisplay());l(),b(U.newValue,U.newCursor);return}if(a.current.hasPending()){r.preventDefault();let T=a.current.flush(),L=h(i,d,T+S);l(),b(L.newValue,L.newCursor)}},[_,s,i,h,l,b]),le=(0,c.useCallback)(r=>{if(A.current=r.currentTarget,!_)return;let f=r.currentTarget,S=f.selectionStart,O=f.selectionEnd,C=S,I=S!==O;if(ie(r.key)&&!r.ctrlKey&&!r.metaKey&&!r.altKey){r.preventDefault(),E.current=r.key;let d=i,p=C;if(I){if(a.current.hasPending()){let j=a.current.flush();d=h(d,p,j).newValue,N.current=0}let P=Math.min(S,O),U=Math.max(S,O);d=d.slice(0,P)+d.slice(U),p=P,a.current.reset(),N.current=0}let T=a.current.processKey(r.key),L=h(d,p,T.toCommit+a.current.getSpeculativeDisplay());l(),b(L.newValue,L.newCursor);return}if(r.key==="Backspace"&&!r.ctrlKey&&!r.metaKey&&!I&&a.current.hasPending()){r.preventDefault(),a.current.backspace();let d=a.current.getSpeculativeDisplay(),p=h(i,C,d);l(),b(p.newValue,p.newCursor);return}if((r.ctrlKey||r.metaKey)&&r.key==="a"){if(a.current.hasPending()){let d=a.current.flush(),p=h(i,C,d);l(),x.current=-1,g(p.newValue)}return}if((r.ctrlKey||r.metaKey)&&(r.key==="z"||r.key==="y")){a.current.reset(),N.current=0,l(),x.current=-1;return}if((r.ctrlKey||r.metaKey)&&r.key==="Enter"){r.preventDefault();let d=i;if(a.current.reset(),l(),d){let p=H(d,u);b(p,p.length),n&&p!==d&&n(d,p)}return}if(r.key.length===1&&!ie(r.key)&&!r.ctrlKey&&!r.metaKey&&!r.altKey){if(a.current.hasPending()){r.preventDefault(),E.current=r.key;let d=a.current.flush(),p=h(i,C,d+r.key);l(),b(p.newValue,p.newCursor);return}return}if(r.key==="Enter"&&!r.ctrlKey&&!r.metaKey){if(a.current.hasPending()){r.preventDefault();let d=a.current.flush(),p=h(i,C,d+`
6
+ `);l(),b(p.newValue,p.newCursor);return}return}if(["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End"].includes(r.key)){if(a.current.hasPending()){let d=a.current.flush(),p=h(i,C,d);l(),b(p.newValue,p.newCursor)}return}if(r.key==="Backspace"||r.key==="Delete"){if(a.current.hasPending()){r.preventDefault();let d=a.current.flush(),p=h(i,C,d);l();let T=p.newValue,L=p.newCursor;if(I){let P=Math.min(S,O),U=Math.max(S,O);T=T.slice(0,P)+T.slice(U),L=P}else r.key==="Backspace"&&L>0?(T=T.slice(0,L-1)+T.slice(L),L=L-1):r.key==="Delete"&&L<T.length&&(T=T.slice(0,L)+T.slice(L+1));b(T,L)}return}},[_,i,u,n,h,l,b]),ae=(0,c.useCallback)(r=>{if(A.current=r.currentTarget,!_){g(r.target.value);return}if(v.current!==null&&r.target.value===v.current){v.current=null;return}v.current=null,a.current.reset(),N.current=0,l(),x.current=-1,g(r.target.value)},[_,l]),ce=(0,c.useCallback)(r=>{if(A.current=r.currentTarget,!!_&&a.current.hasPending()){let f=A.current?.selectionStart??i.length,S=a.current.flush(),O=h(i,f,S);l(),g(O.newValue)}},[_,i,h,l]),he=(0,c.useCallback)(r=>{if(A.current=r.currentTarget,!!_&&a.current.hasPending()){let f=A.current?.selectionStart??i.length,S=w(f,i);b(S.value,S.cursor)}},[_,i,w,b]);(0,c.useLayoutEffect)(()=>{let r=x.current;r>=0&&A.current&&(A.current.selectionStart=r,A.current.selectionEnd=r),x.current=-1},[i]);let fe=(0,c.useMemo)(()=>({ref:z,value:i,onKeyDown:le,onBeforeInput:ue,onChange:ae,onPaste:ce,onBlur:he}),[z,i,le,ue,ae,ce,he]);return(0,c.useMemo)(()=>({inputProps:fe,value:i,setValue:ee,bufferDisplay:V,enabled:_,toggle:X,setEnabled:K,focus:y,clear:R,convertAll:F,flushPending:G}),[fe,i,ee,V,_,X,K,y,R,F,G])}var nt='input[type="text"], input[type="search"], textarea';function Ve(t={}){let{enabled:e=!0,selector:n=nt,exclude:u,onAttach:o}=t,s=!1,i=null,g=new WeakSet,_=new WeakMap;function k(E){return!(g.has(E)||u&&E.matches(u))}function V(E){if(!k(E))return;let v=new D;_.set(E,v),g.add(E);let l=E instanceof HTMLTextAreaElement,h=E instanceof HTMLInputElement;if(!l&&!h)return;function b(y){if(y.key==="Backspace"){v.backspace()&&(y.preventDefault(),void 0);return}if(y.key==="Enter"||y.key==="Escape"){let R=v.flush();R&&w(R);return}if(y.key.length===1&&!y.ctrlKey&&!y.metaKey&&!y.altKey){y.preventDefault();let R=v.processKey(y.key);R.toCommit&&w(R.toCommit)}}function M(){let y=v.flush();y&&w(y)}function w(y){let R=E,F=R.selectionStart??R.value.length,G=R.selectionEnd??R.value.length,K=R.value.substring(0,F),X=R.value.substring(G);R.value=K+y+X;let z=F+y.length;R.setSelectionRange(z,z),R.dispatchEvent(new Event("input",{bubbles:!0}))}function ee(){}E.addEventListener("keydown",b),E.addEventListener("blur",M),o?.(E)}function m(){document.querySelectorAll(n).forEach(V)}function A(){i=new MutationObserver(E=>{for(let v of E)for(let l of v.addedNodes){if(l.nodeType!==Node.ELEMENT_NODE)continue;let h=l;h.matches(n)&&V(h),h.querySelectorAll(n).forEach(V)}}),i.observe(document.body,{childList:!0,subtree:!0})}function a(){s||(s=!0,m(),A())}function N(){s&&(s=!1,i?.disconnect(),i=null)}function x(){s?N():a()}return e&&a(),{start:a,stop:N,isRunning:()=>s,toggle:x}}var Ie="singlish-enabled";function ke(t={}){let{position:e="bottom-right",theme:n="auto",showLabel:u=!0,onToggle:o}=t,s=_(),i=null,g=null;function _(){if(typeof localStorage>"u")return!0;let l=localStorage.getItem(Ie);return l===null?!0:l==="true"}function k(l){typeof localStorage>"u"||localStorage.setItem(Ie,String(l))}function V(){let l=document.createElement("button");return l.className=`singlish-toggle singlish-toggle--${e}`,l.setAttribute("aria-label","Toggle Singlish input"),l.setAttribute("type","button"),m(l),l.addEventListener("click",A),l.addEventListener("keydown",h=>{(h.key==="Enter"||h.key===" ")&&(h.preventDefault(),A())}),l}function m(l){let h=s?"\u{1F1F1}\u{1F1F0}":"EN",b=s?"\u0DC3\u0DD2\u0D82":"English";u?l.innerHTML=`<span class="singlish-toggle__icon">${h}</span><span class="singlish-toggle__label">${b}</span>`:l.innerHTML=`<span class="singlish-toggle__icon">${h}</span>`,l.classList.toggle("singlish-toggle--enabled",s),l.setAttribute("aria-pressed",String(s))}function A(){s=!s,k(s),i&&m(i),o?.(s)}function a(l){i||(g=l||document.body,i=V(),g.appendChild(i),v())}function N(){i&&(i.remove(),i=null,g=null)}function x(l){s!==l&&(s=l,k(l),i&&m(i))}function E(){return s}function v(){if(document.getElementById("singlish-toggle-styles"))return;let l=document.createElement("style");l.id="singlish-toggle-styles",l.textContent=rt(n),document.head.appendChild(l)}return{mount:a,unmount:N,setEnabled:x,getEnabled:E}}function rt(t){let e="rgba(255, 255, 255, 0.9)",n="rgba(30, 30, 30, 0.9)",u="#1a1a1a",o="#ffffff",s=t==="dark"?n:e,i=t==="dark"?o:u;return`
7
+ .singlish-toggle {
8
+ position: fixed;
9
+ z-index: 9999;
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 8px;
13
+ padding: 10px 16px;
14
+ background: ${s};
15
+ backdrop-filter: blur(10px);
16
+ border: 1px solid rgba(255, 255, 255, 0.2);
17
+ border-radius: 12px;
18
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
19
+ color: ${i};
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
+ font-size: 14px;
22
+ font-weight: 500;
23
+ cursor: pointer;
24
+ transition: all 0.2s ease;
25
+ user-select: none;
26
+ }
27
+
28
+ .singlish-toggle:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
31
+ }
32
+
33
+ .singlish-toggle:active {
34
+ transform: translateY(0);
35
+ }
36
+
37
+ .singlish-toggle:focus {
38
+ outline: 2px solid #4A90E2;
39
+ outline-offset: 2px;
40
+ }
41
+
42
+ .singlish-toggle--top-right {
43
+ top: 20px;
44
+ right: 20px;
45
+ }
46
+
47
+ .singlish-toggle--top-left {
48
+ top: 20px;
49
+ left: 20px;
50
+ }
51
+
52
+ .singlish-toggle--bottom-right {
53
+ bottom: 20px;
54
+ right: 20px;
55
+ }
56
+
57
+ .singlish-toggle--bottom-left {
58
+ bottom: 20px;
59
+ left: 20px;
60
+ }
61
+
62
+ .singlish-toggle__icon {
63
+ font-size: 18px;
64
+ line-height: 1;
65
+ }
66
+
67
+ .singlish-toggle__label {
68
+ font-size: 13px;
69
+ font-weight: 600;
70
+ }
71
+
72
+ .singlish-toggle--enabled {
73
+ background: linear-gradient(135deg, rgba(74, 144, 226, 0.9), rgba(106, 90, 205, 0.9));
74
+ color: white;
75
+ border-color: rgba(255, 255, 255, 0.3);
76
+ }
77
+
78
+ @media (prefers-color-scheme: dark) {
79
+ .singlish-toggle {
80
+ background: ${t==="auto"?n:s};
81
+ color: ${t==="auto"?o:i};
82
+ }
83
+ }
84
+
85
+ @media (max-width: 768px) {
86
+ .singlish-toggle {
87
+ padding: 8px 12px;
88
+ font-size: 13px;
89
+ }
90
+
91
+ .singlish-toggle__icon {
92
+ font-size: 16px;
93
+ }
94
+ }
95
+ `}0&&(module.exports={CONSONANTS,HAL_CHAR,SPECIAL,SinglishIME,VOWELS,VOWEL_MODIFIERS_MAP,ZWJ_CHAR,containsSinhala,convertLastWord,convertSinglishToSinhala,convertWithMetadata,createAutoAttach,createUIToggle,isSinhalaChar,resolveBuffer,segmentText,useSinglishConverter});
@@ -0,0 +1,247 @@
1
+ interface ConversionOptions {
2
+ /**
3
+ * Whether to preserve non-Sinhala characters (numbers, punctuation, English)
4
+ * @default true
5
+ */
6
+ preserveNonSinhala?: boolean;
7
+ /**
8
+ * Maximum pattern length to attempt matching
9
+ * @default 4
10
+ */
11
+ maxPatternLength?: number;
12
+ }
13
+ interface ConversionResult {
14
+ /**
15
+ * The converted Sinhala text
16
+ */
17
+ text: string;
18
+ /**
19
+ * Original Singlish input
20
+ */
21
+ original: string;
22
+ /**
23
+ * Number of characters converted
24
+ */
25
+ conversions: number;
26
+ }
27
+ interface UseSinglishConverterOptions {
28
+ /**
29
+ * Initial enabled state
30
+ * @default false
31
+ */
32
+ enabled?: boolean;
33
+ /**
34
+ * Callback fired when conversion occurs
35
+ */
36
+ onConvert?: (original: string, converted: string) => void;
37
+ /**
38
+ * Conversion options
39
+ */
40
+ options?: ConversionOptions;
41
+ /**
42
+ * Initial input value
43
+ * @default ''
44
+ */
45
+ initialValue?: string;
46
+ /**
47
+ * Experimental mobile support.
48
+ * Use with caution.
49
+ */
50
+ mobileSupport?: boolean;
51
+ }
52
+ interface SinglishTextareaInputProps {
53
+ ref: React.RefCallback<HTMLTextAreaElement>;
54
+ value: string;
55
+ onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
56
+ onBeforeInput: (e: React.FormEvent<HTMLTextAreaElement>) => void;
57
+ onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
58
+ onPaste: (e: React.ClipboardEvent<HTMLTextAreaElement>) => void;
59
+ onBlur: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
60
+ }
61
+ interface UseSinglishConverterReturn {
62
+ /**
63
+ * Spread this object onto a textarea to enable real-time IME behavior.
64
+ */
65
+ inputProps: SinglishTextareaInputProps;
66
+ /**
67
+ * Current textarea value.
68
+ */
69
+ value: string;
70
+ /**
71
+ * Set textarea value while resetting IME internal state.
72
+ */
73
+ setValue: (value: string) => void;
74
+ /**
75
+ * Current raw IME buffer (romanized form), useful for debug UI.
76
+ */
77
+ bufferDisplay: string;
78
+ /**
79
+ * Current enabled state
80
+ */
81
+ enabled: boolean;
82
+ /**
83
+ * Toggle enabled state
84
+ */
85
+ toggle: () => void;
86
+ /**
87
+ * Set enabled state explicitly.
88
+ */
89
+ setEnabled: (enabled: boolean) => void;
90
+ /**
91
+ * Focus the bound textarea.
92
+ */
93
+ focus: () => void;
94
+ /**
95
+ * Clear textarea and reset IME.
96
+ */
97
+ clear: () => void;
98
+ /**
99
+ * Convert whole current value using stateless converter.
100
+ */
101
+ convertAll: () => void;
102
+ /**
103
+ * Commit pending IME buffer into value and return the committed text.
104
+ */
105
+ flushPending: () => string;
106
+ }
107
+ interface AutoAttachOptions {
108
+ /**
109
+ * Start with auto-attach enabled
110
+ * @default true
111
+ */
112
+ enabled?: boolean;
113
+ /**
114
+ * CSS selector for elements to attach to
115
+ * @default 'input[type="text"], input[type="search"], textarea'
116
+ */
117
+ selector?: string;
118
+ /**
119
+ * CSS selector for elements to exclude
120
+ */
121
+ exclude?: string;
122
+ /**
123
+ * Callback when element is attached
124
+ */
125
+ onAttach?: (element: HTMLElement) => void;
126
+ }
127
+ interface AutoAttachInstance {
128
+ start: () => void;
129
+ stop: () => void;
130
+ isRunning: () => boolean;
131
+ toggle: () => void;
132
+ }
133
+ interface UIToggleOptions {
134
+ /**
135
+ * Position of the toggle button
136
+ * @default 'bottom-right'
137
+ */
138
+ position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
139
+ /**
140
+ * Theme for the toggle button
141
+ * @default 'auto'
142
+ */
143
+ theme?: 'light' | 'dark' | 'auto';
144
+ /**
145
+ * Show text label alongside icon
146
+ * @default true
147
+ */
148
+ showLabel?: boolean;
149
+ /**
150
+ * Callback when toggle state changes
151
+ */
152
+ onToggle?: (enabled: boolean) => void;
153
+ }
154
+ interface UIToggleInstance {
155
+ mount: (container?: HTMLElement) => void;
156
+ unmount: () => void;
157
+ setEnabled: (enabled: boolean) => void;
158
+ getEnabled: () => boolean;
159
+ }
160
+
161
+ /**
162
+ * Core Singlish to Sinhala conversion logic.
163
+ *
164
+ * Approaches conversion in two steps:
165
+ * 1. Tokenize Singlish -> Phonemes (greedy matching)
166
+ * 2. Phonemes -> Sinhala Unicode (context aware)
167
+ */
168
+
169
+ /**
170
+ * Main conversion function: Singlish → Sinhala
171
+ */
172
+ declare function convertSinglishToSinhala(text: string, _options?: ConversionOptions): string;
173
+ /**
174
+ * Convert only the last word (for real-time typing)
175
+ */
176
+ declare function convertLastWord(text: string, options?: ConversionOptions): string;
177
+ /**
178
+ * Convert with metadata
179
+ */
180
+ declare function convertWithMetadata(text: string, options?: ConversionOptions): ConversionResult;
181
+ /**
182
+ * Check if character is Sinhala
183
+ */
184
+ declare function isSinhalaChar(char: string): boolean;
185
+ /**
186
+ * Check if text contains Sinhala
187
+ */
188
+ declare function containsSinhala(text: string): boolean;
189
+ /**
190
+ * Segment text into Sinhala and non-Sinhala parts
191
+ */
192
+ declare function segmentText(text: string): Array<{
193
+ text: string;
194
+ isSinhala: boolean;
195
+ }>;
196
+ declare const VOWELS: Record<string, string>;
197
+ declare const VOWEL_MODIFIERS_MAP: Set<string>;
198
+ declare const CONSONANTS: Set<string>;
199
+ declare const SPECIAL: {
200
+ x: string;
201
+ H: string;
202
+ };
203
+ declare const HAL_CHAR = "\u0DCA";
204
+ declare const ZWJ_CHAR = "\u200D";
205
+
206
+ declare function useSinglishConverter(hookOptions?: UseSinglishConverterOptions): UseSinglishConverterReturn;
207
+
208
+ /**
209
+ * Real-time IME for Singlish input.
210
+ */
211
+ interface ResolveResult {
212
+ /** Sinhala text to commit (may be empty if nothing is committable yet) */
213
+ toCommit: string;
214
+ /** Remaining Roman buffer (ambiguous tail that could extend further) */
215
+ remaining: string;
216
+ }
217
+ declare function resolveBuffer(buffer: string): ResolveResult;
218
+ interface IMEState {
219
+ committed: string;
220
+ pending: string;
221
+ }
222
+ declare class SinglishIME {
223
+ private buffer;
224
+ getBuffer(): string;
225
+ getSpeculativeDisplay(): string;
226
+ processKey(key: string): ResolveResult;
227
+ backspace(): boolean;
228
+ flush(): string;
229
+ hasPending(): boolean;
230
+ reset(): void;
231
+ }
232
+
233
+ /**
234
+ * Auto-attach Singlish conversion to input elements.
235
+ * Provides opt-in DOM integration with cleanup support.
236
+ */
237
+
238
+ declare function createAutoAttach(options?: AutoAttachOptions): AutoAttachInstance;
239
+
240
+ /**
241
+ * UI toggle button for Singlish conversion.
242
+ * Provides a floating button with state management.
243
+ */
244
+
245
+ declare function createUIToggle(options?: UIToggleOptions): UIToggleInstance;
246
+
247
+ export { type AutoAttachInstance, type AutoAttachOptions, CONSONANTS, type ConversionOptions, type ConversionResult, HAL_CHAR, type IMEState, type ResolveResult, SPECIAL, SinglishIME, type SinglishTextareaInputProps, type UIToggleInstance, type UIToggleOptions, type UseSinglishConverterOptions, type UseSinglishConverterReturn, VOWELS, VOWEL_MODIFIERS_MAP, ZWJ_CHAR, containsSinhala, convertLastWord, convertSinglishToSinhala, convertWithMetadata, createAutoAttach, createUIToggle, isSinhalaChar, resolveBuffer, segmentText, useSinglishConverter };
@@ -0,0 +1,247 @@
1
+ interface ConversionOptions {
2
+ /**
3
+ * Whether to preserve non-Sinhala characters (numbers, punctuation, English)
4
+ * @default true
5
+ */
6
+ preserveNonSinhala?: boolean;
7
+ /**
8
+ * Maximum pattern length to attempt matching
9
+ * @default 4
10
+ */
11
+ maxPatternLength?: number;
12
+ }
13
+ interface ConversionResult {
14
+ /**
15
+ * The converted Sinhala text
16
+ */
17
+ text: string;
18
+ /**
19
+ * Original Singlish input
20
+ */
21
+ original: string;
22
+ /**
23
+ * Number of characters converted
24
+ */
25
+ conversions: number;
26
+ }
27
+ interface UseSinglishConverterOptions {
28
+ /**
29
+ * Initial enabled state
30
+ * @default false
31
+ */
32
+ enabled?: boolean;
33
+ /**
34
+ * Callback fired when conversion occurs
35
+ */
36
+ onConvert?: (original: string, converted: string) => void;
37
+ /**
38
+ * Conversion options
39
+ */
40
+ options?: ConversionOptions;
41
+ /**
42
+ * Initial input value
43
+ * @default ''
44
+ */
45
+ initialValue?: string;
46
+ /**
47
+ * Experimental mobile support.
48
+ * Use with caution.
49
+ */
50
+ mobileSupport?: boolean;
51
+ }
52
+ interface SinglishTextareaInputProps {
53
+ ref: React.RefCallback<HTMLTextAreaElement>;
54
+ value: string;
55
+ onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
56
+ onBeforeInput: (e: React.FormEvent<HTMLTextAreaElement>) => void;
57
+ onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
58
+ onPaste: (e: React.ClipboardEvent<HTMLTextAreaElement>) => void;
59
+ onBlur: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
60
+ }
61
+ interface UseSinglishConverterReturn {
62
+ /**
63
+ * Spread this object onto a textarea to enable real-time IME behavior.
64
+ */
65
+ inputProps: SinglishTextareaInputProps;
66
+ /**
67
+ * Current textarea value.
68
+ */
69
+ value: string;
70
+ /**
71
+ * Set textarea value while resetting IME internal state.
72
+ */
73
+ setValue: (value: string) => void;
74
+ /**
75
+ * Current raw IME buffer (romanized form), useful for debug UI.
76
+ */
77
+ bufferDisplay: string;
78
+ /**
79
+ * Current enabled state
80
+ */
81
+ enabled: boolean;
82
+ /**
83
+ * Toggle enabled state
84
+ */
85
+ toggle: () => void;
86
+ /**
87
+ * Set enabled state explicitly.
88
+ */
89
+ setEnabled: (enabled: boolean) => void;
90
+ /**
91
+ * Focus the bound textarea.
92
+ */
93
+ focus: () => void;
94
+ /**
95
+ * Clear textarea and reset IME.
96
+ */
97
+ clear: () => void;
98
+ /**
99
+ * Convert whole current value using stateless converter.
100
+ */
101
+ convertAll: () => void;
102
+ /**
103
+ * Commit pending IME buffer into value and return the committed text.
104
+ */
105
+ flushPending: () => string;
106
+ }
107
+ interface AutoAttachOptions {
108
+ /**
109
+ * Start with auto-attach enabled
110
+ * @default true
111
+ */
112
+ enabled?: boolean;
113
+ /**
114
+ * CSS selector for elements to attach to
115
+ * @default 'input[type="text"], input[type="search"], textarea'
116
+ */
117
+ selector?: string;
118
+ /**
119
+ * CSS selector for elements to exclude
120
+ */
121
+ exclude?: string;
122
+ /**
123
+ * Callback when element is attached
124
+ */
125
+ onAttach?: (element: HTMLElement) => void;
126
+ }
127
+ interface AutoAttachInstance {
128
+ start: () => void;
129
+ stop: () => void;
130
+ isRunning: () => boolean;
131
+ toggle: () => void;
132
+ }
133
+ interface UIToggleOptions {
134
+ /**
135
+ * Position of the toggle button
136
+ * @default 'bottom-right'
137
+ */
138
+ position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
139
+ /**
140
+ * Theme for the toggle button
141
+ * @default 'auto'
142
+ */
143
+ theme?: 'light' | 'dark' | 'auto';
144
+ /**
145
+ * Show text label alongside icon
146
+ * @default true
147
+ */
148
+ showLabel?: boolean;
149
+ /**
150
+ * Callback when toggle state changes
151
+ */
152
+ onToggle?: (enabled: boolean) => void;
153
+ }
154
+ interface UIToggleInstance {
155
+ mount: (container?: HTMLElement) => void;
156
+ unmount: () => void;
157
+ setEnabled: (enabled: boolean) => void;
158
+ getEnabled: () => boolean;
159
+ }
160
+
161
+ /**
162
+ * Core Singlish to Sinhala conversion logic.
163
+ *
164
+ * Approaches conversion in two steps:
165
+ * 1. Tokenize Singlish -> Phonemes (greedy matching)
166
+ * 2. Phonemes -> Sinhala Unicode (context aware)
167
+ */
168
+
169
+ /**
170
+ * Main conversion function: Singlish → Sinhala
171
+ */
172
+ declare function convertSinglishToSinhala(text: string, _options?: ConversionOptions): string;
173
+ /**
174
+ * Convert only the last word (for real-time typing)
175
+ */
176
+ declare function convertLastWord(text: string, options?: ConversionOptions): string;
177
+ /**
178
+ * Convert with metadata
179
+ */
180
+ declare function convertWithMetadata(text: string, options?: ConversionOptions): ConversionResult;
181
+ /**
182
+ * Check if character is Sinhala
183
+ */
184
+ declare function isSinhalaChar(char: string): boolean;
185
+ /**
186
+ * Check if text contains Sinhala
187
+ */
188
+ declare function containsSinhala(text: string): boolean;
189
+ /**
190
+ * Segment text into Sinhala and non-Sinhala parts
191
+ */
192
+ declare function segmentText(text: string): Array<{
193
+ text: string;
194
+ isSinhala: boolean;
195
+ }>;
196
+ declare const VOWELS: Record<string, string>;
197
+ declare const VOWEL_MODIFIERS_MAP: Set<string>;
198
+ declare const CONSONANTS: Set<string>;
199
+ declare const SPECIAL: {
200
+ x: string;
201
+ H: string;
202
+ };
203
+ declare const HAL_CHAR = "\u0DCA";
204
+ declare const ZWJ_CHAR = "\u200D";
205
+
206
+ declare function useSinglishConverter(hookOptions?: UseSinglishConverterOptions): UseSinglishConverterReturn;
207
+
208
+ /**
209
+ * Real-time IME for Singlish input.
210
+ */
211
+ interface ResolveResult {
212
+ /** Sinhala text to commit (may be empty if nothing is committable yet) */
213
+ toCommit: string;
214
+ /** Remaining Roman buffer (ambiguous tail that could extend further) */
215
+ remaining: string;
216
+ }
217
+ declare function resolveBuffer(buffer: string): ResolveResult;
218
+ interface IMEState {
219
+ committed: string;
220
+ pending: string;
221
+ }
222
+ declare class SinglishIME {
223
+ private buffer;
224
+ getBuffer(): string;
225
+ getSpeculativeDisplay(): string;
226
+ processKey(key: string): ResolveResult;
227
+ backspace(): boolean;
228
+ flush(): string;
229
+ hasPending(): boolean;
230
+ reset(): void;
231
+ }
232
+
233
+ /**
234
+ * Auto-attach Singlish conversion to input elements.
235
+ * Provides opt-in DOM integration with cleanup support.
236
+ */
237
+
238
+ declare function createAutoAttach(options?: AutoAttachOptions): AutoAttachInstance;
239
+
240
+ /**
241
+ * UI toggle button for Singlish conversion.
242
+ * Provides a floating button with state management.
243
+ */
244
+
245
+ declare function createUIToggle(options?: UIToggleOptions): UIToggleInstance;
246
+
247
+ export { type AutoAttachInstance, type AutoAttachOptions, CONSONANTS, type ConversionOptions, type ConversionResult, HAL_CHAR, type IMEState, type ResolveResult, SPECIAL, SinglishIME, type SinglishTextareaInputProps, type UIToggleInstance, type UIToggleOptions, type UseSinglishConverterOptions, type UseSinglishConverterReturn, VOWELS, VOWEL_MODIFIERS_MAP, ZWJ_CHAR, containsSinhala, convertLastWord, convertSinglishToSinhala, convertWithMetadata, createAutoAttach, createUIToggle, isSinhalaChar, resolveBuffer, segmentText, useSinglishConverter };
package/dist/index.js ADDED
@@ -0,0 +1,95 @@
1
+ var Re=Object.defineProperty;var ve=(n,e,t)=>e in n?Re(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var de=(n,e,t)=>ve(n,typeof e!="symbol"?e+"":e,t);var Oe=[["zdha","SANYAKA_DHA"],["chh","ASPIRATED_CH"],["thh","ASPIRATED_TH"],["dhh","ASPIRATED_DH"],["zga","SANYAKA_GA"],["zja","SANYAKA_JA"],["zda","SANYAKA_DA"],["zqa","SANYAKA_DHA"],["zka","SANYAKA_KA"],["zha","SANYAKA_HA"],["aa","V_AA"],["Aa","V_AE_LONG"],["AA","V_AE_LONG"],["ai","V_AI"],["au","V_AU"],["ou","V_AU"],["ii","V_II"],["uu","V_UU"],["ee","V_EE"],["oo","V_OO"],["Ru","V_RU_LONG"],["Lu","SPECIAL_LU"],["kh","KH"],["gh","GH"],["ch","CH"],["ph","PH"],["bh","BH"],["th","TH"],["dh","DH"],["Sh","RETROFLEX_S"],["sh","SH"],["ng","N_G"],["Th","RETROFLEX_TH"],["Dh","RETROFLEX_DH"],["Ba","SANYAKA_BA"],["a","V_A"],["A","V_AE"],["i","V_I"],["u","V_U"],["U","V_UU"],["e","V_E"],["E","V_EE"],["o","V_O"],["O","V_OO"],["R","V_RU"],["k","K"],["g","G"],["j","J"],["t","RETROFLEX_T"],["d","RETROFLEX_D"],["T","RETROFLEX_TH_SINGLE"],["D","RETROFLEX_DH_SINGLE"],["n","N"],["N","RETROFLEX_N"],["p","P"],["b","B_LOWER"],["B","SANYAKA_B"],["m","M"],["y","Y"],["r","R_CONS"],["l","L_CONS"],["L","RETROFLEX_L"],["v","V_CONS"],["w","V_CONS"],["s","S_CONS"],["S","RETROFLEX_S"],["h","H_CONS"],["f","F"],["q","DH"],["x","ANUSVARA"],["X","MAHAPRANAANUSVARA"],["H","VISARGA"],[" "," "],[`
2
+ `,`
3
+ `],[" "," "],[":",":"],[";",";"],[".","."],["-","-"],[",",","],["/","/"],["?","?"],["!","!"],["(","("],[")",")"],["[","["],["]","]"],['"','"'],["'","'"],["0","0"],["1","1"],["2","2"],["3","3"],["4","4"],["5","5"],["6","6"],["7","7"],["8","8"],["9","9"]],Y={K:"\u0D9A",KH:"\u0D9B",G:"\u0D9C",GH:"\u0D9D",CH:"\u0DA0",ASPIRATED_CH:"\u0DA1",J:"\u0DA2",RETROFLEX_T:"\u0DA7",RETROFLEX_TH:"\u0DA8",RETROFLEX_TH_SINGLE:"\u0DA8",RETROFLEX_D:"\u0DA9",RETROFLEX_DH:"\u0DAA",RETROFLEX_DH_SINGLE:"\u0DAA",RETROFLEX_N:"\u0DAB",TH:"\u0DAD",ASPIRATED_TH:"\u0DAE",DH:"\u0DAF",ASPIRATED_DH:"\u0DB0",N:"\u0DB1",P:"\u0DB4",PH:"\u0DB5",B_LOWER:"\u0DB6",BH:"\u0DB7",M:"\u0DB8",Y:"\u0DBA",R_CONS:"\u0DBB",L_CONS:"\u0DBD",RETROFLEX_L:"\u0DC5",V_CONS:"\u0DC0",SH:"\u0DC1",RETROFLEX_S:"\u0DC2",S_CONS:"\u0DC3",H_CONS:"\u0DC4",F:"\u0DC6"},pe=new Set(Object.keys(Y)),Ae={SANYAKA_GA:"\u0D9F",SANYAKA_JA:"\u0DA6",SANYAKA_DA:"\u0DAC",SANYAKA_DHA:"\u0DB3",SANYAKA_KA:"\u0DA4",SANYAKA_HA:"\u0DA5",SANYAKA_B:"\u0DB9",SANYAKA_BA:"\u0DB9"},Ne=new Set(Object.keys(Ae)),ne={V_A:"\u0D85",V_AA:"\u0D86",V_AE:"\u0D87",V_AE_LONG:"\u0D88",V_I:"\u0D89",V_II:"\u0D8A",V_U:"\u0D8B",V_UU:"\u0D8C",V_RU:"\u0D8D",V_RU_LONG:"\u0D8E",V_E:"\u0D91",V_EE:"\u0D92",V_AI:"\u0D93",V_O:"\u0D94",V_OO:"\u0D95",V_AU:"\u0D96"},G={V_AA:"\u0DCF",V_AE:"\u0DD0",V_AE_LONG:"\u0DD1",V_I:"\u0DD2",V_II:"\u0DD3",V_U:"\u0DD4",V_UU:"\u0DD6",V_E:"\u0DD9",V_EE:"\u0DDA",V_AI:"\u0DDB",V_O:"\u0DDC",V_OO:"\u0DDD",V_AU:"\u0DDE"},Ce=new Set(Object.keys(ne)),Ee=new Set(Object.keys(G)),ee={ANUSVARA:"\u0D82",MAHAPRANAANUSVARA:"\u0D9E",VISARGA:"\u0D83",SPECIAL_LU:"\u0DC5\u0DD4"},te="N_G";function B(n){return pe.has(n)}function Z(n){return Ne.has(n)}function He(n){return Ce.has(n)}function q(n){return n==="V_A"}function $(n){return Ee.has(n)}function xe(n){return n===void 0?!0:[" ",".",",","!","?",";",":",`
4
+ `," ","-","/","(",")","[","]",'"',"'"].includes(n)}function Ve(n){return n==="Y"||n==="R_CONS"}function Ie(n){let e=[],t=0;for(;t<n.length;){let u=!1;for(let[i,s]of Oe)if(n.substring(t,t+i.length)===i){e.push(s),t+=i.length,u=!0;break}u||(e.push(n[t]),t++)}return e}function ke(n){let e=[],t=0;for(;t<n.length;){let u=n[t],i=n[t+1];if(ee[u]!==void 0){e.push(ee[u]),t++;continue}if(u===te){let s=Y.N,o=Y.G;i!==void 0&&$(i)?(e.push(s+"\u0DCA"),e.push(o),e.push(G[i]),t+=2):i!==void 0&&q(i)?(e.push(s+"\u0DCA"),e.push(o),t+=2):(i!==void 0&&B(i),e.push(s+"\u0DCA"),e.push(o+"\u0DCA"),t++);continue}if(He(u)&&!B(u)){e.push(ne[u]),t++;continue}if(Z(u)){let s=Ae[u];u==="SANYAKA_B"?i!==void 0&&$(i)?(e.push(s),e.push(G[i]),t+=2):i!==void 0&&q(i)?(e.push(s),t+=2):(i!==void 0&&(B(i)||Z(i)),e.push(s+"\u0DCA"),t++):(e.push(s),t++);continue}if(B(u)){let s=Y[u];if(i!==void 0&&Ve(i)){let o=n[t+2],f=Y[i];if(i==="R_CONS"&&o==="V_U"){e.push(s+"\u0DD8"),t+=3;continue}if(i==="R_CONS"&&o==="V_UU"){e.push(s+"\u0DF2"),t+=3;continue}if(o!==void 0&&$(o)){e.push(s+"\u0DCA\u200D"+f),e.push(G[o]),t+=3;continue}else if(o!==void 0&&q(o)){e.push(s+"\u0DCA\u200D"+f),t+=3;continue}else if(o!==void 0&&(B(o)||Z(o)||o===te)){e.push(s+"\u0DCA\u200D"+f+"\u0DCA"),t+=2;continue}else{e.push(s+"\u0DCA\u200D"+f+"\u0DCA"),t+=2;continue}}if(i!==void 0&&$(i)){e.push(s),e.push(G[i]),t+=2;continue}if(i!==void 0&&q(i)){e.push(s),t+=2;continue}if(i!==void 0&&(B(i)||Z(i)||i===te)){e.push(s+"\u0DCA"),t++;continue}if(xe(i)||i===void 0){e.push(s+"\u0DCA"),t++;continue}if(i!==void 0&&ee[i]!==void 0){e.push(s),t++;continue}e.push(s),t++;continue}e.push(u),t++}return e.join("")}function H(n,e){if(!n)return n;let t=Ie(n);return ke(t)}function Me(n,e){if(!n)return n;let t=-1;for(let s=n.length-1;s>=0;s--)if(n[s]===" "||n[s]===`
5
+ `){t=s;break}if(t===-1)return H(n,e);let u=n.substring(0,t+1),i=n.substring(t+1);return i?u+H(i,e):n}function we(n,e){let t=n,u=H(n,e),i=0;for(let s=0;s<Math.min(t.length,u.length);s++)t[s]!==u[s]&&i++;return{text:u,original:t,conversions:i}}function re(n){let e=n.charCodeAt(0);return e>=3456&&e<=3583}function De(n){for(let e of n)if(re(e))return!0;return!1}function Pe(n){let e=[],t="",u=null;for(let i of n){let s=re(i);u===null&&(u=s),s===u?t+=i:(t&&e.push({text:t,isSinhala:u}),t=i,u=s)}return t&&u!==null&&e.push({text:t,isSinhala:u}),e}var Ke=ne,Ue=Ee,Be=pe,Fe={x:"\u0D82",H:"\u0D83"},ze="\u0DCA",We="\u200D";import{useState as oe,useCallback as R,useMemo as be,useRef as F,useLayoutEffect as Ze}from"react";function Se(){return{children:new Map,isTerminal:!1}}var Ye=["zdha","chh","thh","dhh","zga","zja","zda","zqa","zka","zha","aa","Aa","AA","ai","au","ou","ii","uu","ee","oo","Ru","Lu","kh","gh","ch","ph","bh","th","dh","Sh","sh","ng","Th","Dh","Ba","a","A","i","u","U","e","E","o","O","R","k","g","j","t","d","T","D","n","N","p","b","B","m","y","r","l","L","v","w","s","S","h","f","q","x","X","H"],se=Ge(Ye);function Ge(n){let e=Se();for(let t of n){let u=e;for(let i of t){let s=u.children.get(i);s||(s=Se(),u.children.set(i,s)),u=s}u.isTerminal=!0}return e}function me(n){if(n.length===0)return!0;let e=se;for(let t of n){let u=e.children.get(t);if(!u)return!1;e=u}return e.children.size>0}function Xe(n){if(n.length===0)return!0;let e=se;for(let t of n){let u=e.children.get(t);if(!u)return!1;e=u}return!0}function je(n,e){let t=se,u=0;for(let i=e;i<n.length;i++){let s=t.children.get(n[i]);if(!s)break;t=s,t.isTerminal&&(u=i-e+1)}return u}var _e=new Set(["k","kh","g","gh","ch","chh","j","t","Th","d","Dh","T","D","th","thh","dh","dhh","n","N","p","ph","b","bh","B","Ba","m","y","r","l","L","v","w","sh","Sh","S","s","h","f","q","ng","zga","zja","zda","zdha","zqa","zka","zha"]),Je=new Set(["a","aa","A","Aa","AA","i","ii","u","uu","U","e","ee","E","o","oo","O","ai","au","ou","R","Ru"]);function Te(n){if(!n)return{toCommit:"",remaining:""};let e=[],t=0,u=-1;for(;t<n.length;){let S=je(n,t);if(S>0)e.push({start:t,length:S,pattern:n.slice(t,t+S),isPassthrough:!1}),t+=S;else if(Xe(n[t])){u=t;break}else e.push({start:t,length:1,pattern:n[t],isPassthrough:!0}),t+=1}if(e.length===0)return{toCommit:"",remaining:n};if(u>=0){let S=e.length;for(;S>0;){let a=e[S-1];if(!a.isPassthrough&&_e.has(a.pattern)){S--;continue}break}let p=S>0?e[S-1].start+e[S-1].length:0;return p===0?{toCommit:"",remaining:n}:{toCommit:H(n.slice(0,p)),remaining:n.slice(p)}}let i=e.length-1,s=e[i],o=s.start+s.length;if(e.length===1)return s.isPassthrough?{toCommit:s.pattern,remaining:""}:{toCommit:"",remaining:n};let f=i;for(o===n.length&&me(s.pattern)&&(f=i);f>0;){let S=e[f],p=e[f-1];if(!p.isPassthrough&&_e.has(p.pattern)&&!S.isPassthrough&&(Je.has(S.pattern)||me(S.pattern))){f--;continue}break}let m=e[f].start;if(m===0)return{toCommit:"",remaining:n};let k=n.slice(0,m),V=n.slice(m);return{toCommit:H(k),remaining:V}}var P=class{constructor(){de(this,"buffer","")}getBuffer(){return this.buffer}getSpeculativeDisplay(){return this.buffer?H(this.buffer):""}processKey(e){this.buffer+=e;let t=Te(this.buffer);return this.buffer=t.remaining,t}backspace(){return this.buffer.length>0?(this.buffer=this.buffer.slice(0,-1),!0):!1}flush(){if(!this.buffer)return"";let e=H(this.buffer);return this.buffer="",e}hasPending(){return this.buffer.length>0}reset(){this.buffer=""}};function ie(n){return n.length===1&&/[a-zA-Z]/.test(n)}function qe(n){let{enabled:e=!1,onConvert:t,options:u,initialValue:i="",mobileSupport:s=!1}=n||{},[o,f]=oe(i),[m,k]=oe(e),[V,S]=oe(""),p=F(null),a=F(new P),N=F(0),x=F(-1),A=F(null),v=F(null),l=R(()=>{let r=a.current.getBuffer(),h=a.current.getSpeculativeDisplay();N.current=h.length,S(r)},[]),c=R((r,h,E)=>{let O=N.current,C=Math.max(0,h-O),I=r.slice(0,C),g=r.slice(h);return{newValue:I+E+g,newCursor:I.length+E.length}},[]),T=R((r,h)=>{x.current=h,v.current=r,f(r)},[]),M=R(()=>{a.current.reset(),N.current=0,l(),x.current=-1,A.current=null,v.current=null},[l]),w=R((r,h)=>{if(!a.current.hasPending())return{value:h,cursor:r};let E=a.current.flush(),O=c(h,r,E);return l(),{value:O.newValue,cursor:O.newCursor}},[c,l]),Q=R(r=>{M(),f(r)},[M]),L=R(()=>{p.current?.focus()},[]),y=R(()=>{M(),f("")},[M]),z=R(()=>{if(!o)return;let r=o,h=H(r,u);M(),v.current=h,f(h),t&&h!==r&&t(r,h)},[o,u,t,M]),X=R(()=>{if(!a.current.hasPending())return o;let r=p.current?.selectionStart??o.length,h=w(r,o);return T(h.value,h.cursor),h.value},[o,w,T]),K=R(r=>{if(!r&&a.current.hasPending()){let h=p.current?.selectionStart??o.length,E=a.current.flush(),O=c(o,h,E);l(),f(O.newValue)}a.current.reset(),l(),k(r)},[o,c,l]),j=R(()=>{K(!m)},[m,K]),W=R(r=>{p.current=r},[]),ue=R(r=>{if(p.current=r.currentTarget,!m||!s)return;let h=r.nativeEvent;if(h.inputType!=="insertText")return;let E=h.data??"";if(E.length!==1)return;if(A.current===E){r.preventDefault(),A.current=null;return}A.current=null;let O=r.currentTarget,C=O.selectionStart,I=O.selectionEnd,g=C,d=C!==I;if(ie(E)){r.preventDefault();let _=o,b=g;if(d){if(a.current.hasPending()){let ye=a.current.flush();_=c(_,b,ye).newValue,N.current=0}let J=Math.min(C,I),ge=Math.max(C,I);_=_.slice(0,J)+_.slice(ge),b=J,a.current.reset(),N.current=0}let D=a.current.processKey(E),U=c(_,b,D.toCommit+a.current.getSpeculativeDisplay());l(),T(U.newValue,U.newCursor);return}if(a.current.hasPending()){r.preventDefault();let _=a.current.flush(),b=c(o,g,_+E);l(),T(b.newValue,b.newCursor)}},[m,s,o,c,l,T]),le=R(r=>{if(p.current=r.currentTarget,!m)return;let h=r.currentTarget,E=h.selectionStart,O=h.selectionEnd,C=E,I=E!==O;if(ie(r.key)&&!r.ctrlKey&&!r.metaKey&&!r.altKey){r.preventDefault(),A.current=r.key;let g=o,d=C;if(I){if(a.current.hasPending()){let J=a.current.flush();g=c(g,d,J).newValue,N.current=0}let D=Math.min(E,O),U=Math.max(E,O);g=g.slice(0,D)+g.slice(U),d=D,a.current.reset(),N.current=0}let _=a.current.processKey(r.key),b=c(g,d,_.toCommit+a.current.getSpeculativeDisplay());l(),T(b.newValue,b.newCursor);return}if(r.key==="Backspace"&&!r.ctrlKey&&!r.metaKey&&!I&&a.current.hasPending()){r.preventDefault(),a.current.backspace();let g=a.current.getSpeculativeDisplay(),d=c(o,C,g);l(),T(d.newValue,d.newCursor);return}if((r.ctrlKey||r.metaKey)&&r.key==="a"){if(a.current.hasPending()){let g=a.current.flush(),d=c(o,C,g);l(),x.current=-1,f(d.newValue)}return}if((r.ctrlKey||r.metaKey)&&(r.key==="z"||r.key==="y")){a.current.reset(),N.current=0,l(),x.current=-1;return}if((r.ctrlKey||r.metaKey)&&r.key==="Enter"){r.preventDefault();let g=o;if(a.current.reset(),l(),g){let d=H(g,u);T(d,d.length),t&&d!==g&&t(g,d)}return}if(r.key.length===1&&!ie(r.key)&&!r.ctrlKey&&!r.metaKey&&!r.altKey){if(a.current.hasPending()){r.preventDefault(),A.current=r.key;let g=a.current.flush(),d=c(o,C,g+r.key);l(),T(d.newValue,d.newCursor);return}return}if(r.key==="Enter"&&!r.ctrlKey&&!r.metaKey){if(a.current.hasPending()){r.preventDefault();let g=a.current.flush(),d=c(o,C,g+`
6
+ `);l(),T(d.newValue,d.newCursor);return}return}if(["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End"].includes(r.key)){if(a.current.hasPending()){let g=a.current.flush(),d=c(o,C,g);l(),T(d.newValue,d.newCursor)}return}if(r.key==="Backspace"||r.key==="Delete"){if(a.current.hasPending()){r.preventDefault();let g=a.current.flush(),d=c(o,C,g);l();let _=d.newValue,b=d.newCursor;if(I){let D=Math.min(E,O),U=Math.max(E,O);_=_.slice(0,D)+_.slice(U),b=D}else r.key==="Backspace"&&b>0?(_=_.slice(0,b-1)+_.slice(b),b=b-1):r.key==="Delete"&&b<_.length&&(_=_.slice(0,b)+_.slice(b+1));T(_,b)}return}},[m,o,u,t,c,l,T]),ae=R(r=>{if(p.current=r.currentTarget,!m){f(r.target.value);return}if(v.current!==null&&r.target.value===v.current){v.current=null;return}v.current=null,a.current.reset(),N.current=0,l(),x.current=-1,f(r.target.value)},[m,l]),ce=R(r=>{if(p.current=r.currentTarget,!!m&&a.current.hasPending()){let h=p.current?.selectionStart??o.length,E=a.current.flush(),O=c(o,h,E);l(),f(O.newValue)}},[m,o,c,l]),he=R(r=>{if(p.current=r.currentTarget,!!m&&a.current.hasPending()){let h=p.current?.selectionStart??o.length,E=w(h,o);T(E.value,E.cursor)}},[m,o,w,T]);Ze(()=>{let r=x.current;r>=0&&p.current&&(p.current.selectionStart=r,p.current.selectionEnd=r),x.current=-1},[o]);let fe=be(()=>({ref:W,value:o,onKeyDown:le,onBeforeInput:ue,onChange:ae,onPaste:ce,onBlur:he}),[W,o,le,ue,ae,ce,he]);return be(()=>({inputProps:fe,value:o,setValue:Q,bufferDisplay:V,enabled:m,toggle:j,setEnabled:K,focus:L,clear:y,convertAll:z,flushPending:X}),[fe,o,Q,V,m,j,K,L,y,z,X])}var $e='input[type="text"], input[type="search"], textarea';function Qe(n={}){let{enabled:e=!0,selector:t=$e,exclude:u,onAttach:i}=n,s=!1,o=null,f=new WeakSet,m=new WeakMap;function k(A){return!(f.has(A)||u&&A.matches(u))}function V(A){if(!k(A))return;let v=new P;m.set(A,v),f.add(A);let l=A instanceof HTMLTextAreaElement,c=A instanceof HTMLInputElement;if(!l&&!c)return;function T(L){if(L.key==="Backspace"){v.backspace()&&(L.preventDefault(),void 0);return}if(L.key==="Enter"||L.key==="Escape"){let y=v.flush();y&&w(y);return}if(L.key.length===1&&!L.ctrlKey&&!L.metaKey&&!L.altKey){L.preventDefault();let y=v.processKey(L.key);y.toCommit&&w(y.toCommit)}}function M(){let L=v.flush();L&&w(L)}function w(L){let y=A,z=y.selectionStart??y.value.length,X=y.selectionEnd??y.value.length,K=y.value.substring(0,z),j=y.value.substring(X);y.value=K+L+j;let W=z+L.length;y.setSelectionRange(W,W),y.dispatchEvent(new Event("input",{bubbles:!0}))}function Q(){}A.addEventListener("keydown",T),A.addEventListener("blur",M),i?.(A)}function S(){document.querySelectorAll(t).forEach(V)}function p(){o=new MutationObserver(A=>{for(let v of A)for(let l of v.addedNodes){if(l.nodeType!==Node.ELEMENT_NODE)continue;let c=l;c.matches(t)&&V(c),c.querySelectorAll(t).forEach(V)}}),o.observe(document.body,{childList:!0,subtree:!0})}function a(){s||(s=!0,S(),p())}function N(){s&&(s=!1,o?.disconnect(),o=null)}function x(){s?N():a()}return e&&a(),{start:a,stop:N,isRunning:()=>s,toggle:x}}var Le="singlish-enabled";function et(n={}){let{position:e="bottom-right",theme:t="auto",showLabel:u=!0,onToggle:i}=n,s=m(),o=null,f=null;function m(){if(typeof localStorage>"u")return!0;let l=localStorage.getItem(Le);return l===null?!0:l==="true"}function k(l){typeof localStorage>"u"||localStorage.setItem(Le,String(l))}function V(){let l=document.createElement("button");return l.className=`singlish-toggle singlish-toggle--${e}`,l.setAttribute("aria-label","Toggle Singlish input"),l.setAttribute("type","button"),S(l),l.addEventListener("click",p),l.addEventListener("keydown",c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),p())}),l}function S(l){let c=s?"\u{1F1F1}\u{1F1F0}":"EN",T=s?"\u0DC3\u0DD2\u0D82":"English";u?l.innerHTML=`<span class="singlish-toggle__icon">${c}</span><span class="singlish-toggle__label">${T}</span>`:l.innerHTML=`<span class="singlish-toggle__icon">${c}</span>`,l.classList.toggle("singlish-toggle--enabled",s),l.setAttribute("aria-pressed",String(s))}function p(){s=!s,k(s),o&&S(o),i?.(s)}function a(l){o||(f=l||document.body,o=V(),f.appendChild(o),v())}function N(){o&&(o.remove(),o=null,f=null)}function x(l){s!==l&&(s=l,k(l),o&&S(o))}function A(){return s}function v(){if(document.getElementById("singlish-toggle-styles"))return;let l=document.createElement("style");l.id="singlish-toggle-styles",l.textContent=tt(t),document.head.appendChild(l)}return{mount:a,unmount:N,setEnabled:x,getEnabled:A}}function tt(n){let e="rgba(255, 255, 255, 0.9)",t="rgba(30, 30, 30, 0.9)",u="#1a1a1a",i="#ffffff",s=n==="dark"?t:e,o=n==="dark"?i:u;return`
7
+ .singlish-toggle {
8
+ position: fixed;
9
+ z-index: 9999;
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 8px;
13
+ padding: 10px 16px;
14
+ background: ${s};
15
+ backdrop-filter: blur(10px);
16
+ border: 1px solid rgba(255, 255, 255, 0.2);
17
+ border-radius: 12px;
18
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
19
+ color: ${o};
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
+ font-size: 14px;
22
+ font-weight: 500;
23
+ cursor: pointer;
24
+ transition: all 0.2s ease;
25
+ user-select: none;
26
+ }
27
+
28
+ .singlish-toggle:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
31
+ }
32
+
33
+ .singlish-toggle:active {
34
+ transform: translateY(0);
35
+ }
36
+
37
+ .singlish-toggle:focus {
38
+ outline: 2px solid #4A90E2;
39
+ outline-offset: 2px;
40
+ }
41
+
42
+ .singlish-toggle--top-right {
43
+ top: 20px;
44
+ right: 20px;
45
+ }
46
+
47
+ .singlish-toggle--top-left {
48
+ top: 20px;
49
+ left: 20px;
50
+ }
51
+
52
+ .singlish-toggle--bottom-right {
53
+ bottom: 20px;
54
+ right: 20px;
55
+ }
56
+
57
+ .singlish-toggle--bottom-left {
58
+ bottom: 20px;
59
+ left: 20px;
60
+ }
61
+
62
+ .singlish-toggle__icon {
63
+ font-size: 18px;
64
+ line-height: 1;
65
+ }
66
+
67
+ .singlish-toggle__label {
68
+ font-size: 13px;
69
+ font-weight: 600;
70
+ }
71
+
72
+ .singlish-toggle--enabled {
73
+ background: linear-gradient(135deg, rgba(74, 144, 226, 0.9), rgba(106, 90, 205, 0.9));
74
+ color: white;
75
+ border-color: rgba(255, 255, 255, 0.3);
76
+ }
77
+
78
+ @media (prefers-color-scheme: dark) {
79
+ .singlish-toggle {
80
+ background: ${n==="auto"?t:s};
81
+ color: ${n==="auto"?i:o};
82
+ }
83
+ }
84
+
85
+ @media (max-width: 768px) {
86
+ .singlish-toggle {
87
+ padding: 8px 12px;
88
+ font-size: 13px;
89
+ }
90
+
91
+ .singlish-toggle__icon {
92
+ font-size: 16px;
93
+ }
94
+ }
95
+ `}export{Be as CONSONANTS,ze as HAL_CHAR,Fe as SPECIAL,P as SinglishIME,Ke as VOWELS,Ue as VOWEL_MODIFIERS_MAP,We as ZWJ_CHAR,De as containsSinhala,Me as convertLastWord,H as convertSinglishToSinhala,we as convertWithMetadata,Qe as createAutoAttach,et as createUIToggle,re as isSinhalaChar,Te as resolveBuffer,Pe as segmentText,qe as useSinglishConverter};
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@siyabasa/singlish",
3
+ "version": "1.0.0",
4
+ "description": "Deterministic Singlish-to-Sinhala transliteration engine with phonetic precision and zero-latency IME.",
5
+ "type": "module",
6
+ "sideEffects": [
7
+ "**/*.css"
8
+ ],
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
26
+ "build": "tsup src/index.ts --format esm,cjs --clean --dts --minify",
27
+ "lint": "eslint src/**/*.ts",
28
+ "test": "vitest run",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "singlish",
33
+ "sinhala",
34
+ "transliteration",
35
+ "ime",
36
+ "unicode",
37
+ "sri-lanka",
38
+ "i18n",
39
+ "siyabasa",
40
+ "remeinium"
41
+ ],
42
+ "author": "Remeinium Siyabasa Labs <support@remeinium.com>",
43
+ "license": "ROSL-1.0",
44
+ "homepage": "https://labs.remeinium.com/siyabasa/singlish",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/remeinium/singlish.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/remeinium/singlish/issues"
51
+ },
52
+ "engines": {
53
+ "node": ">=16.0.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": ">=16.8.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "react": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@types/react": "^18.3.28",
65
+ "tsup": "^8.5.1",
66
+ "typescript": "^5.9.3",
67
+ "vitest": "^1.6.1"
68
+ },
69
+ "publishConfig": {
70
+ "access": "public",
71
+ "registry": "https://registry.npmjs.org/"
72
+ }
73
+ }