@j1nn0/vanilla-autokana 2.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 +8 -0
- package/README.md +218 -0
- package/dist/AutoKana.d.cts +79 -0
- package/dist/AutoKana.d.ts +79 -0
- package/dist/ElementResolver.d.cts +7 -0
- package/dist/ElementResolver.d.ts +7 -0
- package/dist/KanaConverter.d.cts +4 -0
- package/dist/KanaConverter.d.ts +4 -0
- package/dist/KanaExtractor.d.cts +7 -0
- package/dist/KanaExtractor.d.ts +7 -0
- package/dist/autokana.cjs +1 -0
- package/dist/autokana.es.js +268 -0
- package/dist/autokana.umd.js +1 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/katakanaMap.d.cts +1 -0
- package/dist/katakanaMap.d.ts +1 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright 2018 Ryo Utsunomiya
|
|
2
|
+
Modifications Copyright 2026 j1nn0
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# vanilla-autokana
|
|
2
|
+
|
|
3
|
+
[English README is here](./README_en.md)
|
|
4
|
+
|
|
5
|
+
このプロジェクトは [ryo-utsunomiya/vanilla-autokana](https://github.com/ryo-utsunomiya/vanilla-autokana) からフォークした実装です。
|
|
6
|
+
|
|
7
|
+
フォームのフィールドに文字を入力すると、別のフィールドにかなを自動入力するライブラリです。
|
|
8
|
+
|
|
9
|
+
## 特徴
|
|
10
|
+
|
|
11
|
+
- jQueryに依存していません
|
|
12
|
+
- scriptタグからの読み込みとESModulesのimportに対応しています
|
|
13
|
+
|
|
14
|
+
## インストール方法
|
|
15
|
+
|
|
16
|
+
### npm
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm i @j1nn0/vanilla-autokana # or yarn add @j1nn0/vanilla-autokana
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### npmを使わない方法
|
|
23
|
+
|
|
24
|
+
このリポジトリの `dist/autokana.umd.js` をダウンロードし、scriptタグで読み込んでください。
|
|
25
|
+
|
|
26
|
+
## 使用方法
|
|
27
|
+
|
|
28
|
+
- `AutoKana.bind()` メソッドの第1引数にふりがな入力元の input 要素を指定します。第2引数にはふりがな出力先の input 要素を指定できますが、省略も可能です
|
|
29
|
+
- 要素の指定には `#` / `.` / `[` / `:` で始まるセレクタ文字列、または input / textarea 要素を渡せます。IDのみを渡した場合も従来どおり動作します
|
|
30
|
+
- input / textarea 要素が見つけられない場合は正常に動作できないため、DOMContentLoadedイベント内での実行を推奨します
|
|
31
|
+
- ライブラリ本体はDOMのライフサイクルイベントに依存しないため、ライブラリの読み込みには`defer`属性の追加を推奨します
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<input name="name" id="name">
|
|
35
|
+
<input name="furigana" id="furigana">
|
|
36
|
+
<script src="autokana.umd.js" defer></script>
|
|
37
|
+
<script>
|
|
38
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
39
|
+
// ひらがなで出力(デフォルト)
|
|
40
|
+
AutoKana.bind("#name", "#furigana");
|
|
41
|
+
// 全角カタカナで出力したい場合
|
|
42
|
+
// AutoKana.bind("#name", "#furigana", { katakana: 'full' });
|
|
43
|
+
// 半角カタカナで出力したい場合
|
|
44
|
+
// AutoKana.bind("#name", "#furigana", { katakana: 'half' });
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### モジュールとしてimportする
|
|
50
|
+
|
|
51
|
+
ESModulesとしてimportすることができます。
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import * as AutoKana from '@j1nn0/vanilla-autokana';
|
|
55
|
+
|
|
56
|
+
AutoKana.bind('#name', '#furigana');
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### オプション
|
|
60
|
+
|
|
61
|
+
`AutoKana.bind(name, furigana?, option)` の第3引数には以下を指定できます。
|
|
62
|
+
|
|
63
|
+
- `name`: `#` / `.` / `[` / `:` で始まるセレクタ文字列、または input / textarea 要素
|
|
64
|
+
- `furigana`: `#` / `.` / `[` / `:` で始まるセレクタ文字列、input / textarea 要素、または省略
|
|
65
|
+
|
|
66
|
+
- `katakana`: `'hiragana' | 'full' | 'half'`
|
|
67
|
+
- `debug`: `boolean`
|
|
68
|
+
- `onChange`: `(furigana: string) => void` — ふりがなが変更されるたびに呼ばれるコールバック
|
|
69
|
+
|
|
70
|
+
`katakana` の値ごとの挙動:
|
|
71
|
+
|
|
72
|
+
- `'hiragana'`: ひらがなで出力(デフォルト)
|
|
73
|
+
- `'full'`: 全角カタカナで出力
|
|
74
|
+
- `'half'`: 半角カタカナで出力(全角空白は半角空白に正規化)
|
|
75
|
+
|
|
76
|
+
### メソッド
|
|
77
|
+
|
|
78
|
+
- `getFurigana()`: 現在のふりがな文字列を返す
|
|
79
|
+
- `start()`: ふりがなの自動追跡を再開する
|
|
80
|
+
- `stop()`: ふりがなの自動追跡を一時停止する
|
|
81
|
+
- `toggle(event?)`: ふりがなの自動追跡を切り替える。チェックボックスの変更イベントを渡すと、その `checked` 状態を使う
|
|
82
|
+
- `reset()`: 内部状態(ふりがな、変換フラグなど)をすべてリセットする
|
|
83
|
+
- `destroy()`: イベントリスナーをすべて削除する
|
|
84
|
+
|
|
85
|
+
> **注意**: `initializeValues()` は非推奨です。代わりに `reset()` を使用してください。
|
|
86
|
+
|
|
87
|
+
### Vue.jsと組み合わせる
|
|
88
|
+
|
|
89
|
+
`onChange` コールバックを使うと、`getFurigana()` のポーリングなしでふりがなの変更を検知できます。
|
|
90
|
+
また、出力先の input 要素を指定している場合は、その要素に `bubbles: true` の `input` イベントも発火します。
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<template>
|
|
94
|
+
<div id="app">
|
|
95
|
+
<div>
|
|
96
|
+
<label for="name">名前</label>
|
|
97
|
+
<input name="name" id="name" v-model="name">
|
|
98
|
+
</div>
|
|
99
|
+
<div>
|
|
100
|
+
<label for="furigana">ふりがな</label>
|
|
101
|
+
<input name="furigana" id="furigana" v-model="furigana">
|
|
102
|
+
</div>
|
|
103
|
+
<h2>入力内容の確認</h2>
|
|
104
|
+
<p>名前: {{ name }}</p>
|
|
105
|
+
<p>ふりがな: {{ furigana }}</p>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<script setup>
|
|
110
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
111
|
+
import { bind } from '@j1nn0/vanilla-autokana';
|
|
112
|
+
|
|
113
|
+
const name = ref('');
|
|
114
|
+
const furigana = ref('');
|
|
115
|
+
let autokana;
|
|
116
|
+
|
|
117
|
+
onMounted(() => {
|
|
118
|
+
autokana = bind('#name', '#furigana', {
|
|
119
|
+
onChange: (value) => {
|
|
120
|
+
furigana.value = value;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
onUnmounted(() => {
|
|
126
|
+
autokana?.destroy();
|
|
127
|
+
});
|
|
128
|
+
</script>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`v-model`を使用している場合でも、出力先 input には `input` イベントが発火します。ただし、状態同期には `onChange` コールバックの利用を推奨します。
|
|
132
|
+
`onChange` コールバックを使わずに `getFurigana` メソッドでふりがなを取り出すこともできますが、`onChange` の使用を推奨します。
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<!-- 非推奨: getFurigana() のポーリング -->
|
|
136
|
+
<input name="name" id="name" v-model="name" @input="handleNameInput">
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### React.jsと組み合わせる
|
|
140
|
+
|
|
141
|
+
Vue.jsと同様に `onChange` コールバックが使えます。
|
|
142
|
+
|
|
143
|
+
```jsx
|
|
144
|
+
import { useEffect, useState } from 'react';
|
|
145
|
+
import { bind } from '@j1nn0/vanilla-autokana';
|
|
146
|
+
|
|
147
|
+
function App() {
|
|
148
|
+
const [name, setName] = useState('');
|
|
149
|
+
const [furigana, setFurigana] = useState('');
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
const autokana = bind('#name', '#furigana', {
|
|
153
|
+
onChange: (value) => {
|
|
154
|
+
setFurigana(value);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return () => {
|
|
159
|
+
autokana.destroy();
|
|
160
|
+
};
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="App">
|
|
165
|
+
<div>
|
|
166
|
+
<label htmlFor="name">名前</label>
|
|
167
|
+
<input
|
|
168
|
+
name="name"
|
|
169
|
+
id="name"
|
|
170
|
+
value={name}
|
|
171
|
+
onInput={(e) => setName(e.target.value)}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
<div>
|
|
175
|
+
<label htmlFor="furigana">ふりがな</label>
|
|
176
|
+
<input name="furigana" id="furigana" value={furigana} readOnly />
|
|
177
|
+
</div>
|
|
178
|
+
<h2>入力内容の確認</h2>
|
|
179
|
+
<p>名前: {name}</p>
|
|
180
|
+
<p>ふりがな: {furigana}</p>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default App;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 移行ガイド(v1 → v2)
|
|
189
|
+
|
|
190
|
+
v2.0.0 で `katakana` オプションの値が変更されました。
|
|
191
|
+
|
|
192
|
+
### 変更点
|
|
193
|
+
|
|
194
|
+
| 変更前 (v1) | 変更後 (v2) |
|
|
195
|
+
|-------------|-------------|
|
|
196
|
+
| `katakana: false` | `katakana: 'hiragana'` |
|
|
197
|
+
| `katakana: 'full'` | `katakana: 'full'`(変更なし) |
|
|
198
|
+
| `katakana: 'half'` | `katakana: 'half'`(変更なし) |
|
|
199
|
+
|
|
200
|
+
### 移行手順
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
// v1
|
|
204
|
+
AutoKana.bind('#name', '#furigana', { katakana: false });
|
|
205
|
+
|
|
206
|
+
// v2
|
|
207
|
+
AutoKana.bind('#name', '#furigana', { katakana: 'hiragana' });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## ライセンス
|
|
211
|
+
|
|
212
|
+
MIT
|
|
213
|
+
|
|
214
|
+
## 謝辞
|
|
215
|
+
|
|
216
|
+
このプロジェクトは [ryo-utsunomiya/vanilla-autokana](https://github.com/ryo-utsunomiya/vanilla-autokana) をベースにしています。
|
|
217
|
+
|
|
218
|
+
このライブラリの設計・実装は jquery-autokana(https://github.com/harisenbon/autokana) に大きく影響を受けています。
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Bindable } from './ElementResolver.cjs';
|
|
2
|
+
export type { Bindable };
|
|
3
|
+
export type { KatakanaOption };
|
|
4
|
+
type KatakanaOption = 'hiragana' | 'full' | 'half';
|
|
5
|
+
export interface AutoKanaOption {
|
|
6
|
+
/** Output format for furigana. `'hiragana'` = hiragana, `'full'` = full-width katakana, `'half'` = half-width katakana. */
|
|
7
|
+
katakana?: KatakanaOption;
|
|
8
|
+
/** When `true`, logs debug information to the console. */
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
/** Callback invoked with the current furigana string whenever it changes. */
|
|
11
|
+
onChange?: (furigana: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export default class AutoKana {
|
|
14
|
+
isActive: boolean;
|
|
15
|
+
option: AutoKanaOption & {
|
|
16
|
+
katakana: KatakanaOption;
|
|
17
|
+
debug: boolean;
|
|
18
|
+
};
|
|
19
|
+
private elName;
|
|
20
|
+
private elFurigana?;
|
|
21
|
+
private committedKana;
|
|
22
|
+
private furigana;
|
|
23
|
+
private isComposing;
|
|
24
|
+
private lastConvertedInput;
|
|
25
|
+
private lastNewInput;
|
|
26
|
+
private pendingKana;
|
|
27
|
+
private previousRawInput;
|
|
28
|
+
private blurHandler;
|
|
29
|
+
private focusHandler;
|
|
30
|
+
private compositionStartHandler;
|
|
31
|
+
private compositionEndHandler;
|
|
32
|
+
private inputHandler;
|
|
33
|
+
constructor(name: Bindable, furigana?: Bindable, option?: Partial<AutoKanaOption>);
|
|
34
|
+
/**
|
|
35
|
+
* Get the current furigana string.
|
|
36
|
+
*
|
|
37
|
+
* @returns The current furigana string.
|
|
38
|
+
*/
|
|
39
|
+
getFurigana(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Resume auto-kana tracking.
|
|
42
|
+
*/
|
|
43
|
+
start(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Pause auto-kana tracking.
|
|
46
|
+
*/
|
|
47
|
+
stop(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Toggle auto-kana tracking on or off.
|
|
50
|
+
*
|
|
51
|
+
* @param event Optional checkbox change event. When provided, uses the checked state of the target.
|
|
52
|
+
*/
|
|
53
|
+
toggle(event?: {
|
|
54
|
+
target: {
|
|
55
|
+
checked: boolean;
|
|
56
|
+
};
|
|
57
|
+
}): void;
|
|
58
|
+
/**
|
|
59
|
+
* Reset all internal state (committed kana, furigana, composing flag, etc.).
|
|
60
|
+
*/
|
|
61
|
+
reset(): void;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Use reset() instead.
|
|
64
|
+
*/
|
|
65
|
+
initializeValues(): void;
|
|
66
|
+
private registerEvents;
|
|
67
|
+
setFurigana(force?: boolean): void;
|
|
68
|
+
private extractNewInput;
|
|
69
|
+
detectAndCommitConversion(newPendingKana: string[]): void;
|
|
70
|
+
private handleCompositionInput;
|
|
71
|
+
private handleNormalInput;
|
|
72
|
+
processValue(): void;
|
|
73
|
+
commitPendingKana(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Remove all event listeners (blur, focus, compositionstart, compositionend, input) from the name element.
|
|
76
|
+
*/
|
|
77
|
+
destroy(): void;
|
|
78
|
+
private debug;
|
|
79
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Bindable } from './ElementResolver.js';
|
|
2
|
+
export type { Bindable };
|
|
3
|
+
export type { KatakanaOption };
|
|
4
|
+
type KatakanaOption = 'hiragana' | 'full' | 'half';
|
|
5
|
+
export interface AutoKanaOption {
|
|
6
|
+
/** Output format for furigana. `'hiragana'` = hiragana, `'full'` = full-width katakana, `'half'` = half-width katakana. */
|
|
7
|
+
katakana?: KatakanaOption;
|
|
8
|
+
/** When `true`, logs debug information to the console. */
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
/** Callback invoked with the current furigana string whenever it changes. */
|
|
11
|
+
onChange?: (furigana: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export default class AutoKana {
|
|
14
|
+
isActive: boolean;
|
|
15
|
+
option: AutoKanaOption & {
|
|
16
|
+
katakana: KatakanaOption;
|
|
17
|
+
debug: boolean;
|
|
18
|
+
};
|
|
19
|
+
private elName;
|
|
20
|
+
private elFurigana?;
|
|
21
|
+
private committedKana;
|
|
22
|
+
private furigana;
|
|
23
|
+
private isComposing;
|
|
24
|
+
private lastConvertedInput;
|
|
25
|
+
private lastNewInput;
|
|
26
|
+
private pendingKana;
|
|
27
|
+
private previousRawInput;
|
|
28
|
+
private blurHandler;
|
|
29
|
+
private focusHandler;
|
|
30
|
+
private compositionStartHandler;
|
|
31
|
+
private compositionEndHandler;
|
|
32
|
+
private inputHandler;
|
|
33
|
+
constructor(name: Bindable, furigana?: Bindable, option?: Partial<AutoKanaOption>);
|
|
34
|
+
/**
|
|
35
|
+
* Get the current furigana string.
|
|
36
|
+
*
|
|
37
|
+
* @returns The current furigana string.
|
|
38
|
+
*/
|
|
39
|
+
getFurigana(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Resume auto-kana tracking.
|
|
42
|
+
*/
|
|
43
|
+
start(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Pause auto-kana tracking.
|
|
46
|
+
*/
|
|
47
|
+
stop(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Toggle auto-kana tracking on or off.
|
|
50
|
+
*
|
|
51
|
+
* @param event Optional checkbox change event. When provided, uses the checked state of the target.
|
|
52
|
+
*/
|
|
53
|
+
toggle(event?: {
|
|
54
|
+
target: {
|
|
55
|
+
checked: boolean;
|
|
56
|
+
};
|
|
57
|
+
}): void;
|
|
58
|
+
/**
|
|
59
|
+
* Reset all internal state (committed kana, furigana, composing flag, etc.).
|
|
60
|
+
*/
|
|
61
|
+
reset(): void;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Use reset() instead.
|
|
64
|
+
*/
|
|
65
|
+
initializeValues(): void;
|
|
66
|
+
private registerEvents;
|
|
67
|
+
setFurigana(force?: boolean): void;
|
|
68
|
+
private extractNewInput;
|
|
69
|
+
detectAndCommitConversion(newPendingKana: string[]): void;
|
|
70
|
+
private handleCompositionInput;
|
|
71
|
+
private handleNormalInput;
|
|
72
|
+
processValue(): void;
|
|
73
|
+
commitPendingKana(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Remove all event listeners (blur, focus, compositionstart, compositionend, input) from the name element.
|
|
76
|
+
*/
|
|
77
|
+
destroy(): void;
|
|
78
|
+
private debug;
|
|
79
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** A CSS selector string or a DOM Element. */
|
|
2
|
+
export type KanaElement = HTMLInputElement | HTMLTextAreaElement;
|
|
3
|
+
export type Bindable = string | KanaElement;
|
|
4
|
+
export declare function getElementLabel(selectorOrElement: Bindable): string;
|
|
5
|
+
export declare function ensureElement(selectorOrElement: Bindable): HTMLElement | null;
|
|
6
|
+
export declare function isKanaElement(el: HTMLElement): el is KanaElement;
|
|
7
|
+
export declare function requireElement(selectorOrElement: Bindable): KanaElement;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** A CSS selector string or a DOM Element. */
|
|
2
|
+
export type KanaElement = HTMLInputElement | HTMLTextAreaElement;
|
|
3
|
+
export type Bindable = string | KanaElement;
|
|
4
|
+
export declare function getElementLabel(selectorOrElement: Bindable): string;
|
|
5
|
+
export declare function ensureElement(selectorOrElement: Bindable): HTMLElement | null;
|
|
6
|
+
export declare function isKanaElement(el: HTMLElement): el is KanaElement;
|
|
7
|
+
export declare function requireElement(selectorOrElement: Bindable): KanaElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{static{this.EXTRACTION_PATTERN=/[^ ぁあ-んゔー]/g}static{this.COMPACTING_PATTERN=/[ぁぃぅぇぉっゃゅょ]/g}static extract(e){return e.replace(this.EXTRACTION_PATTERN,``).split(``)}static compact(e){return e.replace(this.COMPACTING_PATTERN,``)}static containsNonKana(e){return e.search(this.EXTRACTION_PATTERN)!==-1}},t={ァ:`ァ`,ア:`ア`,ィ:`ィ`,イ:`イ`,ゥ:`ゥ`,ウ:`ウ`,ェ:`ェ`,エ:`エ`,ォ:`ォ`,オ:`オ`,カ:`カ`,ガ:`ガ`,キ:`キ`,ギ:`ギ`,ク:`ク`,グ:`グ`,ケ:`ケ`,ゲ:`ゲ`,コ:`コ`,ゴ:`ゴ`,サ:`サ`,ザ:`ザ`,シ:`シ`,ジ:`ジ`,ス:`ス`,ズ:`ズ`,セ:`セ`,ゼ:`ゼ`,ソ:`ソ`,ゾ:`ゾ`,タ:`タ`,ダ:`ダ`,チ:`チ`,ヂ:`ヂ`,ッ:`ッ`,ツ:`ツ`,ヅ:`ヅ`,テ:`テ`,デ:`デ`,ト:`ト`,ド:`ド`,ナ:`ナ`,ニ:`ニ`,ヌ:`ヌ`,ネ:`ネ`,ノ:`ノ`,ハ:`ハ`,バ:`バ`,パ:`パ`,ヒ:`ヒ`,ビ:`ビ`,ピ:`ピ`,フ:`フ`,ブ:`ブ`,プ:`プ`,ヘ:`ヘ`,ベ:`ベ`,ペ:`ペ`,ホ:`ホ`,ボ:`ボ`,ポ:`ポ`,マ:`マ`,ミ:`ミ`,ム:`ム`,メ:`メ`,モ:`モ`,ャ:`ャ`,ヤ:`ヤ`,ュ:`ュ`,ユ:`ユ`,ョ:`ョ`,ヨ:`ヨ`,ラ:`ラ`,リ:`リ`,ル:`ル`,レ:`レ`,ロ:`ロ`,ワ:`ワ`,ヰ:`イ`,ヱ:`エ`,ヲ:`ヲ`,ヺ:`ヺ`,ン:`ン`,ヴ:`ヴ`,ー:`ー`,"。":`。`,"、":`、`},n=12353,r=12436,i=12445,a=12446;function o(e){return e>=n&&e<=r||e===i||e===a}var s=class{static toKatakana(e,n){if(n===`hiragana`)return e;let r,i=``;for(let t=0;t<e.length;t+=1)r=e.charCodeAt(t),o(r)?i+=String.fromCharCode(r+96):i+=e.charAt(t);return n===`half`?i.replace(/[ァ-ヴヺー。、]/g,e=>t[e]??e):i}};function c(e){return typeof e==`string`?`"${e}"`:`the provided element`}function l(e){if(typeof e==`string`){if(!/^[[.#:]/.test(e))return document.getElementById(e);try{return document.querySelector(e)}catch{throw Error(`AutoKana: Invalid selector for ${c(e)}.`)}}return e instanceof HTMLElement?e:null}function u(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function d(e){let t=l(e);if(!t){let t=c(e);throw Error(`AutoKana: Element not found for ${t}. Ensure the DOM element exists before calling bind(). For Vue/React, call bind() inside onMounted()/useEffect() or after the component is mounted.`)}if(!u(t)){let t=c(e);throw Error(`AutoKana: Element must be an input or textarea for ${t}.`)}return t}function f(e){if(e===void 0||e===``)return;let t=l(e);if(t){if(!u(t))throw Error(`AutoKana: Element must be an input or textarea for ${c(e)}.`);return t}}var p=class{constructor(e,t=``,n={}){this.blurHandler=()=>{this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.debug(`focus`),this.elFurigana&&(this.committedKana=this.elFurigana.value),this.isComposing=!1,this.pendingKana=[],this.lastNewInput=``,this.previousRawInput=``,this.lastConvertedInput=this.elName.value,this.processValue()},this.compositionStartHandler=()=>{this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.debug(`compositionend`),this.isComposing=!1,this.lastNewInput=``,this.previousRawInput=``,this.processValue()},this.inputHandler=e=>{this.debug(`input`,e.isComposing),this.processValue()},this.isActive=!0,this.committedKana=``,this.furigana=``,this.isComposing=!1,this.lastConvertedInput=``,this.lastNewInput=``,this.pendingKana=[],this.previousRawInput=``,this.option=Object.assign({katakana:`hiragana`,debug:!1},n);let r=d(e),i=f(t);this.elName=r,i&&(this.elFurigana=i),this.registerEvents(this.elName)}getFurigana(){return this.furigana}start(){this.isActive=!0}stop(){this.isActive=!1}toggle(e){e?this.isActive=e.target.checked:this.isActive=!this.isActive}reset(){this.committedKana=``,this.furigana=``,this.isComposing=!1,this.lastConvertedInput=``,this.lastNewInput=``,this.pendingKana=[],this.previousRawInput=``}initializeValues(){this.reset()}registerEvents(e){e.addEventListener(`blur`,this.blurHandler),e.addEventListener(`focus`,this.focusHandler),e.addEventListener(`compositionstart`,this.compositionStartHandler),e.addEventListener(`compositionend`,this.compositionEndHandler),e.addEventListener(`input`,this.inputHandler)}setFurigana(e=!1){if(this.isActive){let t=s.toKatakana(this.committedKana+this.pendingKana.join(``),this.option.katakana),n=this.option.katakana===`half`?t.replace(/ /g,` `):t;if(!e&&n===this.furigana)return;this.furigana=n,this.elFurigana&&(this.elFurigana.value=this.furigana,this.elFurigana.dispatchEvent(new Event(`input`,{bubbles:!0}))),this.option.onChange&&this.option.onChange(this.furigana)}}extractNewInput(e){if(e.indexOf(this.lastConvertedInput)!==-1)return e.replace(this.lastConvertedInput,``);let t=this.lastConvertedInput.split(``),n=e.split(``);for(let e=0;e<t.length;e+=1)t[e]===n[e]&&(n[e]=``);return n.join(``)}detectAndCommitConversion(t){if(Math.abs(this.pendingKana.length-t.length)>1){let n=this.pendingKana.join(``),r=t.join(``);if(!r.startsWith(n)){let t=e.compact(r).split(``);Math.abs(this.pendingKana.length-t.length)>1&&this.commitPendingKana()}}else this.pendingKana.length===this.lastNewInput.length&&this.pendingKana.join(``)!==this.lastNewInput&&e.containsNonKana(this.lastNewInput)&&this.commitPendingKana()}handleCompositionInput(t){let n=e.extract(t);n.length>=this.pendingKana.length&&(this.pendingKana=n),this.setFurigana()}handleNormalInput(t,n){if(this.lastNewInput===t)return;let r=n.length<this.previousRawInput.length;this.lastNewInput=t,this.previousRawInput=n;let i=e.extract(t);if(!r){let e=this.committedKana;if(this.detectAndCommitConversion(i),this.committedKana!==e){this.lastConvertedInput=n,this.setFurigana();return}}this.pendingKana=i,this.setFurigana()}processValue(){let e=this.elName.value;if(e===``){this.reset(),this.setFurigana(!0);return}let t=this.extractNewInput(e);if(this.isComposing){this.handleCompositionInput(t);return}this.handleNormalInput(t,e)}commitPendingKana(){this.committedKana+=this.pendingKana.join(``),this.pendingKana=[]}destroy(){this.elName.removeEventListener(`blur`,this.blurHandler),this.elName.removeEventListener(`focus`,this.focusHandler),this.elName.removeEventListener(`compositionstart`,this.compositionStartHandler),this.elName.removeEventListener(`compositionend`,this.compositionEndHandler),this.elName.removeEventListener(`input`,this.inputHandler),this.elName=null,this.elFurigana=void 0}debug(...e){this.option.debug&&console.log(...e)}};function m(e,t,n={}){return new p(e,t,n)}exports.AutoKana=p,exports.KanaConverter=s,exports.KanaExtractor=e,exports.bind=m;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
//#region src/KanaExtractor.ts
|
|
2
|
+
var e = class {
|
|
3
|
+
static {
|
|
4
|
+
this.EXTRACTION_PATTERN = /[^ ぁあ-んゔー]/g;
|
|
5
|
+
}
|
|
6
|
+
static {
|
|
7
|
+
this.COMPACTING_PATTERN = /[ぁぃぅぇぉっゃゅょ]/g;
|
|
8
|
+
}
|
|
9
|
+
static extract(e) {
|
|
10
|
+
return e.replace(this.EXTRACTION_PATTERN, "").split("");
|
|
11
|
+
}
|
|
12
|
+
static compact(e) {
|
|
13
|
+
return e.replace(this.COMPACTING_PATTERN, "");
|
|
14
|
+
}
|
|
15
|
+
static containsNonKana(e) {
|
|
16
|
+
return e.search(this.EXTRACTION_PATTERN) !== -1;
|
|
17
|
+
}
|
|
18
|
+
}, t = {
|
|
19
|
+
ァ: "ァ",
|
|
20
|
+
ア: "ア",
|
|
21
|
+
ィ: "ィ",
|
|
22
|
+
イ: "イ",
|
|
23
|
+
ゥ: "ゥ",
|
|
24
|
+
ウ: "ウ",
|
|
25
|
+
ェ: "ェ",
|
|
26
|
+
エ: "エ",
|
|
27
|
+
ォ: "ォ",
|
|
28
|
+
オ: "オ",
|
|
29
|
+
カ: "カ",
|
|
30
|
+
ガ: "ガ",
|
|
31
|
+
キ: "キ",
|
|
32
|
+
ギ: "ギ",
|
|
33
|
+
ク: "ク",
|
|
34
|
+
グ: "グ",
|
|
35
|
+
ケ: "ケ",
|
|
36
|
+
ゲ: "ゲ",
|
|
37
|
+
コ: "コ",
|
|
38
|
+
ゴ: "ゴ",
|
|
39
|
+
サ: "サ",
|
|
40
|
+
ザ: "ザ",
|
|
41
|
+
シ: "シ",
|
|
42
|
+
ジ: "ジ",
|
|
43
|
+
ス: "ス",
|
|
44
|
+
ズ: "ズ",
|
|
45
|
+
セ: "セ",
|
|
46
|
+
ゼ: "ゼ",
|
|
47
|
+
ソ: "ソ",
|
|
48
|
+
ゾ: "ゾ",
|
|
49
|
+
タ: "タ",
|
|
50
|
+
ダ: "ダ",
|
|
51
|
+
チ: "チ",
|
|
52
|
+
ヂ: "ヂ",
|
|
53
|
+
ッ: "ッ",
|
|
54
|
+
ツ: "ツ",
|
|
55
|
+
ヅ: "ヅ",
|
|
56
|
+
テ: "テ",
|
|
57
|
+
デ: "デ",
|
|
58
|
+
ト: "ト",
|
|
59
|
+
ド: "ド",
|
|
60
|
+
ナ: "ナ",
|
|
61
|
+
ニ: "ニ",
|
|
62
|
+
ヌ: "ヌ",
|
|
63
|
+
ネ: "ネ",
|
|
64
|
+
ノ: "ノ",
|
|
65
|
+
ハ: "ハ",
|
|
66
|
+
バ: "バ",
|
|
67
|
+
パ: "パ",
|
|
68
|
+
ヒ: "ヒ",
|
|
69
|
+
ビ: "ビ",
|
|
70
|
+
ピ: "ピ",
|
|
71
|
+
フ: "フ",
|
|
72
|
+
ブ: "ブ",
|
|
73
|
+
プ: "プ",
|
|
74
|
+
ヘ: "ヘ",
|
|
75
|
+
ベ: "ベ",
|
|
76
|
+
ペ: "ペ",
|
|
77
|
+
ホ: "ホ",
|
|
78
|
+
ボ: "ボ",
|
|
79
|
+
ポ: "ポ",
|
|
80
|
+
マ: "マ",
|
|
81
|
+
ミ: "ミ",
|
|
82
|
+
ム: "ム",
|
|
83
|
+
メ: "メ",
|
|
84
|
+
モ: "モ",
|
|
85
|
+
ャ: "ャ",
|
|
86
|
+
ヤ: "ヤ",
|
|
87
|
+
ュ: "ュ",
|
|
88
|
+
ユ: "ユ",
|
|
89
|
+
ョ: "ョ",
|
|
90
|
+
ヨ: "ヨ",
|
|
91
|
+
ラ: "ラ",
|
|
92
|
+
リ: "リ",
|
|
93
|
+
ル: "ル",
|
|
94
|
+
レ: "レ",
|
|
95
|
+
ロ: "ロ",
|
|
96
|
+
ワ: "ワ",
|
|
97
|
+
ヰ: "イ",
|
|
98
|
+
ヱ: "エ",
|
|
99
|
+
ヲ: "ヲ",
|
|
100
|
+
ヺ: "ヺ",
|
|
101
|
+
ン: "ン",
|
|
102
|
+
ヴ: "ヴ",
|
|
103
|
+
ー: "ー",
|
|
104
|
+
"。": "。",
|
|
105
|
+
"、": "、"
|
|
106
|
+
}, n = 12353, r = 12436, i = 12445, a = 12446;
|
|
107
|
+
function o(e) {
|
|
108
|
+
return e >= n && e <= r || e === i || e === a;
|
|
109
|
+
}
|
|
110
|
+
var s = class {
|
|
111
|
+
static toKatakana(e, n) {
|
|
112
|
+
if (n === "hiragana") return e;
|
|
113
|
+
let r, i = "";
|
|
114
|
+
for (let t = 0; t < e.length; t += 1) r = e.charCodeAt(t), o(r) ? i += String.fromCharCode(r + 96) : i += e.charAt(t);
|
|
115
|
+
return n === "half" ? i.replace(/[ァ-ヴヺー。、]/g, (e) => t[e] ?? e) : i;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/ElementResolver.ts
|
|
120
|
+
function c(e) {
|
|
121
|
+
return typeof e == "string" ? `"${e}"` : "the provided element";
|
|
122
|
+
}
|
|
123
|
+
function l(e) {
|
|
124
|
+
if (typeof e == "string") {
|
|
125
|
+
if (!/^[[.#:]/.test(e)) return document.getElementById(e);
|
|
126
|
+
try {
|
|
127
|
+
return document.querySelector(e);
|
|
128
|
+
} catch {
|
|
129
|
+
throw Error(`AutoKana: Invalid selector for ${c(e)}.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return e instanceof HTMLElement ? e : null;
|
|
133
|
+
}
|
|
134
|
+
function u(e) {
|
|
135
|
+
return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement;
|
|
136
|
+
}
|
|
137
|
+
function d(e) {
|
|
138
|
+
let t = l(e);
|
|
139
|
+
if (!t) {
|
|
140
|
+
let t = c(e);
|
|
141
|
+
throw Error(`AutoKana: Element not found for ${t}. Ensure the DOM element exists before calling bind(). For Vue/React, call bind() inside onMounted()/useEffect() or after the component is mounted.`);
|
|
142
|
+
}
|
|
143
|
+
if (!u(t)) {
|
|
144
|
+
let t = c(e);
|
|
145
|
+
throw Error(`AutoKana: Element must be an input or textarea for ${t}.`);
|
|
146
|
+
}
|
|
147
|
+
return t;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/AutoKana.ts
|
|
151
|
+
function f(e) {
|
|
152
|
+
if (e === void 0 || e === "") return;
|
|
153
|
+
let t = l(e);
|
|
154
|
+
if (t) {
|
|
155
|
+
if (!u(t)) throw Error(`AutoKana: Element must be an input or textarea for ${c(e)}.`);
|
|
156
|
+
return t;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var p = class {
|
|
160
|
+
constructor(e, t = "", n = {}) {
|
|
161
|
+
this.blurHandler = () => {
|
|
162
|
+
this.debug("blur"), this.isComposing = !1;
|
|
163
|
+
}, this.focusHandler = () => {
|
|
164
|
+
this.debug("focus"), this.elFurigana && (this.committedKana = this.elFurigana.value), this.isComposing = !1, this.pendingKana = [], this.lastNewInput = "", this.previousRawInput = "", this.lastConvertedInput = this.elName.value, this.processValue();
|
|
165
|
+
}, this.compositionStartHandler = () => {
|
|
166
|
+
this.debug("compositionstart"), this.isComposing = !0;
|
|
167
|
+
}, this.compositionEndHandler = () => {
|
|
168
|
+
this.debug("compositionend"), this.isComposing = !1, this.lastNewInput = "", this.previousRawInput = "", this.processValue();
|
|
169
|
+
}, this.inputHandler = (e) => {
|
|
170
|
+
this.debug("input", e.isComposing), this.processValue();
|
|
171
|
+
}, this.isActive = !0, this.committedKana = "", this.furigana = "", this.isComposing = !1, this.lastConvertedInput = "", this.lastNewInput = "", this.pendingKana = [], this.previousRawInput = "", this.option = Object.assign({
|
|
172
|
+
katakana: "hiragana",
|
|
173
|
+
debug: !1
|
|
174
|
+
}, n);
|
|
175
|
+
let r = d(e), i = f(t);
|
|
176
|
+
this.elName = r, i && (this.elFurigana = i), this.registerEvents(this.elName);
|
|
177
|
+
}
|
|
178
|
+
getFurigana() {
|
|
179
|
+
return this.furigana;
|
|
180
|
+
}
|
|
181
|
+
start() {
|
|
182
|
+
this.isActive = !0;
|
|
183
|
+
}
|
|
184
|
+
stop() {
|
|
185
|
+
this.isActive = !1;
|
|
186
|
+
}
|
|
187
|
+
toggle(e) {
|
|
188
|
+
e ? this.isActive = e.target.checked : this.isActive = !this.isActive;
|
|
189
|
+
}
|
|
190
|
+
reset() {
|
|
191
|
+
this.committedKana = "", this.furigana = "", this.isComposing = !1, this.lastConvertedInput = "", this.lastNewInput = "", this.pendingKana = [], this.previousRawInput = "";
|
|
192
|
+
}
|
|
193
|
+
initializeValues() {
|
|
194
|
+
this.reset();
|
|
195
|
+
}
|
|
196
|
+
registerEvents(e) {
|
|
197
|
+
e.addEventListener("blur", this.blurHandler), e.addEventListener("focus", this.focusHandler), e.addEventListener("compositionstart", this.compositionStartHandler), e.addEventListener("compositionend", this.compositionEndHandler), e.addEventListener("input", this.inputHandler);
|
|
198
|
+
}
|
|
199
|
+
setFurigana(e = !1) {
|
|
200
|
+
if (this.isActive) {
|
|
201
|
+
let t = s.toKatakana(this.committedKana + this.pendingKana.join(""), this.option.katakana), n = this.option.katakana === "half" ? t.replace(/ /g, " ") : t;
|
|
202
|
+
if (!e && n === this.furigana) return;
|
|
203
|
+
this.furigana = n, this.elFurigana && (this.elFurigana.value = this.furigana, this.elFurigana.dispatchEvent(new Event("input", { bubbles: !0 }))), this.option.onChange && this.option.onChange(this.furigana);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
extractNewInput(e) {
|
|
207
|
+
if (e.indexOf(this.lastConvertedInput) !== -1) return e.replace(this.lastConvertedInput, "");
|
|
208
|
+
let t = this.lastConvertedInput.split(""), n = e.split("");
|
|
209
|
+
for (let e = 0; e < t.length; e += 1) t[e] === n[e] && (n[e] = "");
|
|
210
|
+
return n.join("");
|
|
211
|
+
}
|
|
212
|
+
detectAndCommitConversion(t) {
|
|
213
|
+
if (Math.abs(this.pendingKana.length - t.length) > 1) {
|
|
214
|
+
let n = this.pendingKana.join(""), r = t.join("");
|
|
215
|
+
if (!r.startsWith(n)) {
|
|
216
|
+
let t = e.compact(r).split("");
|
|
217
|
+
Math.abs(this.pendingKana.length - t.length) > 1 && this.commitPendingKana();
|
|
218
|
+
}
|
|
219
|
+
} else this.pendingKana.length === this.lastNewInput.length && this.pendingKana.join("") !== this.lastNewInput && e.containsNonKana(this.lastNewInput) && this.commitPendingKana();
|
|
220
|
+
}
|
|
221
|
+
handleCompositionInput(t) {
|
|
222
|
+
let n = e.extract(t);
|
|
223
|
+
n.length >= this.pendingKana.length && (this.pendingKana = n), this.setFurigana();
|
|
224
|
+
}
|
|
225
|
+
handleNormalInput(t, n) {
|
|
226
|
+
if (this.lastNewInput === t) return;
|
|
227
|
+
let r = n.length < this.previousRawInput.length;
|
|
228
|
+
this.lastNewInput = t, this.previousRawInput = n;
|
|
229
|
+
let i = e.extract(t);
|
|
230
|
+
if (!r) {
|
|
231
|
+
let e = this.committedKana;
|
|
232
|
+
if (this.detectAndCommitConversion(i), this.committedKana !== e) {
|
|
233
|
+
this.lastConvertedInput = n, this.setFurigana();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.pendingKana = i, this.setFurigana();
|
|
238
|
+
}
|
|
239
|
+
processValue() {
|
|
240
|
+
let e = this.elName.value;
|
|
241
|
+
if (e === "") {
|
|
242
|
+
this.reset(), this.setFurigana(!0);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
let t = this.extractNewInput(e);
|
|
246
|
+
if (this.isComposing) {
|
|
247
|
+
this.handleCompositionInput(t);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.handleNormalInput(t, e);
|
|
251
|
+
}
|
|
252
|
+
commitPendingKana() {
|
|
253
|
+
this.committedKana += this.pendingKana.join(""), this.pendingKana = [];
|
|
254
|
+
}
|
|
255
|
+
destroy() {
|
|
256
|
+
this.elName.removeEventListener("blur", this.blurHandler), this.elName.removeEventListener("focus", this.focusHandler), this.elName.removeEventListener("compositionstart", this.compositionStartHandler), this.elName.removeEventListener("compositionend", this.compositionEndHandler), this.elName.removeEventListener("input", this.inputHandler), this.elName = null, this.elFurigana = void 0;
|
|
257
|
+
}
|
|
258
|
+
debug(...e) {
|
|
259
|
+
this.option.debug && console.log(...e);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/index.ts
|
|
264
|
+
function m(e, t, n = {}) {
|
|
265
|
+
return new p(e, t, n);
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
export { p as AutoKana, s as KanaConverter, e as KanaExtractor, m as bind };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.AutoKana={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class{static{this.EXTRACTION_PATTERN=/[^ ぁあ-んゔー]/g}static{this.COMPACTING_PATTERN=/[ぁぃぅぇぉっゃゅょ]/g}static extract(e){return e.replace(this.EXTRACTION_PATTERN,``).split(``)}static compact(e){return e.replace(this.COMPACTING_PATTERN,``)}static containsNonKana(e){return e.search(this.EXTRACTION_PATTERN)!==-1}},n={ァ:`ァ`,ア:`ア`,ィ:`ィ`,イ:`イ`,ゥ:`ゥ`,ウ:`ウ`,ェ:`ェ`,エ:`エ`,ォ:`ォ`,オ:`オ`,カ:`カ`,ガ:`ガ`,キ:`キ`,ギ:`ギ`,ク:`ク`,グ:`グ`,ケ:`ケ`,ゲ:`ゲ`,コ:`コ`,ゴ:`ゴ`,サ:`サ`,ザ:`ザ`,シ:`シ`,ジ:`ジ`,ス:`ス`,ズ:`ズ`,セ:`セ`,ゼ:`ゼ`,ソ:`ソ`,ゾ:`ゾ`,タ:`タ`,ダ:`ダ`,チ:`チ`,ヂ:`ヂ`,ッ:`ッ`,ツ:`ツ`,ヅ:`ヅ`,テ:`テ`,デ:`デ`,ト:`ト`,ド:`ド`,ナ:`ナ`,ニ:`ニ`,ヌ:`ヌ`,ネ:`ネ`,ノ:`ノ`,ハ:`ハ`,バ:`バ`,パ:`パ`,ヒ:`ヒ`,ビ:`ビ`,ピ:`ピ`,フ:`フ`,ブ:`ブ`,プ:`プ`,ヘ:`ヘ`,ベ:`ベ`,ペ:`ペ`,ホ:`ホ`,ボ:`ボ`,ポ:`ポ`,マ:`マ`,ミ:`ミ`,ム:`ム`,メ:`メ`,モ:`モ`,ャ:`ャ`,ヤ:`ヤ`,ュ:`ュ`,ユ:`ユ`,ョ:`ョ`,ヨ:`ヨ`,ラ:`ラ`,リ:`リ`,ル:`ル`,レ:`レ`,ロ:`ロ`,ワ:`ワ`,ヰ:`イ`,ヱ:`エ`,ヲ:`ヲ`,ヺ:`ヺ`,ン:`ン`,ヴ:`ヴ`,ー:`ー`,"。":`。`,"、":`、`},r=12353,i=12436,a=12445,o=12446;function s(e){return e>=r&&e<=i||e===a||e===o}var c=class{static toKatakana(e,t){if(t===`hiragana`)return e;let r,i=``;for(let t=0;t<e.length;t+=1)r=e.charCodeAt(t),s(r)?i+=String.fromCharCode(r+96):i+=e.charAt(t);return t===`half`?i.replace(/[ァ-ヴヺー。、]/g,e=>n[e]??e):i}};function l(e){return typeof e==`string`?`"${e}"`:`the provided element`}function u(e){if(typeof e==`string`){if(!/^[[.#:]/.test(e))return document.getElementById(e);try{return document.querySelector(e)}catch{throw Error(`AutoKana: Invalid selector for ${l(e)}.`)}}return e instanceof HTMLElement?e:null}function d(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function f(e){let t=u(e);if(!t){let t=l(e);throw Error(`AutoKana: Element not found for ${t}. Ensure the DOM element exists before calling bind(). For Vue/React, call bind() inside onMounted()/useEffect() or after the component is mounted.`)}if(!d(t)){let t=l(e);throw Error(`AutoKana: Element must be an input or textarea for ${t}.`)}return t}function p(e){if(e===void 0||e===``)return;let t=u(e);if(t){if(!d(t))throw Error(`AutoKana: Element must be an input or textarea for ${l(e)}.`);return t}}var m=class{constructor(e,t=``,n={}){this.blurHandler=()=>{this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.debug(`focus`),this.elFurigana&&(this.committedKana=this.elFurigana.value),this.isComposing=!1,this.pendingKana=[],this.lastNewInput=``,this.previousRawInput=``,this.lastConvertedInput=this.elName.value,this.processValue()},this.compositionStartHandler=()=>{this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.debug(`compositionend`),this.isComposing=!1,this.lastNewInput=``,this.previousRawInput=``,this.processValue()},this.inputHandler=e=>{this.debug(`input`,e.isComposing),this.processValue()},this.isActive=!0,this.committedKana=``,this.furigana=``,this.isComposing=!1,this.lastConvertedInput=``,this.lastNewInput=``,this.pendingKana=[],this.previousRawInput=``,this.option=Object.assign({katakana:`hiragana`,debug:!1},n);let r=f(e),i=p(t);this.elName=r,i&&(this.elFurigana=i),this.registerEvents(this.elName)}getFurigana(){return this.furigana}start(){this.isActive=!0}stop(){this.isActive=!1}toggle(e){e?this.isActive=e.target.checked:this.isActive=!this.isActive}reset(){this.committedKana=``,this.furigana=``,this.isComposing=!1,this.lastConvertedInput=``,this.lastNewInput=``,this.pendingKana=[],this.previousRawInput=``}initializeValues(){this.reset()}registerEvents(e){e.addEventListener(`blur`,this.blurHandler),e.addEventListener(`focus`,this.focusHandler),e.addEventListener(`compositionstart`,this.compositionStartHandler),e.addEventListener(`compositionend`,this.compositionEndHandler),e.addEventListener(`input`,this.inputHandler)}setFurigana(e=!1){if(this.isActive){let t=c.toKatakana(this.committedKana+this.pendingKana.join(``),this.option.katakana),n=this.option.katakana===`half`?t.replace(/ /g,` `):t;if(!e&&n===this.furigana)return;this.furigana=n,this.elFurigana&&(this.elFurigana.value=this.furigana,this.elFurigana.dispatchEvent(new Event(`input`,{bubbles:!0}))),this.option.onChange&&this.option.onChange(this.furigana)}}extractNewInput(e){if(e.indexOf(this.lastConvertedInput)!==-1)return e.replace(this.lastConvertedInput,``);let t=this.lastConvertedInput.split(``),n=e.split(``);for(let e=0;e<t.length;e+=1)t[e]===n[e]&&(n[e]=``);return n.join(``)}detectAndCommitConversion(e){if(Math.abs(this.pendingKana.length-e.length)>1){let n=this.pendingKana.join(``),r=e.join(``);if(!r.startsWith(n)){let e=t.compact(r).split(``);Math.abs(this.pendingKana.length-e.length)>1&&this.commitPendingKana()}}else this.pendingKana.length===this.lastNewInput.length&&this.pendingKana.join(``)!==this.lastNewInput&&t.containsNonKana(this.lastNewInput)&&this.commitPendingKana()}handleCompositionInput(e){let n=t.extract(e);n.length>=this.pendingKana.length&&(this.pendingKana=n),this.setFurigana()}handleNormalInput(e,n){if(this.lastNewInput===e)return;let r=n.length<this.previousRawInput.length;this.lastNewInput=e,this.previousRawInput=n;let i=t.extract(e);if(!r){let e=this.committedKana;if(this.detectAndCommitConversion(i),this.committedKana!==e){this.lastConvertedInput=n,this.setFurigana();return}}this.pendingKana=i,this.setFurigana()}processValue(){let e=this.elName.value;if(e===``){this.reset(),this.setFurigana(!0);return}let t=this.extractNewInput(e);if(this.isComposing){this.handleCompositionInput(t);return}this.handleNormalInput(t,e)}commitPendingKana(){this.committedKana+=this.pendingKana.join(``),this.pendingKana=[]}destroy(){this.elName.removeEventListener(`blur`,this.blurHandler),this.elName.removeEventListener(`focus`,this.focusHandler),this.elName.removeEventListener(`compositionstart`,this.compositionStartHandler),this.elName.removeEventListener(`compositionend`,this.compositionEndHandler),this.elName.removeEventListener(`input`,this.inputHandler),this.elName=null,this.elFurigana=void 0}debug(...e){this.option.debug&&console.log(...e)}};function h(e,t,n={}){return new m(e,t,n)}e.AutoKana=m,e.KanaConverter=c,e.KanaExtractor=t,e.bind=h});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { default as AutoKana, AutoKanaOption, Bindable, KatakanaOption } from './AutoKana.cjs';
|
|
2
|
+
import { KanaConverter } from './KanaConverter.cjs';
|
|
3
|
+
import { KanaExtractor } from './KanaExtractor.cjs';
|
|
4
|
+
/**
|
|
5
|
+
* Bind elements to AutoKana.
|
|
6
|
+
*
|
|
7
|
+
* @param name Selector string starting with `#`, `.`, `[`, or `:`, or an element of the name input. Bare ID strings are also supported.
|
|
8
|
+
* @param furigana Selector string starting with `#`, `.`, `[`, or `:`, or an element of the furigana output input. Optional. Bare ID strings are also supported.
|
|
9
|
+
* @param option Option.
|
|
10
|
+
* @returns An AutoKana instance.
|
|
11
|
+
*/
|
|
12
|
+
export declare function bind(name: Bindable, furigana?: Bindable, option?: AutoKanaOption): AutoKana;
|
|
13
|
+
export { AutoKana, KanaConverter, KanaExtractor };
|
|
14
|
+
export type { AutoKanaOption, Bindable, KatakanaOption };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { default as AutoKana, AutoKanaOption, Bindable, KatakanaOption } from './AutoKana.js';
|
|
2
|
+
import { KanaConverter } from './KanaConverter.js';
|
|
3
|
+
import { KanaExtractor } from './KanaExtractor.js';
|
|
4
|
+
/**
|
|
5
|
+
* Bind elements to AutoKana.
|
|
6
|
+
*
|
|
7
|
+
* @param name Selector string starting with `#`, `.`, `[`, or `:`, or an element of the name input. Bare ID strings are also supported.
|
|
8
|
+
* @param furigana Selector string starting with `#`, `.`, `[`, or `:`, or an element of the furigana output input. Optional. Bare ID strings are also supported.
|
|
9
|
+
* @param option Option.
|
|
10
|
+
* @returns An AutoKana instance.
|
|
11
|
+
*/
|
|
12
|
+
export declare function bind(name: Bindable, furigana?: Bindable, option?: AutoKanaOption): AutoKana;
|
|
13
|
+
export { AutoKana, KanaConverter, KanaExtractor };
|
|
14
|
+
export type { AutoKanaOption, Bindable, KatakanaOption };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fullToHalfKatakanaMap: Record<string, string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fullToHalfKatakanaMap: Record<string, string>;
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@j1nn0/vanilla-autokana",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A JavaScript library to complete furigana automatically (forked from ryo-utsunomiya/vanilla-autokana).",
|
|
5
|
+
"author": "j1nn0 <j1nn0.github@gmail.com>",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"j1nn0 <j1nn0.github@gmail.com>",
|
|
8
|
+
"Ryo Utsunomiya <millionsnowliving@gmail.com>"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "dist/autokana.cjs",
|
|
12
|
+
"module": "dist/autokana.es.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/autokana.es.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/autokana.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24",
|
|
33
|
+
"pnpm": ">=10"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/j1nn0/vanilla-autokana.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/j1nn0/vanilla-autokana/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/j1nn0/vanilla-autokana#readme",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"provenance": true
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@storybook/html": "^10.4.2",
|
|
49
|
+
"@storybook/html-vite": "^10.4.2",
|
|
50
|
+
"@types/node": "^25.9.2",
|
|
51
|
+
"@types/react": "^19.2.17",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
54
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
55
|
+
"jsdom": "^29.1.1",
|
|
56
|
+
"oxfmt": "^0.53.0",
|
|
57
|
+
"oxlint": "^1.68.0",
|
|
58
|
+
"react": "^19.2.7",
|
|
59
|
+
"react-dom": "^19.2.7",
|
|
60
|
+
"storybook": "^10.4.2",
|
|
61
|
+
"typescript": "^6.0.2",
|
|
62
|
+
"unplugin-dts": "^1.0.2",
|
|
63
|
+
"vite": "^8.0.16",
|
|
64
|
+
"vitest": "^4.1.8",
|
|
65
|
+
"vue": "^3.5.35"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "vite build && node scripts/prepare-cjs-types.mjs",
|
|
69
|
+
"watch": "vite build --watch",
|
|
70
|
+
"lint": "oxlint src __tests__",
|
|
71
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.build.json",
|
|
72
|
+
"format": "oxfmt --write src",
|
|
73
|
+
"test": "vitest",
|
|
74
|
+
"test:coverage": "vitest run --coverage",
|
|
75
|
+
"verify:exports": "node scripts/verify-package-exports.mjs",
|
|
76
|
+
"ci": "oxlint src __tests__ && tsc --noEmit && tsc --noEmit -p tsconfig.build.json && vitest run --coverage && vite build && node scripts/prepare-cjs-types.mjs && node scripts/verify-package-exports.mjs",
|
|
77
|
+
"storybook": "storybook dev -p 6006",
|
|
78
|
+
"build-storybook": "storybook build"
|
|
79
|
+
}
|
|
80
|
+
}
|