@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.
- package/dist/trustquery.js +262 -0
- package/dist/trustquery.js.map +1 -1
- package/package.json +2 -2
- package/src/MobileKeyboardHandler.js +240 -0
- package/src/TrustQuery.js +22 -0
package/dist/trustquery.js
CHANGED
|
@@ -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);
|