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. - -![](../../ios_images/banner.png) - -When this is off, - -![](../../ios_images/no-banner.png) - -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. +![](../ios_images/down-flick-i.gif) + * **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. + +![](../ios_images/settings-suggestions-i.png) + +* 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. + +![](../ios_images/themed-banner.png) \ 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)' + }); } }