Editor keyboard: docked at the bottom of the window and the page keeps the writing line above it; a close key; search keeps the header keyboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bc51aa66ab
commit
6a62df9ee0
@ -211,7 +211,8 @@ import { brand } from '../lib/brand';
|
||||
kb.innerHTML = `<div>${L[0].map((c) => key(c)).join('')}</div><div>${L[1].map((c) => key(c)).join('')}</div>` +
|
||||
`<div><button type="button" data-act="shift" class="fn${shift ? ' on' : ''}" aria-label="شفٹ">⇧</button>${L[2].map((c) => key(c)).join('')}` +
|
||||
'<button type="button" data-act="bs" class="fn" aria-label="مٹائیں">⌫</button></div>' +
|
||||
`<div>${key('،')}<button type="button" data-ch=" " class="wide">فاصلہ</button>${key('۔')}${key('؟')}</div>`;
|
||||
`<div>${key('،')}<button type="button" data-ch=" " class="wide">فاصلہ</button>${key('۔')}${key('؟')}` +
|
||||
'<button type="button" data-act="close" class="fn" aria-label="کی بورڈ بند کریں" title="بند کریں">✕</button></div>';
|
||||
};
|
||||
render();
|
||||
// the Urdu keyboard and Roman typing work on the search box and on any input marked data-urdu
|
||||
@ -232,6 +233,7 @@ import { brand } from '../lib/brand';
|
||||
kb.onclick = (ev) => {
|
||||
const b = ev.target.closest('button'); if (!b) return;
|
||||
if (b.dataset.act === 'shift') { shift = !shift; render(); return; }
|
||||
if (b.dataset.act === 'close') { closeKb(); return; }
|
||||
if (b.dataset.act === 'bs') {
|
||||
live();
|
||||
if (target.isContentEditable) { target.focus(); document.execCommand('delete'); return; }
|
||||
@ -244,7 +246,41 @@ import { brand } from '../lib/brand';
|
||||
};
|
||||
kbBtn.onclick = () => {
|
||||
kb.hidden = !kb.hidden; kbBtn.setAttribute('aria-pressed', String(!kb.hidden)); target.focus();
|
||||
if (kb.hidden) home(); else place();
|
||||
};
|
||||
// back to its place in the header (not floating)
|
||||
const home = () => { kb.classList.remove('kb-float'); document.body.style.paddingBottom = ''; if (kb.parentElement !== kbHome[0]) kbHome[0].insertBefore(kb, kbHome[1]); };
|
||||
const closeKb = () => {
|
||||
kb.hidden = true; kbBtn.setAttribute('aria-pressed', 'false'); home();
|
||||
document.querySelectorAll('[data-urdu-kb][aria-pressed=true]').forEach((x) => x.setAttribute('aria-pressed', 'false'));
|
||||
};
|
||||
// For the content editor the keyboard is docked at the bottom of the window (like a phone's) and the page follows
|
||||
// the writing cursor: the line being written is kept in view just above the keyboard, never under it. The page
|
||||
// gets room at the bottom while it is open, so the last lines can come up above it.
|
||||
const caret = () => {
|
||||
const sel = getSelection();
|
||||
if (sel?.rangeCount && target.contains(sel.anchorNode)) {
|
||||
const r = sel.getRangeAt(0), rect = r.getClientRects()[0] ?? r.getBoundingClientRect();
|
||||
if (rect && (rect.width || rect.height)) return rect;
|
||||
const el = sel.anchorNode.nodeType === 1 ? sel.anchorNode : sel.anchorNode.parentElement;
|
||||
if (el) return el.getBoundingClientRect(); // an empty line has no caret box of its own
|
||||
}
|
||||
return null;
|
||||
};
|
||||
let placing = 0;
|
||||
function place() {
|
||||
if (kb.hidden || !kb.classList.contains('kb-float')) return;
|
||||
cancelAnimationFrame(placing);
|
||||
placing = requestAnimationFrame(() => {
|
||||
document.body.style.paddingBottom = `${kb.offsetHeight + 24}px`;
|
||||
const c = caret(); if (!c) return;
|
||||
const room = kb.getBoundingClientRect().top - 16, top = 90; // keep the line between the header area and the keyboard
|
||||
if (c.bottom > room) scrollBy({ top: c.bottom - room + 40, behavior: 'smooth' });
|
||||
else if (c.top < top) scrollBy({ top: c.top - top - 40, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
document.addEventListener('selectionchange', place);
|
||||
addEventListener('resize', place);
|
||||
romBtn.onclick = () => { romBtn.setAttribute('aria-pressed', String(romBtn.getAttribute('aria-pressed') !== 'true')); target.focus(); };
|
||||
// other inputs can open the same keyboard / Roman typing with their own buttons
|
||||
document.addEventListener('click', (e) => {
|
||||
@ -252,14 +288,14 @@ import { brand } from '../lib/brand';
|
||||
target = document.getElementById(b.dataset.urduKb ?? b.dataset.urduRom)?.querySelector('[contenteditable]') ?? document.getElementById(b.dataset.urduKb ?? b.dataset.urduRom);
|
||||
// in an open dialog (the note box) the header can't be reached: the keyboard moves under the box, and back when it closes
|
||||
const box = b.dataset.urduKb && target.closest('dialog');
|
||||
if (box) target.closest('.in-box-field')?.after(kb); else if (b.dataset.urduKb) kbHome[0].insertBefore(kb, kbHome[1]);
|
||||
if (box) { kb.classList.remove('kb-float'); target.closest('.in-box-field')?.after(kb); }
|
||||
else if (b.dataset.urduKb && target.isContentEditable) { kb.classList.add('kb-float'); document.body.append(kb); } // the editor: floating
|
||||
else if (b.dataset.urduKb) home();
|
||||
(b.dataset.urduKb ? kbBtn : romBtn).click();
|
||||
b.setAttribute('aria-pressed', (b.dataset.urduKb ? kbBtn : romBtn).getAttribute('aria-pressed'));
|
||||
});
|
||||
document.addEventListener('close', (ev) => { // a dialog closing takes nothing of the header's with it
|
||||
if (!ev.target.contains?.(kb)) return;
|
||||
kb.hidden = true; kbBtn.setAttribute('aria-pressed', 'false'); kbHome[0].insertBefore(kb, kbHome[1]);
|
||||
ev.target.querySelectorAll('[data-urdu-kb]').forEach((x) => x.setAttribute('aria-pressed', 'false'));
|
||||
if (ev.target.contains?.(kb)) closeKb();
|
||||
}, true);
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (!ev.target.matches?.('[data-urdu]')) return;
|
||||
|
||||
@ -249,6 +249,10 @@ h1 + .muted { text-align: center; margin-top: 0; }
|
||||
.kb button { font-size: var(--fs-lg); min-width: 2.4em; padding: 2px 6px; background: var(--surface-raised); border-radius: var(--r-sm); }
|
||||
.kb .wide { min-width: 12em; }
|
||||
.kb .fn { min-width: 3.4em; background: var(--surface-sunken); }
|
||||
/* the content editor's keyboard: docked at the bottom of the window; the page keeps the writing line above it */
|
||||
.kb.kb-float { position: fixed; z-index: 40; inset-inline: 0; bottom: 0; margin: 0 auto; max-width: min(720px, 100vw); padding: 6px 10px 12px;
|
||||
background: var(--surface-raised); border: 1.5px solid var(--border); border-bottom: 0; border-radius: var(--r-lg) var(--r-lg) 0 0;
|
||||
box-shadow: 0 -8px 28px rgb(0 0 0 / .18); }
|
||||
|
||||
/* word sidebar (Wiktionary): left side on wide screens, bottom sheet on phones */
|
||||
.dict { position: fixed; top: 0; left: 0; bottom: 0; width: min(340px, 92vw); z-index: 20; overflow-y: auto; padding: 14px 18px 24px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user