@salesforcedevs/docs-components 1.17.5-edit → 1.17.6-hover-edit
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/lwc.config.json +1 -0
- package/package.json +1 -1
- package/src/modules/doc/textSelectionSearch/README.md +185 -0
- package/src/modules/doc/textSelectionSearch/textSelectionSearch.css +286 -0
- package/src/modules/doc/textSelectionSearch/textSelectionSearch.html +90 -0
- package/src/modules/doc/textSelectionSearch/textSelectionSearch.js +236 -0
- package/src/modules/doc/textSelectionSearch/textSelectionSearch.ts +257 -0
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Text Selection Search Component
|
|
2
|
+
|
|
3
|
+
A Lightning Web Component that detects text selection on the page and shows a search popover for finding related content.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Text Detection**: Detects text selection via mouse and keyboard
|
|
8
|
+
- **Positioned Popover**: Shows search interface near the selected text
|
|
9
|
+
- **Search Functionality**: Makes API calls to backend search endpoints
|
|
10
|
+
- **Modern UI**: Clean, responsive interface with loading states
|
|
11
|
+
- **Keyboard Navigation**: Escape key to close popover
|
|
12
|
+
- **Event Handling**: Dispatches events for search results and clicks
|
|
13
|
+
- **Customizable**: Configurable appearance and behavior
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Implementation
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<doc-text-selection-search
|
|
21
|
+
search-api-url="/api/search"
|
|
22
|
+
placeholder="Search for..."
|
|
23
|
+
popover-position="top"
|
|
24
|
+
max-results="10">
|
|
25
|
+
</doc-text-selection-search>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### With Event Listeners
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<doc-text-selection-search
|
|
32
|
+
search-api-url="/api/search"
|
|
33
|
+
@searchresults="handleSearchResults"
|
|
34
|
+
@resultclick="handleResultClick">
|
|
35
|
+
</doc-text-selection-search>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
handleSearchResults(event) {
|
|
40
|
+
const { query, results, selectedText } = event.detail;
|
|
41
|
+
console.log('Search results:', results);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handleResultClick(event) {
|
|
45
|
+
const { result, query, selectedText } = event.detail;
|
|
46
|
+
console.log('Result clicked:', result);
|
|
47
|
+
// Navigate to result or perform action
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### Properties
|
|
54
|
+
|
|
55
|
+
| Property | Type | Default | Description |
|
|
56
|
+
|----------|------|---------|-------------|
|
|
57
|
+
| `searchApiUrl` | string | `""` | The URL endpoint for the search API |
|
|
58
|
+
| `placeholder` | string | `"Search for..."` | Placeholder text for the search input |
|
|
59
|
+
| `popoverPosition` | `"top" \| "bottom"` | `"top"` | Position of popover relative to selected text |
|
|
60
|
+
| `maxResults` | number | `10` | Maximum number of search results to display |
|
|
61
|
+
|
|
62
|
+
### Events
|
|
63
|
+
|
|
64
|
+
| Event | Detail | Description |
|
|
65
|
+
|-------|--------|-------------|
|
|
66
|
+
| `searchresults` | `{ query, results, selectedText }` | Fired when search results are received |
|
|
67
|
+
| `resultclick` | `{ result, query, selectedText }` | Fired when a search result is clicked |
|
|
68
|
+
|
|
69
|
+
### Backend API Format
|
|
70
|
+
|
|
71
|
+
The component expects your backend API to:
|
|
72
|
+
|
|
73
|
+
**Request (POST):**
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"query": "search term",
|
|
77
|
+
"maxResults": 10,
|
|
78
|
+
"selectedText": "originally selected text"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Response:**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"results": [
|
|
86
|
+
{
|
|
87
|
+
"title": "Result Title",
|
|
88
|
+
"description": "Result description or snippet",
|
|
89
|
+
"url": "https://example.com/result"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
### Knowledge Base Search
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<doc-text-selection-search
|
|
101
|
+
search-api-url="https://api.example.com/kb/search"
|
|
102
|
+
placeholder="Search knowledge base..."
|
|
103
|
+
popover-position="bottom"
|
|
104
|
+
max-results="15">
|
|
105
|
+
</doc-text-selection-search>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Documentation Search
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<doc-text-selection-search
|
|
112
|
+
search-api-url="/api/docs/search"
|
|
113
|
+
placeholder="Search documentation..."
|
|
114
|
+
popover-position="top"
|
|
115
|
+
max-results="8">
|
|
116
|
+
</doc-text-selection-search>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Custom Styling
|
|
120
|
+
|
|
121
|
+
You can customize the appearance by overriding CSS classes:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.text-selection-search-popover {
|
|
125
|
+
/* Custom popover styling */
|
|
126
|
+
border-radius: 12px;
|
|
127
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.result-item:hover {
|
|
131
|
+
/* Custom hover effects */
|
|
132
|
+
background: #f0f8ff;
|
|
133
|
+
border-color: #0066cc;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Browser Support
|
|
138
|
+
|
|
139
|
+
- Chrome 60+
|
|
140
|
+
- Firefox 55+
|
|
141
|
+
- Safari 12+
|
|
142
|
+
- Edge 79+
|
|
143
|
+
|
|
144
|
+
## Accessibility
|
|
145
|
+
|
|
146
|
+
- Keyboard navigation support (Escape to close)
|
|
147
|
+
- Screen reader friendly
|
|
148
|
+
- High contrast mode support
|
|
149
|
+
- Focus management
|
|
150
|
+
|
|
151
|
+
## Performance Considerations
|
|
152
|
+
|
|
153
|
+
- Event listeners are properly cleaned up on component destruction
|
|
154
|
+
- Debounced text selection detection
|
|
155
|
+
- Efficient DOM updates
|
|
156
|
+
- Minimal re-renders
|
|
157
|
+
|
|
158
|
+
## Troubleshooting
|
|
159
|
+
|
|
160
|
+
### Common Issues
|
|
161
|
+
|
|
162
|
+
1. **Popover not appearing**: Ensure text selection is enabled and the component is properly mounted
|
|
163
|
+
2. **API calls failing**: Check the `searchApiUrl` and ensure CORS is configured
|
|
164
|
+
3. **Positioning issues**: The popover position calculation may need adjustment for complex layouts
|
|
165
|
+
|
|
166
|
+
### Debug Mode
|
|
167
|
+
|
|
168
|
+
Enable console logging for debugging:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// Add to your component
|
|
172
|
+
connectedCallback() {
|
|
173
|
+
this.debug = true; // Enable debug logging
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Contributing
|
|
178
|
+
|
|
179
|
+
When contributing to this component:
|
|
180
|
+
|
|
181
|
+
1. Follow the existing code style and patterns
|
|
182
|
+
2. Add appropriate tests for new features
|
|
183
|
+
3. Update documentation for API changes
|
|
184
|
+
4. Ensure accessibility compliance
|
|
185
|
+
5. Test across different browsers and devices
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
.text-selection-search-popover {
|
|
2
|
+
background: white;
|
|
3
|
+
border: 1px solid #e1e5e9;
|
|
4
|
+
border-radius: 8px;
|
|
5
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
6
|
+
padding: 0;
|
|
7
|
+
min-width: 320px;
|
|
8
|
+
max-width: 480px;
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
10
|
+
position: relative;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
opacity: 1;
|
|
13
|
+
visibility: visible;
|
|
14
|
+
transition: opacity 0.1s ease, visibility 0.1s ease;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.text-selection-search-popover.hidden {
|
|
18
|
+
opacity: 0;
|
|
19
|
+
visibility: hidden;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
position: absolute;
|
|
22
|
+
top: -9999px;
|
|
23
|
+
left: -9999px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Header Styles */
|
|
27
|
+
.popover-header {
|
|
28
|
+
background: #f8f9fa;
|
|
29
|
+
border-bottom: 1px solid #e9ecef;
|
|
30
|
+
padding: 12px 16px;
|
|
31
|
+
margin: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.popover-title {
|
|
35
|
+
margin: 0;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
color: #495057;
|
|
39
|
+
text-transform: uppercase;
|
|
40
|
+
letter-spacing: 0.5px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Content Container */
|
|
44
|
+
.text-selection-search-popover > *:not(.popover-header):not(.popover-close) {
|
|
45
|
+
padding: 16px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Section Labels */
|
|
49
|
+
.section-label {
|
|
50
|
+
display: block;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
color: #6c757d;
|
|
54
|
+
margin-bottom: 6px;
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
letter-spacing: 0.5px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Selected Text Display */
|
|
60
|
+
.selected-text-section {
|
|
61
|
+
margin-bottom: 16px;
|
|
62
|
+
padding-bottom: 12px;
|
|
63
|
+
border-bottom: 1px solid #f1f3f4;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.selected-text-display {
|
|
67
|
+
background: #f8f9fa;
|
|
68
|
+
border: 1px solid #e9ecef;
|
|
69
|
+
border-radius: 4px;
|
|
70
|
+
padding: 8px 12px;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
line-height: 1.4;
|
|
73
|
+
color: #495057;
|
|
74
|
+
min-height: 32px;
|
|
75
|
+
max-height: 60px;
|
|
76
|
+
overflow-y: auto;
|
|
77
|
+
word-wrap: break-word;
|
|
78
|
+
font-style: italic;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Edit Form */
|
|
82
|
+
.edit-form {
|
|
83
|
+
margin-bottom: 16px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.input-group {
|
|
87
|
+
margin-bottom: 16px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.input-group:last-child {
|
|
91
|
+
margin-bottom: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.input-with-button {
|
|
95
|
+
display: flex;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
align-items: flex-end;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.edit-input {
|
|
101
|
+
flex: 1;
|
|
102
|
+
padding: 12px 16px;
|
|
103
|
+
border: 2px solid #e5e7eb;
|
|
104
|
+
border-radius: 6px;
|
|
105
|
+
font-size: 14px;
|
|
106
|
+
transition: border-color 0.2s ease;
|
|
107
|
+
background: white;
|
|
108
|
+
min-height: 48px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.edit-input:focus {
|
|
112
|
+
outline: none;
|
|
113
|
+
border-color: #0070d2;
|
|
114
|
+
box-shadow: 0 0 0 3px rgba(0, 112, 210, 0.1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.edit-input::placeholder {
|
|
118
|
+
color: #9ca3af;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.edit-button {
|
|
122
|
+
padding: 8px 16px;
|
|
123
|
+
background: #0070d2;
|
|
124
|
+
color: white;
|
|
125
|
+
border: none;
|
|
126
|
+
border-radius: 4px;
|
|
127
|
+
font-weight: 500;
|
|
128
|
+
font-size: 13px;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: all 0.2s ease;
|
|
131
|
+
white-space: nowrap;
|
|
132
|
+
min-height: 48px;
|
|
133
|
+
text-transform: uppercase;
|
|
134
|
+
letter-spacing: 0.5px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.edit-button:hover:not(:disabled) {
|
|
138
|
+
background: #005fb2;
|
|
139
|
+
transform: translateY(-1px);
|
|
140
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.edit-button:active:not(:disabled) {
|
|
144
|
+
transform: translateY(0);
|
|
145
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.edit-button:disabled {
|
|
149
|
+
background: #9ca3af;
|
|
150
|
+
cursor: not-allowed;
|
|
151
|
+
transform: none;
|
|
152
|
+
box-shadow: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Loading State */
|
|
156
|
+
.loading-state {
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 12px;
|
|
160
|
+
padding: 12px;
|
|
161
|
+
background: #f8f9fa;
|
|
162
|
+
border-radius: 6px;
|
|
163
|
+
margin-top: 12px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.loading-text {
|
|
167
|
+
font-size: 13px;
|
|
168
|
+
color: #6c757d;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Error State */
|
|
172
|
+
.error-state {
|
|
173
|
+
padding: 12px;
|
|
174
|
+
background: #fef2f2;
|
|
175
|
+
border: 1px solid #fecaca;
|
|
176
|
+
border-radius: 6px;
|
|
177
|
+
margin-top: 12px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.error-message {
|
|
181
|
+
color: #dc2626;
|
|
182
|
+
font-size: 13px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Success State */
|
|
186
|
+
.success-state {
|
|
187
|
+
padding: 12px;
|
|
188
|
+
background: #f0fdf4;
|
|
189
|
+
border: 1px solid #bbf7d0;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
margin-top: 12px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.success-message {
|
|
195
|
+
color: #16a34a;
|
|
196
|
+
font-size: 13px;
|
|
197
|
+
font-weight: 500;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Close Button */
|
|
201
|
+
.popover-close {
|
|
202
|
+
position: absolute;
|
|
203
|
+
top: 8px;
|
|
204
|
+
right: 8px;
|
|
205
|
+
z-index: 10;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.close-button {
|
|
209
|
+
background: rgba(108, 117, 125, 0.1);
|
|
210
|
+
border: none;
|
|
211
|
+
color: #6c757d;
|
|
212
|
+
width: 24px;
|
|
213
|
+
height: 24px;
|
|
214
|
+
border-radius: 4px;
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
font-size: 14px;
|
|
220
|
+
font-weight: bold;
|
|
221
|
+
transition: background-color 0.2s ease;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.close-button:hover {
|
|
225
|
+
background: rgba(108, 117, 125, 0.2);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Responsive Design */
|
|
229
|
+
@media (max-width: 480px) {
|
|
230
|
+
.text-selection-search-popover {
|
|
231
|
+
min-width: 280px;
|
|
232
|
+
max-width: 90vw;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.input-with-button {
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
align-items: stretch;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.edit-button {
|
|
241
|
+
margin-top: 8px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.popover-header {
|
|
245
|
+
padding: 10px 12px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.popover-title {
|
|
249
|
+
font-size: 13px;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Animation for popover appearance */
|
|
254
|
+
@keyframes popoverFadeIn {
|
|
255
|
+
from {
|
|
256
|
+
opacity: 0;
|
|
257
|
+
transform: translateY(-10px);
|
|
258
|
+
}
|
|
259
|
+
to {
|
|
260
|
+
opacity: 1;
|
|
261
|
+
transform: translateY(0);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.text-selection-search-popover {
|
|
266
|
+
animation: popoverFadeIn 0.2s ease-out;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Scrollbar styling for results */
|
|
270
|
+
.results-list::-webkit-scrollbar {
|
|
271
|
+
width: 6px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.results-list::-webkit-scrollbar-track {
|
|
275
|
+
background: #f1f1f1;
|
|
276
|
+
border-radius: 3px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.results-list::-webkit-scrollbar-thumb {
|
|
280
|
+
background: #c1c1c1;
|
|
281
|
+
border-radius: 3px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.results-list::-webkit-scrollbar-thumb:hover {
|
|
285
|
+
background: #a8a8a8;
|
|
286
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Text Selection Search Popover -->
|
|
3
|
+
<template if:true={isVisible}>
|
|
4
|
+
<div
|
|
5
|
+
class="text-selection-search-popover"
|
|
6
|
+
class:hidden={isHidden}
|
|
7
|
+
onkeydown={handleKeyDown}
|
|
8
|
+
onclick={handlePopoverClick}
|
|
9
|
+
>
|
|
10
|
+
<!-- Header with Title -->
|
|
11
|
+
<div class="popover-header">
|
|
12
|
+
<h3 class="popover-title">Text Editor</h3>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Selected Text Display -->
|
|
16
|
+
<div class="selected-text-section">
|
|
17
|
+
<label class="section-label">Selected Text</label>
|
|
18
|
+
<div class="selected-text-display">
|
|
19
|
+
{selectedText}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Edit Form -->
|
|
24
|
+
<div class="edit-form">
|
|
25
|
+
<form onsubmit={handleSearchSubmit}>
|
|
26
|
+
<div class="input-group">
|
|
27
|
+
<label class="section-label">Edit to:</label>
|
|
28
|
+
<div class="input-with-button">
|
|
29
|
+
<input
|
|
30
|
+
type="text"
|
|
31
|
+
class="edit-input"
|
|
32
|
+
value={searchQuery}
|
|
33
|
+
onchange={handleSearchInputChange}
|
|
34
|
+
onclick={handleInputClick}
|
|
35
|
+
placeholder="Enter your edited text here..."
|
|
36
|
+
autocomplete="off"
|
|
37
|
+
autofocus
|
|
38
|
+
/>
|
|
39
|
+
<dx-button
|
|
40
|
+
type="submit"
|
|
41
|
+
variant="primary"
|
|
42
|
+
disabled={searchButtonDisabled}
|
|
43
|
+
class="edit-button"
|
|
44
|
+
>
|
|
45
|
+
Edit
|
|
46
|
+
</dx-button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</form>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Loading State -->
|
|
53
|
+
<template if:true={isLoading}>
|
|
54
|
+
<div class="loading-state">
|
|
55
|
+
<dx-spinner
|
|
56
|
+
size="small"
|
|
57
|
+
alternative-text="Editing..."
|
|
58
|
+
></dx-spinner>
|
|
59
|
+
<span class="loading-text">Processing your edit...</span>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<!-- Error State -->
|
|
64
|
+
<template if:true={hasError}>
|
|
65
|
+
<div class="error-state">
|
|
66
|
+
<span class="error-message">{errorMessage}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<!-- Success State -->
|
|
71
|
+
<template if:true={isSuccess}>
|
|
72
|
+
<div class="success-state">
|
|
73
|
+
<span class="success-message">Text edited successfully!</span>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<!-- Close Button -->
|
|
78
|
+
<div class="popover-close">
|
|
79
|
+
<dx-button
|
|
80
|
+
onclick={hidePopover}
|
|
81
|
+
variant="tertiary"
|
|
82
|
+
class="close-button"
|
|
83
|
+
title="Close editor"
|
|
84
|
+
>
|
|
85
|
+
×
|
|
86
|
+
</dx-button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
</template>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { LightningElement, api, track } from "lwc";
|
|
2
|
+
|
|
3
|
+
export default class TextSelectionSearch extends LightningElement {
|
|
4
|
+
@api searchApiUrl = "";
|
|
5
|
+
@api repoUrl = "";
|
|
6
|
+
@api placeholder = "Search for...";
|
|
7
|
+
@api popoverPosition = "top";
|
|
8
|
+
@api maxResults = 10;
|
|
9
|
+
|
|
10
|
+
@track isVisible = false;
|
|
11
|
+
@track isHidden = false;
|
|
12
|
+
@track searchQuery = "";
|
|
13
|
+
@track isLoading = false;
|
|
14
|
+
@track isSuccess = false;
|
|
15
|
+
@track errorMessage = "";
|
|
16
|
+
@track popoverStyle = "";
|
|
17
|
+
|
|
18
|
+
selectedText = "";
|
|
19
|
+
selectionStart = 0;
|
|
20
|
+
selectionEnd = 0;
|
|
21
|
+
initialPopoverStyle = "";
|
|
22
|
+
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
this.setupTextSelectionListener();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
disconnectedCallback() {
|
|
28
|
+
this.removeTextSelectionListener();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setupTextSelectionListener() {
|
|
32
|
+
console.log('TextSelectionSearch: Setting up text selection listeners');
|
|
33
|
+
document.addEventListener("mouseup", (event) => this.handleTextSelection(event));
|
|
34
|
+
document.addEventListener("keyup", (event) => this.handleTextSelection(event));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
removeTextSelectionListener() {
|
|
38
|
+
console.log('TextSelectionSearch: Removing text selection listeners');
|
|
39
|
+
document.removeEventListener("mouseup", (event) => this.handleTextSelection(event));
|
|
40
|
+
document.removeEventListener("keyup", (event) => this.handleTextSelection(event));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleTextSelection(event) {
|
|
44
|
+
if (this.isVisible) {
|
|
45
|
+
console.log('TextSelectionSearch: Popover is visible, ignoring text selection event');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('TextSelectionSearch: Text selection event triggered');
|
|
50
|
+
|
|
51
|
+
if (event && event.target) {
|
|
52
|
+
const target = event.target;
|
|
53
|
+
const popover = this.template.querySelector('.text-selection-search-popover');
|
|
54
|
+
if (popover && popover.contains(target)) {
|
|
55
|
+
console.log('TextSelectionSearch: Click inside popover, ignoring selection event');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const selection = window.getSelection();
|
|
61
|
+
|
|
62
|
+
if (!selection || selection.toString().trim() === "") {
|
|
63
|
+
if (this.isVisible) {
|
|
64
|
+
console.log('TextSelectionSearch: No text selected, but popover is visible - keeping it open');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log('TextSelectionSearch: No text selected, hiding popover');
|
|
68
|
+
this.hidePopover();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.selectedText = selection.toString().trim();
|
|
73
|
+
console.log('TextSelectionSearch: Selected text:', this.selectedText);
|
|
74
|
+
|
|
75
|
+
if (this.selectedText.length > 0) {
|
|
76
|
+
this.showPopover();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
showPopover() {
|
|
81
|
+
console.log('TextSelectionSearch: Showing popover');
|
|
82
|
+
const selection = window.getSelection();
|
|
83
|
+
if (!selection || selection.rangeCount === 0) {
|
|
84
|
+
console.log('TextSelectionSearch: No selection range found');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const range = selection.getRangeAt(0);
|
|
89
|
+
const rect = range.getBoundingClientRect();
|
|
90
|
+
|
|
91
|
+
const popoverTop = this.popoverPosition === "top"
|
|
92
|
+
? Math.max(10, rect.top - 80)
|
|
93
|
+
: Math.min(window.innerHeight - 200, rect.bottom + 20);
|
|
94
|
+
|
|
95
|
+
const popoverLeft = Math.max(10, Math.min(window.innerWidth - 350, rect.left + (rect.width / 2) - 175));
|
|
96
|
+
|
|
97
|
+
this.popoverStyle = `position: fixed; top: ${popoverTop}px; left: ${popoverLeft}px; z-index: 9999; width: 350px;`;
|
|
98
|
+
this.initialPopoverStyle = this.popoverStyle;
|
|
99
|
+
console.log('TextSelectionSearch: Popover style:', this.popoverStyle);
|
|
100
|
+
|
|
101
|
+
this.isVisible = true;
|
|
102
|
+
this.isHidden = false;
|
|
103
|
+
this.searchQuery = this.selectedText;
|
|
104
|
+
this.errorMessage = "";
|
|
105
|
+
this.isSuccess = false;
|
|
106
|
+
|
|
107
|
+
console.log('TextSelectionSearch: Popover should now be visible');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
hidePopover() {
|
|
111
|
+
console.log('TextSelectionSearch: Hiding popover');
|
|
112
|
+
this.isHidden = true;
|
|
113
|
+
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
this.isVisible = false;
|
|
116
|
+
this.searchQuery = "";
|
|
117
|
+
this.errorMessage = "";
|
|
118
|
+
this.isSuccess = false;
|
|
119
|
+
this.popoverStyle = "";
|
|
120
|
+
this.initialPopoverStyle = "";
|
|
121
|
+
|
|
122
|
+
const selection = window.getSelection();
|
|
123
|
+
if (selection) {
|
|
124
|
+
selection.removeAllRanges();
|
|
125
|
+
}
|
|
126
|
+
}, 100);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
handlePopoverClick(event) {
|
|
130
|
+
event.stopPropagation();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
handleInputClick(event) {
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
handleSearchInputChange(event) {
|
|
138
|
+
const target = event.target;
|
|
139
|
+
this.searchQuery = target.value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async handleSearchSubmit(event) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
|
|
145
|
+
if (!this.searchQuery.trim()) {
|
|
146
|
+
this.errorMessage = "Please enter edited text";
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!this.repoUrl.trim()) {
|
|
151
|
+
this.errorMessage = "Repository URL not configured";
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!this.searchApiUrl) {
|
|
156
|
+
this.errorMessage = "Search API URL not configured";
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await this.performSearch();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async performSearch() {
|
|
164
|
+
this.isLoading = true;
|
|
165
|
+
this.errorMessage = "";
|
|
166
|
+
this.isSuccess = false;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch(this.searchApiUrl, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json"
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
repoUrl: this.repoUrl,
|
|
176
|
+
selectedText: this.selectedText,
|
|
177
|
+
editedText: this.searchQuery
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (response.ok) {
|
|
182
|
+
this.isSuccess = true;
|
|
183
|
+
|
|
184
|
+
this.dispatchEvent(
|
|
185
|
+
new CustomEvent("textedited", {
|
|
186
|
+
detail: {
|
|
187
|
+
repoUrl: this.repoUrl,
|
|
188
|
+
selectedText: this.selectedText,
|
|
189
|
+
editedText: this.searchQuery,
|
|
190
|
+
success: true
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
this.hidePopover();
|
|
197
|
+
}, 2000);
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error(`Edit failed: ${response.status}`);
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error("Edit failed:", error);
|
|
203
|
+
this.errorMessage = "Edit failed. Please try again.";
|
|
204
|
+
} finally {
|
|
205
|
+
this.isLoading = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
handleKeyDown(event) {
|
|
210
|
+
if (event.key === "Escape") {
|
|
211
|
+
this.hidePopover();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get hasError() {
|
|
216
|
+
return this.errorMessage.length > 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get searchButtonDisabled() {
|
|
220
|
+
return this.isLoading || !this.searchQuery.trim();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
renderedCallback() {
|
|
224
|
+
if (this.isVisible && this.initialPopoverStyle) {
|
|
225
|
+
const popover = this.template.querySelector('.text-selection-search-popover');
|
|
226
|
+
if (popover) {
|
|
227
|
+
popover.style.cssText = this.initialPopoverStyle;
|
|
228
|
+
}
|
|
229
|
+
} else if (!this.isVisible) {
|
|
230
|
+
const popover = this.template.querySelector('.text-selection-search-popover');
|
|
231
|
+
if (popover) {
|
|
232
|
+
popover.style.cssText = '';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { LightningElement, api, track } from "lwc";
|
|
2
|
+
|
|
3
|
+
export default class TextSelectionSearch extends LightningElement {
|
|
4
|
+
@api searchApiUrl: string = "";
|
|
5
|
+
@api repoUrl: string = "";
|
|
6
|
+
@api placeholder: string = "Search for...";
|
|
7
|
+
@api popoverPosition: "top" | "bottom" = "top";
|
|
8
|
+
@api maxResults: number = 10;
|
|
9
|
+
|
|
10
|
+
@track isVisible: boolean = false;
|
|
11
|
+
@track isHidden: boolean = false;
|
|
12
|
+
@track searchQuery: string = "";
|
|
13
|
+
@track isLoading: boolean = false;
|
|
14
|
+
@track isSuccess: boolean = false;
|
|
15
|
+
@track errorMessage: string = "";
|
|
16
|
+
@track popoverStyle: string = "";
|
|
17
|
+
|
|
18
|
+
private selectedText: string = "";
|
|
19
|
+
private selectionStart: number = 0;
|
|
20
|
+
private selectionEnd: number = 0;
|
|
21
|
+
private initialPopoverStyle: string = ""; // Store initial position
|
|
22
|
+
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
this.setupTextSelectionListener();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
disconnectedCallback() {
|
|
28
|
+
this.removeTextSelectionListener();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Setup text selection listener
|
|
32
|
+
setupTextSelectionListener() {
|
|
33
|
+
console.log('TextSelectionSearch: Setting up text selection listeners');
|
|
34
|
+
document.addEventListener("mouseup", (event) => this.handleTextSelection(event));
|
|
35
|
+
document.addEventListener("keyup", (event) => this.handleTextSelection(event));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
removeTextSelectionListener() {
|
|
39
|
+
console.log('TextSelectionSearch: Removing text selection listeners');
|
|
40
|
+
document.removeEventListener("mouseup", (event) => this.handleTextSelection(event));
|
|
41
|
+
document.removeEventListener("keyup", (event) => this.handleTextSelection(event));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle text selection
|
|
45
|
+
handleTextSelection(event?: Event) {
|
|
46
|
+
// If popover is visible, completely ignore all text selection events
|
|
47
|
+
if (this.isVisible) {
|
|
48
|
+
console.log('TextSelectionSearch: Popover is visible, ignoring text selection event');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('TextSelectionSearch: Text selection event triggered');
|
|
53
|
+
|
|
54
|
+
// Check if the event target is inside our popover
|
|
55
|
+
if (event && event.target) {
|
|
56
|
+
const target = event.target as Element;
|
|
57
|
+
const popover = this.template.querySelector('.text-selection-search-popover');
|
|
58
|
+
if (popover && popover.contains(target)) {
|
|
59
|
+
console.log('TextSelectionSearch: Click inside popover, ignoring selection event');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const selection = window.getSelection();
|
|
65
|
+
|
|
66
|
+
if (!selection || selection.toString().trim() === "") {
|
|
67
|
+
// Only hide popover if it's currently visible and we're not clicking inside it
|
|
68
|
+
if (this.isVisible) {
|
|
69
|
+
console.log('TextSelectionSearch: No text selected, but popover is visible - keeping it open');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log('TextSelectionSearch: No text selected, hiding popover');
|
|
73
|
+
this.hidePopover();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.selectedText = selection.toString().trim();
|
|
78
|
+
console.log('TextSelectionSearch: Selected text:', this.selectedText);
|
|
79
|
+
|
|
80
|
+
if (this.selectedText.length > 0) {
|
|
81
|
+
this.showPopover();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Show popover at selection position
|
|
86
|
+
showPopover() {
|
|
87
|
+
console.log('TextSelectionSearch: Showing popover');
|
|
88
|
+
const selection = window.getSelection();
|
|
89
|
+
if (!selection || selection.rangeCount === 0) {
|
|
90
|
+
console.log('TextSelectionSearch: No selection range found');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const range = selection.getRangeAt(0);
|
|
95
|
+
const rect = range.getBoundingClientRect();
|
|
96
|
+
|
|
97
|
+
// Calculate popover position with more conservative positioning
|
|
98
|
+
const popoverTop = this.popoverPosition === "top"
|
|
99
|
+
? Math.max(10, rect.top - 80) // Ensure it doesn't go off-screen
|
|
100
|
+
: Math.min(window.innerHeight - 200, rect.bottom + 20);
|
|
101
|
+
|
|
102
|
+
const popoverLeft = Math.max(10, Math.min(window.innerWidth - 350, rect.left + (rect.width / 2) - 175));
|
|
103
|
+
|
|
104
|
+
this.popoverStyle = `position: fixed; top: ${popoverTop}px; left: ${popoverLeft}px; z-index: 9999; width: 350px;`;
|
|
105
|
+
this.initialPopoverStyle = this.popoverStyle; // Store the initial position
|
|
106
|
+
console.log('TextSelectionSearch: Popover style:', this.popoverStyle);
|
|
107
|
+
|
|
108
|
+
this.isVisible = true;
|
|
109
|
+
this.isHidden = false;
|
|
110
|
+
this.searchQuery = this.selectedText;
|
|
111
|
+
this.errorMessage = "";
|
|
112
|
+
this.isSuccess = false;
|
|
113
|
+
|
|
114
|
+
console.log('TextSelectionSearch: Popover should now be visible');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Hide popover
|
|
118
|
+
hidePopover() {
|
|
119
|
+
console.log('TextSelectionSearch: Hiding popover');
|
|
120
|
+
this.isHidden = true;
|
|
121
|
+
|
|
122
|
+
// Use setTimeout to ensure the hidden class is applied before hiding
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
this.isVisible = false;
|
|
125
|
+
this.searchQuery = "";
|
|
126
|
+
this.errorMessage = "";
|
|
127
|
+
this.isSuccess = false;
|
|
128
|
+
this.popoverStyle = ""; // Reset the positioning style
|
|
129
|
+
this.initialPopoverStyle = ""; // Reset the initial position
|
|
130
|
+
|
|
131
|
+
// Clear any text selection to prevent immediate re-showing
|
|
132
|
+
const selection = window.getSelection();
|
|
133
|
+
if (selection) {
|
|
134
|
+
selection.removeAllRanges();
|
|
135
|
+
}
|
|
136
|
+
}, 100);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Handle popover click to prevent closing
|
|
140
|
+
handlePopoverClick(event: Event) {
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle input click to prevent closing
|
|
145
|
+
handleInputClick(event: Event) {
|
|
146
|
+
event.stopPropagation();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle search input change
|
|
150
|
+
handleSearchInputChange(event: Event) {
|
|
151
|
+
const target = event.target as HTMLInputElement;
|
|
152
|
+
this.searchQuery = target.value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle search submission
|
|
156
|
+
async handleSearchSubmit(event: Event) {
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
|
|
159
|
+
if (!this.searchQuery.trim()) {
|
|
160
|
+
this.errorMessage = "Please enter edited text";
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!this.repoUrl.trim()) {
|
|
165
|
+
this.errorMessage = "Repository URL not configured";
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!this.searchApiUrl) {
|
|
170
|
+
this.errorMessage = "Search API URL not configured";
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await this.performSearch();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Perform search API call
|
|
178
|
+
async performSearch() {
|
|
179
|
+
this.isLoading = true;
|
|
180
|
+
this.errorMessage = "";
|
|
181
|
+
this.isSuccess = false;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const response = await fetch(this.searchApiUrl, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: {
|
|
187
|
+
"Content-Type": "application/json"
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
repoUrl: this.repoUrl,
|
|
191
|
+
selectedText: this.selectedText,
|
|
192
|
+
editedText: this.searchQuery
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (response.ok) {
|
|
197
|
+
this.isSuccess = true;
|
|
198
|
+
|
|
199
|
+
// Dispatch success event
|
|
200
|
+
this.dispatchEvent(
|
|
201
|
+
new CustomEvent("textedited", {
|
|
202
|
+
detail: {
|
|
203
|
+
repoUrl: this.repoUrl,
|
|
204
|
+
selectedText: this.selectedText,
|
|
205
|
+
editedText: this.searchQuery,
|
|
206
|
+
success: true
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Auto-hide popover after success
|
|
212
|
+
setTimeout(() => {
|
|
213
|
+
this.hidePopover();
|
|
214
|
+
}, 2000);
|
|
215
|
+
} else {
|
|
216
|
+
throw new Error(`Edit failed: ${response.status}`);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error("Edit failed:", error);
|
|
220
|
+
this.errorMessage = "Edit failed. Please try again.";
|
|
221
|
+
} finally {
|
|
222
|
+
this.isLoading = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Handle escape key
|
|
227
|
+
handleKeyDown(event: KeyboardEvent) {
|
|
228
|
+
if (event.key === "Escape") {
|
|
229
|
+
this.hidePopover();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Getters for UI state
|
|
234
|
+
get hasError() {
|
|
235
|
+
return this.errorMessage.length > 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
get searchButtonDisabled() {
|
|
239
|
+
return this.isLoading || !this.searchQuery.trim();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
renderedCallback() {
|
|
243
|
+
if (this.isVisible && this.initialPopoverStyle) {
|
|
244
|
+
const popover = this.template.querySelector('.text-selection-search-popover') as HTMLElement;
|
|
245
|
+
if (popover) {
|
|
246
|
+
// Always use the initial position, never recalculate
|
|
247
|
+
popover.style.cssText = this.initialPopoverStyle;
|
|
248
|
+
}
|
|
249
|
+
} else if (!this.isVisible) {
|
|
250
|
+
// Clear the style when popover is hidden
|
|
251
|
+
const popover = this.template.querySelector('.text-selection-search-popover') as HTMLElement;
|
|
252
|
+
if (popover) {
|
|
253
|
+
popover.style.cssText = '';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|