@trustquery/browser 0.2.9 → 0.3.0
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 +270 -2
- package/dist/trustquery.js.map +1 -1
- package/package.json +2 -2
- package/src/DropdownManager.js +8 -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.
|
|
3
|
+
"version": "0.3.0",
|
|
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"
|
package/src/DropdownManager.js
CHANGED
|
@@ -102,8 +102,9 @@ export default class DropdownManager {
|
|
|
102
102
|
// Setup keyboard navigation
|
|
103
103
|
this.setupKeyboardHandlers();
|
|
104
104
|
|
|
105
|
-
// Close on click outside
|
|
105
|
+
// Close on click outside (using mousedown to match icon behavior)
|
|
106
106
|
setTimeout(() => {
|
|
107
|
+
document.addEventListener('mousedown', this.closeDropdownHandler);
|
|
107
108
|
document.addEventListener('click', this.closeDropdownHandler);
|
|
108
109
|
}, 0);
|
|
109
110
|
|
|
@@ -428,6 +429,7 @@ export default class DropdownManager {
|
|
|
428
429
|
this.dropdownOptions = null;
|
|
429
430
|
this.dropdownMatchData = null;
|
|
430
431
|
this.selectedDropdownIndex = 0;
|
|
432
|
+
document.removeEventListener('mousedown', this.closeDropdownHandler);
|
|
431
433
|
document.removeEventListener('click', this.closeDropdownHandler);
|
|
432
434
|
document.removeEventListener('keydown', this.keyboardHandler);
|
|
433
435
|
}
|
|
@@ -437,8 +439,12 @@ export default class DropdownManager {
|
|
|
437
439
|
* Close dropdown handler (bound to document)
|
|
438
440
|
*/
|
|
439
441
|
closeDropdownHandler = (e) => {
|
|
440
|
-
// Only close if clicking outside the dropdown
|
|
442
|
+
// Only close if clicking outside the dropdown AND not on the trigger element
|
|
441
443
|
if (this.activeDropdown && !this.activeDropdown.contains(e.target)) {
|
|
444
|
+
// Check if clicking on the trigger element itself - don't close in that case
|
|
445
|
+
if (this.activeDropdownMatch && (this.activeDropdownMatch === e.target || this.activeDropdownMatch.contains(e.target))) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
442
448
|
this.hideDropdown();
|
|
443
449
|
}
|
|
444
450
|
}
|
|
@@ -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);
|