@trustquery/browser 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3002,6 +3002,247 @@ class ValidationStateManager {
3002
3002
  }
3003
3003
  }
3004
3004
 
3005
+ // MobileKeyboardHandler - Handles mobile virtual keyboard behavior
3006
+ // Detects keyboard appearance and adjusts layout to keep textarea visible
3007
+
3008
+ class MobileKeyboardHandler {
3009
+ /**
3010
+ * Create mobile keyboard handler
3011
+ * @param {Object} options - Configuration options
3012
+ */
3013
+ constructor(options = {}) {
3014
+ this.options = {
3015
+ textarea: options.textarea || null,
3016
+ wrapper: options.wrapper || null,
3017
+ debug: options.debug || false,
3018
+ ...options
3019
+ };
3020
+
3021
+ this.isKeyboardVisible = false;
3022
+ this.lastViewportHeight = window.innerHeight;
3023
+ this.visualViewport = window.visualViewport;
3024
+
3025
+ if (this.options.debug) {
3026
+ console.log('[MobileKeyboardHandler] Initialized');
3027
+ }
3028
+ }
3029
+
3030
+ /**
3031
+ * Initialize keyboard detection
3032
+ */
3033
+ init() {
3034
+ if (!this.options.textarea) {
3035
+ console.warn('[MobileKeyboardHandler] No textarea provided');
3036
+ return;
3037
+ }
3038
+
3039
+ // Use Visual Viewport API if available (preferred method)
3040
+ if (this.visualViewport) {
3041
+ this.visualViewport.addEventListener('resize', this.handleViewportResize);
3042
+ this.visualViewport.addEventListener('scroll', this.handleViewportScroll);
3043
+
3044
+ if (this.options.debug) {
3045
+ console.log('[MobileKeyboardHandler] Using Visual Viewport API');
3046
+ }
3047
+ } else {
3048
+ // Fallback to window resize
3049
+ window.addEventListener('resize', this.handleWindowResize);
3050
+
3051
+ if (this.options.debug) {
3052
+ console.log('[MobileKeyboardHandler] Using window resize fallback');
3053
+ }
3054
+ }
3055
+
3056
+ // Handle focus events
3057
+ this.options.textarea.addEventListener('focus', this.handleFocus);
3058
+ this.options.textarea.addEventListener('blur', this.handleBlur);
3059
+ }
3060
+
3061
+ /**
3062
+ * Handle Visual Viewport resize (keyboard appearance/disappearance)
3063
+ */
3064
+ handleViewportResize = () => {
3065
+ if (!this.visualViewport) return;
3066
+
3067
+ const viewportHeight = this.visualViewport.height;
3068
+ const windowHeight = window.innerHeight;
3069
+
3070
+ if (this.options.debug) {
3071
+ console.log('[MobileKeyboardHandler] Viewport resize:', {
3072
+ viewportHeight,
3073
+ windowHeight,
3074
+ scale: this.visualViewport.scale
3075
+ });
3076
+ }
3077
+
3078
+ // Keyboard is visible if viewport height is significantly smaller than window height
3079
+ const wasKeyboardVisible = this.isKeyboardVisible;
3080
+ this.isKeyboardVisible = viewportHeight < windowHeight * 0.75;
3081
+
3082
+ if (this.isKeyboardVisible !== wasKeyboardVisible) {
3083
+ if (this.isKeyboardVisible) {
3084
+ this.onKeyboardShow();
3085
+ } else {
3086
+ this.onKeyboardHide();
3087
+ }
3088
+ }
3089
+
3090
+ // Always adjust layout when viewport changes
3091
+ if (this.isKeyboardVisible) {
3092
+ this.adjustLayout();
3093
+ }
3094
+ };
3095
+
3096
+ /**
3097
+ * Handle Visual Viewport scroll
3098
+ */
3099
+ handleViewportScroll = () => {
3100
+ if (this.isKeyboardVisible) {
3101
+ // Ensure textarea stays in view during scroll
3102
+ this.ensureTextareaVisible();
3103
+ }
3104
+ };
3105
+
3106
+ /**
3107
+ * Handle window resize (fallback)
3108
+ */
3109
+ handleWindowResize = () => {
3110
+ const currentHeight = window.innerHeight;
3111
+ const heightDifference = this.lastViewportHeight - currentHeight;
3112
+
3113
+ if (this.options.debug) {
3114
+ console.log('[MobileKeyboardHandler] Window resize:', {
3115
+ lastHeight: this.lastViewportHeight,
3116
+ currentHeight,
3117
+ difference: heightDifference
3118
+ });
3119
+ }
3120
+
3121
+ // Significant decrease in height suggests keyboard appeared
3122
+ if (heightDifference > 150) {
3123
+ if (!this.isKeyboardVisible) {
3124
+ this.isKeyboardVisible = true;
3125
+ this.onKeyboardShow();
3126
+ }
3127
+ }
3128
+ // Significant increase suggests keyboard hidden
3129
+ else if (heightDifference < -150) {
3130
+ if (this.isKeyboardVisible) {
3131
+ this.isKeyboardVisible = false;
3132
+ this.onKeyboardHide();
3133
+ }
3134
+ }
3135
+
3136
+ this.lastViewportHeight = currentHeight;
3137
+ };
3138
+
3139
+ /**
3140
+ * Handle textarea focus
3141
+ */
3142
+ handleFocus = () => {
3143
+ if (this.options.debug) {
3144
+ console.log('[MobileKeyboardHandler] Textarea focused');
3145
+ }
3146
+
3147
+ // Delay to allow keyboard to appear
3148
+ setTimeout(() => {
3149
+ this.ensureTextareaVisible();
3150
+ }, 300);
3151
+ };
3152
+
3153
+ /**
3154
+ * Handle textarea blur
3155
+ */
3156
+ handleBlur = () => {
3157
+ if (this.options.debug) {
3158
+ console.log('[MobileKeyboardHandler] Textarea blurred');
3159
+ }
3160
+ };
3161
+
3162
+ /**
3163
+ * Called when keyboard appears
3164
+ */
3165
+ onKeyboardShow() {
3166
+ if (this.options.debug) {
3167
+ console.log('[MobileKeyboardHandler] Keyboard shown');
3168
+ }
3169
+
3170
+ this.adjustLayout();
3171
+ this.ensureTextareaVisible();
3172
+ }
3173
+
3174
+ /**
3175
+ * Called when keyboard hides
3176
+ */
3177
+ onKeyboardHide() {
3178
+ if (this.options.debug) {
3179
+ console.log('[MobileKeyboardHandler] Keyboard hidden');
3180
+ }
3181
+
3182
+ // Reset wrapper height to auto
3183
+ if (this.options.wrapper) {
3184
+ this.options.wrapper.style.maxHeight = '';
3185
+ }
3186
+ }
3187
+
3188
+ /**
3189
+ * Adjust layout to accommodate keyboard
3190
+ */
3191
+ adjustLayout() {
3192
+ if (!this.visualViewport || !this.options.wrapper) return;
3193
+
3194
+ const viewportHeight = this.visualViewport.height;
3195
+
3196
+ // Set wrapper max-height to visible viewport height minus some padding
3197
+ const maxHeight = viewportHeight - 20; // 20px padding
3198
+ this.options.wrapper.style.maxHeight = `${maxHeight}px`;
3199
+ this.options.wrapper.style.overflow = 'auto';
3200
+
3201
+ if (this.options.debug) {
3202
+ console.log('[MobileKeyboardHandler] Adjusted wrapper height:', maxHeight);
3203
+ }
3204
+ }
3205
+
3206
+ /**
3207
+ * Ensure textarea is visible above the keyboard
3208
+ */
3209
+ ensureTextareaVisible() {
3210
+ if (!this.options.textarea) return;
3211
+
3212
+ // Scroll textarea into view
3213
+ this.options.textarea.scrollIntoView({
3214
+ behavior: 'smooth',
3215
+ block: 'center',
3216
+ inline: 'nearest'
3217
+ });
3218
+
3219
+ if (this.options.debug) {
3220
+ console.log('[MobileKeyboardHandler] Scrolled textarea into view');
3221
+ }
3222
+ }
3223
+
3224
+ /**
3225
+ * Cleanup event listeners
3226
+ */
3227
+ destroy() {
3228
+ if (this.visualViewport) {
3229
+ this.visualViewport.removeEventListener('resize', this.handleViewportResize);
3230
+ this.visualViewport.removeEventListener('scroll', this.handleViewportScroll);
3231
+ } else {
3232
+ window.removeEventListener('resize', this.handleWindowResize);
3233
+ }
3234
+
3235
+ if (this.options.textarea) {
3236
+ this.options.textarea.removeEventListener('focus', this.handleFocus);
3237
+ this.options.textarea.removeEventListener('blur', this.handleBlur);
3238
+ }
3239
+
3240
+ if (this.options.debug) {
3241
+ console.log('[MobileKeyboardHandler] Destroyed');
3242
+ }
3243
+ }
3244
+ }
3245
+
3005
3246
  // TrustQuery - Lightweight library to make textareas interactive
