@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trustquery/browser",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Turn any textarea into an interactive trigger-based editor with inline styling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/trustquery.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/RonItelman/trustquery-browser.git"
|
|
39
|
+
"url": "git+https://github.com/RonItelman/trustquery-browser.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/RonItelman/trustquery-browser/issues"
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// MobileKeyboardHandler - Handles mobile virtual keyboard behavior
|
|
2
|
+
// Detects keyboard appearance and adjusts layout to keep textarea visible
|
|
3
|
+
|
|
4
|
+
export default class MobileKeyboardHandler {
|
|
5
|
+
/**
|
|
6
|
+
* Create mobile keyboard handler
|
|
7
|
+
* @param {Object} options - Configuration options
|
|
8
|
+
*/
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
textarea: options.textarea || null,
|
|
12
|
+
wrapper: options.wrapper || null,
|
|
13
|
+
debug: options.debug || false,
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
this.isKeyboardVisible = false;
|
|
18
|
+
this.lastViewportHeight = window.innerHeight;
|
|
19
|
+
this.visualViewport = window.visualViewport;
|
|
20
|
+
|
|
21
|
+
if (this.options.debug) {
|
|
22
|
+
console.log('[MobileKeyboardHandler] Initialized');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize keyboard detection
|
|
28
|
+
*/
|
|
29
|
+
init() {
|
|
30
|
+
if (!this.options.textarea) {
|
|
31
|
+
console.warn('[MobileKeyboardHandler] No textarea provided');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Use Visual Viewport API if available (preferred method)
|
|
36
|
+
if (this.visualViewport) {
|
|
37
|
+
this.visualViewport.addEventListener('resize', this.handleViewportResize);
|
|
38
|
+
this.visualViewport.addEventListener('scroll', this.handleViewportScroll);
|
|
39
|
+
|
|
40
|
+
if (this.options.debug) {
|
|
41
|
+
console.log('[MobileKeyboardHandler] Using Visual Viewport API');
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
// Fallback to window resize
|
|
45
|
+
window.addEventListener('resize', this.handleWindowResize);
|
|
46
|
+
|
|
47
|
+
if (this.options.debug) {
|
|
48
|
+
console.log('[MobileKeyboardHandler] Using window resize fallback');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle focus events
|
|
53
|
+
this.options.textarea.addEventListener('focus', this.handleFocus);
|
|
54
|
+
this.options.textarea.addEventListener('blur', this.handleBlur);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle Visual Viewport resize (keyboard appearance/disappearance)
|
|
59
|
+
*/
|
|
60
|
+
handleViewportResize = () => {
|
|
61
|
+
if (!this.visualViewport) return;
|
|
62
|
+
|
|
63
|
+
const viewportHeight = this.visualViewport.height;
|
|
64
|
+
const windowHeight = window.innerHeight;
|
|
65
|
+
|
|
66
|
+
if (this.options.debug) {
|
|
67
|
+
console.log('[MobileKeyboardHandler] Viewport resize:', {
|
|
68
|
+
viewportHeight,
|
|
69
|
+
windowHeight,
|
|
70
|
+
scale: this.visualViewport.scale
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Keyboard is visible if viewport height is significantly smaller than window height
|
|
75
|
+
const wasKeyboardVisible = this.isKeyboardVisible;
|
|
76
|
+
this.isKeyboardVisible = viewportHeight < windowHeight * 0.75;
|
|
77
|
+
|
|
78
|
+
if (this.isKeyboardVisible !== wasKeyboardVisible) {
|
|
79
|
+
if (this.isKeyboardVisible) {
|
|
80
|
+
this.onKeyboardShow();
|
|
81
|
+
} else {
|
|
82
|
+
this.onKeyboardHide();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Always adjust layout when viewport changes
|
|
87
|
+
if (this.isKeyboardVisible) {
|
|
88
|
+
this.adjustLayout();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handle Visual Viewport scroll
|
|
94
|
+
*/
|
|
95
|
+
handleViewportScroll = () => {
|
|
96
|
+
if (this.isKeyboardVisible) {
|
|
97
|
+
// Ensure textarea stays in view during scroll
|
|
98
|
+
this.ensureTextareaVisible();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handle window resize (fallback)
|
|
104
|
+
*/
|
|
105
|
+
handleWindowResize = () => {
|
|
106
|
+
const currentHeight = window.innerHeight;
|
|
107
|
+
const heightDifference = this.lastViewportHeight - currentHeight;
|
|
108
|
+
|
|
109
|
+
if (this.options.debug) {
|
|
110
|
+
console.log('[MobileKeyboardHandler] Window resize:', {
|
|
111
|
+
lastHeight: this.lastViewportHeight,
|
|
112
|
+
currentHeight,
|
|
113
|
+
difference: heightDifference
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Significant decrease in height suggests keyboard appeared
|
|
118
|
+
if (heightDifference > 150) {
|
|
119
|
+
if (!this.isKeyboardVisible) {
|
|
120
|
+
this.isKeyboardVisible = true;
|
|
121
|
+
this.onKeyboardShow();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Significant increase suggests keyboard hidden
|
|
125
|
+
else if (heightDifference < -150) {
|
|
126
|
+
if (this.isKeyboardVisible) {
|
|
127
|
+
this.isKeyboardVisible = false;
|
|
128
|
+
this.onKeyboardHide();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.lastViewportHeight = currentHeight;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Handle textarea focus
|
|
137
|
+
*/
|
|
138
|
+
handleFocus = () => {
|
|
139
|
+
if (this.options.debug) {
|
|
140
|
+
console.log('[MobileKeyboardHandler] Textarea focused');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Delay to allow keyboard to appear
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
this.ensureTextareaVisible();
|
|
146
|
+
}, 300);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handle textarea blur
|
|
151
|
+
*/
|
|
152
|
+
handleBlur = () => {
|
|
153
|
+
if (this.options.debug) {
|
|
154
|
+
console.log('[MobileKeyboardHandler] Textarea blurred');
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Called when keyboard appears
|
|
160
|
+
*/
|
|
161
|
+
onKeyboardShow() {
|
|
162
|
+
if (this.options.debug) {
|
|
163
|
+
console.log('[MobileKeyboardHandler] Keyboard shown');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.adjustLayout();
|
|
167
|
+
this.ensureTextareaVisible();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Called when keyboard hides
|
|
172
|
+
*/
|
|
173
|
+
onKeyboardHide() {
|
|
174
|
+
if (this.options.debug) {
|
|
175
|
+
console.log('[MobileKeyboardHandler] Keyboard hidden');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Reset wrapper height to auto
|
|
179
|
+
if (this.options.wrapper) {
|
|
180
|
+
this.options.wrapper.style.maxHeight = '';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Adjust layout to accommodate keyboard
|
|
186
|
+
*/
|
|
187
|
+
adjustLayout() {
|
|
188
|
+
if (!this.visualViewport || !this.options.wrapper) return;
|
|
189
|
+
|
|
190
|
+
const viewportHeight = this.visualViewport.height;
|
|
191
|
+
|
|
192
|
+
// Set wrapper max-height to visible viewport height minus some padding
|
|
193
|
+
const maxHeight = viewportHeight - 20; // 20px padding
|
|
194
|
+
this.options.wrapper.style.maxHeight = `${maxHeight}px`;
|
|
195
|
+
this.options.wrapper.style.overflow = 'auto';
|
|
196
|
+
|
|
197
|
+
if (this.options.debug) {
|
|
198
|
+
console.log('[MobileKeyboardHandler] Adjusted wrapper height:', maxHeight);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Ensure textarea is visible above the keyboard
|
|
204
|
+
*/
|
|
205
|
+
ensureTextareaVisible() {
|
|
206
|
+
if (!this.options.textarea) return;
|
|
207
|
+
|
|
208
|
+
// Scroll textarea into view
|
|
209
|
+
this.options.textarea.scrollIntoView({
|
|
210
|
+
behavior: 'smooth',
|
|
211
|
+
block: 'center',
|
|
212
|
+
inline: 'nearest'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (this.options.debug) {
|
|
216
|
+
console.log('[MobileKeyboardHandler] Scrolled textarea into view');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Cleanup event listeners
|
|
222
|
+
*/
|
|
223
|
+
destroy() {
|
|
224
|
+
if (this.visualViewport) {
|
|
225
|
+
this.visualViewport.removeEventListener('resize', this.handleViewportResize);
|
|
226
|
+
this.visualViewport.removeEventListener('scroll', this.handleViewportScroll);
|
|
227
|
+
} else {
|
|
228
|
+
window.removeEventListener('resize', this.handleWindowResize);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.options.textarea) {
|
|
232
|
+
this.options.textarea.removeEventListener('focus', this.handleFocus);
|
|
233
|
+
this.options.textarea.removeEventListener('blur', this.handleBlur);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.options.debug) {
|
|
237
|
+
console.log('[MobileKeyboardHandler] Destroyed');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/TrustQuery.js
CHANGED
|
@@ -8,6 +8,7 @@ import StyleManager from './StyleManager.js';
|
|
|
8
8
|
import CommandHandlerRegistry from './CommandHandlers.js';
|
|
9
9
|
import AutoGrow from './AutoGrow.js';
|
|
10
10
|
import ValidationStateManager from './ValidationStateManager.js';
|
|
11
|
+
import MobileKeyboardHandler from './MobileKeyboardHandler.js';
|
|
11
12
|
|
|
12
13
|
export default class TrustQuery {
|
|
13
14
|
// Store all instances
|
|
@@ -212,6 +213,17 @@ export default class TrustQuery {
|
|
|
212
213
|
console.log('[TrustQuery] AutoGrow feature enabled');
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
// Mobile keyboard handler (enabled by default, can be disabled via options)
|
|
217
|
+
if (this.options.mobileKeyboard !== false) {
|
|
218
|
+
this.features.mobileKeyboard = new MobileKeyboardHandler({
|
|
219
|
+
textarea: this.textarea,
|
|
220
|
+
wrapper: this.wrapper,
|
|
221
|
+
debug: this.options.debug
|
|
222
|
+
});
|
|
223
|
+
this.features.mobileKeyboard.init();
|
|
224
|
+
console.log('[TrustQuery] Mobile keyboard handler enabled');
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
// Debug logging feature
|
|
216
228
|
if (this.options.debug) {
|
|
217
229
|
this.enableDebugLogging();
|
|
@@ -502,6 +514,16 @@ export default class TrustQuery {
|
|
|
502
514
|
this.interactionHandler.destroy();
|
|
503
515
|
}
|
|
504
516
|
|
|
517
|
+
// Cleanup mobile keyboard handler
|
|
518
|
+
if (this.features.mobileKeyboard) {
|
|
519
|
+
this.features.mobileKeyboard.destroy();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Cleanup auto-grow
|
|
523
|
+
if (this.features.autoGrow) {
|
|
524
|
+
this.features.autoGrow.destroy();
|
|
525
|
+
}
|
|
526
|
+
|
|
505
527
|
// Unwrap textarea
|
|
506
528
|
const parent = this.wrapper.parentNode;
|
|
507
529
|
parent.insertBefore(this.textarea, this.wrapper);
|