vue2-excel-grid 1.1.13 → 1.1.16

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.
@@ -220,23 +220,19 @@
220
220
  >
221
221
  <div v-if="isMergedAway(ri, ci)" style="display:none"></div>
222
222
  <template v-else-if="isEditing(ri, ci)">
223
- <slot v-if="hasEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
223
+ <slot v-if="hasCellSlot(col)" :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :isEditing="true" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
224
+ <slot v-else-if="hasLegacyEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
224
225
  <input v-else ref="editInput" class="eg-cell-input" v-model="editValue" @blur="commitEdit" @keydown.stop="onEditKeyDown" />
225
226
  </template>
226
227
  <template v-else-if="col.type === 'checkbox'">
227
- <label class="eg-checkbox" :class="{ 'eg-checkbox--disabled': !isCellEditable(ri, ci) }">
228
- <input
229
- type="checkbox"
230
- class="eg-checkbox-input"
231
- :checked="isCheckboxChecked(ri, ci)"
232
- :disabled="!isCellEditable(ri, ci)"
233
- @change="onCheckboxChange($event, ri, ci)"
234
- />
235
- <span class="eg-checkbox-box"></span>
236
- </label>
228
+ <eg-checkbox
229
+ :value="isCheckboxChecked(ri, ci)"
230
+ :disabled="!isCellEditable(ri, ci)"
231
+ @change="(v) => setCheckboxValue(ri, ci, v)"
232
+ />
237
233
  </template>
238
234
  <template v-else>
239
- <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)">
235
+ <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :isEditing="false" :done="null" :cancel="null">
240
236
  {{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}
241
237
  </slot>
242
238
  </template>
@@ -278,7 +274,8 @@
278
274
  >
279
275
  <div v-if="isMergedAway(ri, ci)" style="display:none"></div>
280
276
  <template v-else-if="isEditing(ri, ci)">
281
- <slot v-if="hasEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
277
+ <slot v-if="hasCellSlot(col)" :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :isEditing="true" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
278
+ <slot v-else-if="hasLegacyEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
282
279
  <input
283
280
  v-else
284
281
  ref="editInput"
@@ -289,19 +286,14 @@
289
286
  />
290
287
  </template>
291
288
  <template v-else-if="col.type === 'checkbox'">
292
- <label class="eg-checkbox" :class="{ 'eg-checkbox--disabled': !isCellEditable(ri, ci) }">
293
- <input
294
- type="checkbox"
295
- class="eg-checkbox-input"
296
- :checked="isCheckboxChecked(ri, ci)"
297
- :disabled="!isCellEditable(ri, ci)"
298
- @change="onCheckboxChange($event, ri, ci)"
299
- />
300
- <span class="eg-checkbox-box"></span>
301
- </label>
289
+ <eg-checkbox
290
+ :value="isCheckboxChecked(ri, ci)"
291
+ :disabled="!isCellEditable(ri, ci)"
292
+ @change="(v) => setCheckboxValue(ri, ci, v)"
293
+ />
302
294
  </template>
303
295
  <template v-else>
304
- <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)">
296
+ <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, ci)" :isEditing="false" :done="null" :cancel="null">
305
297
  {{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}
306
298
  </slot>
307
299
  </template>
@@ -336,23 +328,19 @@
336
328
  >
337
329
  <div v-if="isMergedAway(ri, col._flatIndex)" style="display:none"></div>
338
330
  <template v-else-if="isEditing(ri, col._flatIndex)">
339
- <slot v-if="hasEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, col._flatIndex)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
331
+ <slot v-if="hasCellSlot(col)" :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, col._flatIndex)" :isEditing="true" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
332
+ <slot v-else-if="hasLegacyEditSlot(col)" :name="'edit-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, col._flatIndex)" :done="(v) => commitEditSlot(v)" :cancel="cancelEdit" />
340
333
  <input v-else ref="editInput" class="eg-cell-input" v-model="editValue" @blur="commitEdit" @keydown.stop="onEditKeyDown" />
341
334
  </template>
342
335
  <template v-else-if="col.type === 'checkbox'">
