@oix1987/yjd 1.0.0 → 1.0.2
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/README.md +73 -22
- package/dist/rich-editor.esm.js +2 -0
- package/dist/rich-editor.esm.js.map +1 -0
- package/dist/rich-editor.min.js +2 -0
- package/dist/rich-editor.min.js.map +1 -0
- package/package.json +12 -7
- package/index.js +0 -221
- package/lib/core/editor.js +0 -1175
- package/lib/core/format.js +0 -542
- package/lib/core/module.js +0 -81
- package/lib/core/registry.js +0 -152
- package/lib/formats/background.js +0 -212
- package/lib/formats/bold.js +0 -67
- package/lib/formats/capitalization.js +0 -563
- package/lib/formats/color.js +0 -165
- package/lib/formats/emoji.js +0 -282
- package/lib/formats/font-family.js +0 -547
- package/lib/formats/heading.js +0 -502
- package/lib/formats/image.js +0 -344
- package/lib/formats/import.js +0 -385
- package/lib/formats/indent.js +0 -297
- package/lib/formats/italic.js +0 -27
- package/lib/formats/line-height.js +0 -558
- package/lib/formats/link.js +0 -251
- package/lib/formats/list.js +0 -635
- package/lib/formats/strike.js +0 -31
- package/lib/formats/subscript.js +0 -36
- package/lib/formats/superscript.js +0 -35
- package/lib/formats/table.js +0 -288
- package/lib/formats/tag.js +0 -304
- package/lib/formats/text-align.js +0 -421
- package/lib/formats/text-size.js +0 -497
- package/lib/formats/underline.js +0 -30
- package/lib/formats/video.js +0 -372
- package/lib/modules/block-toolbar.js +0 -628
- package/lib/modules/code-view.js +0 -434
- package/lib/modules/history.js +0 -410
- package/lib/modules/resize-handles.js +0 -677
- package/lib/modules/table-toolbar.js +0 -618
- package/lib/modules/toolbar.js +0 -424
- package/lib/styles-loader.js +0 -144
- package/lib/styles.css +0 -2123
- package/lib/ui/color-picker.js +0 -296
- package/lib/ui/customselect.js +0 -319
- package/lib/ui/emoji-picker.js +0 -196
- package/lib/ui/icons.js +0 -413
- package/lib/ui/image-popup.js +0 -444
- package/lib/ui/import-popup.js +0 -288
- package/lib/ui/link-popup.js +0 -191
- package/lib/ui/list-picker.js +0 -307
- package/lib/ui/select-button.js +0 -61
- package/lib/ui/table-popup.js +0 -171
- package/lib/ui/tag-popup.js +0 -249
- package/lib/ui/text-align-picker.js +0 -281
- package/lib/ui/video-popup.js +0 -422
- package/lib/utils/history-helper.js +0 -50
- package/lib/utils/popup-helper.js +0 -219
- package/lib/utils/popup-positioning.js +0 -231
package/lib/ui/color-picker.js
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Color Picker Component - Simple color picker with popup
|
|
3
|
-
*/
|
|
4
|
-
import IconUtils from './icons.js';
|
|
5
|
-
import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
|
|
6
|
-
|
|
7
|
-
class ColorPicker {
|
|
8
|
-
constructor(options = {}) {
|
|
9
|
-
this.options = {
|
|
10
|
-
colors: [
|
|
11
|
-
'#000000', '#333333', '#666666', '#999999', '#cccccc', '#eeeeee',
|
|
12
|
-
'#ff0000', '#ff6600', '#ffcc00', '#ffff00', '#99ff00', '#00ff00',
|
|
13
|
-
'#00ffcc', '#00ccff', '#0066ff', '#0000ff', '#6600ff', '#cc00ff',
|
|
14
|
-
'#ff00cc', '#ff0066', '#800000', '#ff8000', '#808000', '#008000',
|
|
15
|
-
'#008080', '#0080ff', '#004080', '#800080', '#804080', '#ff0080'
|
|
16
|
-
],
|
|
17
|
-
customColorEnabled: true,
|
|
18
|
-
onColorSelect: null,
|
|
19
|
-
editor: null,
|
|
20
|
-
...options
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
this.popup = null;
|
|
24
|
-
this.isVisible = false;
|
|
25
|
-
this.currentColor = '#000000';
|
|
26
|
-
this.clickOutsideHandler = null;
|
|
27
|
-
|
|
28
|
-
this.createColorPicker();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Create color picker popup
|
|
33
|
-
*/
|
|
34
|
-
createColorPicker() {
|
|
35
|
-
// Create popup
|
|
36
|
-
this.popup = document.createElement('div');
|
|
37
|
-
this.popup.className = 'color-picker-popup';
|
|
38
|
-
|
|
39
|
-
// Create color grid
|
|
40
|
-
this.createColorGrid();
|
|
41
|
-
|
|
42
|
-
// Create custom color input if enabled
|
|
43
|
-
if (this.options.customColorEnabled) {
|
|
44
|
-
this.createCustomColorInput();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Add popup to container
|
|
48
|
-
appendPopup(this.popup);
|
|
49
|
-
|
|
50
|
-
// Prevent focus loss when clicking on popup
|
|
51
|
-
if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {
|
|
52
|
-
this.options.editor.preventFocusLoss(this.popup);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Create color grid
|
|
58
|
-
*/
|
|
59
|
-
createColorGrid() {
|
|
60
|
-
const grid = document.createElement('div');
|
|
61
|
-
grid.className = 'color-grid';
|
|
62
|
-
|
|
63
|
-
this.options.colors.forEach(color => {
|
|
64
|
-
const colorButton = document.createElement('button');
|
|
65
|
-
colorButton.type = 'button';
|
|
66
|
-
colorButton.className = 'color-button';
|
|
67
|
-
colorButton.style.backgroundColor = color;
|
|
68
|
-
colorButton.dataset.color = color;
|
|
69
|
-
colorButton.title = color;
|
|
70
|
-
|
|
71
|
-
colorButton.addEventListener('click', (e) => {
|
|
72
|
-
e.preventDefault();
|
|
73
|
-
e.stopPropagation();
|
|
74
|
-
this.selectColor(color);
|
|
75
|
-
// Maintain editor focus after color selection
|
|
76
|
-
if (this.options.editor) {
|
|
77
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
grid.appendChild(colorButton);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
this.popup.appendChild(grid);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Create custom color input
|
|
89
|
-
*/
|
|
90
|
-
createCustomColorInput() {
|
|
91
|
-
const customContainer = document.createElement('div');
|
|
92
|
-
customContainer.className = 'custom-color-container';
|
|
93
|
-
|
|
94
|
-
// No color button
|
|
95
|
-
const noColorButton = document.createElement('button');
|
|
96
|
-
noColorButton.type = 'button';
|
|
97
|
-
noColorButton.className = 'color-button no-color-button';
|
|
98
|
-
noColorButton.title = 'No Color';
|
|
99
|
-
noColorButton.style.backgroundColor = 'transparent';
|
|
100
|
-
|
|
101
|
-
// Add icon to button
|
|
102
|
-
const noColorIcon = IconUtils.createIconElement('no-color', {
|
|
103
|
-
width: '24',
|
|
104
|
-
height: '24'
|
|
105
|
-
});
|
|
106
|
-
noColorButton.appendChild(noColorIcon);
|
|
107
|
-
|
|
108
|
-
noColorButton.addEventListener('click', (e) => {
|
|
109
|
-
e.preventDefault();
|
|
110
|
-
e.stopPropagation();
|
|
111
|
-
this.selectColor('transparent');
|
|
112
|
-
// Maintain editor focus after color selection
|
|
113
|
-
if (this.options.editor) {
|
|
114
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// White button
|
|
119
|
-
const whiteButton = document.createElement('button');
|
|
120
|
-
whiteButton.type = 'button';
|
|
121
|
-
whiteButton.className = 'color-button white-button';
|
|
122
|
-
whiteButton.style.backgroundColor = '#ffffff';
|
|
123
|
-
whiteButton.style.border = '1px solid #ccc';
|
|
124
|
-
whiteButton.title = 'White';
|
|
125
|
-
|
|
126
|
-
whiteButton.addEventListener('click', (e) => {
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
e.stopPropagation();
|
|
129
|
-
this.selectColor('#ffffff');
|
|
130
|
-
// Maintain editor focus after color selection
|
|
131
|
-
if (this.options.editor) {
|
|
132
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Black button
|
|
137
|
-
const blackButton = document.createElement('button');
|
|
138
|
-
blackButton.type = 'button';
|
|
139
|
-
blackButton.className = 'color-button black-button';
|
|
140
|
-
blackButton.style.backgroundColor = '#000000';
|
|
141
|
-
blackButton.title = 'Black';
|
|
142
|
-
|
|
143
|
-
blackButton.addEventListener('click', (e) => {
|
|
144
|
-
e.preventDefault();
|
|
145
|
-
e.stopPropagation();
|
|
146
|
-
this.selectColor('#000000');
|
|
147
|
-
// Maintain editor focus after color selection
|
|
148
|
-
if (this.options.editor) {
|
|
149
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Custom color button with hidden input
|
|
154
|
-
const customColorButton = document.createElement('button');
|
|
155
|
-
customColorButton.type = 'button';
|
|
156
|
-
customColorButton.className = 'color-button custom-color-button';
|
|
157
|
-
customColorButton.title = 'Custom Color';
|
|
158
|
-
customColorButton.style.backgroundColor = 'transparent';
|
|
159
|
-
customColorButton.style.border = '1px solid #ccc';
|
|
160
|
-
customColorButton.style.font = 'none !important';
|
|
161
|
-
// Add icon to button
|
|
162
|
-
const iconElement = IconUtils.createIconElement('custom-color', {
|
|
163
|
-
width: '16px',
|
|
164
|
-
height: '16px'
|
|
165
|
-
});
|
|
166
|
-
customColorButton.appendChild(iconElement);
|
|
167
|
-
|
|
168
|
-
const customInput = document.createElement('input');
|
|
169
|
-
customInput.type = 'color';
|
|
170
|
-
customInput.className = 'custom-color-input';
|
|
171
|
-
customInput.value = this.currentColor;
|
|
172
|
-
customInput.style.visibility = 'hidden';
|
|
173
|
-
customInput.style.pointerEvents = 'none'; // ngăn không cho click
|
|
174
|
-
customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác
|
|
175
|
-
customColorButton.addEventListener('click', (e) => {
|
|
176
|
-
customInput.style.visibility = 'visible';
|
|
177
|
-
customInput.style.pointerEvents = 'auto';
|
|
178
|
-
customInput.style.opacity = '1';
|
|
179
|
-
e.preventDefault();
|
|
180
|
-
e.stopPropagation();
|
|
181
|
-
customInput.click();
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
customInput.addEventListener('change', (e) => {
|
|
186
|
-
customInput.style.visibility = 'hidden';
|
|
187
|
-
customInput.style.pointerEvents = 'none'; // ngăn không cho click
|
|
188
|
-
customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác
|
|
189
|
-
this.selectColor(e.target.value);
|
|
190
|
-
// Maintain editor focus after color selection
|
|
191
|
-
if (this.options.editor) {
|
|
192
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
customContainer.appendChild(noColorButton);
|
|
197
|
-
customContainer.appendChild(whiteButton);
|
|
198
|
-
customContainer.appendChild(blackButton);
|
|
199
|
-
customContainer.appendChild(customColorButton);
|
|
200
|
-
customContainer.appendChild(customInput);
|
|
201
|
-
|
|
202
|
-
this.popup.appendChild(customContainer);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Setup click outside handler
|
|
207
|
-
*/
|
|
208
|
-
setupClickOutside() {
|
|
209
|
-
if (this.clickOutsideHandler) {
|
|
210
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
this.clickOutsideHandler = (e) => {
|
|
214
|
-
if (!this.popup.contains(e.target)) {
|
|
215
|
-
this.hide();
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Add slight delay to avoid immediate close
|
|
220
|
-
setTimeout(() => {
|
|
221
|
-
document.addEventListener('click', this.clickOutsideHandler);
|
|
222
|
-
}, 100);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Remove click outside handler
|
|
227
|
-
*/
|
|
228
|
-
removeClickOutside() {
|
|
229
|
-
if (this.clickOutsideHandler) {
|
|
230
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
231
|
-
this.clickOutsideHandler = null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Show color picker popup
|
|
237
|
-
* @param {HTMLElement} anchor - Element to position popup relative to
|
|
238
|
-
*/
|
|
239
|
-
show(anchor) {
|
|
240
|
-
if (!anchor) return;
|
|
241
|
-
|
|
242
|
-
// Ensure popup is in DOM
|
|
243
|
-
if (!document.body.contains(this.popup)) {
|
|
244
|
-
appendPopup(this.popup);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Calculate and set popup position
|
|
248
|
-
const position = calculatePopupPosition(anchor, this.popup, {
|
|
249
|
-
offsetY: 5,
|
|
250
|
-
offsetX: 0
|
|
251
|
-
});
|
|
252
|
-
setPopupPosition(this.popup, position);
|
|
253
|
-
|
|
254
|
-
// Show popup by adding visible class
|
|
255
|
-
this.popup.classList.add('visible');
|
|
256
|
-
this.isVisible = true;
|
|
257
|
-
|
|
258
|
-
// Setup click outside handler
|
|
259
|
-
this.setupClickOutside();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Hide color picker popup
|
|
264
|
-
*/
|
|
265
|
-
hide() {
|
|
266
|
-
this.popup.classList.remove('visible');
|
|
267
|
-
this.isVisible = false;
|
|
268
|
-
this.removeClickOutside();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Select color and trigger callback
|
|
273
|
-
* @param {string} color - Selected color
|
|
274
|
-
*/
|
|
275
|
-
selectColor(color) {
|
|
276
|
-
this.currentColor = color;
|
|
277
|
-
|
|
278
|
-
if (this.options.onColorSelect) {
|
|
279
|
-
this.options.onColorSelect(color);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
this.hide();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Destroy color picker
|
|
287
|
-
*/
|
|
288
|
-
destroy() {
|
|
289
|
-
this.removeClickOutside();
|
|
290
|
-
if (this.popup && this.popup.parentNode) {
|
|
291
|
-
this.popup.parentNode.removeChild(this.popup);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export default ColorPicker;
|
package/lib/ui/customselect.js
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import IconUtils from './icons.js';
|
|
2
|
-
import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Custom Select Component - Reusable dropdown/popup select component
|
|
6
|
-
*/
|
|
7
|
-
class CustomSelect {
|
|
8
|
-
constructor(options = {}) {
|
|
9
|
-
this.options = {
|
|
10
|
-
items: [], // Array of items to display
|
|
11
|
-
onItemSelect: null, // Callback when item is selected
|
|
12
|
-
displayProperty: 'label', // Property to display as text
|
|
13
|
-
valueProperty: 'value', // Property to use as value
|
|
14
|
-
className: 'custom-select', // CSS class for the popup
|
|
15
|
-
width: 200, // Popup width
|
|
16
|
-
height: 280, // Popup height
|
|
17
|
-
...options
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
this.popup = null;
|
|
21
|
-
this.isVisible = false;
|
|
22
|
-
this.currentValue = null;
|
|
23
|
-
this.clickOutsideHandler = null;
|
|
24
|
-
this.initialized = false;
|
|
25
|
-
|
|
26
|
-
this.createSelect();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create select popup
|
|
31
|
-
*/
|
|
32
|
-
createSelect() {
|
|
33
|
-
// Create popup
|
|
34
|
-
this.popup = document.createElement('div');
|
|
35
|
-
this.popup.className = `${this.options.className}-popup`;
|
|
36
|
-
|
|
37
|
-
// Add popup to container
|
|
38
|
-
appendPopup(this.popup);
|
|
39
|
-
|
|
40
|
-
// Initialize async
|
|
41
|
-
this.init();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Initialize component with async operations
|
|
46
|
-
*/
|
|
47
|
-
async init() {
|
|
48
|
-
// Create item list
|
|
49
|
-
await this.createItemList();
|
|
50
|
-
this.initialized = true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Create item list
|
|
55
|
-
*/
|
|
56
|
-
async createItemList() {
|
|
57
|
-
const list = document.createElement('div');
|
|
58
|
-
list.className = 'item-list';
|
|
59
|
-
|
|
60
|
-
// Get check icon
|
|
61
|
-
const checkIconSvg = IconUtils.getIcon('check');
|
|
62
|
-
|
|
63
|
-
this.options.items.forEach(item => {
|
|
64
|
-
const itemButton = document.createElement('button');
|
|
65
|
-
itemButton.type = 'button';
|
|
66
|
-
itemButton.className = 'custom-select-item-button';
|
|
67
|
-
itemButton.dataset.value = this.getItemValue(item);
|
|
68
|
-
|
|
69
|
-
// Create item content with text and checkmark
|
|
70
|
-
const itemText = document.createElement('div');
|
|
71
|
-
itemText.className = 'item-text';
|
|
72
|
-
itemText.innerHTML = this.getItemDisplay(item);
|
|
73
|
-
|
|
74
|
-
const checkmark = document.createElement('span');
|
|
75
|
-
checkmark.className = 'item-checkmark';
|
|
76
|
-
checkmark.innerHTML = checkIconSvg || '';
|
|
77
|
-
|
|
78
|
-
itemButton.appendChild(itemText);
|
|
79
|
-
itemButton.appendChild(checkmark);
|
|
80
|
-
|
|
81
|
-
itemButton.addEventListener('click', (e) => {
|
|
82
|
-
e.preventDefault();
|
|
83
|
-
e.stopPropagation();
|
|
84
|
-
this.selectItem(item);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
list.appendChild(itemButton);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
this.popup.appendChild(list);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get display text for item
|
|
95
|
-
*/
|
|
96
|
-
getItemDisplay(item) {
|
|
97
|
-
return item[this.options.displayProperty] || item.toString();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Get value for item
|
|
102
|
-
*/
|
|
103
|
-
getItemValue(item) {
|
|
104
|
-
return item[this.options.valueProperty] || item[this.options.displayProperty] || item;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Update items in the select
|
|
109
|
-
*/
|
|
110
|
-
async updateItems(items) {
|
|
111
|
-
this.options.items = items;
|
|
112
|
-
|
|
113
|
-
// Remove existing list
|
|
114
|
-
const existingList = this.popup.querySelector('.item-list');
|
|
115
|
-
if (existingList) {
|
|
116
|
-
existingList.remove();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Create new list
|
|
120
|
-
await this.createItemList();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Setup click outside handler
|
|
125
|
-
*/
|
|
126
|
-
setupClickOutside() {
|
|
127
|
-
if (this.clickOutsideHandler) {
|
|
128
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.clickOutsideHandler = (e) => {
|
|
132
|
-
// Don't hide if clicking on block toolbar or its buttons
|
|
133
|
-
if (e.target.closest('.block-toolbar')) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!this.popup.contains(e.target)) {
|
|
138
|
-
this.hide();
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Add slight delay to avoid immediate close
|
|
143
|
-
setTimeout(() => {
|
|
144
|
-
document.addEventListener('click', this.clickOutsideHandler);
|
|
145
|
-
}, 100);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Remove click outside handler
|
|
150
|
-
*/
|
|
151
|
-
removeClickOutside() {
|
|
152
|
-
if (this.clickOutsideHandler) {
|
|
153
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
154
|
-
this.clickOutsideHandler = null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Setup scroll handler to update popup position
|
|
160
|
-
*/
|
|
161
|
-
setupScrollHandler() {
|
|
162
|
-
if (this.scrollHandler) {
|
|
163
|
-
window.removeEventListener('scroll', this.scrollHandler);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this.scrollHandler = () => {
|
|
167
|
-
if (this.isVisible) {
|
|
168
|
-
this.updatePosition();
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
window.addEventListener('scroll', this.scrollHandler);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Remove scroll handler
|
|
177
|
-
*/
|
|
178
|
-
removeScrollHandler() {
|
|
179
|
-
if (this.scrollHandler) {
|
|
180
|
-
window.removeEventListener('scroll', this.scrollHandler);
|
|
181
|
-
this.scrollHandler = null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Show select popup
|
|
187
|
-
*/
|
|
188
|
-
async show(anchor) {
|
|
189
|
-
if (!anchor) return;
|
|
190
|
-
|
|
191
|
-
// Wait for initialization if not ready
|
|
192
|
-
if (!this.initialized) {
|
|
193
|
-
await new Promise(resolve => {
|
|
194
|
-
const checkInit = () => {
|
|
195
|
-
if (this.initialized) {
|
|
196
|
-
resolve();
|
|
197
|
-
} else {
|
|
198
|
-
setTimeout(checkInit, 10);
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
checkInit();
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Ensure popup is in DOM
|
|
206
|
-
if (!document.body.contains(this.popup)) {
|
|
207
|
-
appendPopup(this.popup);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Update current selection highlight
|
|
211
|
-
this.highlightCurrentItem(this.currentValue);
|
|
212
|
-
|
|
213
|
-
// Calculate and set popup position
|
|
214
|
-
const position = calculatePopupPosition(anchor, this.popup, {
|
|
215
|
-
offsetY: 5,
|
|
216
|
-
offsetX: 0
|
|
217
|
-
});
|
|
218
|
-
setPopupPosition(this.popup, position);
|
|
219
|
-
|
|
220
|
-
// Show popup by adding visible class
|
|
221
|
-
this.popup.classList.add('visible');
|
|
222
|
-
this.isVisible = true;
|
|
223
|
-
|
|
224
|
-
// Setup click outside handler
|
|
225
|
-
this.setupClickOutside();
|
|
226
|
-
|
|
227
|
-
// Setup scroll handler to update position
|
|
228
|
-
this.setupScrollHandler();
|
|
229
|
-
|
|
230
|
-
// Store reference to anchor for potential repositioning
|
|
231
|
-
this.currentAnchor = anchor;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Hide select popup
|
|
236
|
-
*/
|
|
237
|
-
hide() {
|
|
238
|
-
this.popup.classList.remove('visible');
|
|
239
|
-
this.isVisible = false;
|
|
240
|
-
this.removeClickOutside();
|
|
241
|
-
this.currentAnchor = null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Update popup position based on current anchor
|
|
246
|
-
*/
|
|
247
|
-
updatePosition() {
|
|
248
|
-
if (this.isVisible && this.currentAnchor) {
|
|
249
|
-
// Calculate and set popup position
|
|
250
|
-
const position = calculatePopupPosition(this.currentAnchor, this.popup, {
|
|
251
|
-
offsetY: 5,
|
|
252
|
-
offsetX: 0
|
|
253
|
-
});
|
|
254
|
-
setPopupPosition(this.popup, position);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Set current value
|
|
260
|
-
*/
|
|
261
|
-
setCurrentValue(value) {
|
|
262
|
-
this.currentValue = value;
|
|
263
|
-
this.highlightCurrentItem(value);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Highlight current item in the list
|
|
268
|
-
*/
|
|
269
|
-
highlightCurrentItem(value) {
|
|
270
|
-
// Remove previous highlights
|
|
271
|
-
this.popup.querySelectorAll('.custom-select-item-button.current').forEach(btn => {
|
|
272
|
-
btn.classList.remove('current');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Highlight current item - find by comparing dataset.value directly
|
|
276
|
-
if (value != null) {
|
|
277
|
-
const buttons = this.popup.querySelectorAll('.custom-select-item-button');
|
|
278
|
-
for (const button of buttons) {
|
|
279
|
-
if (button.dataset.value === value.toString()) {
|
|
280
|
-
button.classList.add('current');
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Select item and trigger callback
|
|
289
|
-
*/
|
|
290
|
-
selectItem(item) {
|
|
291
|
-
const value = this.getItemValue(item);
|
|
292
|
-
this.currentValue = value;
|
|
293
|
-
|
|
294
|
-
if (this.options.onItemSelect) {
|
|
295
|
-
this.options.onItemSelect(value, item);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
this.hide();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Get current selected value
|
|
303
|
-
*/
|
|
304
|
-
getCurrentValue() {
|
|
305
|
-
return this.currentValue;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Destroy select component
|
|
310
|
-
*/
|
|
311
|
-
destroy() {
|
|
312
|
-
this.removeClickOutside();
|
|
313
|
-
if (this.popup && this.popup.parentNode) {
|
|
314
|
-
this.popup.parentNode.removeChild(this.popup);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export default CustomSelect;
|