vue2-excel-grid 1.1.50 → 1.1.51

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.
@@ -3082,7 +3082,11 @@ var script$3 = {
3082
3082
  return {
3083
3083
  open: false,
3084
3084
  searchQuery: '',
3085
- highlight: -1
3085
+ highlight: -1,
3086
+ // True right after Arrow/Page keyboard navigation — lets Space commit the
3087
+ // highlighted option instead of typing a literal space into the search box.
3088
+ // Reset on every text edit so normal multi-word search typing is unaffected.
3089
+ _navigatedByKeyboard: false
3086
3090
  }
3087
3091
  },
3088
3092
  computed: {
@@ -3128,7 +3132,10 @@ var script$3 = {
3128
3132
  },
3129
3133
  mounted() {
3130
3134
  if (this.dropdownOnly) {
3131
- this.highlight = -1;
3135
+ const v = String(this.value == null ? '' : this.value);
3136
+ const selectedIndex = this.filteredOptions.findIndex(o => String(o.value) === v);
3137
+ this.highlight = selectedIndex;
3138
+ this._scrollIndexIntoView(selectedIndex);
3132
3139
  if (this.enableSearch) {
3133
3140
  this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus(); });
3134
3141
  }
@@ -3163,6 +3170,7 @@ var script$3 = {
3163
3170
  onSearch(e) {
3164
3171
  this.searchQuery = e.target.value;
3165
3172
  this.highlight = 0;
3173
+ this._navigatedByKeyboard = false;
3166
3174
  },
3167
3175
  onSearchKey(e) {
3168
3176
  const key = e.key;
@@ -3174,23 +3182,33 @@ var script$3 = {
3174
3182
  } else if (key === 'ArrowDown') {
3175
3183
  e.preventDefault();
3176
3184
  this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last);
3185
+ this._navigatedByKeyboard = true;
3177
3186
  this._scrollHighlightedIntoView();
3178
3187
  } else if (key === 'ArrowUp') {
3179
3188
  e.preventDefault();
3180
3189
  this.highlight = Math.max((this.highlight || 0) - 1, 0);
3190
+ this._navigatedByKeyboard = true;
3181
3191
  this._scrollHighlightedIntoView();
3182
3192
  } else if (key === 'PageDown') {
3183
3193
  e.preventDefault();
3184
3194
  this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last);
3195
+ this._navigatedByKeyboard = true;
3185
3196
  this._scrollHighlightedIntoView();
3186
3197
  } else if (key === 'PageUp') {
3187
3198
  e.preventDefault();
3188
3199
  this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0);
3200
+ this._navigatedByKeyboard = true;
3189
3201
  this._scrollHighlightedIntoView();
3190
3202
  } else if (key === 'Enter') {
3191
3203
  e.preventDefault();
3192
3204
  const opt = this.filteredOptions[this.highlight];
3193
3205
  if (opt) this.pickOption(opt);
3206
+ } else if (key === ' ' && this._navigatedByKeyboard) {
3207
+ // Space commits the option reached via Arrow/Page navigation instead of
3208
+ // typing a literal space — a plain (non-navigated) space still types normally.
3209
+ e.preventDefault();
3210
+ const opt = this.filteredOptions[this.highlight];
3211
+ if (opt) this.pickOption(opt);
3194
3212
  }
3195
3213
  },
3196
3214
  handleKey(e) {
@@ -3241,6 +3259,22 @@ var script$3 = {
3241
3259
  }
3242
3260
  });
3243
3261
  },
3262
+ // Aligns option `idx` to the top of the visible list on initial open.
3263
+ // Setting scrollTop past the container's max is clamped by the browser,
3264
+ // so an option near the end of the list naturally ends up scrolled to
3265
+ // the bottom instead of leaving blank space below it.
3266
+ _scrollIndexIntoView(idx) {
3267
+ if (idx < 0) return
3268
+ this.$nextTick(() => {
3269
+ const items = this.$el.querySelectorAll('.eg-select-option');
3270
+ const el = items[idx];
3271
+ if (!el) return
3272
+ const container = this.$el;
3273
+ const searchEl = container.querySelector('.eg-select-search');
3274
+ const stickyH = searchEl ? searchEl.offsetHeight : 0;
3275
+ container.scrollTop = el.offsetTop - stickyH;
3276
+ });
3277
+ },
3244
3278
  onRootMouseDown(e) {
3245
3279
  if (this.dropdownOnly) e.preventDefault();
3246
3280
  },
