vue2-excel-grid 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue2-excel-grid v1.2.0
2
+ * vue2-excel-grid v1.3.0
3
3
  * (c) 2026
4
4
  * Released under the MIT License.
5
5
  */
@@ -4315,6 +4315,12 @@ anime.random = function (min, max) {
4315
4315
  //
4316
4316
  //
4317
4317
  //
4318
+ //
4319
+ //
4320
+ //
4321
+ //
4322
+ //
4323
+ //
4318
4324
 
4319
4325
  // Approximate rendered height of one .eg-select-option row per grid `size`,
4320
4326
  // matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides
@@ -4400,13 +4406,17 @@ var script$3 = {
4400
4406
  var vk = this.valueKey;
4401
4407
  var lk = this.labelKey;
4402
4408
  return (this.options || []).map(function (o) {
4403
- return typeof o === 'string' ? {
4409
+ if (typeof o === 'string') return {
4404
4410
  value: o,
4405
- label: o
4406
- } : {
4411
+ label: o,
4412
+ tooltip: o
4413
+ };
4414
+ var label = o[lk] != null ? o[lk] : String(o[vk]);
4415
+ return {
4407
4416
  value: o[vk],
4408
- label: o[lk] != null ? o[lk] : String(o[vk]),
4409
- color: o.color
4417
+ label: label,
4418
+ color: o.color,
4419
+ tooltip: o.tooltip != null ? o.tooltip : label
4410
4420
  };
4411
4421
  });
4412
4422
  },
@@ -4668,6 +4678,9 @@ var __vue_render__$3 = function () {
4668
4678
  {
4669
4679
  staticClass: "eg-select-trigger-label",
4670
4680
  class: { "eg-select-placeholder": !_vm.selectedOpt },
4681
+ attrs: {
4682
+ title: _vm.selectedOpt ? _vm.selectedOpt.tooltip : null,
4683
+ },
4671
4684
  },
4672
4685
  [
4673
4686
  _vm._v(
@@ -4735,6 +4748,7 @@ var __vue_render__$3 = function () {
4735
4748
  String(opt.value) === String(_vm.value),
4736
4749
  "eg-select-option--highlighted": idx === _vm.highlight,
4737
4750
  },
4751
+ attrs: { title: opt.tooltip },
4738
4752
  on: {
4739
4753
  mousedown: function ($event) {
4740
4754
  $event.preventDefault();
@@ -4822,6 +4836,7 @@ var __vue_render__$3 = function () {
4822
4836
  String(opt.value) === String(_vm.value),
4823
4837
  "eg-select-option--highlighted": idx === _vm.highlight,
4824
4838
  },
4839
+ attrs: { title: opt.tooltip },
4825
4840
  on: {
4826
4841
  mousedown: function ($event) {
4827
4842
  $event.preventDefault();
@@ -4863,7 +4878,7 @@ __vue_render__$3._withStripped = true;
4863
4878
  /* style */
4864
4879
  const __vue_inject_styles__$3 = function (inject) {
4865
4880
  if (!inject) return
4866
- inject("data-v-065b2c09_0", { source: "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Standalone wrapper.\n Every custom property below is read as var(--eg-x, var(--eg-x-default)):\n --eg-x is an optional override slot a host/theme may set (only\n src/excel-grid.css's dark mode and custom host theming ever set the\n bare name); --eg-x-default is always defined on .eg-shell (ExcelGrid.vue)\n for every built-in theme. A bare var(--eg-x) with no fallback resolves to\n nothing in the (default, most common) case where only the \"-default\" var\n is set — and an unresolvable var() in a shorthand like `border` doesn't\n fall back to any other CSS rule, it makes the whole declaration compute\n to its initial value (no border at all). This component's dropdown is\n also duplicated (with the fallback already applied) in ExcelGrid.vue's\n own <style> for cascade-order reasons, so keep the token names in sync\n with that copy when changing tokens here. */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly, var(--eg-color-cell-readonly-default));\n cursor: default;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n box-shadow: var(--eg-shadow-dropdown, var(--eg-shadow-dropdown-default));\n overflow-y: auto;\n max-height: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid var(--eg-color-border-cell, var(--eg-color-border-cell-default));\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n padding: 4px 8px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n outline: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover {\n background: var(--eg-color-option-active-hover-bg, var(--eg-color-option-active-hover-bg-default));\n}\n\n/* Empty state — no options at all, or a search query matched nothing */\n.eg-select-empty {\n padding: 14px 12px;\n text-align: center;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n user-select: none;\n}\n\n/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n", map: {"version":3,"sources":["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgSelect.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8UA;;;;;;;;;;;;8CAYA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2DAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,wEAAA;EACA,yDAAA;EACA,oEAAA;EACA,eAAA;EACA,iBAAA;EACA,yDAAA;EACA,wDAAA;AACA;AACA;EACA,oEAAA;AACA;AACA;EACA,oEAAA;EACA,sFAAA;AACA;AACA;EACA,gFAAA;EACA,eAAA;EACA,qEAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,qEAAA;AACA;AACA;EACA,cAAA;EACA,qEAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,oEAAA;EACA,wEAAA;EACA,yDAAA;EACA,wEAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;;AAEA,iCAAA;AACA;EACA,gBAAA;EACA,yFAAA;EACA,gBAAA;EACA,MAAA;EACA,oEAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,wEAAA;EACA,yDAAA;EACA,gBAAA;EACA,2DAAA;EACA,aAAA;EACA,yDAAA;EACA,oEAAA;EACA,oBAAA;AACA;AACA;EACA,oEAAA;EACA,sFAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,2DAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,yDAAA;AACA;AACA;;EAEA,oFAAA;AACA;AACA;EACA,gBAAA;EACA,6DAAA;AACA;AACA;;EAEA,kGAAA;AACA;;AAEA,uEAAA;AACA;EACA,kBAAA;EACA,kBAAA;EACA,2DAAA;EACA,qEAAA;EACA,iBAAA;AACA;;AAEA,cAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA","file":"EgSelect.vue","sourcesContent":["<template>\n <div\n :class=\"dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'\"\n :style=\"dropdownOnly ? overlayStyle : null\"\n @mousedown=\"onRootMouseDown\"\n >\n <!-- Standalone mode: trigger button -->\n <div\n v-if=\"!dropdownOnly\"\n class=\"eg-select-trigger\"\n :class=\"{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }\"\n @click.stop=\"toggle\"\n >\n <span v-if=\"selectedOpt && selectedOpt.color\" class=\"eg-select-dot\" :style=\"{ background: selectedOpt.color }\"></span>\n <span class=\"eg-select-trigger-label\" :class=\"{ 'eg-select-placeholder': !selectedOpt }\">\n {{ selectedOpt ? selectedOpt.label : placeholder }}\n </span>\n <span class=\"eg-select-caret\">▾</span>\n </div>\n\n <!-- Options panel in grid dropdown-only mode (root IS the dropdown panel) -->\n <template v-if=\"dropdownOnly\">\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </template>\n\n <!-- Options panel in standalone mode (floating dropdown below trigger) -->\n <div\n v-else-if=\"open\"\n class=\"eg-select-dropdown eg-select-dropdown--floating\"\n @mousedown.stop.prevent\n >\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </div>\n </div>\n</template>\n\n<script>\n// Approximate rendered height of one .eg-select-option row per grid `size`,\n// matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides\n// below — used only to estimate whether the dropdown fits below the trigger\n// before it's actually rendered (see overlayStyle).\nconst OPTION_ROW_H = { small: 22, middle: 28, large: 33 }\n\nimport { getMessages } from '../utils/i18n.js'\n\nexport default {\n name: 'EgSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n // Field names to read value/label from when an option is a plain object\n // instead of a string — mirrors EgTreeSelect's valueKey/labelKey so\n // col.optionValueKey/optionLabelKey work the same way for type:'select'.\n valueKey: { type: String, default: 'value' },\n labelKey: { type: String, default: 'label' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: '' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null },\n size: { type: String, default: 'middle' },\n locale: { type: String, default: 'en' }\n },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1,\n // True right after Arrow/Page keyboard navigation — lets Space commit the\n // highlighted option instead of typing a literal space into the search box.\n // Reset on every text edit so normal multi-word search typing is unaffected.\n _navigatedByKeyboard: false\n }\n },\n computed: {\n t() { return getMessages(this.locale) },\n normalizedOptions() {\n const vk = this.valueKey\n const lk = this.labelKey\n return (this.options || []).map(o =>\n typeof o === 'string'\n ? { value: o, label: o }\n : { value: o[vk], label: o[lk] != null ? o[lk] : String(o[vk]), color: o.color }\n )\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions\n const q = this.searchQuery.trim().toLowerCase()\n return this.normalizedOptions.filter(o => String(o.label || '').toLowerCase().includes(q))\n },\n selectedOpt() {\n const v = String(this.value == null ? '' : this.value)\n return this.normalizedOptions.find(o => String(o.value) === v) || null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const rowH = OPTION_ROW_H[this.size] || OPTION_ROW_H.middle\n const approxH = Math.min(this.filteredOptions.length * rowH + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: 'fixed',\n top: top + 'px',\n left: rect.left + 'px',\n minWidth: Math.max(rect.width, 120) + 'px',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n const v = String(this.value == null ? '' : this.value)\n const selectedIndex = this.filteredOptions.findIndex(o => String(o.value) === v)\n this.highlight = selectedIndex\n this._scrollIndexIntoView(selectedIndex)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener('click', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener('click', this._onDocClick)\n }\n },\n methods: {\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n const v = String(this.value == null ? '' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n this.searchQuery = ''\n }\n },\n pickOption(opt) {\n this.$emit('input', opt.value)\n this.$emit('select', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = ''\n this.highlight = -1\n }\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n this._navigatedByKeyboard = false\n },\n onSearchKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit('close')\n else { this.open = false; this.searchQuery = '' }\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'Enter') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n } else if (key === ' ' && this._navigatedByKeyboard) {\n // Space commits the option reached via Arrow/Page navigation instead of\n // typing a literal space — a plain (non-navigated) space still types normally.\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n this.$emit('close')\n } else if (key === 'Enter' || key === ' ') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit('close')\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._scrollHighlightedIntoView()\n }\n },\n _scrollHighlightedIntoView() {\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[this.highlight]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n const elTop = el.offsetTop\n const elBottom = elTop + el.offsetHeight\n const visTop = container.scrollTop + stickyH\n const visBottom = container.scrollTop + container.clientHeight\n if (elTop < visTop) {\n container.scrollTop = elTop - stickyH\n } else if (elBottom > visBottom) {\n container.scrollTop = elBottom - container.clientHeight\n }\n })\n },\n // Aligns option `idx` to the top of the visible list on initial open.\n // Setting scrollTop past the container's max is clamped by the browser,\n // so an option near the end of the list naturally ends up scrolled to\n // the bottom instead of leaving blank space below it.\n _scrollIndexIntoView(idx) {\n if (idx < 0) return\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[idx]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n container.scrollTop = el.offsetTop - stickyH\n })\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n }\n }\n}\n</script>\n\n<style>\n/* Standalone wrapper.\n Every custom property below is read as var(--eg-x, var(--eg-x-default)):\n --eg-x is an optional override slot a host/theme may set (only\n src/excel-grid.css's dark mode and custom host theming ever set the\n bare name); --eg-x-default is always defined on .eg-shell (ExcelGrid.vue)\n for every built-in theme. A bare var(--eg-x) with no fallback resolves to\n nothing in the (default, most common) case where only the \"-default\" var\n is set — and an unresolvable var() in a shorthand like `border` doesn't\n fall back to any other CSS rule, it makes the whole declaration compute\n to its initial value (no border at all). This component's dropdown is\n also duplicated (with the fallback already applied) in ExcelGrid.vue's\n own <style> for cascade-order reasons, so keep the token names in sync\n with that copy when changing tokens here. */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly, var(--eg-color-cell-readonly-default));\n cursor: default;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n box-shadow: var(--eg-shadow-dropdown, var(--eg-shadow-dropdown-default));\n overflow-y: auto;\n max-height: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid var(--eg-color-border-cell, var(--eg-color-border-cell-default));\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n padding: 4px 8px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n outline: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover {\n background: var(--eg-color-option-active-hover-bg, var(--eg-color-option-active-hover-bg-default));\n}\n\n/* Empty state — no options at all, or a search query matched nothing */\n.eg-select-empty {\n padding: 14px 12px;\n text-align: center;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n user-select: none;\n}\n\n/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n</style>\n"]}, media: undefined });
4881
+ inject("data-v-f9983b3a_0", { source: "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Standalone wrapper.\n Every custom property below is read as var(--eg-x, var(--eg-x-default)):\n --eg-x is an optional override slot a host/theme may set (only\n src/excel-grid.css's dark mode and custom host theming ever set the\n bare name); --eg-x-default is always defined on .eg-shell (ExcelGrid.vue)\n for every built-in theme. A bare var(--eg-x) with no fallback resolves to\n nothing in the (default, most common) case where only the \"-default\" var\n is set — and an unresolvable var() in a shorthand like `border` doesn't\n fall back to any other CSS rule, it makes the whole declaration compute\n to its initial value (no border at all). This component's dropdown is\n also duplicated (with the fallback already applied) in ExcelGrid.vue's\n own <style> for cascade-order reasons, so keep the token names in sync\n with that copy when changing tokens here. */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly, var(--eg-color-cell-readonly-default));\n cursor: default;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n box-shadow: var(--eg-shadow-dropdown, var(--eg-shadow-dropdown-default));\n overflow-y: auto;\n max-height: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid var(--eg-color-border-cell, var(--eg-color-border-cell-default));\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n padding: 4px 8px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n outline: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover {\n background: var(--eg-color-option-active-hover-bg, var(--eg-color-option-active-hover-bg-default));\n}\n\n/* Empty state — no options at all, or a search query matched nothing */\n.eg-select-empty {\n padding: 14px 12px;\n text-align: center;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n user-select: none;\n}\n\n/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n", map: {"version":3,"sources":["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgSelect.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoVA;;;;;;;;;;;;8CAYA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2DAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,wEAAA;EACA,yDAAA;EACA,oEAAA;EACA,eAAA;EACA,iBAAA;EACA,yDAAA;EACA,wDAAA;AACA;AACA;EACA,oEAAA;AACA;AACA;EACA,oEAAA;EACA,sFAAA;AACA;AACA;EACA,gFAAA;EACA,eAAA;EACA,qEAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,qEAAA;AACA;AACA;EACA,cAAA;EACA,qEAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,oEAAA;EACA,wEAAA;EACA,yDAAA;EACA,wEAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;;AAEA,iCAAA;AACA;EACA,gBAAA;EACA,yFAAA;EACA,gBAAA;EACA,MAAA;EACA,oEAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,wEAAA;EACA,yDAAA;EACA,gBAAA;EACA,2DAAA;EACA,aAAA;EACA,yDAAA;EACA,oEAAA;EACA,oBAAA;AACA;AACA;EACA,oEAAA;EACA,sFAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,2DAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,yDAAA;AACA;AACA;;EAEA,oFAAA;AACA;AACA;EACA,gBAAA;EACA,6DAAA;AACA;AACA;;EAEA,kGAAA;AACA;;AAEA,uEAAA;AACA;EACA,kBAAA;EACA,kBAAA;EACA,2DAAA;EACA,qEAAA;EACA,iBAAA;AACA;;AAEA,cAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA","file":"EgSelect.vue","sourcesContent":["<template>\n <div\n :class=\"dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'\"\n :style=\"dropdownOnly ? overlayStyle : null\"\n @mousedown=\"onRootMouseDown\"\n >\n <!-- Standalone mode: trigger button -->\n <div\n v-if=\"!dropdownOnly\"\n class=\"eg-select-trigger\"\n :class=\"{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }\"\n @click.stop=\"toggle\"\n >\n <span v-if=\"selectedOpt && selectedOpt.color\" class=\"eg-select-dot\" :style=\"{ background: selectedOpt.color }\"></span>\n <span\n class=\"eg-select-trigger-label\"\n :class=\"{ 'eg-select-placeholder': !selectedOpt }\"\n :title=\"selectedOpt ? selectedOpt.tooltip : null\"\n >\n {{ selectedOpt ? selectedOpt.label : placeholder }}\n </span>\n <span class=\"eg-select-caret\">▾</span>\n </div>\n\n <!-- Options panel in grid dropdown-only mode (root IS the dropdown panel) -->\n <template v-if=\"dropdownOnly\">\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :title=\"opt.tooltip\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </template>\n\n <!-- Options panel in standalone mode (floating dropdown below trigger) -->\n <div\n v-else-if=\"open\"\n class=\"eg-select-dropdown eg-select-dropdown--floating\"\n @mousedown.stop.prevent\n >\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :title=\"opt.tooltip\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </div>\n </div>\n</template>\n\n<script>\n// Approximate rendered height of one .eg-select-option row per grid `size`,\n// matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides\n// below — used only to estimate whether the dropdown fits below the trigger\n// before it's actually rendered (see overlayStyle).\nconst OPTION_ROW_H = { small: 22, middle: 28, large: 33 }\n\nimport { getMessages } from '../utils/i18n.js'\n\nexport default {\n name: 'EgSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n // Field names to read value/label from when an option is a plain object\n // instead of a string — mirrors EgTreeSelect's valueKey/labelKey so\n // col.optionValueKey/optionLabelKey work the same way for type:'select'.\n valueKey: { type: String, default: 'value' },\n labelKey: { type: String, default: 'label' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: '' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null },\n size: { type: String, default: 'middle' },\n locale: { type: String, default: 'en' }\n },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1,\n // True right after Arrow/Page keyboard navigation — lets Space commit the\n // highlighted option instead of typing a literal space into the search box.\n // Reset on every text edit so normal multi-word search typing is unaffected.\n _navigatedByKeyboard: false\n }\n },\n computed: {\n t() { return getMessages(this.locale) },\n normalizedOptions() {\n const vk = this.valueKey\n const lk = this.labelKey\n return (this.options || []).map(o => {\n if (typeof o === 'string') return { value: o, label: o, tooltip: o }\n const label = o[lk] != null ? o[lk] : String(o[vk])\n return { value: o[vk], label, color: o.color, tooltip: o.tooltip != null ? o.tooltip : label }\n })\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions\n const q = this.searchQuery.trim().toLowerCase()\n return this.normalizedOptions.filter(o => String(o.label || '').toLowerCase().includes(q))\n },\n selectedOpt() {\n const v = String(this.value == null ? '' : this.value)\n return this.normalizedOptions.find(o => String(o.value) === v) || null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const rowH = OPTION_ROW_H[this.size] || OPTION_ROW_H.middle\n const approxH = Math.min(this.filteredOptions.length * rowH + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: 'fixed',\n top: top + 'px',\n left: rect.left + 'px',\n minWidth: Math.max(rect.width, 120) + 'px',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n const v = String(this.value == null ? '' : this.value)\n const selectedIndex = this.filteredOptions.findIndex(o => String(o.value) === v)\n this.highlight = selectedIndex\n this._scrollIndexIntoView(selectedIndex)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener('click', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener('click', this._onDocClick)\n }\n },\n methods: {\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n const v = String(this.value == null ? '' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n this.searchQuery = ''\n }\n },\n pickOption(opt) {\n this.$emit('input', opt.value)\n this.$emit('select', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = ''\n this.highlight = -1\n }\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n this._navigatedByKeyboard = false\n },\n onSearchKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit('close')\n else { this.open = false; this.searchQuery = '' }\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'Enter') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n } else if (key === ' ' && this._navigatedByKeyboard) {\n // Space commits the option reached via Arrow/Page navigation instead of\n // typing a literal space — a plain (non-navigated) space still types normally.\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n this.$emit('close')\n } else if (key === 'Enter' || key === ' ') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit('close')\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._scrollHighlightedIntoView()\n }\n },\n _scrollHighlightedIntoView() {\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[this.highlight]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n const elTop = el.offsetTop\n const elBottom = elTop + el.offsetHeight\n const visTop = container.scrollTop + stickyH\n const visBottom = container.scrollTop + container.clientHeight\n if (elTop < visTop) {\n container.scrollTop = elTop - stickyH\n } else if (elBottom > visBottom) {\n container.scrollTop = elBottom - container.clientHeight\n }\n })\n },\n // Aligns option `idx` to the top of the visible list on initial open.\n // Setting scrollTop past the container's max is clamped by the browser,\n // so an option near the end of the list naturally ends up scrolled to\n // the bottom instead of leaving blank space below it.\n _scrollIndexIntoView(idx) {\n if (idx < 0) return\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[idx]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n container.scrollTop = el.offsetTop - stickyH\n })\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n }\n }\n}\n</script>\n\n<style>\n/* Standalone wrapper.\n Every custom property below is read as var(--eg-x, var(--eg-x-default)):\n --eg-x is an optional override slot a host/theme may set (only\n src/excel-grid.css's dark mode and custom host theming ever set the\n bare name); --eg-x-default is always defined on .eg-shell (ExcelGrid.vue)\n for every built-in theme. A bare var(--eg-x) with no fallback resolves to\n nothing in the (default, most common) case where only the \"-default\" var\n is set — and an unresolvable var() in a shorthand like `border` doesn't\n fall back to any other CSS rule, it makes the whole declaration compute\n to its initial value (no border at all). This component's dropdown is\n also duplicated (with the fallback already applied) in ExcelGrid.vue's\n own <style> for cascade-order reasons, so keep the token names in sync\n with that copy when changing tokens here. */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly, var(--eg-color-cell-readonly-default));\n cursor: default;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n box-shadow: var(--eg-shadow-dropdown, var(--eg-shadow-dropdown-default));\n overflow-y: auto;\n max-height: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid var(--eg-color-border-cell, var(--eg-color-border-cell-default));\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--eg-color-border, var(--eg-color-border-default));\n border-radius: var(--eg-radius, var(--eg-radius-default));\n padding: 4px 8px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n outline: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent, var(--eg-color-accent-default));\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text, var(--eg-color-text-default));\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent, var(--eg-color-accent-default));\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover {\n background: var(--eg-color-option-active-hover-bg, var(--eg-color-option-active-hover-bg-default));\n}\n\n/* Empty state — no options at all, or a search query matched nothing */\n.eg-select-empty {\n padding: 14px 12px;\n text-align: center;\n font-size: var(--eg-font-size, var(--eg-font-size-default));\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n user-select: none;\n}\n\n/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n</style>\n"]}, media: undefined });
4867
4882
 
4868
4883
  };
4869
4884
  /* scoped */
@@ -4996,6 +5011,12 @@ __vue_render__$3._withStripped = true;
4996
5011
  //
4997
5012
  //
4998
5013
  //
5014
+ //
5015
+ //
5016
+ //
5017
+ //
5018
+ //
5019
+ //
4999
5020
 
5000
5021
  // Approximate rendered height of one .eg-select-option row per grid `size`,
5001
5022
  // matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides
@@ -5107,6 +5128,14 @@ var script$2 = {
5107
5128
  var found = this._treeFindLabel(this.options || [], this.keys, v);
5108
5129
  return found != null ? found : null;
5109
5130
  },
5131
+ selectedTooltip: function selectedTooltip() {
5132
+ var v = String(this.value == null ? '' : this.value);
5133
+ if (!v) return null;
5134
+ var node = this._treeFindNode(this.options || [], this.keys, v);
5135
+ if (!node) return null;
5136
+ var label = node[this.keys.labelKey] != null ? node[this.keys.labelKey] : String(node[this.keys.valueKey]);
5137
+ return node.tooltip != null ? node.tooltip : label;
5138
+ },
5110
5139
  overlayStyle: function overlayStyle() {
5111
5140
  if (!this.dropdownOnly || !this.triggerRect) return {
5112
5141
  position: 'fixed',
@@ -5338,10 +5367,12 @@ var script$2 = {
5338
5367
  var children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : [];
5339
5368
  var hasChildren = children.length > 0;
5340
5369
  var expanded = hasChildren && !!expandedMap[id];
5370
+ var label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]);
5341
5371
  out.push({
5342
5372
  value: node[keys.valueKey],
5343
- label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]),
5373
+ label: label,
5344
5374
  color: node.color,
5375
+ tooltip: node.tooltip != null ? node.tooltip : label,
5345
5376
  id: id,
5346
5377
  depth: depth,
5347
5378
  hasChildren: hasChildren,
@@ -5356,10 +5387,12 @@ var script$2 = {
5356
5387
  var _walk = function walk(list) {
5357
5388
  (list || []).forEach(function (node) {
5358
5389
  var children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : [];
5390
+ var label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]);
5359
5391
  result.push({
5360
5392
  value: node[keys.valueKey],
5361
- label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]),
5393
+ label: label,
5362
5394
  color: node.color,
5395
+ tooltip: node.tooltip != null ? node.tooltip : label,
5363
5396
  id: String(node[keys.valueKey]),
5364
5397
  depth: 0,
5365
5398
  hasChildren: false,
@@ -5390,6 +5423,23 @@ var script$2 = {
5390
5423
  }
5391
5424
  return null;
5392
5425
  },
5426
+ _treeFindNode: function _treeFindNode(nodes, keys, value) {
5427
+ var _iterator2 = _createForOfIteratorHelper(nodes || []),
5428
+ _step2;
5429
+ try {
5430
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5431
+ var node = _step2.value;
5432
+ if (String(node[keys.valueKey]) === value) return node;
5433
+ var found = this._treeFindNode(node[keys.childrenKey], keys, value);
5434
+ if (found != null) return found;
5435
+ }
5436
+ } catch (err) {
5437
+ _iterator2.e(err);
5438
+ } finally {
5439
+ _iterator2.f();
5440
+ }
5441
+ return null;
5442
+ },
5393
5443
  _treeFindPath: function _treeFindPath(nodes, keys, value, pathArr) {
5394
5444
  for (var i = 0; i < (nodes || []).length; i++) {
5395
5445
  var node = nodes[i];
@@ -5444,6 +5494,7 @@ var __vue_render__$2 = function () {
5444
5494
  {
5445
5495
  staticClass: "eg-select-trigger-label",
5446
5496
  class: { "eg-select-placeholder": _vm.selectedLabel == null },
5497
+ attrs: { title: _vm.selectedTooltip },
5447
5498
  },
5448
5499
  [
5449
5500
  _vm._v(
@@ -5514,6 +5565,7 @@ var __vue_render__$2 = function () {
5514
5565
  style: !_vm.searchQuery
5515
5566
  ? { paddingLeft: 12 + opt.depth * 16 + "px" }
5516
5567
  : null,
5568
+ attrs: { title: opt.tooltip },
5517
5569
  on: {
5518
5570
  mousedown: function ($event) {
5519
5571
  $event.preventDefault();
@@ -5628,6 +5680,7 @@ var __vue_render__$2 = function () {
5628
5680
  style: !_vm.searchQuery
5629
5681
  ? { paddingLeft: 12 + opt.depth * 16 + "px" }
5630
5682
  : null,
5683
+ attrs: { title: opt.tooltip },
5631
5684
  on: {
5632
5685
  mousedown: function ($event) {
5633
5686
  $event.preventDefault();
@@ -5697,7 +5750,7 @@ __vue_render__$2._withStripped = true;
5697
5750
  /* style */
5698
5751
  const __vue_inject_styles__$2 = function (inject) {
5699
5752
  if (!inject) return
5700
- inject("data-v-5c12e210_0", { source: "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n", map: {"version":3,"sources":["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgTreeSelect.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsaA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,qEAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;AACA","file":"EgTreeSelect.vue","sourcesContent":["<template>\n <div\n :class=\"dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'\"\n :style=\"dropdownOnly ? overlayStyle : null\"\n @mousedown=\"onRootMouseDown\"\n >\n <!-- Standalone mode: trigger button -->\n <div\n v-if=\"!dropdownOnly\"\n class=\"eg-select-trigger\"\n :class=\"{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }\"\n @click.stop=\"toggle\"\n >\n <span class=\"eg-select-trigger-label\" :class=\"{ 'eg-select-placeholder': selectedLabel == null }\">\n {{ selectedLabel != null ? selectedLabel : placeholder }}\n </span>\n <span class=\"eg-select-caret\">▾</span>\n </div>\n\n <!-- Options panel in grid dropdown-only mode -->\n <template v-if=\"dropdownOnly\">\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.id != null ? opt.id : String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :style=\"!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span\n v-if=\"!searchQuery\"\n class=\"eg-tree-toggle\"\n :class=\"{ 'eg-tree-toggle--leaf': !opt.hasChildren }\"\n @mousedown.prevent.stop=\"toggleNode(idx)\"\n >{{ opt.hasChildren ? (opt.expanded ? '▾' : '▸') : '' }}</span>\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </template>\n\n <!-- Options panel in standalone mode -->\n <div\n v-else-if=\"open\"\n class=\"eg-select-dropdown eg-select-dropdown--floating\"\n @mousedown.stop.prevent\n >\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.id != null ? opt.id : String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :style=\"!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span\n v-if=\"!searchQuery\"\n class=\"eg-tree-toggle\"\n :class=\"{ 'eg-tree-toggle--leaf': !opt.hasChildren }\"\n @mousedown.prevent.stop=\"toggleNode(idx)\"\n >{{ opt.hasChildren ? (opt.expanded ? '▾' : '▸') : '' }}</span>\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </div>\n </div>\n</template>\n\n<script>\n// Approximate rendered height of one .eg-select-option row per grid `size`,\n// matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides\n// (see ExcelGrid.vue's <style>) — used only to estimate whether the dropdown\n// fits below the trigger before it's actually rendered (see overlayStyle).\nconst OPTION_ROW_H = { small: 22, middle: 28, large: 33 }\n\nimport { getMessages } from '../utils/i18n.js'\n\nexport default {\n name: 'EgTreeSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n valueKey: { type: String, default: 'value' },\n labelKey: { type: String, default: 'label' },\n childrenKey: { type: String, default: 'children' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: '' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null },\n size: { type: String, default: 'middle' },\n locale: { type: String, default: 'en' }\n },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1,\n expandedMap: {},\n // True right after Arrow/Page keyboard navigation — lets Space commit the\n // highlighted option instead of typing a literal space into the search box.\n // Reset on every text edit so normal multi-word search typing is unaffected.\n _navigatedByKeyboard: false\n }\n },\n computed: {\n t() { return getMessages(this.locale) },\n keys() {\n return { valueKey: this.valueKey, labelKey: this.labelKey, childrenKey: this.childrenKey }\n },\n flatOptions() {\n return this._treeFlatten(this.options || [], this.keys, this.expandedMap, 0, [])\n },\n allFlatOptions() {\n if (!this.enableSearch) return null\n return this._treeFlattenAll(this.options || [], this.keys)\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.flatOptions\n const q = this.searchQuery.trim().toLowerCase()\n const pool = this.allFlatOptions || this.flatOptions\n return pool.filter(o => String(o.label || '').toLowerCase().includes(q))\n },\n selectedLabel() {\n const v = String(this.value == null ? '' : this.value)\n if (!v) return null\n const found = this._treeFindLabel(this.options || [], this.keys, v)\n return found != null ? found : null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const rowH = OPTION_ROW_H[this.size] || OPTION_ROW_H.middle\n const approxH = Math.min(this.filteredOptions.length * rowH + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: 'fixed',\n top: top + 'px',\n left: rect.left + 'px',\n minWidth: Math.max(rect.width, 120) + 'px',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n this._initExpanded()\n this._scrollIndexIntoView(this.highlight)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener('click', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener('click', this._onDocClick)\n }\n },\n methods: {\n _initExpanded() {\n const v = String(this.value == null ? '' : this.value)\n const path = this._treeFindPath(this.options || [], this.keys, v, []) || []\n const expandedMap = {}\n path.forEach(id => { expandedMap[id] = true })\n this.expandedMap = expandedMap\n const flat = this.flatOptions\n this.highlight = flat.findIndex(o => String(o.value) === v)\n },\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n this.searchQuery = ''\n this._initExpanded()\n }\n },\n pickOption(opt) {\n this.$emit('input', opt.value)\n this.$emit('select', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = ''\n this.highlight = -1\n }\n },\n toggleNode(idx) {\n const opt = this.flatOptions[idx]\n if (!opt || !opt.hasChildren) return\n const next = { ...this.expandedMap }\n if (next[opt.id]) delete next[opt.id]\n else next[opt.id] = true\n this.expandedMap = next\n const newFlat = this.flatOptions\n const newIdx = newFlat.findIndex(o => o.id === opt.id)\n this.highlight = newIdx >= 0 ? newIdx : this.highlight\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n this._navigatedByKeyboard = false\n },\n onSearchKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit('close')\n else { this.open = false; this.searchQuery = '' }\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'Enter') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n } else if (key === ' ' && this._navigatedByKeyboard) {\n // Space commits the option reached via Arrow/Page navigation instead of\n // typing a literal space — a plain (non-navigated) space still types normally.\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n this.$emit('close')\n } else if (key === 'Enter' || key === ' ') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit('close')\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._scrollHighlightedIntoView()\n } else if (!this.searchQuery && (key === 'ArrowRight' || key === 'ArrowLeft')) {\n e.preventDefault()\n const opt = this.flatOptions[this.highlight]\n if (opt && opt.hasChildren) {\n if (key === 'ArrowRight' && !opt.expanded) this.toggleNode(this.highlight)\n if (key === 'ArrowLeft' && opt.expanded) this.toggleNode(this.highlight)\n }\n }\n },\n _scrollHighlightedIntoView() {\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[this.highlight]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n const elTop = el.offsetTop\n const elBottom = elTop + el.offsetHeight\n const visTop = container.scrollTop + stickyH\n const visBottom = container.scrollTop + container.clientHeight\n if (elTop < visTop) {\n container.scrollTop = elTop - stickyH\n } else if (elBottom > visBottom) {\n container.scrollTop = elBottom - container.clientHeight\n }\n })\n },\n // Aligns option `idx` to the top of the visible list on initial open.\n // Setting scrollTop past the container's max is clamped by the browser,\n // so an option near the end of the list naturally ends up scrolled to\n // the bottom instead of leaving blank space below it.\n _scrollIndexIntoView(idx) {\n if (idx < 0) return\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[idx]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n container.scrollTop = el.offsetTop - stickyH\n })\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n },\n _treeFlatten(nodes, keys, expandedMap, depth, pathArr) {\n let out = []\n ;(nodes || []).forEach((node, idx) => {\n const path = pathArr.concat(idx)\n const id = path.join('-')\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n const hasChildren = children.length > 0\n const expanded = hasChildren && !!expandedMap[id]\n out.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id, depth, hasChildren, expanded })\n if (expanded) out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))\n })\n return out\n },\n _treeFlattenAll(nodes, keys) {\n const result = []\n const walk = (list) => {\n ;(list || []).forEach(node => {\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n result.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id: String(node[keys.valueKey]), depth: 0, hasChildren: false, expanded: false })\n if (children.length) walk(children)\n })\n }\n walk(nodes)\n return result\n },\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\n },\n _treeFindPath(nodes, keys, value, pathArr) {\n for (let i = 0; i < (nodes || []).length; i++) {\n const node = nodes[i]\n const path = pathArr.concat(i)\n if (String(node[keys.valueKey]) === value) return [path.join('-')]\n const children = node[keys.childrenKey]\n if (Array.isArray(children)) {\n const sub = this._treeFindPath(children, keys, value, path)\n if (sub) return [path.join('-')].concat(sub)\n }\n }\n return null\n }\n }\n}\n</script>\n\n<style>\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n"]}, media: undefined });
5753
+ inject("data-v-acef7758_0", { source: "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n", map: {"version":3,"sources":["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgTreeSelect.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8bA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,qEAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;AACA","file":"EgTreeSelect.vue","sourcesContent":["<template>\n <div\n :class=\"dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'\"\n :style=\"dropdownOnly ? overlayStyle : null\"\n @mousedown=\"onRootMouseDown\"\n >\n <!-- Standalone mode: trigger button -->\n <div\n v-if=\"!dropdownOnly\"\n class=\"eg-select-trigger\"\n :class=\"{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }\"\n @click.stop=\"toggle\"\n >\n <span\n class=\"eg-select-trigger-label\"\n :class=\"{ 'eg-select-placeholder': selectedLabel == null }\"\n :title=\"selectedTooltip\"\n >\n {{ selectedLabel != null ? selectedLabel : placeholder }}\n </span>\n <span class=\"eg-select-caret\">▾</span>\n </div>\n\n <!-- Options panel in grid dropdown-only mode -->\n <template v-if=\"dropdownOnly\">\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.id != null ? opt.id : String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :style=\"!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null\"\n :title=\"opt.tooltip\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span\n v-if=\"!searchQuery\"\n class=\"eg-tree-toggle\"\n :class=\"{ 'eg-tree-toggle--leaf': !opt.hasChildren }\"\n @mousedown.prevent.stop=\"toggleNode(idx)\"\n >{{ opt.hasChildren ? (opt.expanded ? '▾' : '▸') : '' }}</span>\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </template>\n\n <!-- Options panel in standalone mode -->\n <div\n v-else-if=\"open\"\n class=\"eg-select-dropdown eg-select-dropdown--floating\"\n @mousedown.stop.prevent\n >\n <div v-if=\"enableSearch\" class=\"eg-select-search\" @mousedown.stop>\n <input\n ref=\"searchInput\"\n class=\"eg-select-search-input\"\n :value=\"searchQuery\"\n placeholder=\"Qidirish...\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n @input=\"onSearch\"\n @keydown.stop=\"onSearchKey\"\n />\n </div>\n <div\n v-for=\"(opt, idx) in filteredOptions\"\n :key=\"opt.id != null ? opt.id : String(opt.value)\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': String(opt.value) === String(value),\n 'eg-select-option--highlighted': idx === highlight\n }\"\n :style=\"!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null\"\n :title=\"opt.tooltip\"\n @mousedown.prevent=\"pickOption(opt)\"\n @mouseover=\"highlight = idx\"\n >\n <span\n v-if=\"!searchQuery\"\n class=\"eg-tree-toggle\"\n :class=\"{ 'eg-tree-toggle--leaf': !opt.hasChildren }\"\n @mousedown.prevent.stop=\"toggleNode(idx)\"\n >{{ opt.hasChildren ? (opt.expanded ? '▾' : '▸') : '' }}</span>\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n <div v-if=\"!filteredOptions.length\" class=\"eg-select-empty\">{{ t.noOptions }}</div>\n </div>\n </div>\n</template>\n\n<script>\n// Approximate rendered height of one .eg-select-option row per grid `size`,\n// matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides\n// (see ExcelGrid.vue's <style>) — used only to estimate whether the dropdown\n// fits below the trigger before it's actually rendered (see overlayStyle).\nconst OPTION_ROW_H = { small: 22, middle: 28, large: 33 }\n\nimport { getMessages } from '../utils/i18n.js'\n\nexport default {\n name: 'EgTreeSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n valueKey: { type: String, default: 'value' },\n labelKey: { type: String, default: 'label' },\n childrenKey: { type: String, default: 'children' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: '' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null },\n size: { type: String, default: 'middle' },\n locale: { type: String, default: 'en' }\n },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1,\n expandedMap: {},\n // True right after Arrow/Page keyboard navigation — lets Space commit the\n // highlighted option instead of typing a literal space into the search box.\n // Reset on every text edit so normal multi-word search typing is unaffected.\n _navigatedByKeyboard: false\n }\n },\n computed: {\n t() { return getMessages(this.locale) },\n keys() {\n return { valueKey: this.valueKey, labelKey: this.labelKey, childrenKey: this.childrenKey }\n },\n flatOptions() {\n return this._treeFlatten(this.options || [], this.keys, this.expandedMap, 0, [])\n },\n allFlatOptions() {\n if (!this.enableSearch) return null\n return this._treeFlattenAll(this.options || [], this.keys)\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.flatOptions\n const q = this.searchQuery.trim().toLowerCase()\n const pool = this.allFlatOptions || this.flatOptions\n return pool.filter(o => String(o.label || '').toLowerCase().includes(q))\n },\n selectedLabel() {\n const v = String(this.value == null ? '' : this.value)\n if (!v) return null\n const found = this._treeFindLabel(this.options || [], this.keys, v)\n return found != null ? found : null\n },\n selectedTooltip() {\n const v = String(this.value == null ? '' : this.value)\n if (!v) return null\n const node = this._treeFindNode(this.options || [], this.keys, v)\n if (!node) return null\n const label = node[this.keys.labelKey] != null ? node[this.keys.labelKey] : String(node[this.keys.valueKey])\n return node.tooltip != null ? node.tooltip : label\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const rowH = OPTION_ROW_H[this.size] || OPTION_ROW_H.middle\n const approxH = Math.min(this.filteredOptions.length * rowH + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: 'fixed',\n top: top + 'px',\n left: rect.left + 'px',\n minWidth: Math.max(rect.width, 120) + 'px',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n this._initExpanded()\n this._scrollIndexIntoView(this.highlight)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener('click', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener('click', this._onDocClick)\n }\n },\n methods: {\n _initExpanded() {\n const v = String(this.value == null ? '' : this.value)\n const path = this._treeFindPath(this.options || [], this.keys, v, []) || []\n const expandedMap = {}\n path.forEach(id => { expandedMap[id] = true })\n this.expandedMap = expandedMap\n const flat = this.flatOptions\n this.highlight = flat.findIndex(o => String(o.value) === v)\n },\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n this.searchQuery = ''\n this._initExpanded()\n }\n },\n pickOption(opt) {\n this.$emit('input', opt.value)\n this.$emit('select', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = ''\n this.highlight = -1\n }\n },\n toggleNode(idx) {\n const opt = this.flatOptions[idx]\n if (!opt || !opt.hasChildren) return\n const next = { ...this.expandedMap }\n if (next[opt.id]) delete next[opt.id]\n else next[opt.id] = true\n this.expandedMap = next\n const newFlat = this.flatOptions\n const newIdx = newFlat.findIndex(o => o.id === opt.id)\n this.highlight = newIdx >= 0 ? newIdx : this.highlight\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n this._navigatedByKeyboard = false\n },\n onSearchKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit('close')\n else { this.open = false; this.searchQuery = '' }\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._navigatedByKeyboard = true\n this._scrollHighlightedIntoView()\n } else if (key === 'Enter') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n } else if (key === ' ' && this._navigatedByKeyboard) {\n // Space commits the option reached via Arrow/Page navigation instead of\n // typing a literal space — a plain (non-navigated) space still types normally.\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n const last = this.filteredOptions.length - 1\n if (key === 'Escape') {\n e.preventDefault()\n this.$emit('close')\n } else if (key === 'Enter' || key === ' ') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit('close')\n } else if (key === 'ArrowDown') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'ArrowUp') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageDown') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)\n this._scrollHighlightedIntoView()\n } else if (key === 'PageUp') {\n e.preventDefault()\n this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)\n this._scrollHighlightedIntoView()\n } else if (!this.searchQuery && (key === 'ArrowRight' || key === 'ArrowLeft')) {\n e.preventDefault()\n const opt = this.flatOptions[this.highlight]\n if (opt && opt.hasChildren) {\n if (key === 'ArrowRight' && !opt.expanded) this.toggleNode(this.highlight)\n if (key === 'ArrowLeft' && opt.expanded) this.toggleNode(this.highlight)\n }\n }\n },\n _scrollHighlightedIntoView() {\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[this.highlight]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n const elTop = el.offsetTop\n const elBottom = elTop + el.offsetHeight\n const visTop = container.scrollTop + stickyH\n const visBottom = container.scrollTop + container.clientHeight\n if (elTop < visTop) {\n container.scrollTop = elTop - stickyH\n } else if (elBottom > visBottom) {\n container.scrollTop = elBottom - container.clientHeight\n }\n })\n },\n // Aligns option `idx` to the top of the visible list on initial open.\n // Setting scrollTop past the container's max is clamped by the browser,\n // so an option near the end of the list naturally ends up scrolled to\n // the bottom instead of leaving blank space below it.\n _scrollIndexIntoView(idx) {\n if (idx < 0) return\n this.$nextTick(() => {\n const items = this.$el.querySelectorAll('.eg-select-option')\n const el = items[idx]\n if (!el) return\n const container = this.$el\n const searchEl = container.querySelector('.eg-select-search')\n const stickyH = searchEl ? searchEl.offsetHeight : 0\n container.scrollTop = el.offsetTop - stickyH\n })\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n },\n _treeFlatten(nodes, keys, expandedMap, depth, pathArr) {\n let out = []\n ;(nodes || []).forEach((node, idx) => {\n const path = pathArr.concat(idx)\n const id = path.join('-')\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n const hasChildren = children.length > 0\n const expanded = hasChildren && !!expandedMap[id]\n const label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n out.push({ value: node[keys.valueKey], label, color: node.color, tooltip: node.tooltip != null ? node.tooltip : label, id, depth, hasChildren, expanded })\n if (expanded) out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))\n })\n return out\n },\n _treeFlattenAll(nodes, keys) {\n const result = []\n const walk = (list) => {\n ;(list || []).forEach(node => {\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n const label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n result.push({ value: node[keys.valueKey], label, color: node.color, tooltip: node.tooltip != null ? node.tooltip : label, id: String(node[keys.valueKey]), depth: 0, hasChildren: false, expanded: false })\n if (children.length) walk(children)\n })\n }\n walk(nodes)\n return result\n },\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\n },\n _treeFindNode(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) return node\n const found = this._treeFindNode(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\n },\n _treeFindPath(nodes, keys, value, pathArr) {\n for (let i = 0; i < (nodes || []).length; i++) {\n const node = nodes[i]\n const path = pathArr.concat(i)\n if (String(node[keys.valueKey]) === value) return [path.join('-')]\n const children = node[keys.childrenKey]\n if (Array.isArray(children)) {\n const sub = this._treeFindPath(children, keys, value, path)\n if (sub) return [path.join('-')].concat(sub)\n }\n }\n return null\n }\n }\n}\n</script>\n\n<style>\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n"]}, media: undefined });
5701
5754
 
5702
5755
  };
5703
5756
  /* scoped */
@@ -16866,7 +16919,7 @@ __vue_render__._withStripped = true;
16866
16919
  undefined
16867
16920
  );
16868
16921
 
16869
- var version = "1.2.0";
16922
+ var version = "1.3.0";
16870
16923
 
16871
16924
  var index = {
16872
16925
  version: version,