diff --git a/HISTORY.md b/HISTORY.md
index 1aa044f802..bde9737812 100644
--- a/HISTORY.md
+++ b/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)
diff --git a/VERSION.md b/VERSION.md
index 29d39b7194..a87bdfadd9 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-17.0.304
\ No newline at end of file
+17.0.307
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
index e9cab39752..85c14dfcb9 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
@@ -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;
}
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java
index bdd0f4f65c..a356a20ad5 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java
@@ -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;
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
index c113ab313d..b31fe42506 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
@@ -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.
+ *
Use {@link KMManager#updateSelectionRange(KeyboardType)} instead.
+ *
+ * @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);
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java
index 70774a4e2c..3c5959fc9d 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java
@@ -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);
}
}
diff --git a/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts b/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts
index bc91297534..a15265cb92 100644
--- a/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts
+++ b/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts
@@ -36,51 +36,53 @@ export class AsyncClosureDispatchQueue {
return this.queue.length == 0 && !this.waitLock;
}
- private async setWaitLock(promise: Promise) {
- 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;
+ try {
+ // Is either undefined (return type: void) or is a Promise.
+ result = functor() as undefined | Promise;
+ /* 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;
- try {
- // Is either undefined (return type: void) or is a Promise.
- result = functor() as undefined | Promise;
- /* 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.
diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts
index 4257ec8c25..8a3c14fc6e 100644
--- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts
+++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts
@@ -157,14 +157,14 @@ export class GestureMatcher 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 implements PredecessorMatch<
return this._result;
}
- private addContactInternal(simpleSource: GestureSourceSubview, basePathStats: CumulativePathStats) {
+ private addContactInternal(simpleSource: GestureSourceSubview, basePathStats: CumulativePathStats, whileInitializing?: boolean) {
// The number of already-active contacts tracked for this gesture
const existingContacts = this.pathMatchers.length;
@@ -491,16 +491,22 @@ export class GestureMatcher 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.
diff --git a/ios/help/basic/config/index.md b/ios/help/basic/config/index.md
index 22468a4021..64ea47eac5 100644
--- a/ios/help/basic/config/index.md
+++ b/ios/help/basic/config/index.md
@@ -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.
diff --git a/ios/help/basic/index.md b/ios/help/basic/index.md
index e2c9e82d6d..b67688e03c 100644
--- a/ios/help/basic/index.md
+++ b/ios/help/basic/index.md
@@ -1,6 +1,7 @@
---
title: Basic Help
---
+* [Using the Banner on the Keyboard](using-the-banner)
* [Switching Between Keyboards](switching-between-keyboards)
diff --git a/ios/help/basic/keyboard-usage.md b/ios/help/basic/keyboard-usage.md
index e56b56ae4b..f7834a4e7a 100644
--- a/ios/help/basic/keyboard-usage.md
+++ b/ios/help/basic/keyboard-usage.md
@@ -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 shift will activate caps lock on many keyboards.
diff --git a/ios/help/basic/using-the-banner.md b/ios/help/basic/using-the-banner.md
new file mode 100644
index 0000000000..dc1edf2479
--- /dev/null
+++ b/ios/help/basic/using-the-banner.md
@@ -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.
+
+
\ No newline at end of file
diff --git a/ios/help/index.md b/ios/help/index.md
index 739195dd6e..1bee2a62c9 100644
--- a/ios/help/index.md
+++ b/ios/help/index.md
@@ -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/)
diff --git a/ios/help/ios_images/banner.png b/ios/help/ios_images/banner.png
deleted file mode 100644
index e7d38e8a2e..0000000000
Binary files a/ios/help/ios_images/banner.png and /dev/null differ
diff --git a/ios/help/ios_images/dist-install1-i.png b/ios/help/ios_images/dist-install1-i.png
index e4632eafc9..4871c99ea5 100644
Binary files a/ios/help/ios_images/dist-install1-i.png and b/ios/help/ios_images/dist-install1-i.png differ
diff --git a/ios/help/ios_images/down-flick-i.gif b/ios/help/ios_images/down-flick-i.gif
new file mode 100644
index 0000000000..35734c3d15
Binary files /dev/null and b/ios/help/ios_images/down-flick-i.gif differ
diff --git a/ios/help/ios_images/key-submenu.png b/ios/help/ios_images/key-submenu.png
index faef6f5e8f..58b029ca92 100644
Binary files a/ios/help/ios_images/key-submenu.png and b/ios/help/ios_images/key-submenu.png differ
diff --git a/ios/help/ios_images/lang-list.png b/ios/help/ios_images/lang-list.png
deleted file mode 100644
index a4f966467e..0000000000
Binary files a/ios/help/ios_images/lang-list.png and /dev/null differ
diff --git a/ios/help/ios_images/language-settings.png b/ios/help/ios_images/language-settings.png
index 1cea1ffcc1..80f4a5ea63 100644
Binary files a/ios/help/ios_images/language-settings.png and b/ios/help/ios_images/language-settings.png differ
diff --git a/ios/help/ios_images/no-banner.png b/ios/help/ios_images/no-banner.png
deleted file mode 100644
index 68a19f67f6..0000000000
Binary files a/ios/help/ios_images/no-banner.png and /dev/null differ
diff --git a/ios/help/ios_images/settings-suggestions-i.png b/ios/help/ios_images/settings-suggestions-i.png
new file mode 100644
index 0000000000..7018a16bbd
Binary files /dev/null and b/ios/help/ios_images/settings-suggestions-i.png differ
diff --git a/ios/help/ios_images/system-keyboard-cal.png b/ios/help/ios_images/system-keyboard-cal.png
index 6c60f41a74..49259f7938 100644
Binary files a/ios/help/ios_images/system-keyboard-cal.png and b/ios/help/ios_images/system-keyboard-cal.png differ
diff --git a/ios/help/ios_images/system-picker-i.png b/ios/help/ios_images/system-picker-i.png
index b7e1d7696f..f9d3c1fac7 100644
Binary files a/ios/help/ios_images/system-picker-i.png and b/ios/help/ios_images/system-picker-i.png differ
diff --git a/ios/help/ios_images/themed-banner.png b/ios/help/ios_images/themed-banner.png
new file mode 100644
index 0000000000..a8fd5e2daa
Binary files /dev/null and b/ios/help/ios_images/themed-banner.png differ
diff --git a/oem/firstvoices/keyboards.csv b/oem/firstvoices/keyboards.csv
index e11dfab50b..1bc76fd58e 100644
--- a/oem/firstvoices/keyboards.csv
+++ b/oem/firstvoices/keyboards.csv
@@ -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)
diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts
index 9f9486e28e..3f73620949 100644
--- a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts
+++ b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts
@@ -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));
diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts
index 1803269ef7..589ca96f8c 100644
--- a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts
+++ b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts
@@ -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`.
diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts
index 760a664e12..e7c6ecdd0d 100644
--- a/web/src/engine/osk/src/views/oskView.ts
+++ b/web/src/engine/osk/src/views/oskView.ts
@@ -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.
diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts
index bb90538f29..89253ea8f5 100644
--- a/web/src/engine/osk/src/visualKeyboard.ts
+++ b/web/src/engine/osk/src/visualKeyboard.ts
@@ -1115,38 +1115,6 @@ export default class VisualKeyboard extends EventEmitter 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 = this.spaceBar.key.label;
- let tParent = 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 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 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)'
+ });
}
}