343
- <label class="eg-checkbox" :class="{ 'eg-checkbox--disabled': !isCellEditable(ri, col._flatIndex) }">
344
- <input
345
- type="checkbox"
346
- class="eg-checkbox-input"
347
- :checked="isCheckboxChecked(ri, col._flatIndex)"
348
- :disabled="!isCellEditable(ri, col._flatIndex)"
349
- @change="onCheckboxChange($event, ri, col._flatIndex)"
350
- />
351
- <span class="eg-checkbox-box"></span>
352
- </label>
336
+ <eg-checkbox
337
+ :value="isCheckboxChecked(ri, col._flatIndex)"
338
+ :disabled="!isCellEditable(ri, col._flatIndex)"
339
+ @change="(v) => setCheckboxValue(ri, col._flatIndex, v)"
340
+ />
353
341
  </template>
354
342
  <template v-else>
355
- <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, col._flatIndex)">
343
+ <slot :name="'cell-' + col.key" :row="_dataRaw[ri]" :rowIndex="ri" :column="col" :value="getCellValue(ri, col._flatIndex)" :isEditing="false" :done="null" :cancel="null">
356
344
  {{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}
357
345
  </slot>
358
346
  </template>
@@ -466,49 +454,33 @@
466
454
  </div>
467
455
  </div>
468
456
 
469
- <!-- Select dropdown overlay -->
470
- <div
471
- v-if="selectDropdown"
472
- class="eg-select-dropdown"
473
- :style="dropdownStyle"
474
- @mousedown.prevent
475
- >
476
- <div v-if="selectDropdown.enableSearch" class="eg-select-search" @mousedown.stop>
477
- <input
478
- ref="dropdownSearch"
479
- class="eg-select-search-input"
480
- :value="selectDropdown.searchQuery"
481
- placeholder="Qidirish..."
482
- autocomplete="off"
483
- autocorrect="off"
484
- autocapitalize="off"
485
- spellcheck="false"
486
- @input="onDropdownSearch"
487
- @keydown.stop="onDropdownSearchKeyDown"
488
- />
489
- </div>
490
- <div
491
- v-for="(opt, idx) in filteredSelectOptions"
492
- :key="opt.id != null ? opt.id : opt.value"
493
- class="eg-select-option"
494
- :class="{
495
- 'eg-select-option--active': opt.value === selectDropdown.currentValue,
496
- 'eg-select-option--highlighted': idx === selectDropdown.highlight
497
- }"
498
- :style="selectDropdown.tree && !selectDropdown.searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null"
499
- @mousedown.prevent="commitSelectValue(opt.value)"
500
- @mouseover="selectDropdown.highlight = idx"
501
- >
502
- <span
503
- v-if="selectDropdown.tree && !selectDropdown.searchQuery"
504
- class="eg-tree-toggle"
505
- :class="{ 'eg-tree-toggle--leaf': !opt.hasChildren }"
506
- @mousedown.prevent.stop="toggleTreeNode(idx)"
507
- >{{ opt.hasChildren ? (opt.expanded ? '▾' : '▸') : '' }}</span>
508
- <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>
509
- {{ opt.label }}
510
- </div>
511
- </div>
457
+ <!-- Select dropdown overlay (flat select) -->
458
+ <eg-select
459
+ v-if="selectDropdown && !selectDropdown.tree"
460
+ ref="selectDropdownComp"
461
+ :value="selectDropdown.currentValue"
462
+ :options="selectDropdown.rawOptions"
463
+ :enable-search="selectDropdown.enableSearch"
464
+ :trigger-rect="selectDropdown.rect"
465
+ :dropdown-only="true"
466
+ @select="onComponentSelect"
467
+ @close="closeSelectDropdown"
468
+ />
469
+ <!-- Tree select dropdown overlay -->
470
+ <eg-tree-select
471
+ v-if="selectDropdown && selectDropdown.tree"
472
+ ref="selectDropdownComp"
473
+ :value="selectDropdown.currentValue"
474
+ :options="selectDropdown.rawOptions"
475
+ :value-key="selectDropdown.keys.valueKey"
476
+ :label-key="selectDropdown.keys.labelKey"
477
+ :children-key="selectDropdown.keys.childrenKey"
478
+ :enable-search="selectDropdown.enableSearch"
479
+ :trigger-rect="selectDropdown.rect"
480
+ :dropdown-only="true"
481
+ @select="onComponentSelect"
482
+ @close="closeSelectDropdown"
483
+ />
512
484
 
513
485
  <!-- Validation error tooltip (cellValidator) -->
514
486
  <div
