electron-findbar 2.0.1 → 3.1.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.
Files changed (3) hide show
  1. package/README.md +88 -26
  2. package/index.js +95 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,7 +44,7 @@ const Findbar = require('electron-findbar')
44
44
  You can pass a `BrowserWindow` instance as a single parameter to use it as the parent window. The `BrowserWindow.WebContents` will be used as the findable content:
45
45
 
46
46
  ```js
47
- // Create or retrieve the findbar associated to the browserWindow.webContents. If a new findbar is created, the browserWindow is used as parent.
47
+ // Create or retrieve the findbar associated to the browserWindow.webContents or baseWindow.contentView.children[0]. If a new findbar is created, the browserWindow is used as parent.
48
48
  const findbar = Findbar.from(browserWindow)
49
49
  ```
50
50
 
@@ -55,10 +55,10 @@ Alternatively, you can provide a custom `WebContents` as the second parameter. I
55
55
  const findbar = Findbar.from(baseWindow, webContents)
56
56
  ```
57
57
 
58
- Is also possible to create a findbar without a parent window (even though it is not recommended):
58
+ Is also possible to create a findbar providing only the web contents. The BaseWindow.getAllWindows() will be used to query for the parent window:
59
59
 
60
60
  ```js
61
- // Create or retrieve the findbar associated to the webContents. If a new findbar is created, it will be displayed in the middle of the screen without a parent to connect to.
61
+ // Create or retrieve the findbar associated to the webContents.
62
62
  const findbar = Findbar.from(webContents)
63
63
  ```
64
64
 
@@ -146,9 +146,20 @@ app.whenReady().then(() => {
146
146
  })
147
147
  ```
148
148
 
149
- ### Configuring Keyboard Shortcuts
149
+ ### Keyboard Shortcuts
150
150
 
151
- The Findbar component can be controlled using keyboard shortcuts. Below are two implementation approaches to help you integrate search functionality seamlessly into your application's user experience.
151
+ The Findbar component can be controlled using keyboard shortcuts. The following shortcuts are available by default:
152
+
153
+ | Shortcut | Description |
154
+ |----------|-------------|
155
+ | Enter | Move to next match |
156
+ | Shift+Enter | Move to previous match |
157
+ | Esc | Close the findbar |
158
+
159
+
160
+ ### Configuring Other Shortcuts
161
+
162
+ Below are two implementation approaches to help you integrate search functionality seamlessly into your application's user experience.
152
163
 
153
164
  **Note:** The following examples demonstrate only the ideal (happy path) scenarios. For production use, make sure to thoroughly validate all inputs and handle edge cases appropriately.
154
165
 
@@ -157,27 +168,35 @@ The Findbar component can be controlled using keyboard shortcuts. Below are two
157
168
  The `before-input-event` approach allows you to capture keyboard events directly in the main process before they're processed by the web contents, giving you precise control:
158
169
 
159
170
  ```js
160
- window.webContents.on('before-input-event', (event, input) => {
161
- // Detect Ctrl+F (Windows/Linux) or Command+F (macOS)
162
- if ((input.control || input.meta) && input.key.toLowerCase() === 'f') {
163
- // Prevent default browser behavior
164
- event.preventDefault()
165
-
166
- // Access and open the findbar
167
- const findbar = Findbar.from(window)
168
- if (findbar) {
169
- findbar.open()
171
+ webContents.on('before-input-event', (event, input) => {
172
+ if (input.shift || input.alt) { return }
173
+
174
+ const key = input.key.toLowerCase()
175
+
176
+ // Detect Ctrl+F (Windows/Linux) or Command+F (macOS)
177
+ if (input.control || input.meta) {
178
+ if (key === 'f') {
179
+ // Prevent default behavior
180
+ event.preventDefault()
181
+
182
+ // Access and open the findbar
183
+ Findbar.from(webContents).open()
184
+ }
185
+ return
170
186
  }
171
- }
172
-
173
- // Handle Escape key to close the findbar
174
- if (input.key === 'Escape') {
175
- const findbar = Findbar.from(window)
176
- if (findbar && findbar.isOpen()) {
177
- event.preventDefault()
178
- findbar.close()
187
+
188
+ // Handle Escape key to close the findbar
189
+ if (key === 'escape') {
190
+ const findbar = Findbar.fromIfExists(webContents)
191
+
192
+ if (findbar?.isOpen()) {
193
+ // Prevent default behavior
194
+ event.preventDefault()
195
+
196
+ // Close the findbar
197
+ findbar.close()
198
+ }
179
199
  }
180
- }
181
200
  })
182
201
  ```
