hangul-lube 2.0.0 → 2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0
4
+
5
+ - **`typeLetter(text, letter)` and `eraseLetter(text)` type and erase one key of a two-set keyboard.**
6
+ `typeLetter` composes a compatibility jamo into the last character of `text`: a consonant and a
7
+ vowel make a syllable, two vowels join (`ㅗ` and `ㅏ` into `ㅘ`), a consonant becomes the final
8
+ and may join the final before it (`ㄹ` and `ㄱ` into `ㄺ`), and a vowel after a final moves it
9
+ to a new syllable (`값` and `ㅏ` give `갑사`). Two lone consonants join too (`ㄱ` and `ㅅ` into
10
+ `ㄳ`), and the same key twice never makes a double consonant. `eraseLetter` is one backspace
11
+ that undoes the last key (`갉` into `갈`, `과` into `고`, `가` into `ㄱ`).
12
+ - **`particle(word, notation)` gives the Korean particle that follows a word.** The notation is
13
+ `"은(는)"`, `"이(가)"`, `"을(를)"`, `"과(와)"`, `"아(야)"`, `"(이)"` or `"(으)"`, and any other
14
+ throws a `TypeError`. The form follows the final consonant of the last Hangul syllable, the
15
+ Korean reading of a number (`3` is 삼), the Korean names of the letters of a Latin capital word
16
+ (`URL` ends in ㄹ) or the spelling of a lower-case one, and marks at the end are passed over.
17
+ `"(으)"` gives `""` after ㄹ too, as `(으)로` needs: `"서울" + particle("서울", "(으)") + "로"` is `서울로`.
18
+
3
19
  ## 2.0.0
4
20
 
5
21
  ### Breaking
package/README.md CHANGED
@@ -65,6 +65,39 @@ pattern("1+1") //=> "1\\+1"
65
65
 
66
66
  `searcher(query)?.source` is the source a database can run case-insensitively: `~*` in PostgreSQL, `REGEXP_LIKE(column, searcher_source, 'i')` in MySQL or `$regex` with `$options: "i"` in MongoDB. Use it, not `pattern(query)`, wherever the search box is what the row must match: `pattern` keeps the white space and the invisible characters of the query and lets nothing stand between its letters, so `"도쿄하네다"` misses `"도쿄 하네다"` and a pasted byte order mark makes it match nothing. Such a query reads every row, since a regular expression cannot use an index.
67
67
 
68
+ ### Particle
69
+ ```ts
70
+ import { particle } from "hangul-lube"
71
+
72
+ "사과" + particle("사과", "이(가)") //=> "사과가"
73
+ "밤" + particle("밤", "은(는)") //=> "밤은"
74
+ "HP" + particle("HP", "을(를)") //=> "HP를"
75
+ "서울" + particle("서울", "(으)") + "로" //=> "서울로"
76
+ "3" + particle("3", "과(와)") //=> "3과"
77
+ particle("밤", "(으)") //=> "으"
78
+ ```
79
+ - The notation is one of `"은(는)"`, `"이(가)"`, `"을(를)"`, `"과(와)"`, `"아(야)"`, `"(이)"` and `"(으)"`; any other throws a `TypeError`. `"(이)"` and `"(으)"` give the syllable that goes before the next word (`이에요`, `으로`) and `""` after a vowel; `"(으)"` gives `""` after ㄹ too.
80
+ - The final consonant comes from the last Hangul syllable. A number is read in Korean (`1` is 일, `3` is 삼, `10` is 십, `1,000` is 천), a Latin capital word by the Korean names of its letters (`URL` ends in ㄹ, `HP` in a vowel), and a lower-case word by its spelling (`l` is ㄹ, `m`, `n` and `ng` a consonant).
81
+ - Marks at the end, such as quotes and `!`, are passed over, and anything else reads as ending in a vowel.
82
+
83
+ ### Typing automaton
84
+ ```ts
85
+ import { eraseLetter, typeLetter } from "hangul-lube"
86
+
87
+ typeLetter("ㄱ", "ㅏ") //=> "가"
88
+ typeLetter("가", "ㄱ") //=> "각"
89
+ typeLetter("각", "ㅏ") //=> "가가"
90
+ typeLetter("갑", "ㅅ") //=> "값"
91
+ typeLetter("값", "ㅏ") //=> "갑사"
92
+ eraseLetter("값") //=> "갑"
93
+ eraseLetter("각") //=> "가"
94
+ eraseLetter("가") //=> "ㄱ"
95
+ ```
96
+ - `typeLetter(text, letter)` types one key of a two-set (dubeolsik) keyboard into the last character of `text`. `letter` is a compatibility jamo (`ㄱ`, `ㅏ`, and `ㄲ`, `ㅒ` from the shift key); anything else is appended as it is.
97
+ - Two vowels join (`ㅗ` and `ㅏ` into `ㅘ`), a consonant after a syllable becomes its final and joins the final before it into a compound one (`ㄹ` and `ㄱ` into `ㄺ`), and a vowel after a final moves it to a new syllable, a compound final moving only its second consonant. Two lone consonants join the same way (`ㄱ` and `ㅅ` into `ㄳ`) and a vowel splits them again.
98
+ - The same key never makes a double consonant: `ㄱ` after `각` is appended, since `ㄲ` needs the shift key.
99
+ - `eraseLetter(text)` is one backspace: it takes back the last key, so a compound final loses its second consonant, a joined vowel splits back, and a syllable with a simple vowel and no final leaves its initial. Anything else loses its last code point, and `""` stays `""`.
100
+
68
101
  ### Typing rules