@@ -542,6 +514,9 @@ import { exportToExcel as _exportToExcel } from '../utils/excelExport.js'
542
514
  import { importFromFile as _importFromFile } from '../utils/excelImport.js'
543
515
  import { createExcelImportWorker, createExcelExportWorker } from '../utils/workerFactory'
544
516
  import { getMessages } from '../utils/i18n.js'
517
+ import EgCheckbox from './EgCheckbox.vue'
518
+ import EgSelect from './EgSelect.vue'
519
+ import EgTreeSelect from './EgTreeSelect.vue'
545
520
 
546
521
  const SIZE_CONFIG = {
547
522
  small: { row: 22, header: 24, footer: 24 },
@@ -555,6 +530,8 @@ const OUTLINE_GUTTER = 20
555
530
  export default {
556
531
  name: 'ExcelGrid',
557
532
 
533
+ components: { EgCheckbox, EgSelect, EgTreeSelect },
534
+
558
535
  // v-model → :data + @input
559
536
  model: { prop: 'data', event: 'input' },
560
537
 
@@ -860,32 +837,6 @@ export default {
860
837
  }
861
838
  },
862
839
 
863
- filteredSelectOptions() {
864
- if (!this.selectDropdown) return []
865
- const { options, searchQuery, enableSearch, tree, allFlatOptions } = this.selectDropdown
866
- if (!enableSearch || !searchQuery || !searchQuery.trim()) return options
867
- const q = searchQuery.trim().toLowerCase()
868
- const pool = tree && allFlatOptions ? allFlatOptions : options
869
- return pool.filter(o => String(o.label || '').toLowerCase().includes(q))
870
- },
871
-
872
- dropdownStyle() {
873
- if (!this.selectDropdown) return {}
874
- const { rect } = this.selectDropdown
875
- const optionH = this._rowH + 4
876
- const searchH = this.selectDropdown.enableSearch ? 42 : 0
877
- const approxHeight = Math.min(this.filteredSelectOptions.length * optionH + 8 + searchH, 320)
878
- const spaceBelow = window.innerHeight - rect.bottom
879
- const top = spaceBelow >= approxHeight ? rect.bottom : rect.top - approxHeight
880
- return {
881
- position: 'fixed',
882
- top: top + 'px',
883
- left: rect.left + 'px',
884
- minWidth: Math.max(rect.width, 120) + 'px',
885
- maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',
886
- zIndex: 9999
887
- }
888
- },
889
840
 
890
841
  invalidTooltipStyle() {
891
842
  if (!this.invalidCell || !this.invalidCell.rect) return {}
@@ -1788,7 +1739,7 @@ export default {
1788
1739
  this.invalidCell = null
1789
1740
  if (this.editingCell) {
1790
1741
  const editCol = this.leafCols[this.editingCell.col]
1791
- if (editCol && this.hasEditSlot(editCol)) {
1742
+ if (editCol && this._hasCustomEdit(editCol)) {
1792
1743
  this.cancelEdit()
1793
1744
  } else {
1794
1745
  this.commitEdit()
@@ -1817,7 +1768,7 @@ export default {
1817
1768
 
1818
1769
  // Open select dropdown on click if cell was already active (2nd click)
1819
1770
  const col = this.leafCols[ci]
1820
- if (wasActive && col && this.isSelectLikeCol(col) && !this.hasEditSlot(col) && this.isCellEditable(ri, ci)) {
1771
+ if (wasActive && col && this.isSelectLikeCol(col) && !this._hasCustomEdit(col) && this.isCellEditable(ri, ci)) {
1821
1772
  this.openSelectDropdown(ri, ci, e)
1822
1773
  e.preventDefault()
1823
1774
  this.isMouseDown = true
@@ -1856,7 +1807,7 @@ export default {
1856
1807
  onCellDblClick(ri, ci) {
1857
1808
  const col = this.leafCols[ci]
1858
1809
  if (!this.isCellEditable(ri, ci)) return
1859
- if (col && this.isSelectLikeCol(col) && !this.hasEditSlot(col)) {
1810
+ if (col && this.isSelectLikeCol(col) && !this._hasCustomEdit(col)) {
1860
1811
  this.openSelectDropdownForActive()
1861
1812
  return
1862
1813
  }
@@ -1949,44 +1900,11 @@ export default {
1949
1900
  // KEYBOARD NAVIGATION (3.2)
1950
1901
  // =========================================================
1951
1902
  onKeyDown(e) {
1952
- // Handle open select dropdown keyboard navigation
1903
+ // Delegate keyboard navigation to the active dropdown component
1953
1904
  if (this.selectDropdown) {
1954
- const key = e.key
1955
- if (key === 'Escape') {
1956
- e.preventDefault()
1957
- this.closeSelectDropdown()
1958
- return
1959
- }
1960
- if (key === 'Enter' || key === ' ') {
1961
- e.preventDefault()
1962
- const h = this.selectDropdown.highlight
1963
- if (h >= 0 && this.selectDropdown.options[h]) {
1964
- this.commitSelectValue(this.selectDropdown.options[h].value)
1965
- } else {
1966
- this.closeSelectDropdown()
1967
- }
1968
- return
1969
- }
1970
- if (key === 'ArrowDown') {
1971
- e.preventDefault()
1972
- this.selectDropdown.highlight = Math.min(this.selectDropdown.highlight + 1, this.selectDropdown.options.length - 1)
1973
- return
1974
- }
1975
- if (key === 'ArrowUp') {
1976
- e.preventDefault()
1977
- this.selectDropdown.highlight = Math.max(this.selectDropdown.highlight - 1, 0)
1978
- return
1979
- }
1980
- if (this.selectDropdown.tree && !this.selectDropdown.searchQuery && (key === 'ArrowRight' || key === 'ArrowLeft')) {
1981
- e.preventDefault()
1982
- const opt = this.selectDropdown.options[this.selectDropdown.highlight]
1983
- if (opt && opt.hasChildren) {
1984
- if (key === 'ArrowRight' && !opt.expanded) this.toggleTreeNode(this.selectDropdown.highlight)
1985
- if (key === 'ArrowLeft' && opt.expanded) this.toggleTreeNode(this.selectDropdown.highlight)
1986
- }
1987
- return
1988
- }
1989
- return // block other keys while dropdown open
1905
+ const comp = this.$refs.selectDropdownComp
1906
+ if (comp && comp.handleKey) comp.handleKey(e)
1907
+ return
1990
1908
  }
1991
1909
 
1992
1910
  if (this.editingCell) return // handled by onEditKeyDown
@@ -2034,7 +1952,7 @@ export default {
2034
1952
  if (key === 'F2') {
2035
1953
  e.preventDefault()
2036
1954
  const activeCol = this.leafCols[this.activeCell.col]
2037
- if (activeCol && this.isSelectLikeCol(activeCol) && !this.hasEditSlot(activeCol)) {
1955
+ if (activeCol && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {
2038
1956
  this.openSelectDropdownForActive()
2039
1957
  } else if (activeCol && activeCol.type === 'checkbox') {
2040
1958
  this.toggleCheckbox(this.activeCell.row, this.activeCell.col)
@@ -2130,7 +2048,7 @@ export default {
2130
2048
  if (e.ctrlKey || e.metaKey || e.altKey) return
2131
2049
  if (e.key && e.key.length === 1) {
2132
2050
  const activeCol = this.leafCols[this.activeCell.col]
2133
- if (activeCol && this.isSelectLikeCol(activeCol) && !this.hasEditSlot(activeCol)) {
2051
+ if (activeCol && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {
2134
2052
  e.preventDefault()
2135
2053
  this.openSelectDropdownForActive()
2136
2054
  return
@@ -2205,7 +2123,7 @@ export default {
2205
2123
  const col = this.leafCols[ci]
2206
2124
  if (!col || !this.isCellEditable(ri, ci)) return
2207
2125
  if (ri < 0 || ri >= this.totalRows) return
2208
- if (!this.hasEditSlot(col)) {
2126
+ if (!this._hasCustomEdit(col)) {
2209
2127
  if (this.isSelectLikeCol(col)) { this.openSelectDropdownForActive(); return }
2210
2128
  if (col.type === 'checkbox') return
2211
2129
  }
@@ -2262,9 +2180,19 @@ export default {
2262
2180
  this.$refs.wrapper && this.$refs.wrapper.focus()
2263
2181
  },
2264
2182
 
2265
- hasEditSlot(col) {
2183
+ hasCellSlot(col) {
2184
+ return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['cell-' + col.key])
2185
+ },
2186
+ hasLegacyEditSlot(col) {
2266
2187
  return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['edit-' + col.key])
2267
2188
  },
2189
+ _hasCustomEdit(col) {
2190
+ // For select/treeSelect: cell-{key} slot only customizes display; dropdown still opens normally
2191
+ if (col.type === 'select' || col.type === 'treeSelect') {
2192
+ return this.hasLegacyEditSlot(col)
2193
+ }
2194
+ return this.hasCellSlot(col) || this.hasLegacyEditSlot(col)
2195
+ },
2268
2196
 
2269
2197
  commitEditSlot(value) {
2270
2198
  if (!this.editingCell) return
@@ -2315,10 +2243,6 @@ export default {
2315
2243
  this.setCheckboxValue(ri, ci, !this.isCheckboxChecked(ri, ci))
2316
2244
  },
2317
2245
 
2318
- onCheckboxChange(e, ri, ci) {
2319
- this.setCheckboxValue(ri, ci, e.target.checked)
2320
- },
2321
-
2322
2246
  // =========================================================
2323
2247
  // CELL EDITABILITY (3.17)
2324
2248
  // =========================================================
@@ -2398,45 +2322,6 @@ export default {
2398
2322
  }
2399
2323
  },
2400
2324
 
2401
- // Flattens a tree of option nodes into a visible list, respecting which
2402
- // node ids are currently expanded. Each returned entry carries enough
2403
- // info (depth/hasChildren/expanded/id) to render indentation + a toggle.
2404
- _treeFlatten(nodes, keys, expandedMap, depth, pathArr) {
2405
- let out = []
2406
- ;(nodes || []).forEach((node, idx) => {
2407
- const path = pathArr.concat(idx)
2408
- const id = path.join('-')
2409
- const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []
2410
- const hasChildren = children.length > 0
2411
- const expanded = hasChildren && !!expandedMap[id]
2412
- out.push({
2413
- value: node[keys.valueKey],
2414
- label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]),
2415
- color: node.color,
2416
- id, depth, hasChildren, expanded
2417
- })
2418
- if (expanded) {
2419
- out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))
2420
- }
2421
- })
2422
- return out
2423
- },
2424
-
2425
- _treeFlattenAll(nodes, keys, depth, result) {
2426
- ;(nodes || []).forEach(node => {
2427
- const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []
2428
- result.push({
2429
- value: node[keys.valueKey],
2430
- label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]),
2431
- color: node.color,
2432
- id: String(node[keys.valueKey]),
2433
- depth: 0, hasChildren: false, expanded: false
2434
- })
2435
- if (children.length) this._treeFlattenAll(children, keys, depth + 1, result)
2436
- })
2437
- return result
2438
- },
2439
-
2440
2325
  _treeFindLabel(nodes, keys, value) {
2441
2326
  for (const node of (nodes || [])) {
2442
2327
  if (String(node[keys.valueKey]) === value) {
@@ -2448,28 +2333,9 @@ export default {
2448
2333
  return null
2449
2334
  },
2450
2335
 
2451
- // Returns the chain of ancestor node ids leading to `value` (inclusive),
2452
- // so callers can pre-expand exactly that branch when opening the dropdown.
2453
- _treeFindPath(nodes, keys, value, pathArr) {
2454
- for (let idx = 0; idx < (nodes || []).length; idx++) {
2455
- const node = nodes[idx]
2456
- const path = pathArr.concat(idx)
2457
- if (String(node[keys.valueKey]) === value) return [path.join('-')]
2458
- const children = node[keys.childrenKey]
2459
- if (Array.isArray(children)) {
2460
- const sub = this._treeFindPath(children, keys, value, path)
2461
- if (sub) return [path.join('-')].concat(sub)
2462
- }
2463
- }
2464
- return null
2465
- },
2466
-
2467
2336
  colOptions(ci) {
2468
2337
  const col = this.leafCols[ci]
2469
2338
  if (!col || !Array.isArray(col.options)) return []
2470
- if (col.type === 'treeSelect') {
2471
- return this._treeFlatten(col.options, this.treeOptionKeys(col), {}, 0, [])
2472
- }
2473
2339
  return col.options.map(o =>
2474
2340
  typeof o === 'string' ? { value: o, label: o } : { value: o.value, label: o.label != null ? o.label : String(o.value), color: o.color }
2475
2341
  )
@@ -2491,55 +2357,14 @@ export default {
2491
2357
 
2492
2358
  _buildSelectDropdownState(ri, ci, col, currentValue, rect) {
2493
2359
  const rectObj = { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }
2494
- const enableSearch = !!col.enableSearch
2495
- if (col.type === 'treeSelect') {
2496
- const keys = this.treeOptionKeys(col)
2497
- const path = (Array.isArray(col.options) && this._treeFindPath(col.options, keys, currentValue, [])) || []
2498
- const expandedMap = {}
2499
- path.forEach(id => { expandedMap[id] = true })
2500
- const flat = this._treeFlatten(col.options, keys, expandedMap, 0, [])
2501
- return {
2502
- row: ri, col: ci, tree: true, keys, expandedMap,
2503
- options: flat,
2504
- allFlatOptions: enableSearch ? this._treeFlattenAll(col.options || [], keys, 0, []) : null,
2505
- currentValue,
2506
- highlight: flat.findIndex(o => String(o.value) === currentValue),
2507
- rect: rectObj,
2508
- enableSearch,
2509
- searchQuery: ''
2510
- }
2511
- }
2512
- const opts = this.colOptions(ci)
2513
2360
  return {
2514
- row: ri, col: ci, tree: false,
2515
- options: opts,
2516
- allFlatOptions: null,
2361
+ row: ri, col: ci,
2362
+ tree: col.type === 'treeSelect',
2363
+ keys: col.type === 'treeSelect' ? this.treeOptionKeys(col) : null,
2364
+ rawOptions: col.options || [],
2517
2365
  currentValue,
2518
- highlight: opts.findIndex(o => String(o.value) === currentValue),
2519
2366
  rect: rectObj,
2520
- enableSearch,
2521
- searchQuery: ''
2522
- }
2523
- },
2524
-
2525
- toggleTreeNode(idx) {
2526
- if (!this.selectDropdown || !this.selectDropdown.tree) return
2527
- const opt = this.selectDropdown.options[idx]
2528
- if (!opt || !opt.hasChildren) return
2529
- const { row, col, expandedMap, keys } = this.selectDropdown
2530
- const colDef = this.leafCols[col]
2531
- if (!colDef) return
2532
- const nextExpandedMap = { ...expandedMap }
2533
- if (nextExpandedMap[opt.id]) delete nextExpandedMap[opt.id]
2534
- else nextExpandedMap[opt.id] = true
2535
- const flat = this._treeFlatten(colDef.options, keys, nextExpandedMap, 0, [])
2536
- const newHighlight = flat.findIndex(o => o.id === opt.id)
2537
- this.selectDropdown = {
2538
- ...this.selectDropdown,
2539
- row, col,
2540
- expandedMap: nextExpandedMap,
2541
- options: flat,
2542
- highlight: newHighlight >= 0 ? newHighlight : this.selectDropdown.highlight
2367
+ enableSearch: !!col.enableSearch
2543
2368
  }
2544
2369
  },
2545
2370
 
@@ -2550,9 +2375,6 @@ export default {
2550
2375
  const rect = el.getBoundingClientRect()
2551
2376
  const currentValue = String(this.getCellValue(ri, ci) != null ? this.getCellValue(ri, ci) : '')
2552
2377
  this.selectDropdown = this._buildSelectDropdownState(ri, ci, col, currentValue, rect)
2553
- if (col.enableSearch) {
2554
- this.$nextTick(() => { this.$refs.dropdownSearch && this.$refs.dropdownSearch.focus() })
2555
- }
2556
2378
  },
2557
2379
 
2558
2380
  openSelectDropdownForActive() {
@@ -2565,13 +2387,10 @@ export default {
2565
2387
  const rect = el.getBoundingClientRect()
2566
2388
  const currentValue = String(this.getCellValue(row, col) != null ? this.getCellValue(row, col) : '')
2567
2389
  this.selectDropdown = this._buildSelectDropdownState(row, col, colDef, currentValue, rect)
2568
- if (colDef.enableSearch) {
2569
- this.$nextTick(() => { this.$refs.dropdownSearch && this.$refs.dropdownSearch.focus() })
2570
- }
2571
2390
  })
2572
2391
  },
2573
2392
 
2574
- commitSelectValue(value) {
2393
+ onComponentSelect({ value, label }) {
2575
2394
  if (!this.selectDropdown) return
2576
2395
  const { row, col } = this.selectDropdown
2577
2396
  const colDef = this.leafCols[col]
@@ -2588,59 +2407,30 @@ export default {
2588
2407
  newData[row] = { ...oldData[row], [colDef.key]: value }
2589
2408
  this._emitDataChange(oldData, newData)
2590
2409
  if (typeof colDef.onSelect === 'function') {
2591
- const _opt = this.filteredSelectOptions.find(o => o.value === value)
2592
- colDef.onSelect({ value, label: _opt ? _opt.label : String(value) }, newData[row], row, colDef)
2410
+ colDef.onSelect({ value, label }, newData[row], row, colDef)
2593
2411
  }
2594
2412
  }
2595
2413
  this.closeSelectDropdown()
2596
2414
  this.$refs.wrapper && this.$refs.wrapper.focus()
2597
2415
  },
2598
2416
 
2599
- onDropdownSearch(e) {
2600
- if (!this.selectDropdown) return
2601
- this.selectDropdown.searchQuery = e.target.value
2602
- this.selectDropdown.highlight = 0
2603
- },
2604
-
2605
- onDropdownSearchKeyDown(e) {
2606
- if (!this.selectDropdown) return
2607
- const { key } = e
2608
- if (key === 'Escape') {
2609
- this.closeSelectDropdown()
2610
- this.$refs.wrapper && this.$refs.wrapper.focus()
2611
- } else if (key === 'ArrowDown') {
2612
- e.preventDefault()
2613
- this.selectDropdown.highlight = Math.min(
2614
- (this.selectDropdown.highlight || 0) + 1,
2615
- this.filteredSelectOptions.length - 1
2616
- )
2617
- } else if (key === 'ArrowUp') {
2618
- e.preventDefault()
2619
- this.selectDropdown.highlight = Math.max((this.selectDropdown.highlight || 0) - 1, 0)
2620
- } else if (key === 'Enter') {
2621
- e.preventDefault()
2622
- const h = this.selectDropdown.highlight
2623
- const opts = this.filteredSelectOptions
2624
- if (h >= 0 && opts[h]) this.commitSelectValue(opts[h].value)
2625
- }
2626
- },
2627
-
2628
2417
  closeSelectDropdown() {
2629
2418
  this.selectDropdown = null
2630
2419
  },
2631
2420
 
2632
2421
  _onWindowScrollForDropdown(e) {
2633
2422
  if (!this.selectDropdown) return
2634
- const dd = this.$el ? this.$el.querySelector('.eg-select-dropdown') : null
2423
+ const comp = this.$refs.selectDropdownComp
2424
+ const dd = comp && comp.$el ? comp.$el : null
2635
2425
  if (dd && dd.contains(e.target)) return
2636
2426
  this.closeSelectDropdown()
2637
2427
  },
2638
2428
 
2639
2429
  _onDocMouseDownForDropdown(e) {
2640
2430
  if (!this.selectDropdown) return
2641
- const dd = this.$el ? this.$el.querySelector('.eg-select-dropdown') : null
2431
+ const comp = this.$refs.selectDropdownComp
2432
+ const dd = comp && comp.$el ? comp.$el : null
2642
2433
  if (dd && dd.contains(e.target)) return
2643
- // Click outside dropdown — close it
2644
2434
  this.closeSelectDropdown()
2645
2435
  },
2646
2436
 
@@ -3990,122 +3780,6 @@ export default {
3990
3780
  justify-content: center;
3991
3781
  padding: 0 2px;
3992
3782
  }
3993
- .eg-checkbox {
3994
- position: relative;
3995
- display: inline-flex;
3996
- align-items: center;
3997
- justify-content: center;
3998
- width: 20px;
3999
- height: 20px;
4000
- cursor: pointer;
4001
- }
4002
- .eg-checkbox-input {
4003
- position: absolute;
4004
- inset: 0;
4005
- width: 100%;
4006
- height: 100%;
4007
- margin: 0;
4008
- opacity: 0;
4009
- cursor: pointer;
4010
- }
4011
- .eg-checkbox-box {
4012
- position: relative;
4013
- display: block;
4014
- width: 19px;
4015
- height: 19px;
4016
- box-sizing: border-box;
4017
- background-color: #fff;
4018
- background-repeat: no-repeat;
4019
- background-position: center;
4020
- background-size: 0 0;
4021
- border: 1.5px solid #c2c8d1;
4022
- border-radius: 4px;
4023
- transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;
4024
- }
4025
- .eg-checkbox:hover .eg-checkbox-box {
4026
- border-color: #1a73e8;
4027
- }
4028
- .eg-checkbox-input:checked + .eg-checkbox-box {
4029
- background-color: #1a73e8;
4030
- background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");
4031
- background-size: 70% 70%;
4032
- border-color: #1a73e8;
4033
- }
4034
- .eg-checkbox-input:focus-visible + .eg-checkbox-box {
4035
- box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.2);
4036
- }
4037
- .eg-checkbox--disabled,
4038
- .eg-checkbox--disabled .eg-checkbox-input {
4039
- cursor: default;
4040
- }
4041
- .eg-checkbox-input:disabled + .eg-checkbox-box {
4042
- background-color: #f0f1f3;
4043
- border-color: #dadde2;
4044
- }
4045
- .eg-checkbox-input:disabled:checked + .eg-checkbox-box {
4046
- background-color: #b9c2ce;
4047
- border-color: #b9c2ce;
4048
- }
4049
-
4050
- .eg-select-dropdown {
4051
- background: white;
4052
- border: 1px solid #d0d0d0;
4053
- border-radius: 4px;
4054
- box-shadow: 0 4px 16px rgba(0,0,0,0.15);
4055
- overflow-y: auto;
4056
- max-height: 320px;
4057
- padding: 4px 0;
4058
- }
4059
- .eg-select-search {
4060
- padding: 6px 8px;
4061
- border-bottom: 1px solid #ebebeb;
4062
- position: sticky;
4063
- top: 0;
4064
- background: #fff;
4065
- z-index: 1;
4066
- }
4067
- .eg-select-search-input {
4068
- width: 100%;
4069
- box-sizing: border-box;
4070
- border: 1px solid #d1d5db;
4071
- border-radius: 4px;
4072
- padding: 4px 8px;
4073
- font-size: 13px;
4074
- outline: none;
4075
- color: #333;
4076
- background: #fff;
4077
- font-family: inherit;
4078
- }
4079
- .eg-select-search-input:focus {
4080
- border-color: #1a73e8;
4081
- box-shadow: 0 0 0 2px rgba(26,115,232,0.15);
4082
- }
4083
-
4084
- .eg-select-option {
4085
- display: flex;
4086
- align-items: flex-start;
4087
- gap: 8px;
4088
- padding: 6px 12px;
4089
- font-size: 13px;
4090
- cursor: pointer;
4091
- white-space: normal;
4092
- word-break: break-word;
4093
- user-select: none;
4094
- color: #333;
4095
- }
4096
- .eg-select-option:hover,
4097
- .eg-select-option--highlighted {
4098
- background: #f0f4ff;
4099
- }
4100
- .eg-select-option--active {
4101
- font-weight: 600;
4102
- color: #1a73e8;
4103
- }
4104
- .eg-select-option--active.eg-select-option--highlighted,
4105
- .eg-select-option--active:hover {
4106
- background: #e8f0fe;
4107
- }
4108
-
4109
3783
  .eg-validation-tooltip {
4110
3784
  background: #fff0f0;
4111
3785
  border: 1px solid #e53935;
@@ -4118,29 +3792,6 @@ export default {
4118
3792
  pointer-events: none;
4119
3793
  }
4120
3794
 
4121
- .eg-select-dot {
4122
- width: 9px;
4123
- height: 9px;
4124
- border-radius: 50%;
4125
- flex-shrink: 0;
4126
- display: inline-block;
4127
- }
4128
-
4129
- .eg-tree-toggle {
4130
- display: inline-flex;
4131
- align-items: center;
4132
- justify-content: center;
4133
- width: 14px;
4134
- height: 14px;
4135
- flex-shrink: 0;
4136
- font-size: 10px;
4137
- color: #888;
4138
- margin-top: 1px;
4139
- }
4140
- .eg-tree-toggle--leaf {
4141
- visibility: hidden;
4142
- }
4143
-
4144
3795
  /* ===== ROW TRANSITION ===== */
4145
3796
  .eg-row-enter-active,
4146
3797
  .eg-row-leave-active {