183
202
 
@@ -198,12 +217,12 @@ appMenu.append(new MenuItem({
198
217
  submenu: [
199
218
  {
200
219
  label: 'Find in Page',
201
- click: () => Findbar.from(parent)?.open(),
220
+ click: () => Findbar.from(parent).open(),
202
221
  accelerator: 'CommandOrControl+F'
203
222
  },
204
223
  {
205
224
  label: 'Close Find',
206
- click: () => Findbar.from(parent)?.close(),
225
+ click: () => Findbar.from(parent).close(),
207
226
  accelerator: 'Esc'
208
227
  }
209
228
  ]
@@ -308,6 +327,49 @@ FindbarRemote.open()
308
327
  FindbarRemote.inputChange('findIt')
309
328
  ```
310
329
 
330
+ ## Changing the Parent Window
331
+
332
+ There are scenarios where you might need to change the parent window.
333
+
334
+ ### Using updateParentWindow
335
+
336
+ The `updateParentWindow` method allows you to change the parent window while preserving the findbar instance and its state:
337
+
338
+ ```javascript
339
+ // Create a findbar for the initial window
340
+ const findbar = Findbar.from(oldWindow, webContents)
341
+
342
+ // Later, when you need to change the parent:
343
+ findbar.updateParentWindow(newWindow)
344
+ ```
345
+
346
+ This approach keeps the same findbar instance connected to the same webContents, but changes which window it's attached to. The findbar will close immediately.
347
+
348
+ ### Using detach
349
+
350
+ Alternatively, the `detach` method disconnects a findbar instance from its webContents, allowing you to create a new instance in the next `Findbar.from` call:
351
+
352
+ ```javascript
353
+ // Get the existing findbar
354
+ const oldFindbar = Findbar.fromIfExists(webContents)
355
+
356
+ // Detach it to free the association
357
+ if (oldFindbar) {
358
+ oldFindbar.detach()
359
+ }
360
+
361
+ // Now create a new findbar with a different parent
362
+ const newFindbar = Findbar.from([newWindow, ]webContents)
363
+ ```
364
+
365
+ This approach is useful when you want to completely reset the findbar's configuration or when moving between very different window configurations.
366
+
367
+ ### Important Considerations
368
+
369
+ - If the findbar is currently open when you change the parent window, it will automatically close.
370
+ - Window options and handlers will be preserved when using `updateParentWindow`.
371
+ - After calling `detach`, the old findbar instance can no longer be used.
372
+
311
373
  ## Author
312
374
 
313
375
  Created by [Emerson Capuchi Romaneli](https://github.com/ECRomaneli) (@ECRomaneli).
package/index.js CHANGED
@@ -44,28 +44,29 @@ class Findbar {
44
44
  * Configure the findbar and link to the web contents.
45
45
  *
46
46
  * @overload
47
+ * @param {WebContents} webContents Findable web contents. The parent window will be defined by using BaseWindow.getAllWindows() and
48
+ * matching the webContents with the webContents of the window or its contentView children.
49
+ * @returns {Findbar} The findbar instance if it exists.
50
+ * @throws {Error} If no webContents is provided.
51
+ *
52
+ * @overload
47
53
  * @param {BrowserWindow} browserWindow Parent window.
48
- * @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the web contents of the BrowserWindow will be used.
54
+ * @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the browserWindow.webContents will be used.
49
55
  * @returns {Findbar} The findbar instance if it exists.
50
56
  *
51
57
  * @overload
52
58
  * @param {BaseWindow} baseWindow Parent window.
53
- * @param {WebContents} webContents Findable web contents.
54
- * @returns {Findbar} The findbar instance if it exists.
55
- * @throws {Error} If no webContents is provided.
56
- * *
57
- * @overload
58
- * @param {WebContents} webContents Findable web contents. The parent window will be undefined.
59
+ * @param {WebContents} [webContents] Custom findable web contents. If not provided, the win.contentView.children[0] will be used.
59
60
  * @returns {Findbar} The findbar instance if it exists.
60
61
  * @throws {Error} If no webContents is provided.
61
62
  */
62
63
  constructor (parent, webContents) {
63
64
  if (isFindable(parent)) {
64
- this.#parent = void 0
65
65
  this.#findableContents = parent
66
+ this.#parent = Findbar.#getBaseWindowFromWebContents(this.#findableContents)
66
67
  } else {
67
68
  this.#parent = parent
68
- this.#findableContents = webContents ?? parent.webContents
69
+ this.#findableContents = webContents ?? Findbar.#retrieveWebContents(parent)
69
70
  }
70
71
 
71
72
  if (!this.#findableContents) {
@@ -73,8 +74,10 @@ class Findbar {
73
74
  }
74
75
 
75
76
  this.#findableContents._findbar = this
77
+
78
+ this.#findableContents.once('destroyed', () => { this.detach() })
76
79
  }
77
-
80
+
78
81
  /**
79
82
  * Open the findbar. If the findbar is already opened, focus the input text.
80
83
  * @returns {void}
@@ -92,7 +95,6 @@ class Findbar {
92
95
  this.#registerListeners()
93
96
 
94
97
  this.#windowHandler && this.#windowHandler(this.#window)
95
-
96
98
  this.#window.loadFile(`${__dirname}/web/findbar.html`)
97
99
  }
98
100
 
@@ -101,10 +103,30 @@ class Findbar {
101
103
  * @returns {void}
102
104
  */
103
105
  close() {
104
- if (!this.#window || this.#window.isDestroyed()) { return }
105
- if (!this.#window.isVisible()) { this.#window.close(); return }
106
- this.#window.on('hide', () => { this.#window.close() })
107
- this.#window.hide()
106
+ if (this.#window && !this.#window.isDestroyed()) {
107
+ this.#window.close()
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Detach the findbar from the web contents and close it if opened. After detaching, the findbar instance will be unusable.
113
+ * @returns {void}
114
+ */
115
+ detach() {
116
+ this.close()
117
+ this.#findableContents._findbar = void 0
118
+ if (this.#window) { this.#window.webContents._findbar = void 0 }
119
+ }
120
+
121
+ /**
122
+ * Update the parent window of the findbar.
123
+ * @param {BaseWindow} [newParent] - The new parent window. If not provided, the parent will be set to the window containing the web contents.
124
+ * @returns {void}
125
+ */
126
+ updateParentWindow(newParent) {
127
+ if (this.#parent === newParent) { return }
128
+ this.close()
129
+ this.#parent = newParent ?? Findbar.#getBaseWindowFromWebContents(this.#findableContents)
108
130
  }
109
131
 
110
132
  /**
@@ -235,6 +257,32 @@ class Findbar {
235
257
  this.#boundsHandler = boundsHandler
236
258
  }
237
259
 
260
+ #registerKeyboardShortcuts(event, input) {
261
+ if (input.meta || input.control || input.alt) { return }
262
+
263
+ const key = input.key.toLowerCase()
264
+
265
+ if (input.shift) {
266
+ if (key === 'enter') {
267
+ this.findPrevious()
268
+ event.preventDefault()
269
+ }
270
+ return;
271
+ }
272
+
273
+ if (key === 'enter') {
274
+ this.findNext()
275
+ event.preventDefault()
276
+ return;
277
+ }
278
+
279
+ if (key === 'escape') {
280
+ if (this.isOpen()) {
281
+ this.close()
282
+ event.preventDefault()
283
+ }
284
+ }
285
+ }
238
286
  /**
239
287
  * @param {Electron.FindInPageOptions} options
240
288
  */
@@ -269,7 +317,7 @@ class Findbar {
269
317
  this.#parent.prependListener('move', boundsHandler)
270
318
  }
271
319
 
272
- this.#window.once('close', () => {
320
+ this.#window.once('closed', () => {
273
321
  if (this.#parent && !this.#parent.isDestroyed()) {
274
322
  this.#parent.off('show', showCascade)
275
323
  this.#parent.off('hide', hideCascade)
@@ -281,6 +329,7 @@ class Findbar {
281
329
  })
282
330
 
283
331
  this.#window.prependOnceListener('ready-to-show', () => { this.#window.show() })
332
+ this.#window.webContents.prependListener('before-input-event', this.#registerKeyboardShortcuts.bind(this))
284
333
 
285
334
  this.#findableContents.prependOnceListener('destroyed', () => { this.close() })
286
335
  this.#findableContents.prependListener('found-in-page', (_e, result) => { this.#sendMatchesCount(result.activeMatchOrdinal, result.matches) })
@@ -310,6 +359,24 @@ class Findbar {
310
359
  this.#window.webContents.send('electron-findbar/input-focus')
311
360
  }
312
361
 
362
+ /**
363
+ * Retrieve web contents from a BrowserWindow or BaseWindow.
364
+ * @param {BrowserWindow | BaseWindow} window
365
+ * @returns {WebContents | undefined} The web contents if any.
366
+ */
367
+ static #retrieveWebContents(window) {
368
+ return window.webContents ?? window.contentView?.children[0]
369
+ }
370
+
371
+ /**
372
+ * Get the parent window from web contents.
373
+ * @param {WebContents} cont
374
+ * @returns {BaseWindow | undefined} Parent window if any.
375
+ */
376
+ static #getBaseWindowFromWebContents(cont) {
377
+ return BaseWindow.getAllWindows().find(win => win.webContents === cont || win.contentView.children.some(child => child.webContents === cont))
378
+ }
379
+
313
380
  /**
314
381
  * Set default findbar position.
315
382
  * @param {Rectangle} parentBounds
@@ -358,25 +425,25 @@ class Findbar {
358
425
  * If no findbar instance exists, it will return a new one linked to the web contents.
359
426
  *
360
427
  * @overload
361
- * @param {BrowserWindow} browserWindow Parent window.
362
- * @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the web contents of the BrowserWindow will be used.
428
+ * @param {WebContents} webContents Findable web contents. The parent window will be defined by using BaseWindow.getAllWindows() and
429
+ * matching the webContents with the webContents of the window or its contentView children.
363
430
  * @returns {Findbar} The findbar instance if it exists.
431
+ * @throws {Error} If no webContents is provided.
364
432
  *
365
433
  * @overload
366
- * @param {WebContents} webContents Findable web contents. The parent window will be undefined.
434
+ * @param {BrowserWindow} browserWindow Parent window.
435
+ * @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the browserWindow.webContents will be used.
367
436
  * @returns {Findbar} The findbar instance if it exists.
368
- * @throws {Error} If no webContents is provided.
369
- *
437
+ *
370
438
  * @overload
371
439
  * @param {BaseWindow} baseWindow Parent window.
372
- * @param {WebContents} webContents Findable web contents.
440
+ * @param {WebContents} [webContents] Custom findable web contents. If not provided, the win.contentView.children[0] will be used.
373
441
  * @returns {Findbar} The findbar instance if it exists.
374
442
  * @throws {Error} If no webContents is provided.
375
443
  */
376
444
  static from(windowOrWebContents, customWebContents) {
377
- let webContents = isFindable(windowOrWebContents) ?
378
- windowOrWebContents : customWebContents ?? windowOrWebContents.webContents
379
-
445
+ const webContents = isFindable(windowOrWebContents) ? windowOrWebContents : customWebContents ?? Findbar.#retrieveWebContents(windowOrWebContents)
446
+ if (!webContents) { throw new Error('[Findbar] There are no searchable web contents.') }
380
447
  return webContents._findbar || new Findbar(windowOrWebContents, customWebContents)
381
448
  }
382
449
 
@@ -386,7 +453,9 @@ class Findbar {
386
453
  * @returns {Findbar | undefined} The findbar instance if it exists, otherwise undefined.
387
454
  */
388
455
  static fromIfExists(windowOrWebContents) {
389
- return (isFindable(windowOrWebContents) ? windowOrWebContents : windowOrWebContents.webContents)._findbar
456
+ const webContents = isFindable(windowOrWebContents) ? windowOrWebContents : Findbar.#retrieveWebContents(windowOrWebContents)
457
+ if (!webContents) { throw new Error('[Findbar] There are no searchable web contents.') }
458
+ return webContents._findbar
390
459
  }
391
460
  }
392
461
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-findbar",
3
- "version": "2.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Chrome-like findbar for your Electron app.",
5
5
  "main": "index.js",
6
6
  "files": [