@@ -3480,7 +3514,7 @@ __vue_render__$3._withStripped = true;
3480
3514
  /* style */
3481
3515
  const __vue_inject_styles__$3 = function (inject) {
3482
3516
  if (!inject) return
3483
- inject("data-v-1a11683a_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/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size);\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);\n border-radius: var(--eg-radius);\n background: var(--eg-color-cell-bg);\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text);\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent);\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly);\n cursor: default;\n color: var(--eg-color-text-muted);\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);\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted);\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg);\n border: 1px solid var(--eg-color-border);\n border-radius: var(--eg-radius);\n box-shadow: var(--eg-shadow-dropdown);\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);\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg);\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);\n border-radius: var(--eg-radius);\n padding: 4px 8px;\n font-size: var(--eg-font-size);\n outline: none;\n color: var(--eg-color-text);\n background: var(--eg-color-cell-bg);\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\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);\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text);\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg);\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent);\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);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuRA,uBAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,8BAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,wCAAA;EACA,+BAAA;EACA,mCAAA;EACA,eAAA;EACA,iBAAA;EACA,2BAAA;EACA,wDAAA;AACA;AACA;EACA,oCAAA;AACA;AACA;EACA,oCAAA;EACA,iDAAA;AACA;AACA;EACA,yCAAA;EACA,eAAA;EACA,iCAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,iCAAA;AACA;AACA;EACA,cAAA;EACA,iCAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,mCAAA;EACA,wCAAA;EACA,+BAAA;EACA,qCAAA;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,oDAAA;EACA,gBAAA;EACA,MAAA;EACA,mCAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,wCAAA;EACA,+BAAA;EACA,gBAAA;EACA,8BAAA;EACA,aAAA;EACA,2BAAA;EACA,mCAAA;EACA,oBAAA;AACA;AACA;EACA,oCAAA;EACA,iDAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,8BAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,2BAAA;AACA;AACA;;EAEA,2CAAA;AACA;AACA;EACA,gBAAA;EACA,6BAAA;AACA;AACA;;EAEA,kDAAA;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 </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>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'EgSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\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 },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1\n }\n },\n computed: {\n normalizedOptions() {\n return (this.options || []).map(o =>\n typeof o === 'string'\n ? { value: o, label: o }\n : { value: o.value, label: o.label != null ? o.label : String(o.value), 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 approxH = Math.min(this.filteredOptions.length * 28 + 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.highlight = -1\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 },\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._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 (key === 'Enter') {\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 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.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size);\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);\n border-radius: var(--eg-radius);\n background: var(--eg-color-cell-bg);\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text);\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent);\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly);\n cursor: default;\n color: var(--eg-color-text-muted);\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);\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted);\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg);\n border: 1px solid var(--eg-color-border);\n border-radius: var(--eg-radius);\n box-shadow: var(--eg-shadow-dropdown);\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);\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg);\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);\n border-radius: var(--eg-radius);\n padding: 4px 8px;\n font-size: var(--eg-font-size);\n outline: none;\n color: var(--eg-color-text);\n background: var(--eg-color-cell-bg);\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\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);\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text);\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg);\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent);\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);\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 });
3517
+ inject("data-v-991deba0_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/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size);\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);\n border-radius: var(--eg-radius);\n background: var(--eg-color-cell-bg);\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text);\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent);\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly);\n cursor: default;\n color: var(--eg-color-text-muted);\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);\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted);\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg);\n border: 1px solid var(--eg-color-border);\n border-radius: var(--eg-radius);\n box-shadow: var(--eg-shadow-dropdown);\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);\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg);\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);\n border-radius: var(--eg-radius);\n padding: 4px 8px;\n font-size: var(--eg-font-size);\n outline: none;\n color: var(--eg-color-text);\n background: var(--eg-color-cell-bg);\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\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);\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text);\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg);\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent);\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);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyTA,uBAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,8BAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,wCAAA;EACA,+BAAA;EACA,mCAAA;EACA,eAAA;EACA,iBAAA;EACA,2BAAA;EACA,wDAAA;AACA;AACA;EACA,oCAAA;AACA;AACA;EACA,oCAAA;EACA,iDAAA;AACA;AACA;EACA,yCAAA;EACA,eAAA;EACA,iCAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,iCAAA;AACA;AACA;EACA,cAAA;EACA,iCAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,mCAAA;EACA,wCAAA;EACA,+BAAA;EACA,qCAAA;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,oDAAA;EACA,gBAAA;EACA,MAAA;EACA,mCAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,wCAAA;EACA,+BAAA;EACA,gBAAA;EACA,8BAAA;EACA,aAAA;EACA,2BAAA;EACA,mCAAA;EACA,oBAAA;AACA;AACA;EACA,oCAAA;EACA,iDAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,8BAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,2BAAA;AACA;AACA;;EAEA,2CAAA;AACA;AACA;EACA,gBAAA;EACA,6BAAA;AACA;AACA;;EAEA,kDAAA;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 </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>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'EgSelect',\n model: { prop: 'value', event: 'input' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\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 },\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 normalizedOptions() {\n return (this.options || []).map(o =>\n typeof o === 'string'\n ? { value: o, label: o }\n : { value: o.value, label: o.label != null ? o.label : String(o.value), 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 approxH = Math.min(this.filteredOptions.length * 28 + 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.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: var(--eg-font-size);\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);\n border-radius: var(--eg-radius);\n background: var(--eg-color-cell-bg);\n cursor: pointer;\n user-select: none;\n color: var(--eg-color-text);\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: var(--eg-color-accent);\n}\n.eg-select-trigger--open {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\n}\n.eg-select-trigger--disabled {\n background: var(--eg-color-cell-readonly);\n cursor: default;\n color: var(--eg-color-text-muted);\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);\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: var(--eg-color-text-muted);\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: var(--eg-color-cell-bg);\n border: 1px solid var(--eg-color-border);\n border-radius: var(--eg-radius);\n box-shadow: var(--eg-shadow-dropdown);\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);\n position: sticky;\n top: 0;\n background: var(--eg-color-cell-bg);\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);\n border-radius: var(--eg-radius);\n padding: 4px 8px;\n font-size: var(--eg-font-size);\n outline: none;\n color: var(--eg-color-text);\n background: var(--eg-color-cell-bg);\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: var(--eg-color-accent);\n box-shadow: 0 0 0 2px var(--eg-color-accent-soft);\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);\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: var(--eg-color-text);\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: var(--eg-color-option-hover-bg);\n}\n.eg-select-option--active {\n font-weight: 600;\n color: var(--eg-color-accent);\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);\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 });
3484
3518
 
3485
3519
  };
3486
3520
  /* scoped */
@@ -3632,7 +3666,11 @@ var script$2 = {
3632
3666
  open: false,
3633
3667
  searchQuery: '',
3634
3668
  highlight: -1,
3635
- expandedMap: {}
3669
+ expandedMap: {},
3670
+ // True right after Arrow/Page keyboard navigation — lets Space commit the
3671
+ // highlighted option instead of typing a literal space into the search box.
3672
+ // Reset on every text edit so normal multi-word search typing is unaffected.
3673
+ _navigatedByKeyboard: false
3636
3674
  }
3637
3675
  },
3638
3676
  computed: {
@@ -3685,7 +3723,7 @@ var script$2 = {
3685
3723
  mounted() {
3686
3724
  if (this.dropdownOnly) {
3687
3725
  this._initExpanded();
3688
- this.highlight = -1;
3726
+ this._scrollIndexIntoView(this.highlight);
3689
3727
  if (this.enableSearch) {
3690
3728
  this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus(); });
3691
3729
  }
@@ -3739,6 +3777,7 @@ var script$2 = {
3739
3777
  onSearch(e) {
3740
3778
  this.searchQuery = e.target.value;
3741
3779
  this.highlight = 0;
3780
+ this._navigatedByKeyboard = false;
3742
3781
  },
3743
3782
  onSearchKey(e) {
3744
3783
  const key = e.key;
@@ -3750,23 +3789,33 @@ var script$2 = {
3750
3789
  } else if (key === 'ArrowDown') {
3751
3790
  e.preventDefault();
3752
3791
  this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last);
3792
+ this._navigatedByKeyboard = true;
3753
3793
  this._scrollHighlightedIntoView();
3754
3794
  } else if (key === 'ArrowUp') {
3755
3795
  e.preventDefault();
3756
3796
  this.highlight = Math.max((this.highlight || 0) - 1, 0);
3797
+ this._navigatedByKeyboard = true;
3757
3798
  this._scrollHighlightedIntoView();
3758
3799
  } else if (key === 'PageDown') {
3759
3800
  e.preventDefault();
3760
3801
  this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last);
3802
+ this._navigatedByKeyboard = true;
3761
3803
  this._scrollHighlightedIntoView();
3762
3804
  } else if (key === 'PageUp') {
3763
3805
  e.preventDefault();
3764
3806
  this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0);
3807
+ this._navigatedByKeyboard = true;
3765
3808
  this._scrollHighlightedIntoView();
3766
3809
  } else if (key === 'Enter') {
3767
3810
  e.preventDefault();
3768
3811
  const opt = this.filteredOptions[this.highlight];
3769
3812
  if (opt) this.pickOption(opt);
3813
+ } else if (key === ' ' && this._navigatedByKeyboard) {
3814
+ // Space commits the option reached via Arrow/Page navigation instead of
3815
+ // typing a literal space — a plain (non-navigated) space still types normally.
3816
+ e.preventDefault();
3817
+ const opt = this.filteredOptions[this.highlight];
3818
+ if (opt) this.pickOption(opt);
3770
3819
  }
3771
3820
  },
3772
3821
  handleKey(e) {
@@ -3824,6 +3873,22 @@ var script$2 = {
3824
3873
  }
3825
3874
  });