69
102
  - A consonant matches itself and every syllable it starts, and the last syllable matches the syllables it grows into while typing: `된자` matches `된장찌개` and `도` matches `돼지국밥`. A lone letter grows the same way: `ㄱ` matches `ㄳ` and `ㅗ` matches `ㅘ`.
70
103
  - Every earlier syllable is finished and matches only itself: `오리` matches `오리털` but not `올리브`, while `ㅇ리` and `ㅇㄹ` match both.
package/package.json CHANGED
@@ -4,9 +4,6 @@
4
4
  "url": "https://github.com/artxe/lube-series/issues"
5
5
  },
6
6
  "description": "Simplify Korean Consonant Search in JavaScript",
7
- "devDependencies": {
8
- "@typescript/native": "npm:typescript@^7.0.2"
9
- },
10
7
  "engines": {
11
8
  "node": ">=14"
12
9
  },
@@ -27,8 +24,10 @@
27
24
  "choseong",
28
25
  "hangul",
29
26
  "hangul-lube",
27
+ "josa",
30
28
  "korean",
31
29
  "lube",
30
+ "particle",
32
31
  "regexp",
33
32
  "search",
34
33
  "syntax sugar",
@@ -45,10 +44,10 @@
45
44
  "sideEffects": false,
46
45
  "type": "module",
47
46
  "types": "types/index.d.ts",
48
- "version": "2.0.0",
47
+ "version": "2.1.0",
49
48
  "scripts": {
50
49
  "dts": "node ../../scripts/tsc_with_clean.js hangul-lube",
51
50
  "lint": "pnpm -w lint",
52
- "test": "pnpm -w exec vitest run --coverage test/hangul-lube"
51
+ "test": "pnpm -w exec vitest run test/hangul-lube"
53
52
  }
54
53
  }
@@ -0,0 +1,52 @@
1
+ import compound_consonant_letters from "./compound_consonant_letters.js"
2
+ import {
3
+ final_letters,
4
+ initial_letters,
5
+ medial_letters,
6
+ parts_of,
7
+ split_vowels,
8
+ syllable
9
+ } from "./jamo.js"
10
+ /**
11
+ * Takes back one key on a two-set (dubeolsik) Korean keyboard: returns the text after one
12
+ * backspace, undoing what `typeLetter` composed. A compound final loses its second consonant, a
13
+ * single final goes, a joined vowel splits back into its first vowel, a syllable with a simple
14
+ * vowel and no final leaves its initial consonant, and a lone joined vowel or compound consonant
15
+ * loses its second part. Anything else loses its last code point, so an emoji goes whole.
16
+ * @param {string} text
17
+ * @returns {string}
18
+ * @example eraseLetter("갉") //=> "갈"
19
+ *
20
+ * eraseLetter("각") //=> "가"
21
+ *
22
+ * eraseLetter("과") //=> "고"
23
+ *
24
+ * eraseLetter("가") //=> "ㄱ"
25
+ *
26
+ * eraseLetter("ㅘ") //=> "ㅗ"
27
+ */
28
+ export default function(text) {
29
+ const chars = Array.from(text)
30
+ const last = chars.pop() ?? ""
31
+ const head = chars.join("")
32
+ const parts = parts_of(last)
33
+ if (parts) {
34
+ if (parts.final > 0) {
35
+ const pair = compound_consonant_letters[final_letters.charAt(parts.final - 1)]
36
+ return head + syllable(
37
+ parts.initial,
38
+ parts.medial,
39
+ pair ? pair[0] : ""
40
+ )
41
+ }
42
+ const split = split_vowels[medial_letters.charAt(parts.medial)]
43
+ return head + (split
44
+ ? syllable(
45
+ parts.initial,
46
+ medial_letters.indexOf(split[0]),
47
+ ""
48
+ )
49
+ : initial_letters.charAt(parts.initial))
50
+ }
51
+ return head + (split_vowels[last]?.[0] ?? compound_consonant_letters[last]?.[0] ?? "")
52
+ }
@@ -20,10 +20,16 @@ export default function(letter, gap, last) {
20
20
  }
