Merge branch 'beta' into fix/core/10955-reset-on-frame
17
HISTORY.md
|
|
@ -1,5 +1,22 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 17.0.306 beta 2024-04-11
|
||||
|
||||
* docs(ios): updates iOS app help for 17.0 banner changes (#11200)
|
||||
* chore(oem/fv): Add fv sguuxs and update keyboard versions (#11198)
|
||||
|
||||
## 17.0.305 beta 2024-04-10
|
||||
|
||||
* (#11169)
|
||||
* change(web): merges split async method in gesture engine 🪠 (#11142)
|
||||
* fix(web): blocks nextLayer for keys quickly typed when multitapping to new layer when final tap is held (#11189)
|
||||
* refactor(web): OSK spacebar-label updates now managed by layer object 🪠 (#11175)
|
||||
|
||||
## 17.0.304 beta 2024-04-09
|
||||
|
||||
* fix(android): atomically updates selection with text (#11188)
|
||||
* (#11178)
|
||||
|
||||
## 17.0.303 beta 2024-04-05
|
||||
|
||||
* fix(windows): decode uri for Package ID and filename (#11152)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.304
|
||||
17.0.307
|
||||
|
|
@ -130,7 +130,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
@Override
|
||||
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
|
||||
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
|
||||
KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM, newSelStart, newSelEnd);
|
||||
KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -170,9 +170,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
|
||||
if (icText != null) {
|
||||
boolean didUpdateText = KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString());
|
||||
int selStart = icText.startOffset + icText.selectionStart;
|
||||
int selEnd = icText.startOffset + icText.selectionEnd;
|
||||
boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM, selStart, selEnd);
|
||||
boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
if (!didUpdateText || !didUpdateSelection)
|
||||
exText = icText;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ final class KMKeyboard extends WebView {
|
|||
return result;
|
||||
}
|
||||
|
||||
protected boolean updateSelectionRange(int selStart, int selEnd) {
|
||||
protected boolean updateSelectionRange() {
|
||||
boolean result = false;
|
||||
InputConnection ic = KMManager.getInputConnection(this.keyboardType);
|
||||
if (ic != null) {
|
||||
|
|
@ -175,6 +175,16 @@ final class KMKeyboard extends WebView {
|
|||
String rawText = icText.text.toString();
|
||||
updateText(rawText.toString());
|
||||
|
||||
int selStart = icText.selectionStart;
|
||||
int selEnd = icText.selectionEnd;
|
||||
|
||||
int selMin = selStart, selMax = selEnd;
|
||||
if (selStart > selEnd) {
|
||||
// Selection is reversed so "swap"
|
||||
selMin = selEnd;
|
||||
selMax = selStart;
|
||||
}
|
||||
|
||||
/*
|
||||
The values of selStart & selEnd provided by the system are in code units,
|
||||
not code-points. We need to account for surrogate pairs here.
|
||||
|
|
@ -193,8 +203,8 @@ final class KMKeyboard extends WebView {
|
|||
|
||||
selStart -= pairsAtStart;
|
||||
selEnd -= (pairsAtStart + pairsSelected);
|
||||
this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selStart, selEnd));
|
||||
}
|
||||
this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selStart, selEnd));
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -2137,23 +2137,40 @@ public final class KMManager {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the active range for selected text.
|
||||
* @deprecated
|
||||
* This method no longer needs the `selStart` and `selEnd` parameters.
|
||||
* <p>Use {@link KMManager#updateSelectionRange(KeyboardType)} instead.</p>
|
||||
*
|
||||
* @param kbType A value indicating if this request is for the in-app keyboard or the system keyboard
|
||||
* @param selStart (deprecated) the start index for the range
|
||||
* @param selEnd (deprecated) the end index for the selected range
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean updateSelectionRange(KeyboardType kbType, int selStart, int selEnd) {
|
||||
return updateSelectionRange(kbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a synchronization check for the active range for selected text,
|
||||
* ensuring it matches the text-editor's current state.
|
||||
* @param kbType A value indicating if this request is for the in-app or system keyboard.
|
||||
* @return
|
||||
*/
|
||||
public static boolean updateSelectionRange(KeyboardType kbType) {
|
||||
boolean result = false;
|
||||
int selMin = selStart, selMax = selEnd;
|
||||
if (selStart > selEnd) {
|
||||
// Selection is reversed so "swap"
|
||||
selMin = selEnd;
|
||||
selMax = selStart;
|
||||
}
|
||||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = InAppKeyboard.updateSelectionRange(selMin, selMax);
|
||||
result = InAppKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
||||
InAppKeyboard.setShouldIgnoreSelectionChange(false);
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = SystemKeyboard.updateSelectionRange(selMin, selMax);
|
||||
result = SystemKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
||||
SystemKeyboard.setShouldIgnoreSelectionChange(false);
|
||||
|
|
|
|||
|
|
@ -63,10 +63,8 @@ public final class KMTextView extends AppCompatEditText {
|
|||
*/
|
||||
public static void updateTextContext() {
|
||||
KMTextView textView = (KMTextView) activeView;
|
||||
int selStart = textView.getSelectionStart();
|
||||
int selEnd = textView.getSelectionEnd();
|
||||
KMManager.updateText(KeyboardType.KEYBOARD_TYPE_INAPP, textView.getText().toString());
|
||||
if (KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) {
|
||||
if (KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +165,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
if (activeView != null && activeView.equals(this)) {
|
||||
if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) {
|
||||
if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,51 +36,53 @@ export class AsyncClosureDispatchQueue {
|
|||
return this.queue.length == 0 && !this.waitLock;
|
||||
}
|
||||
|
||||
private async setWaitLock(promise: Promise<any>) {
|
||||
this.waitLock = promise;
|
||||
private async triggerNextClosure() {
|
||||
if(this.queue.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const functor = this.queue.shift();
|
||||
|
||||
// A stand-in so that `ready` doesn't report true while the closure runs.
|
||||
this.waitLock = Promise.resolve();
|
||||
|
||||
/*
|
||||
It is imperative that any errors triggered by the functor do not prevent this method from setting
|
||||
the wait lock that will trigger the following event (if it exists). Failure to do so will
|
||||
result in all further queued closures never getting the opportunity to run!
|
||||
*/
|
||||
let result: undefined | Promise<any>;
|
||||
try {
|
||||
// Is either undefined (return type: void) or is a Promise.
|
||||
result = functor() as undefined | Promise<any>;
|
||||
/* c8 ignore start */
|
||||
} catch (err) {
|
||||
reportError('Error from queued closure', err);
|
||||
}
|
||||
/* c8 ignore end */
|
||||
|
||||
/*
|
||||
Replace the stand-in with the _true_ post-closure wait.
|
||||
|
||||
If the closure returns a Promise, the implication is that the further processing of queued
|
||||
functions should be blocked until that Promise is fulfilled.
|
||||
|
||||
If not, we just add a default delay.
|
||||
*/
|
||||
result = result ?? this.defaultWaitFactory();
|
||||
this.waitLock = result;
|
||||
|
||||
try {
|
||||
await promise;
|
||||
await result;
|
||||
} catch(err) {
|
||||
reportError('Async error from queued closure', err);
|
||||
}
|
||||
|
||||
this.waitLock = null;
|
||||
// if queue is length zero, auto-returns.
|
||||
this.triggerNextClosure();
|
||||
}
|
||||
|
||||
private async triggerNextClosure() {
|
||||
if(this.queue.length > 0) {
|
||||
const functor = this.queue.shift();
|
||||
|
||||
// A stand-in so that `ready` doesn't report true while the closure runs.
|
||||
this.waitLock = Promise.resolve();
|
||||
|
||||
/*
|
||||
It is imperative that any errors triggered by the functor do not prevent this method from setting
|
||||
the wait lock that will trigger the following event (if it exists). Failure to do so will
|
||||
result in all further queued closures never getting the opportunity to run!
|
||||
*/
|
||||
let result: undefined | Promise<any>;
|
||||
try {
|
||||
// Is either undefined (return type: void) or is a Promise.
|
||||
result = functor() as undefined | Promise<any>;
|
||||
/* c8 ignore start */
|
||||
} catch (err) {
|
||||
reportError('Error from queued closure', err);
|
||||
}
|
||||
/* c8 ignore end */
|
||||
|
||||
/*
|
||||
If the closure returns a Promise, the implication is that the further processing of queued
|
||||
functions should be blocked until that Promise is fulfilled.
|
||||
|
||||
If not, we still add a delay according to the specified default.
|
||||
*/
|
||||
this.setWaitLock(result ?? this.defaultWaitFactory());
|
||||
}
|
||||
}
|
||||
|
||||
runAsync(closure: QueueClosure) {
|
||||
// Check before putting the closure on the internal queue; the check is based in part
|
||||
// upon the existing queue length.
|
||||
|
|
|
|||
|
|
@ -157,14 +157,14 @@ export class GestureMatcher<Type, StateToken = any> implements PredecessorMatch<
|
|||
return;
|
||||
case 'full':
|
||||
contact = srcContact.constructSubview(false, true);
|
||||
this.addContactInternal(contact, srcContact.path.stats);
|
||||
this.addContactInternal(contact, srcContact.path.stats, true);
|
||||
continue;
|
||||
case 'partial':
|
||||
preserveBaseItem = true;
|
||||
// Intentional fall-through
|
||||
case 'chop':
|
||||
contact = srcContact.constructSubview(true, preserveBaseItem);
|
||||
this.addContactInternal(contact, srcContact.path.stats);
|
||||
this.addContactInternal(contact, srcContact.path.stats, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -367,7 +367,7 @@ export class GestureMatcher<Type, StateToken = any> implements PredecessorMatch<
|
|||
return this._result;
|
||||
}
|
||||
|
||||
private addContactInternal(simpleSource: GestureSourceSubview<Type>, basePathStats: CumulativePathStats<Type>) {
|
||||
private addContactInternal(simpleSource: GestureSourceSubview<Type>, basePathStats: CumulativePathStats<Type>, whileInitializing?: boolean) {
|
||||
// The number of already-active contacts tracked for this gesture
|
||||
const existingContacts = this.pathMatchers.length;
|
||||
|
||||
|
|
@ -491,16 +491,22 @@ export class GestureMatcher<Type, StateToken = any> implements PredecessorMatch<
|
|||
const result = contactModel.update();
|
||||
if(result?.type == 'reject') {
|
||||
/*
|
||||
Refer to the earlier comment in this method re: use of 'cancelled'; we need to
|
||||
prevent any and all further attempts to match against this model We'd
|
||||
instantly reject it anyway due to its rejected initial state. Failing to do so
|
||||
can cause an infinite async loop.
|
||||
Refer to the earlier comment in this method re: use of 'cancelled'; we
|
||||
need to prevent any and all further attempts to match against this model
|
||||
We'd instantly reject it anyway due to its rejected initial state.
|
||||
Failing to do so can cause an infinite async loop.
|
||||
|
||||
If we weren't using 'cancelled', 'path' would correspond best with a rejection
|
||||
here, as the decision is made due to the GestureSource's current path being
|
||||
rejected by one of the `PathModel`s comprising the `GestureModel`.
|
||||
If we weren't using 'cancelled', 'path' would correspond best with a
|
||||
rejection here, as the decision is made due to the GestureSource's
|
||||
current path being rejected by one of the `PathModel`s comprising the
|
||||
`GestureModel`.
|
||||
|
||||
If the model's already been initialized, it's possible that a _new_
|
||||
incoming touch needs special handling. We'll allow one reset. In the
|
||||
case that it would try to restart itself, the restarted model will
|
||||
instantly fail and thus cancel.
|
||||
*/
|
||||
this.finalize(false, 'cancelled');
|
||||
this.finalize(false, whileInitializing ? 'cancelled' : 'path');
|
||||
}
|
||||
|
||||
// Standard path: trigger either resolution or rejection when the contact model signals either.
|
||||
|
|
|
|||
|
|
@ -26,17 +26,6 @@ screen where you may view the languages you have installed and, by clicking any
|
|||
|
||||
Click on this to [search for a keyboard or language](../../start/searching-for-keyboards).
|
||||
|
||||
## Show Banner
|
||||
This adds a banner to the top of the keyboard to allow space for pop-up keys to render above the top row of keys.
|
||||
|
||||

|
||||
|
||||
When this is off,
|
||||
|
||||

|
||||
|
||||
pop-up keys in the top row render horizontally.
|
||||
|
||||
## Show "Get Started" on startup
|
||||
When enabled, the Keyman app will display the 'Get Started' screen on app startup.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
title: Basic Help
|
||||
---
|
||||
* [Using the Banner on the Keyboard](using-the-banner)
|
||||
|
||||
* [Switching Between Keyboards](switching-between-keyboards)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,5 +22,7 @@ The Gestures:
|
|||
|
||||
* **Flick:** hold and slide in various directions on a key to reach alternate outputs. The key will animate to show you the expected output. For example, a keyboard could support sliding down on keys in the top row to output numerals.
|
||||
|
||||

|
||||
|
||||
* **Multitap:** some keys produce alternate outputs with rapid, repeated taps. For example, tapping rapidly twice on <kbd>shift</kbd> will activate <kbd>caps lock</kbd> on many keyboards.
|
||||
|
||||
|
|
|
|||
29
ios/help/basic/using-the-banner.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: Using the Banner on the Keyboard - Keyman for iPhone and iPad Help
|
||||
---
|
||||
|
||||
## About the Keyboard Banner
|
||||
|
||||
Keyman keyboards now always display a banner above the keyboard for one of the following functionalities:
|
||||
|
||||
* Display suggestions (See "Using the Suggestion Banner" below)
|
||||
* Display a Keyman-themed banner so popups and gestures for the top row of keys are visible
|
||||
* Reserved for future functionality
|
||||
|
||||
## Using the Suggestion Banner
|
||||
|
||||
If a [dictionary is installed](installing-custom-keyboards-dictionaries) and enabled for the active Keyman
|
||||
keyboard, the banner will display suggestions that can be selected.
|
||||
|
||||

|
||||
|
||||
* Drag the banner horizontally to see more suggestions
|
||||
* Overly-long suggestions are partially hidden, but expand when a finger is held on them
|
||||
* The banner will display up to 8 suggestions at a time.
|
||||
|
||||
## The Keyman-Themed Banner
|
||||
|
||||
When suggestions are disabled or unavailable, the Keyman-themed banner will display instead.
|
||||
This is displayed so that key-popups and gestures for the top row of keys are visible.
|
||||
|
||||

|
||||
|
|
@ -15,6 +15,7 @@ title: Keyman for iPhone and iPad 17.0 Help
|
|||
|
||||
|
||||
## [Using Keyman for iPhone and iPad](basic/)
|
||||
* [Using the Banner on the Keyboard](basic/using-the-banner)
|
||||
* [Switching Between Keyboards](basic/switching-between-keyboards)
|
||||
* [Using a Keyboard](basic/keyboard-usage)
|
||||
* [Using the Menu](context/)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 60 KiB |
BIN
ios/help/ios_images/down-flick-i.gif
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 21 KiB |
BIN
ios/help/ios_images/settings-suggestions-i.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
BIN
ios/help/ios_images/themed-banner.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
|
|
@ -11,11 +11,11 @@ fv,fv_uwikala,'Uwik̓ala,BC Coast,fv_uwikala_kmw-9.0.js,9.3,hei,Heiltsuk
|
|||
fv,fv_dexwlesucid,Dəxʷləšucid,BC Coast,fv_dexwlesucid_kmw-9.0.js,9.2.1,lut-Latn,Lushootseed (Latin)
|
||||
fv,fv_diitiidatx,Diidiitidq,BC Coast,fv_diitiidatx_kmw-9.0.js,9.1.3,nuk-Latn,Nuu-chah-nulth (Latin)
|
||||
fv,fv_gitsenimx,Gitsenimx̱,BC Coast,fv_gitsenimx_kmw-9.0.js,10.0.1,git,Gitxsan (Latin)
|
||||
fv,fv_hailzaqvla,Haiɫzaqvla,BC Coast,fv_hailzaqvla_kmw-9.0.js,9.5,hei,Heiltsuk (Latin)
|
||||
fv,fv_haisla,Haisla,BC Coast,fv_haisla.js,1.0,has-Latn,Haisla (Latin)
|
||||
fv,fv_hailzaqvla,Haiɫzaqvla,BC Coast,fv_hailzaqvla_kmw-9.0.js,9.5.1,hei,Heiltsuk (Latin)
|
||||
fv,fv_haisla,Haisla,BC Coast,fv_haisla.js,2.0.1,has-Latn,Haisla (Latin)
|
||||
fv,fv_halqemeylem,Halq'eméylem,BC Coast,fv_halqemeylem_kmw-9.0.js,9.1.3,hur-Latn,Halkomelem (Latin)
|
||||
fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,9.1.3,hur-Latn,Halkomelem (Latin)
|
||||
fv,fv_klahoose,Homalco-Klahoose-Sliammon,BC Coast,fv_klahoose_kmw-9.0.js,10.0,coo,Comox
|
||||
fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,10.0.1,hur-Latn,Halkomelem (Latin)
|
||||
fv,fv_klahoose,Homalco-Klahoose-Sliammon,BC Coast,fv_klahoose_kmw-9.0.js,10.1,coo,Comox
|
||||
fv,fv_hulquminum,Hul’q’umi’num’,BC Coast,fv_hulquminum_kmw-9.0.js,9.1,hur-Latn,Halkomelem (Latin)
|
||||
fv,fv_hulquminum_combine,Hul̓q̓umin̓um̓,BC Coast,fv_hulquminum_combine_kmw-9.0.js,1.0,hur-Latn,Halkomelem (Latin)
|
||||
fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.2.5,kwk-Latn,Kwakiutl (Latin)
|
||||
|
|
@ -25,8 +25,9 @@ fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.1.2,ncg-Latn,Nisga'a (Lat
|
|||
fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.1.4,nuk-Latn,Nuu-chah-nulth (Latin)
|
||||
fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,10.0,blc-Latn,Bella Coola (Latin)
|
||||
fv,fv_sencoten,SENĆOŦEN,BC Coast,fv_sencoten_kmw-9.0.js,9.2.1,str,Straits Salish
|
||||
fv,fv_sguuxs,Sgüüx̱s,BC Coast,fv_sguuxs.js,1.0,tsi,Tsimshian
|
||||
fv,fv_shashishalhem,Shashishalhem,BC Coast,fv_shashishalhem_kmw-9.0.js,9.1.3,sec-Latn,Sechelt (Latin)
|
||||
fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,9.2.1,squ-Latn,Squamish (Latin)
|
||||
fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,9.3,squ-Latn,Squamish (Latin)
|
||||
fv,fv_smalgyax,Sm'algya̱x,BC Coast,fv_smalgyax_kmw-9.0.js,9.1.3,tsi-Latn,Tsimshian (Latin)
|
||||
fv,fv_xaislakala,X̄a'ʼislak̓ala,BC Coast,fv_xaislakala_kmw-9.0.js,9.1.1,has-Latn,Haisla (Latin)
|
||||
fv,fv_hlgaagilda_xaayda_kil,X̱aayda-X̱aad Kil,BC Coast,fv_hlgaagilda_xaayda_kil_kmw-9.0.js,9.3,hax,Southern Haida
|
||||
|
|
@ -37,13 +38,13 @@ fv,fv_natwits,Nedut’en-Witsuwit'en,BC Interior,fv_natwits_kmw-9.0.js,9.1.3,caf
|
|||
fv,fv_nlekepmxcin,Nłeʔkepmxcin,BC Interior,fv_nlekepmxcin_kmw-9.0.js,9.2.3,thp-Latn,Thompson (Latin)
|
||||
fv,fv_nlha7kapmxtsin,Nlha7kapmxtsin,BC Interior,fv_nlha7kapmxtsin_kmw-9.0.js,9.1.1,thp-Latn,Thompson (Latin)
|
||||
fv,fv_nsilxcen,Nsilxcən,BC Interior,fv_nsilxcen_kmw-9.0.js,9.3,oka,Okanagan
|
||||
fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.1.2,shs-Latn,Shuswap (Latin)
|
||||
fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.2,shs-Latn,Shuswap (Latin)
|
||||
fv,fv_stlatlimxec,Sƛ̓aƛ̓imxəc,BC Interior,fv_stlatlimxec_kmw-9.0.js,9.2.3,lil-Latn,Lillooet (Latin)
|
||||
fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.1.3,lil-Latn,Lillooet (Latin)
|
||||
fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.1.4,lil-Latn,Lillooet (Latin)
|
||||
fv,fv_taltan,Tāłtān,BC Interior,fv_taltan_kmw-9.0.js,9.1.5,tht-Latn,Tahltan (Latin)
|
||||
fv,fv_tsekehne,Tsek'ehne,BC Interior,fv_tsekehne_kmw-9.0.js,9.1.2,sek-Latn,Sekani (Latin)
|
||||
fv,fv_tsilhqotin,Tŝilhqot'in,BC Interior,fv_tsilhqotin_kmw-9.0.js,9.1.3,clc-Latn,Chilcotin (Latin)
|
||||
fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,9.2.1,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics)
|
||||
fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,10.0,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics)
|
||||
fv,fv_anicinapemi8in,Anicinapemi8in/Anishinàbemiwin,Eastern Subarctic,fv_anicinapemi8in_kmw-9.0.js,9.1.1,alq-Latn,Algonquin (Latin)
|
||||
fv,fv_atikamekw,Atikamekw,Eastern Subarctic,fv_atikamekw_kmw-9.0.js,9.1.1,atj-Latn,Atikamekw (Latin)
|
||||
fv,fv_ilnu_innu_aimun,Ilnu-Innu Aimun,Eastern Subarctic,fv_ilnu_innu_aimun_kmw-9.0.js,9.1.1,moe-Latn,Montagnais (Latin)
|
||||
|
|
@ -69,14 +70,14 @@ fv,fv_wobanakiodwawogan,Wôbanakiôdwawôgan,Great Lakes - St. Lawrence,fv_woban
|
|||
fv,fv_australian,Australian,Pacific,fv_australian_kmw-9.0.js,9.3.1,pjt-Latn,Pitjantjatjara (Latin)
|
||||
fv,fv_maori,Māori,Pacific,fv_maori_kmw-9.0.js,9.1.1,mi-Latn,Maori (Latin)
|
||||
fv,fv_blackfoot,Blackfoot,Prairies,fv_blackfoot_kmw-9.0.js,9.2.1,bla-Latn,Siksika (Latin)
|
||||
fv,fv_cree_latin,Cree - Roman Orthography,Prairies,fv_cree_latin_kmw-9.0.js,10.0,cr-Latn,Cree (Latin)
|
||||
fv,fv_cree_latin,Cree - Roman Orthography,Prairies,fv_cree_latin_kmw-9.0.js,10.0.1,cr-Latn,Cree (Latin)
|
||||
fv,fv_dakota_mb,Dakota,Prairies,fv_dakota_mb_kmw-9.0.js,9.1.1,dak-Latn,Dakota (Latin)
|
||||
fv,fv_dakota_sk,Dakot̄a,Prairies,fV_dakota_sk_kmw-9.0.js,9.1.1,dak-Latn,Dakota (Latin)
|
||||
fv,fv_isga_iabi,Isga Iʔabi,Prairies,fv_isga_iabi_kmw-9.0.js,9.1.1,sto-Latn,Stoney (Latin)
|
||||
fv,fv_lakota,Lak̇ot̄a,Prairies,fv_lakota-9.0.js,9.1.1,lkt-Latn,Lakota (Latin)
|
||||
fv,fv_nakoda,Nakoda,Prairies,fv_nakoda_kmw-9.0.js,9.1.1,asb-Latn,Assiniboine (Latin)
|
||||
fv,fv_tsuutina,Tsúùt'ínà,Prairies,fv_tsuutina_kmw-9.0.js,9.1.1,srs-Latn,Sarsi (Latin)
|
||||
fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,10.0.2,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics)
|
||||
fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,11.0,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics)
|
||||
fv,fv_dine_bizaad,Diné Bizaad,South West,fv_dine_bizaad_kmw-9.0.js,9.1.1,nv-Latn,Navajo (Latin)
|
||||
fv,fv_dane_zaa_zaage,Dane-Z̲aa Z̲áágéʔ,Western Subarctic,fv_dane_zaa_zaage_kmw-9.0.js,9.3,bea,Beaver
|
||||
fv,fv_dene_dzage,Dene Dzage,Western Subarctic,fv_dene_dzage_kmw-9.0.js,9.1.1,kkz-Latn,Kaska (Latin)
|
||||
|
|
|
|||
|
|
|
@ -4,6 +4,11 @@ import OSKRow from './oskRow.js';
|
|||
import OSKBaseKey from './oskBaseKey.js';
|
||||
import VisualKeyboard from '../visualKeyboard.js';
|
||||
|
||||
export interface LayerLayoutParams {
|
||||
keyboardHeight: number;
|
||||
spacebarText: string;
|
||||
}
|
||||
|
||||
export default class OSKLayer {
|
||||
public readonly element: HTMLDivElement;
|
||||
public readonly rows: OSKRow[];
|
||||
|
|
@ -76,6 +81,21 @@ export default class OSKLayer {
|
|||
this.capsKey = this.findKey('K_CAPS');
|
||||
this.numKey = this.findKey('K_NUMLOCK');
|
||||
this.scrollKey = this.findKey('K_SCROLL');
|
||||
|
||||
if(this.spaceBarKey) {
|
||||
const spacebarLabel = this.spaceBarKey.label;
|
||||
let tButton = this.spaceBarKey.btn;
|
||||
|
||||
if (typeof (tButton.className) == 'undefined' || tButton.className == '') {
|
||||
tButton.className = 'kmw-spacebar';
|
||||
} else if (tButton.className.indexOf('kmw-spacebar') == -1) {
|
||||
tButton.className += ' kmw-spacebar';
|
||||
}
|
||||
|
||||
if (spacebarLabel.className != 'kmw-spacebar-caption') {
|
||||
spacebarLabel.className = 'kmw-spacebar-caption';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,7 +116,35 @@ export default class OSKLayer {
|
|||
return null;
|
||||
}
|
||||
|
||||
public refreshLayout(vkbd: VisualKeyboard, layerHeight: number) {
|
||||
/**
|
||||
* Indicate the current language and keyboard on the space bar
|
||||
**/
|
||||
showLanguage(displayName: string) {
|
||||
if(!this.spaceBarKey) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
try {
|
||||
const spacebarLabel = this.spaceBarKey.label;
|
||||
|
||||
// The key can read the text from here during the display update without us
|
||||
// needing to trigger a reflow by running the closure below early.
|
||||
this.spaceBarKey.spec.text = displayName;
|
||||
|
||||
// It sounds redundant, but this dramatically cuts down on browser DOM processing;
|
||||
// but sometimes innerText is reported empty when it actually isn't, so set it
|
||||
// anyway in that case (Safari, iOS 14.4)
|
||||
if (spacebarLabel.innerText != displayName || displayName == '') {
|
||||
spacebarLabel.innerText = displayName;
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
public refreshLayout(vkbd: VisualKeyboard, layoutParams: LayerLayoutParams) {
|
||||
const layerHeight = layoutParams.keyboardHeight;
|
||||
this.showLanguage(layoutParams.spacebarText);
|
||||
|
||||
// Check the heights of each row, in case different layers have different row counts.
|
||||
const nRows = this.rows.length;
|
||||
const rowHeight = this._rowHeight = Math.floor(layerHeight/(nRows == 0 ? 1 : nRows));
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export default class OSKLayerGroup {
|
|||
Assumes there is no fine-tuning of the row ranges to be done - each takes a perfect
|
||||
fraction of the overall layer height without any padding above or below.
|
||||
*/
|
||||
const rowIndex = Math.floor(proportionalCoords.y * layer.rows.length);
|
||||
const rowIndex = Math.max(0, Math.min(layer.rows.length-1, Math.floor(proportionalCoords.y * layer.rows.length)));
|
||||
const row = layer.rows[rowIndex];
|
||||
|
||||
// Assertion: row no longer `null`.
|
||||
|
|
|
|||
|
|
@ -658,6 +658,7 @@ export default abstract class OSKView
|
|||
if(this.bannerView.height > 0) {
|
||||
availableHeight -= this.bannerView.height + 5;
|
||||
}
|
||||
// Triggers the VisualKeyboard.refreshLayout() method, which includes a showLanguage() call.
|
||||
this.vkbd.setSize(this.computedWidth, availableHeight, pending);
|
||||
|
||||
const bs = this._Box.style;
|
||||
|
|
@ -665,9 +666,6 @@ export default abstract class OSKView
|
|||
// visualizations, not to help text or empty views.
|
||||
bs.width = bs.maxWidth = this.computedWidth + 'px';
|
||||
bs.height = bs.maxHeight = this.computedHeight + 'px';
|
||||
|
||||
// Ensure that the layer's spacebar is properly captioned.
|
||||
this.vkbd.showLanguage();
|
||||
} else {
|
||||
const bs = this._Box.style;
|
||||
bs.width = 'auto';
|
||||
|
|
@ -855,9 +853,8 @@ export default abstract class OSKView
|
|||
// Also, only change the layer ID itself if there is an actual corresponding layer
|
||||
// in the OSK.
|
||||
if(this.vkbd?.layerGroup.layers[newValue] && !this.vkbd?.layerLocked) {
|
||||
// triggers state-update + layer refresh automatically.
|
||||
this.vkbd.layerId = newValue;
|
||||
// Ensure that the layer's spacebar is properly captioned.
|
||||
this.vkbd.showLanguage();
|
||||
}
|
||||
|
||||
// Ensure the keyboard view is modeling the correct state. (Correct layer, etc.)
|
||||
|
|
@ -892,10 +889,6 @@ export default abstract class OSKView
|
|||
// First thing after it's made visible.
|
||||
this.refreshLayoutIfNeeded();
|
||||
|
||||
if(this.keyboardView instanceof VisualKeyboard) {
|
||||
this.keyboardView.showLanguage();
|
||||
}
|
||||
|
||||
this._Visible=true;
|
||||
|
||||
/* In case it's still '0' from a hide() operation.
|
||||
|
|
|
|||
|
|
@ -1115,38 +1115,6 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
|
|||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Indicate the current language and keyboard on the space bar
|
||||
**/
|
||||
showLanguage() {
|
||||
let activeStub = this.layoutKeyboardProperties;
|
||||
let displayName: string = activeStub?.displayName ?? '(System keyboard)';
|
||||
|
||||
try {
|
||||
var t = <HTMLElement>this.spaceBar.key.label;
|
||||
let tParent = <HTMLElement>t.parentNode;
|
||||
if (typeof (tParent.className) == 'undefined' || tParent.className == '') {
|
||||
tParent.className = 'kmw-spacebar';
|
||||
} else if (tParent.className.indexOf('kmw-spacebar') == -1) {
|
||||
tParent.className += ' kmw-spacebar';
|
||||
}
|
||||
|
||||
if (t.className != 'kmw-spacebar-caption') {
|
||||
t.className = 'kmw-spacebar-caption';
|
||||
}
|
||||
|
||||
// It sounds redundant, but this dramatically cuts down on browser DOM processing;
|
||||
// but sometimes innerText is reported empty when it actually isn't, so set it
|
||||
// anyway in that case (Safari, iOS 14.4)
|
||||
if (t.innerText != displayName || displayName == '') {
|
||||
t.innerText = displayName;
|
||||
}
|
||||
|
||||
this.spaceBar.key.refreshLayout(this);
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or remove a class from a keyboard key (when touched or clicked)
|
||||
* or add a key preview for phone devices
|
||||
|
|
@ -1269,7 +1237,6 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
|
|||
const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto';
|
||||
|
||||
// Step 2: determine basic layout geometry, refresh things that might update.
|
||||
this.showLanguage(); // In case the spacebar-text mode setting has changed.
|
||||
|
||||
if (fixedSize) {
|
||||
this._computedWidth = this.width;
|
||||
|
|
@ -1307,7 +1274,10 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
|
|||
// Step 4: perform layout operations.
|
||||
// Needs the refreshed layout info to work correctly.
|
||||
if(this.currentLayer) {
|
||||
this.currentLayer.refreshLayout(this, this._computedHeight - this.getVerticalLayerGroupPadding());
|
||||
this.currentLayer.refreshLayout(this, {
|
||||
keyboardHeight: this._computedHeight - this.getVerticalLayerGroupPadding(),
|
||||
spacebarText: this.layoutKeyboardProperties?.displayName ?? '(System keyboard)'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||