ツーサム・インプット

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>ツーサム入力 v3 (変換対応)</title>
<style>
    :root {
        --bg-color: #222;
        --text-color: #fff;
        --accent-color: #00b4d8; /* 選択中の色 */
        --confirm-color: #ff006e; /* 決定ボタン */
        --panel-bg: #333;
    }

    body {
        margin: 0; padding: 0;
        background-color: var(--bg-color);
        color: var(--text-color);
        font-family: sans-serif;
        height: 100vh;
        width: 100vw;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        user-select: none;
        touch-action: manipulation;
    }

    /* --- 表示エリア --- */
    #display-area {
        height: 15vh;
        background: #fff;
        color: #000;
        font-size: 24px;
        padding: 10px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        border-bottom: 2px solid #555;
    }
    
    #input-line {
        white-space: nowrap;
        overflow-x: auto;
    }

    /* 未確定文字のスタイル(下線) */
    .composing {
        border-bottom: 2px solid blue;
        background-color: #e0f7fa;
    }

    /* 変換候補リスト表示 */
    #candidate-view {
        font-size: 14px;
        color: #555;
        height: 20px;
        margin-top: 5px;
    }

    /* --- キーボードエリア --- */
    #keyboard {
        flex: 1;
        display: grid;
        grid-template-columns: 1.5fr 1fr; /* 左:右 = 1.5:1 */
        gap: 2px;
        padding: 2px;
    }

    /* 左パネル(5行x2列 + 機能キー) */
    #left-panel {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: repeat(6, 1fr);
        gap: 2px;
    }

    /* 右パネル(1列x5行) */
    #right-panel {
        display: grid;
        grid-template-columns: 1fr;
        grid-template-rows: repeat(5, 1fr);
        gap: 2px;
    }

    .key {
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 18px;
        font-weight: bold;
        background-color: var(--panel-bg);
        border-radius: 4px;
        cursor: pointer;
        position: relative;
    }

    /* スタイル調整 */
    .key-left.active {
        background-color: var(--accent-color);
        color: #000;
        border: 2px solid #fff;
    }

    .key-right {
        font-size: 26px;
        background-color: #444;
    }

    /* 機能キーの色 */
    .func-key { background-color: #555; font-size: 16px; }
    .convert-key { background-color: #2a9d8f; color: #fff; }

    /* 変換モード中の右パネルスタイル */
    .nav-btn { background-color: #4a4e69; color: #fff; font-size: 20px; }
    .confirm-btn { background-color: var(--confirm-color); font-weight: 900; font-size: 24px; }

    /* タップフィードバック */
    .key:active { opacity: 0.7; transform: scale(0.98); }

</style>
</head>
<body>

<div id="display-area">
    <div id="input-line">
        <span id="committed-text"></span><span id="composing-text" class="composing"></span>|
    </div>
    <div id="candidate-view">未入力</div>
</div>

<div id="keyboard">
    <div id="left-panel">
        <div class="key key-left" onclick="setRow('a', this)">あ行</div>
        <div class="key key-left" onclick="setRow('k', this)">か行</div>
        <div class="key key-left" onclick="setRow('s', this)">さ行</div>
        <div class="key key-left" onclick="setRow('t', this)">た行</div>
        <div class="key key-left" onclick="setRow('n', this)">な行</div>
        <div class="key key-left" onclick="setRow('h', this)">は行</div>
        <div class="key key-left" onclick="setRow('m', this)">ま行</div>
        <div class="key key-left" onclick="setRow('y', this)">や行</div>
        <div class="key key-left" onclick="setRow('r', this)">ら行</div>
        <div class="key key-left" onclick="setRow('w', this)">わ行</div>
        
        <div class="key func-key" onclick="deleteChar()">削除</div>
        <div class="key func-key convert-key" onclick="startConversion()">変換</div>
    </div>

    <div id="right-panel">
        <div class="key key-right" id="r0" onclick="handleRight(0)">あ</div>
        <div class="key key-right" id="r1" onclick="handleRight(1)">い</div>
        <div class="key key-right" id="r2" onclick="handleRight(2)">う</div>
        <div class="key key-right" id="r3" onclick="handleRight(3)">え</div>
        <div class="key key-right" id="r4" onclick="handleRight(4)">お</div>
    </div>
</div>

<script>
    // --- データ ---
    const charMap = {
        'a': ['あ','い','う','え','お'],
        'k': ['か','き','く','け','こ'],
        's': ['さ','し','す','せ','そ'],
        't': ['た','ち','つ','て','と'],
        'n': ['な','に','ぬ','ね','の'],
        'h': ['は','ひ','ふ','へ','ほ'],
        'm': ['ま','み','む','め','も'],
        'y': ['や','(','ゆ',')','よ'],
        'r': ['ら','り','る','れ','ろ'],
        'w': ['わ','を','ん','ー','、']
    };

    // デモ用辞書(ひらがな -> 候補リスト)
    const dictionary = {
        'さ': ['差', '佐', '左', 'さ'],
        'し': ['氏', '市', '四', 'し'],
        'あい': ['愛', '藍', '相', 'あい'],
        'きかい': ['機械', '機会', '奇怪', 'きかい'],
        'こうせい': ['構成', '校正', '公正', '後世', 'こうせい'],
        'わたし': ['私', '渡し', 'わたし'],
        'にほん': ['日本', '二本', 'にほん']
    };

    // --- 状態変数 ---
    let currentRow = 'a';     // 現在選択中の行 (a, k, s...)
    let committedStr = "";    // 確定済みの文字列
    let composingStr = "";    // 入力中(未確定)の文字列
    let isConverting = false; // 変換モードかどうか
    let candidates = [];      // 現在の変換候補リスト
    let candidateIndex = 0;   // 選択中の候補インデックス

    // --- DOM要素 ---
    const elCommitted = document.getElementById('committed-text');
    const elComposing = document.getElementById('composing-text');
    const elCandView = document.getElementById('candidate-view');
    const rightKeys = [
        document.getElementById('r0'),
        document.getElementById('r1'),
        document.getElementById('r2'),
        document.getElementById('r3'),
        document.getElementById('r4')
    ];

    // --- 初期化 ---
    highlightLeftRow(); // あ行を選択状態に

    // --- 左パネル操作(行選択) ---
    function setRow(rowKey, element) {
        // 変換モード中に左を押したら、変換をキャンセルして入力に戻る
        if (isConverting) {
            cancelConversion();
        }

        currentRow = rowKey;
        
        // ハイライト更新
        document.querySelectorAll('.key-left').forEach(k => k.classList.remove('active'));
        if(element) element.classList.add('active');
        
        // 右パネルのラベルを更新
        updateRightLabels();
    }
    
    // 現在のcurrentRowに基づいて左側の見た目を更新(プログラムからの変更用)
    function highlightLeftRow() {
        document.querySelectorAll('.key-left').forEach(k => k.classList.remove('active'));
        // 該当する行を探してハイライト(簡易実装)
        // ※今回はクリック時にclass付与しているので省略可だが、初期化用
    }

    // --- 右パネルのラベル更新 ---
    function updateRightLabels() {
        const chars = charMap[currentRow];
        rightKeys.forEach((key, i) => {
            key.innerText = chars[i];
            // スタイルをリセット
            key.className = "key key-right"; 
            key.style.backgroundColor = "";
        });
    }

    // --- 右パネル操作(文字入力 OR 変換操作) ---
    function handleRight(index) {
        if (isConverting) {
            // ■ 変換モード中の操作
            if (index === 0) { // 上へ
                moveCandidate(-1);
            } else if (index === 1) { // 下へ
                moveCandidate(1);
            } else if (index === 4) { // 決定 (Enter)
                confirmConversion();
            } else if (index === 3) { // 戻る (Cancel)
                cancelConversion();
            }
        } else {
            // ■ 通常入力モード
            const char = charMap[currentRow][index];
            composingStr += char;
            updateDisplay();
        }
    }

    // --- 変換ロジック ---
    function startConversion() {
        if (composingStr === "") return; // 文字がないなら何もしない

        isConverting = true;
        
        // 辞書から検索(なければそのまま)
        if (dictionary[composingStr]) {
            candidates = dictionary[composingStr];
        } else {
            candidates = [composingStr]; // 候補がない場合はひらがなのまま
        }
        
        candidateIndex = 0;
        
        // 右パネルを「操作パネル」に変える
        changeRightPanelToControl();
        
        // 画面更新
        updateDisplay();
    }

    function changeRightPanelToControl() {
        rightKeys[0].innerText = "▲ 前へ";
        rightKeys[0].className = "key nav-btn";
        
        rightKeys[1].innerText = "▼ 次へ";
        rightKeys[1].className = "key nav-btn";
        
        rightKeys[2].innerText = ""; // 空き
        rightKeys[2].className = "key"; 
        rightKeys[2].style.backgroundColor = "#222";

        rightKeys[3].innerText = "戻る";
        rightKeys[3].className = "key nav-btn";

        rightKeys[4].innerText = "確定";
        rightKeys[4].className = "key confirm-btn";
    }

    function moveCandidate(dir) {
        candidateIndex += dir;
        // ループ処理
        if (candidateIndex < 0) candidateIndex = candidates.length - 1;
        if (candidateIndex >= candidates.length) candidateIndex = 0;
        
        updateDisplay();
    }

    function confirmConversion() {
        // 確定文字列に追加
        committedStr += candidates[candidateIndex];
        composingStr = ""; // 未確定をクリア
        isConverting = false;
        
        // 表示を元に戻す
        setRow(currentRow, document.querySelector(`.key-left[onclick*="'${currentRow}'"]`));
        updateDisplay();
    }
    
    function cancelConversion() {
        isConverting = false;
        // 表示を元に戻す(未確定文字列はそのまま)
        setRow(currentRow, null); 
        // アクティブ表示の復元
        // 簡易実装のため、ここでは再描画のみ
        updateDisplay();
    }

    // --- 削除 ---
    function deleteChar() {
        if (isConverting) {
            cancelConversion(); // 変換中なら変換をやめる
            return;
        }
        if (composingStr.length > 0) {
            composingStr = composingStr.slice(0, -1);
        } else if (committedStr.length > 0) {
            committedStr = committedStr.slice(0, -1);
        }
        updateDisplay();
    }

    // --- 画面表示更新 ---
    function updateDisplay() {
        elCommitted.innerText = committedStr;
        
        if (isConverting) {
            // 変換中は選択中の候補を表示
            elComposing.innerText = candidates[candidateIndex];
            elComposing.style.borderBottom = "2px solid #ff006e"; // 変換中は赤線とかにする?
            elCandView.innerText = `候補 [${candidateIndex+1}/${candidates.length}]: ${candidates.join("  ")}`;
        } else {
            // 入力中はひらがなを表示
            elComposing.innerText = composingStr;
            elComposing.style.borderBottom = composingStr ? "2px solid blue" : "none";
            elCandView.innerText = "文字を入力してください...";
        }
    }

</script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
小ぶりなプログラムを試しに作っているんですが、 ここではその説明書きをしていこうと思います。 こういう機能をつけてみてほしいだとかいった要望があれば コメント欄に書いてみて下さい。 ひまをみて対応します。
ツーサム・インプット|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1