3826
3875
  },
3876
+ // Aligns option `idx` to the top of the visible list on initial open.
3877
+ // Setting scrollTop past the container's max is clamped by the browser,
3878
+ // so an option near the end of the list naturally ends up scrolled to
3879
+ // the bottom instead of leaving blank space below it.
3880
+ _scrollIndexIntoView(idx) {
3881
+ if (idx < 0) return
3882
+ this.$nextTick(() => {
3883
+ const items = this.$el.querySelectorAll('.eg-select-option');
3884
+ const el = items[idx];
3885
+ if (!el) return
3886
+ const container = this.$el;
3887
+ const searchEl = container.querySelector('.eg-select-search');
3888
+ const stickyH = searchEl ? searchEl.offsetHeight : 0;
3889
+ container.scrollTop = el.offsetTop - stickyH;
3890
+ });
3891
+ },
3827
3892
  onRootMouseDown(e) {
3828
3893
  if (this.dropdownOnly) e.preventDefault();
3829
3894
  },
@@ -4162,7 +4227,7 @@ __vue_render__$2._withStripped = true;
4162
4227
  /* style */
4163
4228
  const __vue_inject_styles__$2 = function (inject) {
4164
4229
  if (!inject) return
4165
- inject("data-v-58d5e187_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/* 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);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyXA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,iCAAA;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 </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>\n </div>\n</template>\n\n<script>\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 },\n data() {\n return {\n open: false,\n searchQuery: '',\n highlight: -1,\n expandedMap: {}\n }\n },\n computed: {\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 approxH = Math.min(this.filteredOptions.length * 28 + 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.highlight = -1\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 },\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._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 (key === 'Enter') {\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 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);\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n"]}, media: undefined });
4230
+ inject("data-v-6d3e0cc0_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/* 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);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwZA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,iCAAA;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 </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>\n </div>\n</template>\n\n<script>\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 },\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 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 approxH = Math.min(this.filteredOptions.length * 28 + 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);\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n"]}, media: undefined });
4166
4231
 
4167
4232
  };
4168
4233
  /* scoped */