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
|
+
* 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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
16922
|
+
var version = "1.3.0";
|
|
16870
16923
|
|
|
16871
16924
|
var index = {
|
|
16872
16925
|
version: version,
|