@writegeez/ime 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,6 +30,48 @@ function buildReverseMap(inputMethod2, prefix = "") {
30
30
  function isLatinChar(char) {
31
31
  return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
32
32
  }
33
+ function trailingWord(text) {
34
+ return /[^\s\p{P}]*$/u.exec(text)[0];
35
+ }
36
+
37
+ // src/transliterator.ts
38
+ var Transliterator = class {
39
+ constructor(spec) {
40
+ this.inputMap = spec.map;
41
+ this.geezToLatinMap = buildReverseMap(this.inputMap);
42
+ }
43
+ feed(prevChar, key) {
44
+ let prevLatinChar = prevChar;
45
+ if (!isLatinChar(prevChar))
46
+ prevLatinChar = this.geezToLatinMap[prevChar] || prevChar;
47
+ let currentNode = null;
48
+ let tempNode = this.inputMap;
49
+ for (const char of prevLatinChar) {
50
+ if (tempNode[char]) {
51
+ currentNode = tempNode[char];
52
+ tempNode = currentNode.next || {};
53
+ } else {
54
+ currentNode = null;
55
+ break;
56
+ }
57
+ }
58
+ if (currentNode?.next && currentNode.next[key])
59
+ return { replace: true, text: currentNode.next[key].value || key };
60
+ const newNode = this.inputMap[key];
61
+ if (newNode) return { replace: false, text: newNode.value || key };
62
+ return { replace: false, text: key };
63
+ }
64
+ /** Run a whole key sequence, e.g. `"selam"` → `"ሰላም"`. */
65
+ transliterate(keys) {
66
+ const out = [];
67
+ for (const key of keys) {
68
+ const { replace, text } = this.feed(out[out.length - 1] ?? "", key);
69
+ if (replace) out.pop();
70
+ out.push(text);
71
+ }
72
+ return out.join("");
73
+ }
74
+ };
33
75
 
34
76
  // src/webGeezIme.ts
35
77
  var WebGeezIME = class {
@@ -46,8 +88,7 @@ var WebGeezIME = class {
46
88
  this.boundCompositionEnd = () => {
47
89
  this.composing = false;
48
90
  };
49
- this.inputMap = inputMethodSpec.map;
50
- this.geezToLatinMap = buildReverseMap(this.inputMap);
91
+ this.transliterator = new Transliterator(inputMethodSpec);
51
92
  if (typeof options?.enabled === "boolean")
52
93
  this.enabled = options.enabled;
53
94
  this.attachToElement(element);
@@ -103,30 +144,46 @@ var WebGeezIME = class {
103
144
  );
104
145
  }
105
146
  processInput(prevChar, newChar) {
106
- let prevLatinChar = prevChar;
107
- if (!isLatinChar(prevChar))
108
- prevLatinChar = this.geezToLatinMap[prevChar] || prevChar;
109
- let currentNode = null;
110
- let tempNode = this.inputMap;
111
- for (const char of prevLatinChar) {
112
- if (tempNode[char]) {
113
- currentNode = tempNode[char];
114
- tempNode = currentNode.next || {};
115
- } else {
116
- currentNode = null;
117
- break;
118
- }
147
+ const { replace, text } = this.transliterator.feed(prevChar, newChar);
148
+ return { output: text, modifiesPrev: replace };
149
+ }
150
+ /** The word before the caret, e.g. `"ሰላ"` while typing `ሰላም`. */
151
+ currentWord() {
152
+ return trailingWord(this.textBeforeCaret());
153
+ }
154
+ /**
155
+ * Write `text` at the caret, as if typed. With `replaceWord`, the word
156
+ * before the caret is replaced (accepting a suggestion). Returns false
157
+ * while an OS IME composition is in progress, so it cannot be corrupted.
158
+ */
159
+ insertText(text, options) {
160
+ if (!this.activeElement || this.composing) return false;
161
+ const length = options?.replaceWord ? this.currentWord().length : 0;
162
+ if (length) this.replaceGeezChar(text, length);
163
+ else this.insertGeezChar(text);
164
+ this.dispatchSyntheticInput(length ? "insertReplacementText" : "insertText");
165
+ return true;
166
+ }
167
+ textBeforeCaret() {
168
+ if (!this.activeElement) return "";
169
+ if (this.elementType === "input") {
170
+ const el = this.activeElement;
171
+ return el.value.slice(0, el.selectionStart ?? el.value.length);
119
172
  }
120
- if (currentNode?.next && currentNode.next[newChar]) {
121
- return {
122
- output: currentNode.next[newChar].value || newChar,
123
- modifiesPrev: true
124
- };
173
+ if (!this.isContentEditable) return "";
174
+ this.activeElement.normalize();
175
+ const sel = window.getSelection();
176
+ if (!sel) return "";
177
+ if (!sel.rangeCount || !this.activeElement.contains(sel.getRangeAt(0).startContainer)) {
178
+ const range2 = document.createRange();
179
+ range2.selectNodeContents(this.activeElement);
180
+ range2.collapse(false);
181
+ sel.removeAllRanges();
182
+ sel.addRange(range2);
125
183
  }
126
- const newNode = this.inputMap[newChar];
127
- if (newNode)
128
- return { output: newNode.value || newChar, modifiesPrev: false };
129
- return { output: newChar, modifiesPrev: false };
184
+ const range = sel.getRangeAt(0);
185
+ const { node, offset } = this.resolveTextPosition(range.startContainer, range.startOffset);
186
+ return node ? (node.textContent ?? "").slice(0, offset) : "";
130
187
  }
131
188
  handleBeforeInput(event) {
132
189
  if (!this.activeElement) return;
@@ -178,12 +235,13 @@ var WebGeezIME = class {
178
235
  sel.addRange(range);
179
236
  }
180
237
  }
181
- replaceGeezChar(char) {
238
+ /** Replace `length` UTF-16 units before the caret; defaults to the previous grapheme. */
239
+ replaceGeezChar(char, length) {
182
240
  if (!this.activeElement) return;
183
241
  if (this.elementType === "input") {
184
242
  const target = this.activeElement;
185
243
  const caret = target.selectionStart ?? 0;
186
- const start = this.findPrevGraphemeStart(target.value, caret);
244
+ const start = length === void 0 ? this.findPrevGraphemeStart(target.value, caret) : Math.max(0, caret - length);
187
245
  target.setRangeText(char, start, caret, "end");
188
246
  } else if (this.isContentEditable) {
189
247
  const sel = window.getSelection();
@@ -195,7 +253,7 @@ var WebGeezIME = class {
195
253
  );
196
254
  if (node) {
197
255
  const text = node.textContent ?? "";
198
- const start = this.findPrevGraphemeStart(text, offset);
256
+ const start = length === void 0 ? this.findPrevGraphemeStart(text, offset) : Math.max(0, offset - length);
199
257
  const r = document.createRange();
200
258
  r.setStart(node, start);
201
259
  r.setEnd(node, offset);
@@ -2217,14 +2275,20 @@ var inputMethod = {
2217
2275
  var ti_ER_default = inputMethod;
2218
2276
 
2219
2277
  // src/input-methods/index.ts
2220
- var inputMethods = {};
2221
- inputMethods[ti_ER_default.name] = ti_ER_default.map;
2278
+ var inputMethods = [ti_ER_default];
2279
+ function getInputMethod(code) {
2280
+ return inputMethods.find((method) => method.code === code);
2281
+ }
2222
2282
 
2223
2283
  // src/index.ts
2224
2284
  var ime = webGeezIme_exports;
2225
2285
 
2226
2286
  exports.GeezIME = WebGeezIME;
2227
2287
  exports.TigrinyaEritrean = ti_ER_default;
2288
+ exports.Transliterator = Transliterator;
2289
+ exports.getInputMethod = getInputMethod;
2228
2290
  exports.ime = ime;
2291
+ exports.inputMethods = inputMethods;
2292
+ exports.trailingWord = trailingWord;
2229
2293
  //# sourceMappingURL=index.cjs.map
2230
2294
  //# sourceMappingURL=index.cjs.map