@j1nn0/vanilla-autokana 2.1.1 → 2.2.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.
@@ -2,6 +2,12 @@ import { Bindable } from './ElementResolver.cjs';
2
2
  export type { Bindable };
3
3
  export type { KatakanaOption };
4
4
  type KatakanaOption = 'hiragana' | 'full' | 'half';
5
+ /** Minimal shape of a checkbox change event accepted by {@link AutoKana.toggle}. */
6
+ type ToggleEventLike = {
7
+ target: {
8
+ checked: boolean;
9
+ };
10
+ };
5
11
  export interface AutoKanaOption {
6
12
  /** Output format for furigana. `'hiragana'` = hiragana, `'full'` = full-width katakana, `'half'` = half-width katakana. */
7
13
  katakana?: KatakanaOption;
@@ -18,13 +24,9 @@ export default class AutoKana {
18
24
  };
19
25
  private elName;
20
26
  private elFurigana?;
21
- private committedKana;
22
27
  private furigana;
23
28
  private isComposing;
24
- private lastConvertedInput;
25
- private lastNewInput;
26
- private pendingKana;
27
- private previousRawInput;
29
+ private readonly tracker;
28
30
  private blurHandler;
29
31
  private focusHandler;
30
32
  private compositionStartHandler;
@@ -50,11 +52,7 @@ export default class AutoKana {
50
52
  *
51
53
  * @param event Optional checkbox change event. When provided, uses the checked state of the target.
52
54
  */
53
- toggle(event?: {
54
- target: {
55
- checked: boolean;
56
- };
57
- }): void;
55
+ toggle(event?: ToggleEventLike): void;
58
56
  /**
59
57
  * Reset all internal state (committed kana, furigana, composing flag, etc.).
60
58
  */
@@ -64,13 +62,10 @@ export default class AutoKana {
64
62
  */
65
63
  initializeValues(): void;
66
64
  private registerEvents;
65
+ /** @internal Internal mechanics; not part of the supported public API. */
67
66
  setFurigana(force?: boolean): void;
68
- private extractNewInput;
69
- detectAndCommitConversion(newPendingKana: string[]): void;
70
- private handleCompositionInput;
71
- private handleNormalInput;
67
+ /** @internal Internal mechanics; not part of the supported public API. */
72
68
  processValue(): void;
73
- commitPendingKana(): void;
74
69
  /**
75
70
  * Remove all event listeners (blur, focus, compositionstart, compositionend, input) from the name element.
76
71
  */
@@ -2,6 +2,12 @@ import { Bindable } from './ElementResolver.js';
2
2
  export type { Bindable };
3
3
  export type { KatakanaOption };
4
4
  type KatakanaOption = 'hiragana' | 'full' | 'half';
5
+ /** Minimal shape of a checkbox change event accepted by {@link AutoKana.toggle}. */
6
+ type ToggleEventLike = {
7
+ target: {
8
+ checked: boolean;
9
+ };
10
+ };
5
11
  export interface AutoKanaOption {
6
12
  /** Output format for furigana. `'hiragana'` = hiragana, `'full'` = full-width katakana, `'half'` = half-width katakana. */
7
13
  katakana?: KatakanaOption;
@@ -18,13 +24,9 @@ export default class AutoKana {
18
24
  };
19
25
  private elName;
20
26
  private elFurigana?;
21
- private committedKana;
22
27
  private furigana;
23
28
  private isComposing;
24
- private lastConvertedInput;
25
- private lastNewInput;
26
- private pendingKana;
27
- private previousRawInput;
29
+ private readonly tracker;
28
30
  private blurHandler;
29
31
  private focusHandler;
30
32
  private compositionStartHandler;
@@ -50,11 +52,7 @@ export default class AutoKana {
50
52
  *
51
53
  * @param event Optional checkbox change event. When provided, uses the checked state of the target.
52
54
  */
53
- toggle(event?: {
54
- target: {
55
- checked: boolean;
56
- };
57
- }): void;
55
+ toggle(event?: ToggleEventLike): void;
58
56
  /**
59
57
  * Reset all internal state (committed kana, furigana, composing flag, etc.).
60
58
  */
@@ -64,13 +62,10 @@ export default class AutoKana {
64
62
  */
65
63
  initializeValues(): void;
66
64
  private registerEvents;
65
+ /** @internal Internal mechanics; not part of the supported public API. */
67
66
  setFurigana(force?: boolean): void;
68
- private extractNewInput;
69
- detectAndCommitConversion(newPendingKana: string[]): void;
70
- private handleCompositionInput;
71
- private handleNormalInput;
67
+ /** @internal Internal mechanics; not part of the supported public API. */
72
68
  processValue(): void;
73
- commitPendingKana(): void;
74
69
  /**
75
70
  * Remove all event listeners (blur, focus, compositionstart, compositionend, input) from the name element.
76
71
  */
@@ -0,0 +1,78 @@
1
+ import { KatakanaOption } from './AutoKana.cjs';
2
+ /**
3
+ * IME conversion state machine: separates 確定かな (committed) from 未確定かな (pending)
4
+ * and detects 変換 (conversion). AutoKana's DOM adapter owns the elements, events, and
5
+ * output policy (isActive/dedup/onChange).
6
+ *
7
+ * @internal Not part of the supported public API; not exported from the package entry.
8
+ */
9
+ export declare class InputTracker {
10
+ private readonly katakana;
11
+ private committedKana;
12
+ private pendingKana;
13
+ private lastConvertedInput;
14
+ private lastNewInput;
15
+ private previousRawInput;
16
+ constructor(katakana: KatakanaOption);
17
+ /**
18
+ * Feed the current raw field value and composition flag, advancing the state machine.
19
+ *
20
+ * @param raw The full value of the name field.
21
+ * @param isComposing Whether an IME composition is in progress.
22
+ */
23
+ update(raw: string, isComposing: boolean): void;
24
+ /**
25
+ * Reset the input-tracking flags at the end of an IME composition so the next
26
+ * {@link update} runs the full non-composition path (変換 detection, commit, etc.).
27
+ * The accumulated pending kana are intentionally kept so they can still be committed.
28
+ */
29
+ endComposition(): void;
30
+ /**
31
+ * Re-seed the tracker from the live DOM on focus: adopt the existing furigana as already
32
+ * committed kana, treat the current raw value as already converted, and clear pending state.
33
+ *
34
+ * @param raw The current value of the name field.
35
+ * @param committedSeed The current furigana value to adopt as committed kana, or `undefined`
36
+ * when there is no furigana element (leaves committed kana untouched).
37
+ */
38
+ resync(raw: string, committedSeed: string | undefined): void;
39
+ /** Clear all tracking state (committed kana, pending kana, and the diff anchors). */
40
+ reset(): void;
41
+ /** The current furigana string in the configured output format. */
42
+ getFurigana(): string;
43
+ /**
44
+ * Subtract the last converted (committed) portion of the raw input so that only the
45
+ * not-yet-committed remainder is returned for kana extraction.
46
+ *
47
+ * Two strategies:
48
+ * 1. If `lastConvertedInput` appears as a contiguous substring of the current input,
49
+ * slice it out. This is the common case (the committed kanji sits intact inside the
50
+ * field while the user keeps typing before/after it). An empty `lastConvertedInput`
51
+ * matches at index 0 and returns the input unchanged.
52
+ * 2. Otherwise fall back to a positional charCode comparison, keeping only the
53
+ * characters that differ from `lastConvertedInput` at the same index. This handles
54
+ * cases where the committed text is no longer contiguous after an IME conversion.
55
+ */
56
+ private extractNewInput;
57
+ /**
58
+ * Decide whether the user just confirmed an IME conversion and, if so, move the
59
+ * current pending kana (未確定かな) into committed kana (確定かな).
60
+ *
61
+ * Two heuristics signal a conversion:
62
+ *
63
+ * 1. **Large length jump** — the pending kana count changed by more than one in a single
64
+ * input event. A jump that big is not plain typing (which adds/removes one kana at a
65
+ * time); it means the IME replaced the reading with converted text. We skip the case
66
+ * where the new kana merely extends the old (`startsWith`), and re-check after kana
67
+ * compacting (小さなかな除去) so that small kana like っ/ゃ don't inflate the diff and
68
+ * cause a false positive.
69
+ *
70
+ * 2. **Same length, different content with non-kana present** — the pending kana count is
71
+ * unchanged but the raw last input now contains non-kana characters (e.g. kanji). That
72
+ * means the reading was converted in place, so commit it.
73
+ */
74
+ private detectAndCommitConversion;
75
+ private handleCompositionInput;
76
+ private handleNormalInput;
77
+ private commitPendingKana;
78
+ }
@@ -0,0 +1,78 @@
1
+ import { KatakanaOption } from './AutoKana.js';
2
+ /**
3
+ * IME conversion state machine: separates 確定かな (committed) from 未確定かな (pending)
4
+ * and detects 変換 (conversion). AutoKana's DOM adapter owns the elements, events, and
5
+ * output policy (isActive/dedup/onChange).
6
+ *
7
+ * @internal Not part of the supported public API; not exported from the package entry.
8
+ */
9
+ export declare class InputTracker {
10
+ private readonly katakana;
11
+ private committedKana;
12
+ private pendingKana;
13
+ private lastConvertedInput;
14
+ private lastNewInput;
15
+ private previousRawInput;
16
+ constructor(katakana: KatakanaOption);
17
+ /**
18
+ * Feed the current raw field value and composition flag, advancing the state machine.
19
+ *
20
+ * @param raw The full value of the name field.
21
+ * @param isComposing Whether an IME composition is in progress.
22
+ */
23
+ update(raw: string, isComposing: boolean): void;
24
+ /**
25
+ * Reset the input-tracking flags at the end of an IME composition so the next
26
+ * {@link update} runs the full non-composition path (変換 detection, commit, etc.).
27
+ * The accumulated pending kana are intentionally kept so they can still be committed.
28
+ */
29
+ endComposition(): void;
30
+ /**
31
+ * Re-seed the tracker from the live DOM on focus: adopt the existing furigana as already
32
+ * committed kana, treat the current raw value as already converted, and clear pending state.
33
+ *
34
+ * @param raw The current value of the name field.
35
+ * @param committedSeed The current furigana value to adopt as committed kana, or `undefined`
36
+ * when there is no furigana element (leaves committed kana untouched).
37
+ */
38
+ resync(raw: string, committedSeed: string | undefined): void;
39
+ /** Clear all tracking state (committed kana, pending kana, and the diff anchors). */
40
+ reset(): void;
41
+ /** The current furigana string in the configured output format. */
42
+ getFurigana(): string;
43
+ /**
44
+ * Subtract the last converted (committed) portion of the raw input so that only the
45
+ * not-yet-committed remainder is returned for kana extraction.
46
+ *
47
+ * Two strategies:
48
+ * 1. If `lastConvertedInput` appears as a contiguous substring of the current input,
49
+ * slice it out. This is the common case (the committed kanji sits intact inside the
50
+ * field while the user keeps typing before/after it). An empty `lastConvertedInput`
51
+ * matches at index 0 and returns the input unchanged.
52
+ * 2. Otherwise fall back to a positional charCode comparison, keeping only the
53
+ * characters that differ from `lastConvertedInput` at the same index. This handles
54
+ * cases where the committed text is no longer contiguous after an IME conversion.
55
+ */
56
+ private extractNewInput;
57
+ /**
58
+ * Decide whether the user just confirmed an IME conversion and, if so, move the
59
+ * current pending kana (未確定かな) into committed kana (確定かな).
60
+ *
61
+ * Two heuristics signal a conversion:
62
+ *
63
+ * 1. **Large length jump** — the pending kana count changed by more than one in a single
64
+ * input event. A jump that big is not plain typing (which adds/removes one kana at a
65
+ * time); it means the IME replaced the reading with converted text. We skip the case
66
+ * where the new kana merely extends the old (`startsWith`), and re-check after kana
67
+ * compacting (小さなかな除去) so that small kana like っ/ゃ don't inflate the diff and
68
+ * cause a false positive.
69
+ *
70
+ * 2. **Same length, different content with non-kana present** — the pending kana count is
71
+ * unchanged but the raw last input now contains non-kana characters (e.g. kanji). That
72
+ * means the reading was converted in place, so commit it.
73
+ */
74
+ private detectAndCommitConversion;
75
+ private handleCompositionInput;
76
+ private handleNormalInput;
77
+ private commitPendingKana;
78
+ }
package/dist/autokana.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{static{this.EXTRACTION_PATTERN=/[^  ぁあ-んゔー]/g}static{this.CONTAINS_NON_KANA_PATTERN=/[^  ぁあ-んゔー]/}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.CONTAINS_NON_KANA_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=``;for(let i=0;i<e.length;i+=1){let a=e.charCodeAt(i),s=o(a)?String.fromCharCode(a+96):e.charAt(i);r+=n===`half`?t[s]??s:s}return r}};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.option.debug&&this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.option.debug&&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.option.debug&&this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.option.debug&&this.debug(`compositionend`),this.isComposing=!1,this.lastNewInput=``,this.previousRawInput=``,this.processValue()},this.inputHandler=e=>{this.option.debug&&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.indexOf(` `)!==-1?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){let t=e.indexOf(this.lastConvertedInput);if(t!==-1)return e.slice(0,t)+e.slice(t+this.lastConvertedInput.length);let n=``;for(let t=0;t<e.length;t+=1)this.lastConvertedInput.charCodeAt(t)!==e.charCodeAt(t)&&(n+=e.charAt(t));return n}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,t){if(this.option.debug){if(t===void 0){console.log(e);return}console.log(e,t)}}};function m(e,t,n={}){return new p(e,t,n)}exports.AutoKana=p,exports.KanaConverter=s,exports.KanaExtractor=e,exports.bind=m;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{static{this.EXTRACTION_PATTERN=/[^  ぁあ-んゔー]/g}static{this.CONTAINS_NON_KANA_PATTERN=/[^  ぁあ-んゔー]/}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.CONTAINS_NON_KANA_PATTERN)!==-1}},t={ァ:`ァ`,ア:`ア`,ィ:`ィ`,イ:`イ`,ゥ:`ゥ`,ウ:`ウ`,ェ:`ェ`,エ:`エ`,ォ:`ォ`,オ:`オ`,カ:`カ`,ガ:`ガ`,キ:`キ`,ギ:`ギ`,ク:`ク`,グ:`グ`,ケ:`ケ`,ゲ:`ゲ`,コ:`コ`,ゴ:`ゴ`,サ:`サ`,ザ:`ザ`,シ:`シ`,ジ:`ジ`,ス:`ス`,ズ:`ズ`,セ:`セ`,ゼ:`ゼ`,ソ:`ソ`,ゾ:`ゾ`,タ:`タ`,ダ:`ダ`,チ:`チ`,ヂ:`ヂ`,ッ:`ッ`,ツ:`ツ`,ヅ:`ヅ`,テ:`テ`,デ:`デ`,ト:`ト`,ド:`ド`,ナ:`ナ`,ニ:`ニ`,ヌ:`ヌ`,ネ:`ネ`,ノ:`ノ`,ハ:`ハ`,バ:`バ`,パ:`パ`,ヒ:`ヒ`,ビ:`ビ`,ピ:`ピ`,フ:`フ`,ブ:`ブ`,プ:`プ`,ヘ:`ヘ`,ベ:`ベ`,ペ:`ペ`,ホ:`ホ`,ボ:`ボ`,ポ:`ポ`,マ:`マ`,ミ:`ミ`,ム:`ム`,メ:`メ`,モ:`モ`,ャ:`ャ`,ヤ:`ヤ`,ュ:`ュ`,ユ:`ユ`,ョ:`ョ`,ヨ:`ヨ`,ラ:`ラ`,リ:`リ`,ル:`ル`,レ:`レ`,ロ:`ロ`,ワ:`ワ`,ヰ:`イ`,ヱ:`エ`,ヲ:`ヲ`,ヺ:`ヺ`,ン:`ン`,ヴ:`ヴ`,ー:`ー`,"。":`。`,"、":`、`},n=12353,r=12436,i=12445,a=12446,o=96;function s(e){return e>=n&&e<=r||e===i||e===a}var c=class{static toKatakana(e,n){if(n===`hiragana`)return e;let r=``;for(let i=0;i<e.length;i+=1){let a=e.charCodeAt(i),c=s(a)?String.fromCharCode(a+o):e.charAt(i);r+=n===`half`?t[c]??c:c}return n===`half`&&r.indexOf(` `)!==-1?r.replace(/ /g,` `):r}},l=class{constructor(e){this.katakana=e,this.committedKana=``,this.pendingKana=[],this.lastConvertedInput=``,this.lastNewInput=``,this.previousRawInput=``}update(e,t){let n=this.extractNewInput(e);if(t){this.handleCompositionInput(n);return}this.handleNormalInput(n,e)}endComposition(){this.lastNewInput=``,this.previousRawInput=``}resync(e,t){t!==void 0&&(this.committedKana=t),this.pendingKana=[],this.lastNewInput=``,this.previousRawInput=``,this.lastConvertedInput=e}reset(){this.committedKana=``,this.pendingKana=[],this.lastConvertedInput=``,this.lastNewInput=``,this.previousRawInput=``}getFurigana(){return c.toKatakana(this.committedKana+this.pendingKana.join(``),this.katakana)}extractNewInput(e){let t=e.indexOf(this.lastConvertedInput);if(t!==-1)return e.slice(0,t)+e.slice(t+this.lastConvertedInput.length);let n=``;for(let t=0;t<e.length;t+=1)this.lastConvertedInput.charCodeAt(t)!==e.charCodeAt(t)&&(n+=e.charAt(t));return n}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)}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;return}}this.pendingKana=i}commitPendingKana(){this.committedKana+=this.pendingKana.join(``),this.pendingKana=[]}};function u(e){return typeof e==`string`?`"${e}"`:`the provided element`}function d(e){if(typeof e==`string`){if(!/^[[.#:]/.test(e))return document.getElementById(e);try{return document.querySelector(e)}catch{throw Error(`AutoKana: Invalid selector for ${u(e)}.`)}}return e instanceof HTMLElement?e:null}function f(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function p(e){let t=d(e);if(!t){let t=u(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(!f(t)){let t=u(e);throw Error(`AutoKana: Element must be an input or textarea for ${t}.`)}return t}function m(e){if(e===void 0||e===``)return;let t=d(e);if(t){if(!f(t))throw Error(`AutoKana: Element must be an input or textarea for ${u(e)}.`);return t}}var h=class{constructor(e,t=``,n={}){this.blurHandler=()=>{this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.debug(`focus`),this.isComposing=!1,this.tracker.resync(this.elName.value,this.elFurigana?.value),this.processValue()},this.compositionStartHandler=()=>{this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.debug(`compositionend`),this.isComposing=!1,this.tracker.endComposition(),this.processValue()},this.inputHandler=e=>{this.debug(`input`,e.isComposing),this.processValue()},this.isActive=!0,this.furigana=``,this.isComposing=!1,this.option={...n,katakana:n.katakana??`hiragana`,debug:n.debug??!1},this.tracker=new l(this.option.katakana);let r=p(e),i=m(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.furigana=``,this.isComposing=!1,this.tracker.reset()}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)return;let t=this.tracker.getFurigana();!e&&t===this.furigana||(this.furigana=t,this.elFurigana&&(this.elFurigana.value=this.furigana,this.elFurigana.dispatchEvent(new Event(`input`,{bubbles:!0}))),this.option.onChange&&this.option.onChange(this.furigana))}processValue(){let e=this.elName.value;if(e===``){this.reset(),this.setFurigana(!0);return}this.tracker.update(e,this.isComposing),this.setFurigana()}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,t){if(this.option.debug){if(t===void 0){console.log(e);return}console.log(e,t)}}};function g(e,t,n={}){return new h(e,t,n)}exports.AutoKana=h,exports.KanaConverter=c,exports.KanaExtractor=e,exports.bind=g;
@@ -106,79 +106,141 @@ var e = class {
106
106
  ー: "ー",
107
107
  "。": "。",
108
108
  "、": "、"
109
- }, n = 12353, r = 12436, i = 12445, a = 12446;
110
- function o(e) {
109
+ }, n = 12353, r = 12436, i = 12445, a = 12446, o = 96;
110
+ function s(e) {
111
111
  return e >= n && e <= r || e === i || e === a;
112
112
  }
113
- var s = class {
113
+ var c = class {
114
114
  static toKatakana(e, n) {
115
115
  if (n === "hiragana") return e;
116
116
  let r = "";
117
117
  for (let i = 0; i < e.length; i += 1) {
118
- let a = e.charCodeAt(i), s = o(a) ? String.fromCharCode(a + 96) : e.charAt(i);
119
- r += n === "half" ? t[s] ?? s : s;
118
+ let a = e.charCodeAt(i), c = s(a) ? String.fromCharCode(a + o) : e.charAt(i);
119
+ r += n === "half" ? t[c] ?? c : c;
120
120
  }
121
- return r;
121
+ return n === "half" && r.indexOf(" ") !== -1 ? r.replace(/ /g, " ") : r;
122
+ }
123
+ }, l = class {
124
+ constructor(e) {
125
+ this.katakana = e, this.committedKana = "", this.pendingKana = [], this.lastConvertedInput = "", this.lastNewInput = "", this.previousRawInput = "";
126
+ }
127
+ update(e, t) {
128
+ let n = this.extractNewInput(e);
129
+ if (t) {
130
+ this.handleCompositionInput(n);
131
+ return;
132
+ }
133
+ this.handleNormalInput(n, e);
134
+ }
135
+ endComposition() {
136
+ this.lastNewInput = "", this.previousRawInput = "";
137
+ }
138
+ resync(e, t) {
139
+ t !== void 0 && (this.committedKana = t), this.pendingKana = [], this.lastNewInput = "", this.previousRawInput = "", this.lastConvertedInput = e;
140
+ }
141
+ reset() {
142
+ this.committedKana = "", this.pendingKana = [], this.lastConvertedInput = "", this.lastNewInput = "", this.previousRawInput = "";
143
+ }
144
+ getFurigana() {
145
+ return c.toKatakana(this.committedKana + this.pendingKana.join(""), this.katakana);
146
+ }
147
+ extractNewInput(e) {
148
+ let t = e.indexOf(this.lastConvertedInput);
149
+ if (t !== -1) return e.slice(0, t) + e.slice(t + this.lastConvertedInput.length);
150
+ let n = "";
151
+ for (let t = 0; t < e.length; t += 1) this.lastConvertedInput.charCodeAt(t) !== e.charCodeAt(t) && (n += e.charAt(t));
152
+ return n;
153
+ }
154
+ detectAndCommitConversion(t) {
155
+ if (Math.abs(this.pendingKana.length - t.length) > 1) {
156
+ let n = this.pendingKana.join(""), r = t.join("");
157
+ if (!r.startsWith(n)) {
158
+ let t = e.compact(r).split("");
159
+ Math.abs(this.pendingKana.length - t.length) > 1 && this.commitPendingKana();
160
+ }
161
+ } else this.pendingKana.length === this.lastNewInput.length && this.pendingKana.join("") !== this.lastNewInput && e.containsNonKana(this.lastNewInput) && this.commitPendingKana();
162
+ }
163
+ handleCompositionInput(t) {
164
+ let n = e.extract(t);
165
+ n.length >= this.pendingKana.length && (this.pendingKana = n);
166
+ }
167
+ handleNormalInput(t, n) {
168
+ if (this.lastNewInput === t) return;
169
+ let r = n.length < this.previousRawInput.length;
170
+ this.lastNewInput = t, this.previousRawInput = n;
171
+ let i = e.extract(t);
172
+ if (!r) {
173
+ let e = this.committedKana;
174
+ if (this.detectAndCommitConversion(i), this.committedKana !== e) {
175
+ this.lastConvertedInput = n;
176
+ return;
177
+ }
178
+ }
179
+ this.pendingKana = i;
180
+ }
181
+ commitPendingKana() {
182
+ this.committedKana += this.pendingKana.join(""), this.pendingKana = [];
122
183
  }
123
184
  };
124
185
  //#endregion
125
186
  //#region src/ElementResolver.ts
126
- function c(e) {
187
+ function u(e) {
127
188
  return typeof e == "string" ? `"${e}"` : "the provided element";
128
189
  }
129
- function l(e) {
190
+ function d(e) {
130
191
  if (typeof e == "string") {
131
192
  if (!/^[[.#:]/.test(e)) return document.getElementById(e);
132
193
  try {
133
194
  return document.querySelector(e);
134
195
  } catch {
135
- throw Error(`AutoKana: Invalid selector for ${c(e)}.`);
196
+ throw Error(`AutoKana: Invalid selector for ${u(e)}.`);
136
197
  }
137
198
  }
138
199
  return e instanceof HTMLElement ? e : null;
139
200
  }
140
- function u(e) {
201
+ function f(e) {
141
202
  return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement;
142
203
  }
143
- function d(e) {
144
- let t = l(e);
204
+ function p(e) {
205
+ let t = d(e);
145
206
  if (!t) {
146
- let t = c(e);
207
+ let t = u(e);
147
208
  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.`);
148
209
  }
149
- if (!u(t)) {
150
- let t = c(e);
210
+ if (!f(t)) {
211
+ let t = u(e);
151
212
  throw Error(`AutoKana: Element must be an input or textarea for ${t}.`);
152
213
  }
153
214
  return t;
154
215
  }
155
216
  //#endregion
156
217
  //#region src/AutoKana.ts
157
- function f(e) {
218
+ function m(e) {
158
219
  if (e === void 0 || e === "") return;
159
- let t = l(e);
220
+ let t = d(e);
160
221
  if (t) {
161
- if (!u(t)) throw Error(`AutoKana: Element must be an input or textarea for ${c(e)}.`);
222
+ if (!f(t)) throw Error(`AutoKana: Element must be an input or textarea for ${u(e)}.`);
162
223
  return t;
163
224
  }
164
225
  }
165
- var p = class {
226
+ var h = class {
166
227
  constructor(e, t = "", n = {}) {
167
228
  this.blurHandler = () => {
168
- this.option.debug && this.debug("blur"), this.isComposing = !1;
229
+ this.debug("blur"), this.isComposing = !1;
169
230
  }, this.focusHandler = () => {
170
- this.option.debug && 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();
231
+ this.debug("focus"), this.isComposing = !1, this.tracker.resync(this.elName.value, this.elFurigana?.value), this.processValue();
171
232
  }, this.compositionStartHandler = () => {
172
- this.option.debug && this.debug("compositionstart"), this.isComposing = !0;
233
+ this.debug("compositionstart"), this.isComposing = !0;
173
234
  }, this.compositionEndHandler = () => {
174
- this.option.debug && this.debug("compositionend"), this.isComposing = !1, this.lastNewInput = "", this.previousRawInput = "", this.processValue();
235
+ this.debug("compositionend"), this.isComposing = !1, this.tracker.endComposition(), this.processValue();
175
236
  }, this.inputHandler = (e) => {
176
- this.option.debug && this.debug("input", e.isComposing), this.processValue();
177
- }, this.isActive = !0, this.committedKana = "", this.furigana = "", this.isComposing = !1, this.lastConvertedInput = "", this.lastNewInput = "", this.pendingKana = [], this.previousRawInput = "", this.option = Object.assign({
178
- katakana: "hiragana",
179
- debug: !1
180
- }, n);
181
- let r = d(e), i = f(t);
237
+ this.debug("input", e.isComposing), this.processValue();
238
+ }, this.isActive = !0, this.furigana = "", this.isComposing = !1, this.option = {
239
+ ...n,
240
+ katakana: n.katakana ?? "hiragana",
241
+ debug: n.debug ?? !1
242
+ }, this.tracker = new l(this.option.katakana);
243
+ let r = p(e), i = m(t);
182
244
  this.elName = r, i && (this.elFurigana = i), this.registerEvents(this.elName);
183
245
  }
184
246
  getFurigana() {
@@ -194,7 +256,7 @@ var p = class {
194
256
  e ? this.isActive = e.target.checked : this.isActive = !this.isActive;
195
257
  }
196
258
  reset() {
197
- this.committedKana = "", this.furigana = "", this.isComposing = !1, this.lastConvertedInput = "", this.lastNewInput = "", this.pendingKana = [], this.previousRawInput = "";
259
+ this.furigana = "", this.isComposing = !1, this.tracker.reset();
198
260
  }
199
261
  initializeValues() {
200
262
  this.reset();
@@ -203,45 +265,9 @@ var p = class {
203
265
  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);
204
266
  }
205
267
  setFurigana(e = !1) {
206
- if (this.isActive) {
207
- let t = s.toKatakana(this.committedKana + this.pendingKana.join(""), this.option.katakana), n = this.option.katakana === "half" && t.indexOf(" ") !== -1 ? t.replace(/ /g, " ") : t;
208
- if (!e && n === this.furigana) return;
209
- 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);
210
- }
211
- }
212
- extractNewInput(e) {
213
- let t = e.indexOf(this.lastConvertedInput);
214
- if (t !== -1) return e.slice(0, t) + e.slice(t + this.lastConvertedInput.length);
215
- let n = "";
216
- for (let t = 0; t < e.length; t += 1) this.lastConvertedInput.charCodeAt(t) !== e.charCodeAt(t) && (n += e.charAt(t));
217
- return n;
218
- }
219
- detectAndCommitConversion(t) {
220
- if (Math.abs(this.pendingKana.length - t.length) > 1) {
221
- let n = this.pendingKana.join(""), r = t.join("");
222
- if (!r.startsWith(n)) {
223
- let t = e.compact(r).split("");
224
- Math.abs(this.pendingKana.length - t.length) > 1 && this.commitPendingKana();
225
- }
226
- } else this.pendingKana.length === this.lastNewInput.length && this.pendingKana.join("") !== this.lastNewInput && e.containsNonKana(this.lastNewInput) && this.commitPendingKana();
227
- }
228
- handleCompositionInput(t) {
229
- let n = e.extract(t);
230
- n.length >= this.pendingKana.length && (this.pendingKana = n), this.setFurigana();
231
- }
232
- handleNormalInput(t, n) {
233
- if (this.lastNewInput === t) return;
234
- let r = n.length < this.previousRawInput.length;
235
- this.lastNewInput = t, this.previousRawInput = n;
236
- let i = e.extract(t);
237
- if (!r) {
238
- let e = this.committedKana;
239
- if (this.detectAndCommitConversion(i), this.committedKana !== e) {
240
- this.lastConvertedInput = n, this.setFurigana();
241
- return;
242
- }
243
- }
244
- this.pendingKana = i, this.setFurigana();
268
+ if (!this.isActive) return;
269
+ let t = this.tracker.getFurigana();
270
+ !e && t === this.furigana || (this.furigana = t, this.elFurigana && (this.elFurigana.value = this.furigana, this.elFurigana.dispatchEvent(new Event("input", { bubbles: !0 }))), this.option.onChange && this.option.onChange(this.furigana));
245
271
  }
246
272
  processValue() {
247
273
  let e = this.elName.value;
@@ -249,15 +275,7 @@ var p = class {
249
275
  this.reset(), this.setFurigana(!0);
250
276
  return;
251
277
  }
252
- let t = this.extractNewInput(e);
253
- if (this.isComposing) {
254
- this.handleCompositionInput(t);
255
- return;
256
- }
257
- this.handleNormalInput(t, e);
258
- }
259
- commitPendingKana() {
260
- this.committedKana += this.pendingKana.join(""), this.pendingKana = [];
278
+ this.tracker.update(e, this.isComposing), this.setFurigana();
261
279
  }
262
280
  destroy() {
263
281
  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;
@@ -274,8 +292,8 @@ var p = class {
274
292
  };
275
293
  //#endregion
276
294
  //#region src/index.ts
277
- function m(e, t, n = {}) {
278
- return new p(e, t, n);
295
+ function g(e, t, n = {}) {
296
+ return new h(e, t, n);
279
297
  }
280
298
  //#endregion
281
- export { p as AutoKana, s as KanaConverter, e as KanaExtractor, m as bind };
299
+ export { h as AutoKana, c as KanaConverter, e as KanaExtractor, g as bind };
@@ -1 +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.CONTAINS_NON_KANA_PATTERN=/[^  ぁあ-んゔー]/}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.CONTAINS_NON_KANA_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=``;for(let i=0;i<e.length;i+=1){let a=e.charCodeAt(i),o=s(a)?String.fromCharCode(a+96):e.charAt(i);r+=t===`half`?n[o]??o:o}return r}};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.option.debug&&this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.option.debug&&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.option.debug&&this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.option.debug&&this.debug(`compositionend`),this.isComposing=!1,this.lastNewInput=``,this.previousRawInput=``,this.processValue()},this.inputHandler=e=>{this.option.debug&&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.indexOf(` `)!==-1?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){let t=e.indexOf(this.lastConvertedInput);if(t!==-1)return e.slice(0,t)+e.slice(t+this.lastConvertedInput.length);let n=``;for(let t=0;t<e.length;t+=1)this.lastConvertedInput.charCodeAt(t)!==e.charCodeAt(t)&&(n+=e.charAt(t));return n}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,t){if(this.option.debug){if(t===void 0){console.log(e);return}console.log(e,t)}}};function h(e,t,n={}){return new m(e,t,n)}e.AutoKana=m,e.KanaConverter=c,e.KanaExtractor=t,e.bind=h});
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.CONTAINS_NON_KANA_PATTERN=/[^  ぁあ-んゔー]/}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.CONTAINS_NON_KANA_PATTERN)!==-1}},n={ァ:`ァ`,ア:`ア`,ィ:`ィ`,イ:`イ`,ゥ:`ゥ`,ウ:`ウ`,ェ:`ェ`,エ:`エ`,ォ:`ォ`,オ:`オ`,カ:`カ`,ガ:`ガ`,キ:`キ`,ギ:`ギ`,ク:`ク`,グ:`グ`,ケ:`ケ`,ゲ:`ゲ`,コ:`コ`,ゴ:`ゴ`,サ:`サ`,ザ:`ザ`,シ:`シ`,ジ:`ジ`,ス:`ス`,ズ:`ズ`,セ:`セ`,ゼ:`ゼ`,ソ:`ソ`,ゾ:`ゾ`,タ:`タ`,ダ:`ダ`,チ:`チ`,ヂ:`ヂ`,ッ:`ッ`,ツ:`ツ`,ヅ:`ヅ`,テ:`テ`,デ:`デ`,ト:`ト`,ド:`ド`,ナ:`ナ`,ニ:`ニ`,ヌ:`ヌ`,ネ:`ネ`,ノ:`ノ`,ハ:`ハ`,バ:`バ`,パ:`パ`,ヒ:`ヒ`,ビ:`ビ`,ピ:`ピ`,フ:`フ`,ブ:`ブ`,プ:`プ`,ヘ:`ヘ`,ベ:`ベ`,ペ:`ペ`,ホ:`ホ`,ボ:`ボ`,ポ:`ポ`,マ:`マ`,ミ:`ミ`,ム:`ム`,メ:`メ`,モ:`モ`,ャ:`ャ`,ヤ:`ヤ`,ュ:`ュ`,ユ:`ユ`,ョ:`ョ`,ヨ:`ヨ`,ラ:`ラ`,リ:`リ`,ル:`ル`,レ:`レ`,ロ:`ロ`,ワ:`ワ`,ヰ:`イ`,ヱ:`エ`,ヲ:`ヲ`,ヺ:`ヺ`,ン:`ン`,ヴ:`ヴ`,ー:`ー`,"。":`。`,"、":`、`},r=12353,i=12436,a=12445,o=12446,s=96;function c(e){return e>=r&&e<=i||e===a||e===o}var l=class{static toKatakana(e,t){if(t===`hiragana`)return e;let r=``;for(let i=0;i<e.length;i+=1){let a=e.charCodeAt(i),o=c(a)?String.fromCharCode(a+s):e.charAt(i);r+=t===`half`?n[o]??o:o}return t===`half`&&r.indexOf(` `)!==-1?r.replace(/ /g,` `):r}},u=class{constructor(e){this.katakana=e,this.committedKana=``,this.pendingKana=[],this.lastConvertedInput=``,this.lastNewInput=``,this.previousRawInput=``}update(e,t){let n=this.extractNewInput(e);if(t){this.handleCompositionInput(n);return}this.handleNormalInput(n,e)}endComposition(){this.lastNewInput=``,this.previousRawInput=``}resync(e,t){t!==void 0&&(this.committedKana=t),this.pendingKana=[],this.lastNewInput=``,this.previousRawInput=``,this.lastConvertedInput=e}reset(){this.committedKana=``,this.pendingKana=[],this.lastConvertedInput=``,this.lastNewInput=``,this.previousRawInput=``}getFurigana(){return l.toKatakana(this.committedKana+this.pendingKana.join(``),this.katakana)}extractNewInput(e){let t=e.indexOf(this.lastConvertedInput);if(t!==-1)return e.slice(0,t)+e.slice(t+this.lastConvertedInput.length);let n=``;for(let t=0;t<e.length;t+=1)this.lastConvertedInput.charCodeAt(t)!==e.charCodeAt(t)&&(n+=e.charAt(t));return n}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)}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;return}}this.pendingKana=i}commitPendingKana(){this.committedKana+=this.pendingKana.join(``),this.pendingKana=[]}};function d(e){return typeof e==`string`?`"${e}"`:`the provided element`}function f(e){if(typeof e==`string`){if(!/^[[.#:]/.test(e))return document.getElementById(e);try{return document.querySelector(e)}catch{throw Error(`AutoKana: Invalid selector for ${d(e)}.`)}}return e instanceof HTMLElement?e:null}function p(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function m(e){let t=f(e);if(!t){let t=d(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(!p(t)){let t=d(e);throw Error(`AutoKana: Element must be an input or textarea for ${t}.`)}return t}function h(e){if(e===void 0||e===``)return;let t=f(e);if(t){if(!p(t))throw Error(`AutoKana: Element must be an input or textarea for ${d(e)}.`);return t}}var g=class{constructor(e,t=``,n={}){this.blurHandler=()=>{this.debug(`blur`),this.isComposing=!1},this.focusHandler=()=>{this.debug(`focus`),this.isComposing=!1,this.tracker.resync(this.elName.value,this.elFurigana?.value),this.processValue()},this.compositionStartHandler=()=>{this.debug(`compositionstart`),this.isComposing=!0},this.compositionEndHandler=()=>{this.debug(`compositionend`),this.isComposing=!1,this.tracker.endComposition(),this.processValue()},this.inputHandler=e=>{this.debug(`input`,e.isComposing),this.processValue()},this.isActive=!0,this.furigana=``,this.isComposing=!1,this.option={...n,katakana:n.katakana??`hiragana`,debug:n.debug??!1},this.tracker=new u(this.option.katakana);let r=m(e),i=h(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.furigana=``,this.isComposing=!1,this.tracker.reset()}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)return;let t=this.tracker.getFurigana();!e&&t===this.furigana||(this.furigana=t,this.elFurigana&&(this.elFurigana.value=this.furigana,this.elFurigana.dispatchEvent(new Event(`input`,{bubbles:!0}))),this.option.onChange&&this.option.onChange(this.furigana))}processValue(){let e=this.elName.value;if(e===``){this.reset(),this.setFurigana(!0);return}this.tracker.update(e,this.isComposing),this.setFurigana()}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,t){if(this.option.debug){if(t===void 0){console.log(e);return}console.log(e,t)}}};function _(e,t,n={}){return new g(e,t,n)}e.AutoKana=g,e.KanaConverter=l,e.KanaExtractor=t,e.bind=_});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j1nn0/vanilla-autokana",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "A JavaScript library to complete furigana automatically (forked from ryo-utsunomiya/vanilla-autokana).",
5
5
  "author": "j1nn0 <j1nn0.github@gmail.com>",
6
6
  "contributors": [
@@ -69,7 +69,7 @@
69
69
  "watch": "vite build --watch",
70
70
  "lint": "oxlint src __tests__",
71
71
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.build.json",
72
- "format": "oxfmt --write src",
72
+ "format": "oxfmt --write src __tests__",
73
73
  "test": "vitest",
74
74
  "test:coverage": "vitest run --coverage",
75
75
  "verify:exports": "node scripts/verify-package-exports.mjs",