@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustquery/browser",
3
- "version": "0.2.9",
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"
@@ -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);