3006
3247
  // Turns matching words into interactive elements with hover bubbles and click actions
3007
3248
 
@@ -3209,6 +3450,17 @@ class TrustQuery {
3209
3450
  console.log('[TrustQuery] AutoGrow feature enabled');
3210
3451
  }
3211
3452
 
3453
+ // Mobile keyboard handler (enabled by default, can be disabled via options)
3454
+ if (this.options.mobileKeyboard !== false) {
3455
+ this.features.mobileKeyboard = new MobileKeyboardHandler({
3456
+ textarea: this.textarea,
3457
+ wrapper: this.wrapper,
3458
+ debug: this.options.debug
3459
+ });
3460
+ this.features.mobileKeyboard.init();
3461
+ console.log('[TrustQuery] Mobile keyboard handler enabled');
3462
+ }
3463
+
3212
3464
  // Debug logging feature
3213
3465
  if (this.options.debug) {
3214
3466
  this.enableDebugLogging();
@@ -3499,6 +3751,16 @@ class TrustQuery {
3499
3751
  this.interactionHandler.destroy();
3500
3752
  }
3501
3753
 
3754
+ // Cleanup mobile keyboard handler
3755
+ if (this.features.mobileKeyboard) {
3756
+ this.features.mobileKeyboard.destroy();
3757
+ }
3758
+
3759
+ // Cleanup auto-grow
3760
+ if (this.features.autoGrow) {
3761
+ this.features.autoGrow.destroy();
3762
+ }
3763
+
3502
3764
  // Unwrap textarea
3503
3765
  const parent = this.wrapper.parentNode;
3504
3766
  parent.insertBefore(this.textarea, this.wrapper);