21
21
  const consonant = first_consonant_letters[letter]
22
22
  const growth = letter_growths[letter]
23
- if (consonant || growth) return `[${letter}${growth ?? ""}${consonant ?? ""}]`
23
+ if (consonant || growth) {
24
+ return `[${letter}${growth ?? ""}${consonant ?? ""}]`
25
+ }
24
26
  const code = letter.charCodeAt(0)
25
- if (code < 0xAC00 || code > 0xD7A3) return special_character_regex.test(letter) ? "\\" + letter : letter
26
- if (!last) return letter
27
+ if (code < 0xAC00 || code > 0xD7A3) {
28
+ return special_character_regex.test(letter) ? "\\" + letter : letter
29
+ }
30
+ if (!last) {
31
+ return letter
32
+ }
27
33
  const syllable = code - 0xAC00
28
34
  const final = syllable % 28
29
35
  if (!final) {
package/src/index.js CHANGED
@@ -1,2 +1,5 @@
1
+ export { default as eraseLetter } from "./erase_letter.js"
2
+ export { default as particle } from "./particle.js"
1
3
  export { default as pattern } from "./pattern.js"
2
- export { default as searcher } from "./searcher.js"
4
+ export { default as searcher } from "./searcher.js"
5
+ export { default as typeLetter } from "./type_letter.js"
package/src/jamo.js ADDED
@@ -0,0 +1,58 @@
1
+ import compound_consonant_letters from "./compound_consonant_letters.js"
2
+ const HANGUL = 0xac00
3
+ const HANGUL_END = 0xd7a3
4
+ export const final_letters = "ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"
5
+ export const initial_letters = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"
6
+ export const medial_letters = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"
7
+ /** @type {Record<string, string | undefined>} */
8
+ export const joined_finals = Object.fromEntries(
9
+ Object.entries(compound_consonant_letters).flatMap(
10
+ ([ joined, pair ]) => pair ? [ [ pair.join(""), joined ] ] : []
11
+ )
12
+ )
13
+ /** @type {Record<string, string | undefined>} */
14
+ export const joined_vowels = {
15
+ ㅗㅏ: "ㅘ",
16
+ ㅗㅐ: "ㅙ",
17
+ ㅗㅣ: "ㅚ",
18
+ ㅜㅓ: "ㅝ",
19
+ ㅜㅔ: "ㅞ",
20
+ ㅜㅣ: "ㅟ",
21
+ ㅡㅣ: "ㅢ"
22
+ }
23
+ /** @type {Record<string, [ first: string, second: string ] | undefined>} */
24
+ export const split_vowels = Object.fromEntries(
25
+ Object.entries(joined_vowels).map(
26
+ ([ pair, joined ]) => [
27
+ joined,
28
+ [ pair.charAt(0), pair.charAt(1) ]
29
+ ]
30
+ )
31
+ )
32
+ /**
33
+ * @param {string} char
34
+ * @returns {{ final: number, initial: number, medial: number } | null}
35
+ */
36
+ export function parts_of(char) {
37
+ const code = char.charCodeAt(0)
38
+ if (!(code >= HANGUL && code <= HANGUL_END)) {
39
+ return null
40
+ }
41
+ const index = code - HANGUL
42
+ return {
43
+ final: index % 28,
44
+ initial: Math.floor(index / 28 / 21),
45
+ medial: Math.floor(index / 28) % 21
46
+ }
47
+ }
48
+ /**
49
+ * @param {number} initial
50
+ * @param {number} medial
51
+ * @param {string} final
52
+ * @returns {string}
53
+ */
54
+ export function syllable(initial, medial, final) {
55
+ return String.fromCharCode(
56
+ HANGUL + (initial * 21 + medial) * 28 + (final ? final_letters.indexOf(final) + 1 : 0)
57
+ )
58
+ }
package/src/letters_of.js CHANGED
@@ -1,5 +1,4 @@
1
- const final_letters = "ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"
2
- const initial_letters = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"
1
+ import { final_letters, initial_letters } from "./jamo.js"
3
2
  /**
4
3
  * @param {string} text
5
4
  * @returns {string[]}
@@ -0,0 +1,112 @@
1
+ const CONSONANT = 1
2
+ const DIGIT_SOUNDS = "1201001220"
3
+ /** @type {Record<string, [ after_consonant: string, after_vowel: string ]>} */
4
+ const FORMS = {
5
+ "(으)": [ "으", "" ],
6
+ "(이)": [ "이", "" ],
7
+ "과(와)": [ "과", "와" ],
8
+ "아(야)": [ "아", "야" ],
9
+ "은(는)": [ "은", "는" ],
10
+ "을(를)": [ "을", "를" ],
11
+ "이(가)": [ "이", "가" ]
12
+ }
13
+ const GYEONG_ZEROS = 16
14
+ const HANGUL = 0xac00
15
+ const HANGUL_END = 0xd7a3
16
+ const JO_ZEROS = 12
17
+ const LATIN_END = /[A-Za-z]+$/
18
+ const NAMED_CONSONANT = "MN"
19
+ const NAMED_RIEUL = "LR"
20
+ const NUMBER_END = /(\d\.)?([\d,]*\d)$/
21
+ const RIEUL = 2
22
+ const RIEUL_FINAL = 8
23
+ const SEPARATORS = /,/g
24
+ const SPELLED_CONSONANT = /(?:m|n|ng)$/
25
+ const TRAILING_MARKS = /[^\p{L}\p{N}]+$/u
26
+ const TRAILING_ZEROS = /0+$/
27
+ const VOWEL = 0
28
+ /**
29
+ * @param {string} digits
30
+ * @returns {number}
31
+ */
32
+ function digit_sound(digits) {
33
+ return Number(
34
+ DIGIT_SOUNDS[Number(digits.slice(-1))]
35
+ )
36
+ }
37
+ /**
38
+ * @param {string} text
39
+ * @returns {number}
40
+ */
41
+ function final_sound(text) {
42
+ const word = text.replace(TRAILING_MARKS, "")
43
+ const number = NUMBER_END.exec(word)
44
+ if (number) {
45
+ const digits = /** @type {string} */(number[2])/**/.replace(SEPARATORS, "")
46
+ const zeros = digits.length - digits.replace(TRAILING_ZEROS, "").length
47
+ if (number[1] || zeros == 0 || zeros == digits.length) {
48
+ return digit_sound(digits)
49
+ }
50
+ return zeros >= JO_ZEROS && zeros < GYEONG_ZEROS ? VOWEL : CONSONANT
51
+ }
52
+ const code = word.charCodeAt(word.length - 1)
53
+ if (code >= HANGUL && code <= HANGUL_END) {
54
+ const final = (code - HANGUL) % 28
55
+ return final == 0
56
+ ? VOWEL
57
+ : final == RIEUL_FINAL
58
+ ? RIEUL
59
+ : CONSONANT
60
+ }
61
+ const latin = LATIN_END.exec(word)
62
+ if (!latin) {
63
+ return VOWEL
64
+ }
65
+ const run = latin[0]
66
+ if (run.length == 1 || run == run.toUpperCase()) {
67
+ const letter = run.slice(-1).toUpperCase()
68
+ return NAMED_RIEUL.includes(letter)
69
+ ? RIEUL
70
+ : NAMED_CONSONANT.includes(letter)
71
+ ? CONSONANT
72
+ : VOWEL
73
+ }
74
+ const lower = run.toLowerCase()
75
+ return lower.endsWith("l")
76
+ ? RIEUL
77
+ : SPELLED_CONSONANT.test(lower)
78
+ ? CONSONANT
79
+ : VOWEL
80
+ }
81
+ /**
82
+ * The Korean particle that follows a word, chosen by how the word is read aloud: by the final
83
+ * consonant of its last Hangul syllable, by the Korean reading of a number (`1` is 일, `3` is 삼,
84
+ * `10` is 십), and by the Korean name of a Latin letter (`HP` is 에이치피, `URL` ends in ㄹ) or,
85
+ * for a word spelled in lower case, by how its spelling ends (`l` is ㄹ, `m`, `n` and `ng` a consonant).
86
+ * Marks at the end of the word are passed over. Anything else reads as ending in a vowel.
87
+ * The notation is one of `"은(는)"`, `"이(가)"`, `"을(를)"`, `"과(와)"`, `"아(야)"`, `"(이)"` and `"(으)"`;
88
+ * the last two give the syllable that goes between the word and the next one, and nothing after a vowel.
89
+ * `"(으)"` gives nothing after ㄹ too, as `(으)로` needs. Any other notation throws a `TypeError`.
90
+ * @param {string} word
91
+ * @param {string} notation
92
+ * @returns {string}
93
+ * @example particle("사과", "이(가)") //=> "가"
94
+ *
95
+ * particle("밤", "은(는)") //=> "은"
96
+ *
97
+ * "서울" + particle("서울", "(으)") + "로" //=> "서울로"
98
+ *
99
+ * "HP" + particle("HP", "을(를)") //=> "HP를"
100
+ */
101
+ export default function(word, notation) {
102
+ if (!Object.prototype.hasOwnProperty.call(FORMS, notation)) {
103
+ throw new TypeError(
104
+ `Unknown notation ${JSON.stringify(notation)}, expected one of ${Object.keys(FORMS).join(", ")}`
105
+ )
106
+ }
107
+ const sound = final_sound(word)
108
+ const [ after_consonant, after_vowel ] = /** @type {[ string, string ]} */(FORMS[notation])/**/
109
+ return sound == VOWEL || sound == RIEUL && notation == "(으)"
110
+ ? after_vowel
111
+ : after_consonant
112
+ }
package/src/searcher.js CHANGED
@@ -21,10 +21,16 @@ function gap(letters) {
21
21
  * @returns {string}
22
22
  */
23
23
  function separator(letter) {
24
- if (!non_word_letter_regex.test(letter)) return non_word_separator
25
- if (letter.length > 1) return `(?:(?!${letter})[${non_word_class}])*?`
24
+ if (!non_word_letter_regex.test(letter)) {
25
+ return non_word_separator
26
+ }
27
+ if (letter.length > 1) {
28
+ return `(?:(?!${letter})[${non_word_class}])*?`
29
+ }
26
30
  const compound = compound_consonant_letters[letter]
27
- if (compound) return gap(letter + compound[0])
31
+ if (compound) {
32
+ return gap(letter + compound[0])
33
+ }
28
34
  return gap(
29
35
  `${class_special_character_regex.test(letter) ? "\\" : ""}${letter}${letter_growths[letter] ?? ""}`
30
36
  )
@@ -52,11 +58,15 @@ export default function(text) {
52
58
  const letters = letters_of(text).filter(
53
59
  letter => !ignored_regex.test(letter)
54
60
  )
55
- if (letters.length > max_letters) return RegExp(never_source, "i")
61
+ if (letters.length > max_letters) {
62
+ return RegExp(never_source, "i")
63
+ }
56
64
  const last = letters.length - 1
57
65
  let source = ""
58
66
  for (const [ index, letter ] of letters.entries()) {
59
- if (index) source += separator(letter)
67
+ if (index) {
68
+ source += separator(letter)
69
+ }
60
70
  source += get_letter_range(letter, gap, index == last)
61
71
  }
62
72
  return source ? RegExp(source, "i") : null
@@ -0,0 +1,103 @@
1
+ import compound_consonant_letters from "./compound_consonant_letters.js"
2
+ import {
3
+ final_letters,
4
+ initial_letters,
5
+ joined_finals,
6
+ joined_vowels,
7
+ medial_letters,
8
+ parts_of,
9
+ syllable
10
+ } from "./jamo.js"
11
+ /**
12
+ * @param {string} head
13
+ * @param {{ final: number, initial: number, medial: number }} parts
14
+ * @param {number} medial
15
+ * @returns {string}
16
+ */
17
+ function moved_final(head, parts, medial) {
18
+ const final = /** @type {string} */(final_letters[parts.final - 1])/**/
19
+ const [ keep, move ] = compound_consonant_letters[final] ?? [ "", final ]
20
+ return head + syllable(parts.initial, parts.medial, keep) + syllable(
21
+ initial_letters.indexOf(move),
22
+ medial,
23
+ ""
24
+ )
25
+ }
26
+ /**
27
+ * Types one key on a two-set (dubeolsik) Korean keyboard: returns the text with the key composed into
28
+ * its last character, as an input method does. `letter` is a compatibility jamo (`ㄱ`, `ㅏ`, and
29
+ * the double `ㄲ`, `ㅒ` that the shift key gives); any other string is appended as it is.
30
+ * A consonant and a vowel make a syllable, two vowels join (`ㅗ` and `ㅏ` into `ㅘ`), a consonant
31
+ * after a syllable becomes its final and joins the final before it when the two make a compound
32
+ * final (`ㄹ` and `ㄱ` into `ㄺ`), and a vowel after a final moves it onto a new syllable (a compound
33
+ * final moves only its second consonant). Two lone consonants join the same way (`ㄱ` and `ㅅ` into
34
+ * `ㄳ`), and a vowel splits them again. The same key never makes a double consonant: `ㄱ` after
35
+ * `각` is appended, since `ㄲ` needs the shift key.
36
+ * @param {string} text
37
+ * @param {string} letter
38
+ * @returns {string}
39
+ * @example typeLetter("ㄱ", "ㅏ") //=> "가"
40
+ *
41
+ * typeLetter("가", "ㄱ") //=> "각"
42
+ *
43
+ * typeLetter("각", "ㅏ") //=> "가가"
44
+ *
45
+ * typeLetter("고", "ㅏ") //=> "과"
46
+ *
47
+ * typeLetter("갈", "ㄱ") //=> "갉"
48
+ */
49
+ export default function(text, letter) {
50
+ const chars = Array.from(text)
51
+ const last = chars.pop() ?? ""
52
+ const head = chars.join("")
53
+ const parts = parts_of(last)
54
+ const single = letter.length == 1
55
+ const medial = single ? medial_letters.indexOf(letter) : -1
56
+ if (medial >= 0 && last) {
57
+ if (initial_letters.includes(last)) {
58
+ return head + syllable(
59
+ initial_letters.indexOf(last),
60
+ medial,
61
+ ""
62
+ )
63
+ }
64
+ const pair = compound_consonant_letters[last]
65
+ if (pair) {
66
+ return head + pair[0] + syllable(
67
+ initial_letters.indexOf(pair[1]),
68
+ medial,
69
+ ""
70
+ )
71
+ }
72
+ if (parts && parts.final > 0) {
73
+ return moved_final(head, parts, medial)
74
+ }
75
+ const joined = joined_vowels[(parts ? medial_letters.charAt(parts.medial) : last) + letter]
76
+ if (joined) {
77
+ return head + (parts
78
+ ? syllable(
79
+ parts.initial,
80
+ medial_letters.indexOf(joined),
81
+ ""
82
+ )
83
+ : joined)
84
+ }
85
+ } else if (single && initial_letters.includes(letter) && last) {
86
+ if (parts) {
87
+ const final = parts.final > 0
88
+ ? joined_finals[final_letters.charAt(parts.final - 1) + letter]
89
+ : final_letters.includes(letter)
90
+ ? letter
91
+ : undefined
92
+ if (final) {
93
+ return head + syllable(parts.initial, parts.medial, final)
94
+ }
95
+ } else {
96
+ const joined = joined_finals[last + letter]
97
+ if (joined) {
98
+ return head + joined
99
+ }
100
+ }
101
+ }
102
+ return text + letter
103
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Takes back one key on a two-set (dubeolsik) Korean keyboard: returns the text after one
3
+ * backspace, undoing what `typeLetter` composed. A compound final loses its second consonant, a
4
+ * single final goes, a joined vowel splits back into its first vowel, a syllable with a simple
5
+ * vowel and no final leaves its initial consonant, and a lone joined vowel or compound consonant
6
+ * loses its second part. Anything else loses its last code point, so an emoji goes whole.
7
+ * @param {string} text
8
+ * @returns {string}
9
+ * @example eraseLetter("갉") //=> "갈"
10
+ *
11
+ * eraseLetter("각") //=> "가"
12
+ *
13
+ * eraseLetter("과") //=> "고"
14
+ *
15
+ * eraseLetter("가") //=> "ㄱ"
16
+ *
17
+ * eraseLetter("ㅘ") //=> "ㅗ"
18
+ */
19
+ export default function (text: string): string;
20
+ //# sourceMappingURL=erase_letter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erase_letter.d.ts","sourceRoot":"","sources":["../src/erase_letter.js"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAZjB,MAYiB,GAXf,MAAM,CAmClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"get_letter_range.d.ts","sourceRoot":"","sources":["../src/get_letter_range.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,WAAU,MAAM,EALnB,MAKmB,EAAE,GAAG,EAJxB,CAAC,OAAO,EAAE,MAAM,KAAK,MAIG,EAAE,IAAI,EAH9B,OAG8B,GAF5B,MAAM,CA8BlB"}
1
+ {"version":3,"file":"get_letter_range.d.ts","sourceRoot":"","sources":["../src/get_letter_range.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,WAAU,MAAM,EALnB,MAKmB,EAAE,GAAG,EAJxB,CAAC,OAAO,EAAE,MAAM,KAAK,MAIG,EAAE,IAAI,EAH9B,OAG8B,GAF5B,MAAM,CAoClB"}
package/types/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { default as eraseLetter } from "./erase_letter.js";
2
+ export { default as particle } from "./particle.js";
1
3
  export { default as pattern } from "./pattern.js";
2
4
  export { default as searcher } from "./searcher.js";
5
+ export { default as typeLetter } from "./type_letter.js";
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,26 @@
1
+ export declare const final_letters = "\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3139\u313A\u313B\u313C\u313D\u313E\u313F\u3140\u3141\u3142\u3144\u3145\u3146\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
2
+ export declare const initial_letters = "\u3131\u3132\u3134\u3137\u3138\u3139\u3141\u3142\u3143\u3145\u3146\u3147\u3148\u3149\u314A\u314B\u314C\u314D\u314E";
3
+ export declare const medial_letters = "\u314F\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315A\u315B\u315C\u315D\u315E\u315F\u3160\u3161\u3162\u3163";
4
+ /** @type {Record<string, string | undefined>} */
5
+ export declare const joined_finals: Record<string, string | undefined>;
6
+ /** @type {Record<string, string | undefined>} */
7
+ export declare const joined_vowels: Record<string, string | undefined>;
8
+ /** @type {Record<string, [ first: string, second: string ] | undefined>} */
9
+ export declare const split_vowels: Record<string, [first: string, second: string] | undefined>;
10
+ /**
11
+ * @param {string} char
12
+ * @returns {{ final: number, initial: number, medial: number } | null}
13
+ */
14
+ export declare function parts_of(char: string): {
15
+ final: number;
16
+ initial: number;
17
+ medial: number;
18
+ } | null;
19
+ /**
20
+ * @param {number} initial
21
+ * @param {number} medial
22
+ * @param {string} final
23
+ * @returns {string}
24
+ */
25
+ export declare function syllable(initial: number, medial: number, final: string): string;
26
+ //# sourceMappingURL=jamo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jamo.d.ts","sourceRoot":"","sources":["../src/jamo.js"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,uKAAgC,CAAA;AAC1D,eAAO,MAAM,eAAe,uHAAwB,CAAA;AACpD,eAAO,MAAM,cAAc,mIAA0B,CAAA;AACrD,iDAAiD;AACjD,eAAO,MAAM,aAAa,EADf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAK3C,CAAA;AACD,iDAAiD;AACjD,eAAO,MAAM,aAAa,EADf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAS3C,CAAA;AACD,4EAA4E;AAC5E,eAAO,MAAM,YAAY,EADd,MAAM,CAAC,MAAM,EAAE,CAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAE,GAAG,SAAS,CAQtE,CAAA;AACD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,MAGkB,GAFhB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAarE;AACD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EALrB,MAKqB,EAAE,MAAM,EAJ7B,MAI6B,EAAE,KAAK,EAHpC,MAGoC,GAFlC,MAAM,CAMlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"letters_of.d.ts","sourceRoot":"","sources":["../src/letters_of.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAHjB,MAGiB,GAFf,MAAM,EAAE,CAepB"}
1
+ {"version":3,"file":"letters_of.d.ts","sourceRoot":"","sources":["../src/letters_of.js"],"names":[],"mappings":"AACA;;;GAGG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAHjB,MAGiB,GAFf,MAAM,EAAE,CAepB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The Korean particle that follows a word, chosen by how the word is read aloud: by the final
3
+ * consonant of its last Hangul syllable, by the Korean reading of a number (`1` is 일, `3` is 삼,
4
+ * `10` is 십), and by the Korean name of a Latin letter (`HP` is 에이치피, `URL` ends in ㄹ) or,
5
+ * for a word spelled in lower case, by how its spelling ends (`l` is ㄹ, `m`, `n` and `ng` a consonant).
6
+ * Marks at the end of the word are passed over. Anything else reads as ending in a vowel.
7
+ * The notation is one of `"은(는)"`, `"이(가)"`, `"을(를)"`, `"과(와)"`, `"아(야)"`, `"(이)"` and `"(으)"`;
8
+ * the last two give the syllable that goes between the word and the next one, and nothing after a vowel.
9
+ * `"(으)"` gives nothing after ㄹ too, as `(으)로` needs. Any other notation throws a `TypeError`.
10
+ * @param {string} word
11
+ * @param {string} notation
12
+ * @returns {string}
13
+ * @example particle("사과", "이(가)") //=> "가"
14
+ *
15
+ * particle("밤", "은(는)") //=> "은"
16
+ *
17
+ * "서울" + particle("서울", "(으)") + "로" //=> "서울로"
18
+ *
19
+ * "HP" + particle("HP", "을(를)") //=> "HP를"
20
+ */
21
+ export default function (word: string, notation: string): string;
22
+ //# sourceMappingURL=particle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particle.d.ts","sourceRoot":"","sources":["../src/particle.js"],"names":[],"mappings":"AAgFA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAXjB,MAWiB,EAAE,QAAQ,EAV3B,MAU2B,GATzB,MAAM,CAoBlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"searcher.d.ts","sourceRoot":"","sources":["../src/searcher.js"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAXjB,MAWiB,GAVf,MAAM,OAAC,CAsBnB"}
1
+ {"version":3,"file":"searcher.d.ts","sourceRoot":"","sources":["../src/searcher.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAXjB,MAWiB,GAVf,MAAM,OAAC,CA0BnB"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Types one key on a two-set (dubeolsik) Korean keyboard: returns the text with the key composed into
3
+ * its last character, as an input method does. `letter` is a compatibility jamo (`ㄱ`, `ㅏ`, and
4
+ * the double `ㄲ`, `ㅒ` that the shift key gives); any other string is appended as it is.
5
+ * A consonant and a vowel make a syllable, two vowels join (`ㅗ` and `ㅏ` into `ㅘ`), a consonant
6
+ * after a syllable becomes its final and joins the final before it when the two make a compound
7
+ * final (`ㄹ` and `ㄱ` into `ㄺ`), and a vowel after a final moves it onto a new syllable (a compound
8
+ * final moves only its second consonant). Two lone consonants join the same way (`ㄱ` and `ㅅ` into
9
+ * `ㄳ`), and a vowel splits them again. The same key never makes a double consonant: `ㄱ` after
10
+ * `각` is appended, since `ㄲ` needs the shift key.
11
+ * @param {string} text
12
+ * @param {string} letter
13
+ * @returns {string}
14
+ * @example typeLetter("ㄱ", "ㅏ") //=> "가"
15
+ *
16
+ * typeLetter("가", "ㄱ") //=> "각"
17
+ *
18
+ * typeLetter("각", "ㅏ") //=> "가가"
19
+ *
20
+ * typeLetter("고", "ㅏ") //=> "과"
21
+ *
22
+ * typeLetter("갈", "ㄱ") //=> "갉"
23
+ */
24
+ export default function (text: string, letter: string): string;
25
+ //# sourceMappingURL=type_letter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type_letter.d.ts","sourceRoot":"","sources":["../src/type_letter.js"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAbjB,MAaiB,EAAE,MAAM,EAZzB,MAYyB,GAXvB,MAAM,CAiElB"}