@feugene/granularity 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/grSelectStyles-pUt19VGy.js.map +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-Cn1X-sWS.js → GrImageViewer-CsvXQHM9.js} +203 -50
- package/dist/components/GrImageViewer/chunks/GrImageViewer-CsvXQHM9.js.map +1 -0
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-cD6rFO9W.js → GrPagination-BNDEqM8_.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-cD6rFO9W.js.map → GrPagination-BNDEqM8_.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-W9lbAjRZ.js → GrSelect-CnHdbPlf.js} +108 -56
- package/dist/components/GrSelect/chunks/GrSelect-CnHdbPlf.js.map +1 -0
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/index.js +3 -3
- package/dist/types/src/components/GrImageViewer/GrImageViewer.vue.d.ts +30 -0
- package/dist/types/src/components/GrSelect/GrSelect.vue.d.ts +5 -4
- package/dist/types/src/components/GrSelect/grSelectStyles.d.ts +7 -0
- package/dist/types/src/components/GrSelect/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/components/GrImageViewer/chunks/GrImageViewer-Cn1X-sWS.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-W9lbAjRZ.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrPagination-
|
|
1
|
+
{"version":3,"file":"GrPagination-BNDEqM8_.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Окно отображаемых номеров — 5.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst pages = computed(() => {\n const max = pageCount.value\n const current = props.page\n const windowSize = 5\n const half = Math.floor(windowSize / 2)\n\n let start = Math.max(1, current - half)\n const end = Math.min(max, start + windowSize - 1)\n start = Math.max(1, end - windowSize + 1)\n\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n\n return list\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-ds-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.pageSize', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <button\n v-for=\"pageNumber in pages\"\n :key=\"pageNumber\"\n type=\"button\"\n data-ds-pagination-page\n :aria-current=\"pageNumber === page ? 'page' : undefined\"\n :aria-label=\"`Page ${pageNumber}`\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"pageNumber === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(pageNumber)\"\n >\n {{ pageNumber }}\n </button>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Окно отображаемых номеров — 5.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst pages = computed(() => {\n const max = pageCount.value\n const current = props.page\n const windowSize = 5\n const half = Math.floor(windowSize / 2)\n\n let start = Math.max(1, current - half)\n const end = Math.min(max, start + windowSize - 1)\n start = Math.max(1, end - windowSize + 1)\n\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n\n return list\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-ds-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.pageSize', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <button\n v-for=\"pageNumber in pages\"\n :key=\"pageNumber\"\n type=\"button\"\n data-ds-pagination-page\n :aria-current=\"pageNumber === page ? 'page' : undefined\"\n :aria-label=\"`Page ${pageNumber}`\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"pageNumber === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(pageNumber)\"\n >\n {{ pageNumber }}\n </button>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBA,MAAM,EAAE,MAAM,4BAA2B;EAEzC,MAAM,QAAQ;EAId,MAAM,OAAO;EAKb,MAAM,YAAY,eAAe,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,CAAA;EAErF,MAAM,gBAAgB,SAAS;GAC7B,WAAW,OAAO,MAAM,SAAS;GACjC,MAAM,UAAkB,KAAK,mBAAmB,OAAO,MAAM,CAAC;GAC/D,CAAA;EAED,MAAM,kBAAkB,eACtB,MAAM,UAAU,KAAI,cAAa;GAC/B,OAAO,OAAO,SAAS;GACvB,OAAO,OAAO,SAAS;GACxB,EAAE,CACL;EAEA,MAAM,QAAQ,eAAe;GAC3B,MAAM,MAAM,UAAU;GACtB,MAAM,UAAU,MAAM;GACtB,MAAM,aAAa;GACnB,MAAM,OAAO,KAAK,MAAM,aAAa,EAAC;GAEtC,IAAI,QAAQ,KAAK,IAAI,GAAG,UAAU,KAAI;GACtC,MAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,aAAa,EAAC;AAChD,WAAQ,KAAK,IAAI,GAAG,MAAM,aAAa,EAAC;GAExC,MAAM,OAAiB,EAAC;AACxB,QAAK,IAAI,OAAO,OAAO,QAAQ,KAAK,QAAQ,EAC1C,MAAK,KAAK,KAAI;AAEhB,UAAO;IACR;EAED,SAAS,KAAK,MAAoB;AAChC,QAAK,eAAe,KAAI;;EAG1B,SAAS,OAAa;AACpB,QAAK,KAAK,IAAI,GAAG,MAAM,OAAO,EAAE,CAAA;;EAGlC,SAAS,OAAa;AACpB,QAAK,KAAK,IAAI,UAAU,OAAO,MAAM,OAAO,EAAE,CAAA;;;uBAK9C,mBAqCM,OAAA;IApCJ,OAAM;IACN,sBAAA;IACA,MAAK;IACJ,cAAY,MAAA,EAAC,CAAA,0BAAA,aAAA;;IAEd,mBAMM,OANN,YAMM,CALJ,YAIE,kBAAA;iBAHS,cAAA;gFAAa,QAAA;KACrB,SAAS,gBAAA;KACT,cAAY,MAAA,EAAC,CAAA,0BAAA,YAAA;;;;;;IAIlB,YAEW,kBAAA;KAFD,SAAQ;KAAQ,MAAK;KAAM,UAAU,QAAA,QAAI;KAAQ,SAAO;;4BAC3B,CAAA,gBAAA,gBAAlC,MAAA,EAAC,CAAA,sBAAA,OAAA,CAAA,EAAA,EAAA,CAAA,CAAA;;;IAGN,mBAcM,OAdN,YAcM,EAAA,UAAA,KAAA,EAbJ,mBAYS,UAAA,MAAA,WAXc,MAAA,QAAd,eAAU;yBADnB,mBAYS,UAAA;MAVN,KAAK;MACN,MAAK;MACL,2BAAA;MACC,gBAAc,eAAe,QAAA,OAAI,SAAY,KAAA;MAC7C,cAAU,QAAU;MACrB,OAAK,eAAA,CAAC,wJACE,eAAe,QAAA,OAAI,iDAAA,wEAAA,CAAA;MAC1B,UAAK,WAAE,KAAK,WAAU;wBAEpB,WAAU,EAAA,IAAA,WAAA;;IAIjB,YAEW,kBAAA;KAFD,SAAQ;KAAQ,MAAK;KAAM,UAAU,QAAA,QAAQ,UAAA;KAAY,SAAO;;4BACnC,CAAA,gBAAA,gBAAlC,MAAA,EAAC,CAAA,sBAAA,OAAA,CAAA,EAAA,EAAA,CAAA,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrPagination_default } from "./chunks/GrPagination-
|
|
1
|
+
import { t as GrPagination_default } from "./chunks/GrPagination-BNDEqM8_.js";
|
|
2
2
|
import { t as grPaginationConfig } from "../../chunks/config-iuMz79Mg.js";
|
|
3
3
|
export { GrPagination_default as GrPagination, GrPagination_default as default, grPaginationConfig };
|
|
@@ -14,37 +14,39 @@ var _hoisted_2 = {
|
|
|
14
14
|
value: ""
|
|
15
15
|
};
|
|
16
16
|
var _hoisted_3 = ["value"];
|
|
17
|
-
var _hoisted_4 = ["
|
|
18
|
-
var _hoisted_5 =
|
|
17
|
+
var _hoisted_4 = ["label"];
|
|
18
|
+
var _hoisted_5 = ["value"];
|
|
19
|
+
var _hoisted_6 = ["value"];
|
|
20
|
+
var _hoisted_7 = {
|
|
19
21
|
key: 1,
|
|
20
22
|
"data-testid": "ds-select-chevron",
|
|
21
23
|
class: "absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none"
|
|
22
24
|
};
|
|
23
|
-
var
|
|
25
|
+
var _hoisted_8 = [
|
|
24
26
|
"disabled",
|
|
25
27
|
"aria-label",
|
|
26
28
|
"aria-expanded"
|
|
27
29
|
];
|
|
28
|
-
var
|
|
29
|
-
var
|
|
30
|
+
var _hoisted_9 = { class: "min-w-0 flex-1" };
|
|
31
|
+
var _hoisted_10 = {
|
|
30
32
|
key: 0,
|
|
31
33
|
class: "shrink-0 h-4 w-4",
|
|
32
34
|
"aria-hidden": "true"
|
|
33
35
|
};
|
|
34
|
-
var
|
|
36
|
+
var _hoisted_11 = {
|
|
35
37
|
key: 1,
|
|
36
38
|
"data-testid": "ds-select-chevron",
|
|
37
39
|
class: "shrink-0 text-[var(--muted-fg)] pointer-events-none"
|
|
38
40
|
};
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
+
var _hoisted_12 = ["disabled", "aria-label"];
|
|
42
|
+
var _hoisted_13 = {
|
|
41
43
|
key: 0,
|
|
42
44
|
class: "p-2 border-b border-[var(--brd)]"
|
|
43
45
|
};
|
|
44
|
-
var
|
|
45
|
-
var
|
|
46
|
-
var
|
|
47
|
-
var
|
|
46
|
+
var _hoisted_14 = ["aria-multiselectable"];
|
|
47
|
+
var _hoisted_15 = ["aria-selected", "onClick"];
|
|
48
|
+
var _hoisted_16 = { class: "flex items-center gap-2 min-w-0" };
|
|
49
|
+
var _hoisted_17 = { class: "truncate" };
|
|
48
50
|
//#endregion
|
|
49
51
|
//#region src/components/GrSelect/GrSelect.vue
|
|
50
52
|
var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
@@ -90,6 +92,16 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
90
92
|
const rootClass = computed(() => props.view === "link" ? "relative inline-block align-baseline" : "relative w-full");
|
|
91
93
|
const emit = __emit;
|
|
92
94
|
const optionsResolved = computed(() => props.options ?? []);
|
|
95
|
+
function isOptionGroup(item) {
|
|
96
|
+
return Array.isArray(item.options);
|
|
97
|
+
}
|
|
98
|
+
/** Плоский список всех опций (группы «развёрнуты»). Используется для всех вычислений по значениям. */
|
|
99
|
+
const flatOptions = computed(() => {
|
|
100
|
+
const result = [];
|
|
101
|
+
for (const item of optionsResolved.value) if (isOptionGroup(item)) result.push(...item.options);
|
|
102
|
+
else result.push(item);
|
|
103
|
+
return result;
|
|
104
|
+
});
|
|
93
105
|
function modelToArray(value) {
|
|
94
106
|
if (Array.isArray(value)) return value;
|
|
95
107
|
if (!value) return [];
|
|
@@ -104,7 +116,7 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
104
116
|
});
|
|
105
117
|
const hasSelection = computed(() => selectedValues.value.length > 0);
|
|
106
118
|
const selectedOptions = computed(() => {
|
|
107
|
-
const byValue = new Map(
|
|
119
|
+
const byValue = new Map(flatOptions.value.map((o) => [o.value, o]));
|
|
108
120
|
return selectedValues.value.map((v) => byValue.get(v) ?? {
|
|
109
121
|
value: v,
|
|
110
122
|
label: v
|
|
@@ -112,14 +124,14 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
112
124
|
});
|
|
113
125
|
const hasModelInOptions = computed(() => {
|
|
114
126
|
if (props.multiple) return false;
|
|
115
|
-
return
|
|
127
|
+
return flatOptions.value.some((o) => o.value === modelSingle.value);
|
|
116
128
|
});
|
|
117
129
|
const displayLabel = computed(() => {
|
|
118
130
|
if (props.multiple) {
|
|
119
131
|
if (!selectedValues.value.length) return "";
|
|
120
|
-
return selectedValues.value.map((v) =>
|
|
132
|
+
return selectedValues.value.map((v) => flatOptions.value.find((o) => o.value === v)?.label ?? v).join(", ");
|
|
121
133
|
}
|
|
122
|
-
return
|
|
134
|
+
return flatOptions.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value;
|
|
123
135
|
});
|
|
124
136
|
const displayText = computed(() => {
|
|
125
137
|
if (hasSelection.value) return displayLabel.value;
|
|
@@ -209,24 +221,51 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
209
221
|
const panelClasses = computed(() => {
|
|
210
222
|
return grSelectPanelClasses;
|
|
211
223
|
});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
function matchesQuery(option, query) {
|
|
225
|
+
if (!query) return true;
|
|
226
|
+
return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Элемент рендера панели: либо заголовок группы, либо опция.
|
|
230
|
+
* Группировка сохраняется, фильтрация по `customValue` скрывает пустые группы.
|
|
231
|
+
*/
|
|
232
|
+
const panelItems = computed(() => {
|
|
233
|
+
const q = props.allowCustomValue ? customValue.value.trim().toLowerCase() : "";
|
|
234
|
+
const items = [];
|
|
235
|
+
if (props.allowCustomValue && !props.multiple && modelSingle.value !== "" && !hasModelInOptions.value) {
|
|
236
|
+
const custom = {
|
|
220
237
|
value: modelSingle.value,
|
|
221
238
|
label: modelSingle.value
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
239
|
+
};
|
|
240
|
+
if (matchesQuery(custom, q)) items.push({
|
|
241
|
+
kind: "option",
|
|
242
|
+
option: custom,
|
|
243
|
+
key: `__custom__${custom.value}`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
optionsResolved.value.forEach((item, index) => {
|
|
247
|
+
if (isOptionGroup(item)) {
|
|
248
|
+
const matched = item.options.filter((o) => matchesQuery(o, q));
|
|
249
|
+
if (!matched.length) return;
|
|
250
|
+
items.push({
|
|
251
|
+
kind: "group",
|
|
252
|
+
label: item.label,
|
|
253
|
+
key: `__group__${index}`
|
|
254
|
+
});
|
|
255
|
+
for (const option of matched) items.push({
|
|
256
|
+
kind: "option",
|
|
257
|
+
option,
|
|
258
|
+
key: option.value
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (matchesQuery(item, q)) items.push({
|
|
263
|
+
kind: "option",
|
|
264
|
+
option: item,
|
|
265
|
+
key: item.value
|
|
266
|
+
});
|
|
229
267
|
});
|
|
268
|
+
return items;
|
|
230
269
|
});
|
|
231
270
|
const canAddCustom = computed(() => {
|
|
232
271
|
if (!props.allowCustomValue) return false;
|
|
@@ -235,10 +274,10 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
235
274
|
if (!v) return false;
|
|
236
275
|
if (props.multiple) {
|
|
237
276
|
if (selectedValues.value.includes(v)) return false;
|
|
238
|
-
return !
|
|
277
|
+
return !flatOptions.value.some((o) => o.value === v);
|
|
239
278
|
}
|
|
240
279
|
if (v === modelSingle.value) return false;
|
|
241
|
-
return !
|
|
280
|
+
return !flatOptions.value.some((o) => o.value === v);
|
|
242
281
|
});
|
|
243
282
|
function emitValue(value) {
|
|
244
283
|
emit("update:modelValue", value);
|
|
@@ -275,7 +314,7 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
275
314
|
if (props.optionsView !== "native") return false;
|
|
276
315
|
if (props.multiple) return false;
|
|
277
316
|
if (!props.options) return true;
|
|
278
|
-
return !
|
|
317
|
+
return !flatOptions.value.some((o) => o.value === "");
|
|
279
318
|
});
|
|
280
319
|
const panelClearVisible = computed(() => {
|
|
281
320
|
if (!props.clearable) return false;
|
|
@@ -348,11 +387,19 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
348
387
|
}, [nativeClearOptionVisible.value ? (openBlock(), createElementBlock("option", _hoisted_2)) : createCommentVNode("", true), renderSlot(_ctx.$slots, "default", {}, () => [nativeCustomOptionVisible.value ? (openBlock(), createElementBlock("option", {
|
|
349
388
|
key: 0,
|
|
350
389
|
value: modelSingle.value
|
|
351
|
-
}, toDisplayString(modelSingle.value), 9, _hoisted_3)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(
|
|
352
|
-
return openBlock(), createElementBlock("
|
|
353
|
-
key:
|
|
354
|
-
|
|
355
|
-
},
|
|
390
|
+
}, toDisplayString(modelSingle.value), 9, _hoisted_3)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(optionsResolved.value, (item, index) => {
|
|
391
|
+
return openBlock(), createElementBlock(Fragment, { key: index }, [isOptionGroup(item) ? (openBlock(), createElementBlock("optgroup", {
|
|
392
|
+
key: 0,
|
|
393
|
+
label: item.label
|
|
394
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(item.options, (opt) => {
|
|
395
|
+
return openBlock(), createElementBlock("option", {
|
|
396
|
+
key: opt.value,
|
|
397
|
+
value: opt.value
|
|
398
|
+
}, toDisplayString(opt.label), 9, _hoisted_5);
|
|
399
|
+
}), 128))], 8, _hoisted_4)) : (openBlock(), createElementBlock("option", {
|
|
400
|
+
key: 1,
|
|
401
|
+
value: item.value
|
|
402
|
+
}, toDisplayString(item.label), 9, _hoisted_6))], 64);
|
|
356
403
|
}), 128))])], 42, _hoisted_1),
|
|
357
404
|
isLinkNative.value ? (openBlock(), createElementBlock("span", {
|
|
358
405
|
key: 0,
|
|
@@ -360,7 +407,7 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
360
407
|
"aria-hidden": "true",
|
|
361
408
|
class: normalizeClass(linkNativeLabelClassName.value)
|
|
362
409
|
}, toDisplayString(linkNativeDisplayText.value), 3)) : createCommentVNode("", true),
|
|
363
|
-
showNativeChevron.value ? (openBlock(), createElementBlock("span",
|
|
410
|
+
showNativeChevron.value ? (openBlock(), createElementBlock("span", _hoisted_7, [..._cache[1] || (_cache[1] = [createElementVNode("span", {
|
|
364
411
|
class: "i-lucide-chevron-down inline-block h-4 w-4",
|
|
365
412
|
"aria-hidden": "true"
|
|
366
413
|
}, null, -1)])])) : createCommentVNode("", true)
|
|
@@ -382,16 +429,16 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
382
429
|
"aria-expanded": open.value ? "true" : "false",
|
|
383
430
|
class: normalizeClass([baseClassName.value, triggerClassName.value]),
|
|
384
431
|
onClick: toggleDropdown
|
|
385
|
-
}, [createElementVNode("span",
|
|
432
|
+
}, [createElementVNode("span", _hoisted_9, [renderSlot(_ctx.$slots, "value", {
|
|
386
433
|
selectedOptions: selectedOptions.value,
|
|
387
434
|
selectedValues: selectedValues.value,
|
|
388
435
|
displayLabel: displayLabel.value,
|
|
389
436
|
placeholder: __props.placeholder,
|
|
390
437
|
hasSelection: hasSelection.value
|
|
391
|
-
}, () => [createElementVNode("span", { class: normalizeClass(["block truncate", !hasSelection.value ? "text-[var(--muted-fg)]" : ""]) }, toDisplayString(displayText.value), 3)])]), panelClearVisible.value ? (openBlock(), createElementBlock("span",
|
|
438
|
+
}, () => [createElementVNode("span", { class: normalizeClass(["block truncate", !hasSelection.value ? "text-[var(--muted-fg)]" : ""]) }, toDisplayString(displayText.value), 3)])]), panelClearVisible.value ? (openBlock(), createElementBlock("span", _hoisted_10)) : (openBlock(), createElementBlock("span", _hoisted_11, [..._cache[2] || (_cache[2] = [createElementVNode("span", {
|
|
392
439
|
class: "i-lucide-chevron-down inline-block h-4 w-4",
|
|
393
440
|
"aria-hidden": "true"
|
|
394
|
-
}, null, -1)])]))], 10,
|
|
441
|
+
}, null, -1)])]))], 10, _hoisted_8),
|
|
395
442
|
panelClearVisible.value ? (openBlock(), createElementBlock("button", {
|
|
396
443
|
key: 0,
|
|
397
444
|
"data-testid": "ds-select-clear",
|
|
@@ -404,7 +451,7 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
404
451
|
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", {
|
|
405
452
|
class: "i-lucide-x inline-block h-4 w-4",
|
|
406
453
|
"aria-hidden": "true"
|
|
407
|
-
}, null, -1)])], 8,
|
|
454
|
+
}, null, -1)])], 8, _hoisted_12)) : createCommentVNode("", true),
|
|
408
455
|
(openBlock(), createBlock(Teleport, { to: "body" }, [createVNode(Transition, {
|
|
409
456
|
"enter-active-class": "transition ease-out duration-150",
|
|
410
457
|
"enter-from-class": "transform opacity-0 scale-95",
|
|
@@ -420,7 +467,7 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
420
467
|
"data-ds-select-panel": "",
|
|
421
468
|
class: "fixed w-full",
|
|
422
469
|
style: normalizeStyle(panelStyle.value)
|
|
423
|
-
}, [createElementVNode("div", { class: normalizeClass(panelClasses.value) }, [__props.allowCustomValue ? (openBlock(), createElementBlock("div",
|
|
470
|
+
}, [createElementVNode("div", { class: normalizeClass(panelClasses.value) }, [__props.allowCustomValue ? (openBlock(), createElementBlock("div", _hoisted_13, [createVNode(GrInput_default, {
|
|
424
471
|
ref_key: "customInputRef",
|
|
425
472
|
ref: customInputRef,
|
|
426
473
|
modelValue: customValue.value,
|
|
@@ -447,23 +494,28 @@ var GrSelect_default = /* @__PURE__ */ defineComponent({
|
|
|
447
494
|
type: "button",
|
|
448
495
|
class: normalizeClass(["rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]", [__props.view === "link" ? "block min-w-full w-max whitespace-nowrap" : "w-full"]]),
|
|
449
496
|
onClick: addCustom
|
|
450
|
-
}, " Add “" + toDisplayString(customValue.value.trim()) + "” ", 3)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(
|
|
451
|
-
return openBlock(), createElementBlock("
|
|
452
|
-
key:
|
|
497
|
+
}, " Add “" + toDisplayString(customValue.value.trim()) + "” ", 3)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(panelItems.value, (item) => {
|
|
498
|
+
return openBlock(), createElementBlock(Fragment, { key: item.key }, [item.kind === "group" ? (openBlock(), createElementBlock("div", {
|
|
499
|
+
key: 0,
|
|
500
|
+
"data-ds-select-group-label": "",
|
|
501
|
+
role: "presentation",
|
|
502
|
+
class: normalizeClass(["px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-[var(--muted-fg)]", [__props.view === "link" ? "block min-w-full w-max whitespace-nowrap" : ""]])
|
|
503
|
+
}, toDisplayString(item.label), 3)) : (openBlock(), createElementBlock("button", {
|
|
504
|
+
key: 1,
|
|
453
505
|
"data-ds-select-option": "",
|
|
454
506
|
type: "button",
|
|
455
507
|
role: "option",
|
|
456
|
-
"aria-selected": isSelected(
|
|
508
|
+
"aria-selected": isSelected(item.option.value) ? "true" : "false",
|
|
457
509
|
class: normalizeClass(["rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]", [__props.view === "link" ? "block min-w-full w-max whitespace-nowrap" : "w-full"]]),
|
|
458
|
-
onClick: ($event) => toggleValue(
|
|
510
|
+
onClick: ($event) => toggleValue(item.option.value)
|
|
459
511
|
}, [renderSlot(_ctx.$slots, "option", {
|
|
460
|
-
option:
|
|
461
|
-
selected: isSelected(
|
|
462
|
-
}, () => [createElementVNode("span",
|
|
463
|
-
class: normalizeClass(["inline-block h-4 w-4 shrink-0", isSelected(
|
|
512
|
+
option: item.option,
|
|
513
|
+
selected: isSelected(item.option.value)
|
|
514
|
+
}, () => [createElementVNode("span", _hoisted_16, [createElementVNode("span", {
|
|
515
|
+
class: normalizeClass(["inline-block h-4 w-4 shrink-0", isSelected(item.option.value) ? "i-lucide-check text-[var(--primary)]" : ""]),
|
|
464
516
|
"aria-hidden": "true"
|
|
465
|
-
}, null, 2), createElementVNode("span",
|
|
466
|
-
}), 128))], 12,
|
|
517
|
+
}, null, 2), createElementVNode("span", _hoisted_17, toDisplayString(item.option.label), 1)])])], 10, _hoisted_15))], 64);
|
|
518
|
+
}), 128))], 12, _hoisted_14)], 2)], 4), [[vShow, open.value]])]),
|
|
467
519
|
_: 3
|
|
468
520
|
})]))
|
|
469
521
|
], 2)), [[unref(vClickOutside), {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrSelect-CnHdbPlf.js","names":[],"sources":["../../../../src/components/GrSelect/GrSelect.vue","../../../../src/components/GrSelect/GrSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionGroup,\n type GrSelectOptionOrGroup,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionGroup,\n GrSelectOptionOrGroup,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n /** Список опций. Поддерживает плоский массив опций и группы опций (`{ label, options }`). */\n options?: GrSelectOptionOrGroup[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed<GrSelectOptionOrGroup[]>(() => props.options ?? [])\n\nfunction isOptionGroup(item: GrSelectOptionOrGroup): item is GrSelectOptionGroup {\n return Array.isArray((item as GrSelectOptionGroup).options)\n}\n\n/** Плоский список всех опций (группы «развёрнуты»). Используется для всех вычислений по значениям. */\nconst flatOptions = computed<GrSelectOption[]>(() => {\n const result: GrSelectOption[] = []\n for (const item of optionsResolved.value) {\n if (isOptionGroup(item)) result.push(...item.options)\n else result.push(item)\n }\n return result\n})\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(flatOptions.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return flatOptions.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => flatOptions.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return flatOptions.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nfunction matchesQuery(option: GrSelectOption, query: string): boolean {\n if (!query) return true\n return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query)\n}\n\n/**\n * Элемент рендера панели: либо заголовок группы, либо опция.\n * Группировка сохраняется, фильтрация по `customValue` скрывает пустые группы.\n */\ntype GrSelectPanelItem =\n | { kind: 'group', label: string, key: string }\n | { kind: 'option', option: GrSelectOption, key: string }\n\nconst panelItems = computed<GrSelectPanelItem[]>(() => {\n const q = props.allowCustomValue ? customValue.value.trim().toLowerCase() : ''\n const items: GrSelectPanelItem[] = []\n\n // Опция для кастомного значения, которого нет в options (single).\n if (props.allowCustomValue && !props.multiple && modelSingle.value !== '' && !hasModelInOptions.value) {\n const custom: GrSelectOption = { value: modelSingle.value, label: modelSingle.value }\n if (matchesQuery(custom, q)) {\n items.push({ kind: 'option', option: custom, key: `__custom__${custom.value}` })\n }\n }\n\n optionsResolved.value.forEach((item, index) => {\n if (isOptionGroup(item)) {\n const matched = item.options.filter((o) => matchesQuery(o, q))\n if (!matched.length) return\n items.push({ kind: 'group', label: item.label, key: `__group__${index}` })\n for (const option of matched) {\n items.push({ kind: 'option', option, key: option.value })\n }\n return\n }\n\n if (matchesQuery(item, q)) {\n items.push({ kind: 'option', option: item, key: item.value })\n }\n })\n\n return items\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !flatOptions.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !flatOptions.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !flatOptions.value.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <template v-for=\"(item, index) in optionsResolved\" :key=\"index\">\n <optgroup v-if=\"isOptionGroup(item)\" :label=\"item.label\">\n <option v-for=\"opt in item.options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </optgroup>\n <option v-else :value=\"item.value\">\n {{ item.label }}\n </option>\n </template>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <template v-for=\"item in panelItems\" :key=\"item.key\">\n <div\n v-if=\"item.kind === 'group'\"\n data-ds-select-group-label\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-[var(--muted-fg)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : '',\n ]\"\n >\n {{ item.label }}\n </div>\n\n <button\n v-else\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(item.option.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(item.option.value)\"\n >\n <slot name=\"option\" :option=\"item.option\" :selected=\"isSelected(item.option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(item.option.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ item.option.label }}</span>\n </span>\n </slot>\n </button>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionGroup,\n type GrSelectOptionOrGroup,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionGroup,\n GrSelectOptionOrGroup,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n /** Список опций. Поддерживает плоский массив опций и группы опций (`{ label, options }`). */\n options?: GrSelectOptionOrGroup[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed<GrSelectOptionOrGroup[]>(() => props.options ?? [])\n\nfunction isOptionGroup(item: GrSelectOptionOrGroup): item is GrSelectOptionGroup {\n return Array.isArray((item as GrSelectOptionGroup).options)\n}\n\n/** Плоский список всех опций (группы «развёрнуты»). Используется для всех вычислений по значениям. */\nconst flatOptions = computed<GrSelectOption[]>(() => {\n const result: GrSelectOption[] = []\n for (const item of optionsResolved.value) {\n if (isOptionGroup(item)) result.push(...item.options)\n else result.push(item)\n }\n return result\n})\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(flatOptions.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return flatOptions.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => flatOptions.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return flatOptions.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nfunction matchesQuery(option: GrSelectOption, query: string): boolean {\n if (!query) return true\n return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query)\n}\n\n/**\n * Элемент рендера панели: либо заголовок группы, либо опция.\n * Группировка сохраняется, фильтрация по `customValue` скрывает пустые группы.\n */\ntype GrSelectPanelItem =\n | { kind: 'group', label: string, key: string }\n | { kind: 'option', option: GrSelectOption, key: string }\n\nconst panelItems = computed<GrSelectPanelItem[]>(() => {\n const q = props.allowCustomValue ? customValue.value.trim().toLowerCase() : ''\n const items: GrSelectPanelItem[] = []\n\n // Опция для кастомного значения, которого нет в options (single).\n if (props.allowCustomValue && !props.multiple && modelSingle.value !== '' && !hasModelInOptions.value) {\n const custom: GrSelectOption = { value: modelSingle.value, label: modelSingle.value }\n if (matchesQuery(custom, q)) {\n items.push({ kind: 'option', option: custom, key: `__custom__${custom.value}` })\n }\n }\n\n optionsResolved.value.forEach((item, index) => {\n if (isOptionGroup(item)) {\n const matched = item.options.filter((o) => matchesQuery(o, q))\n if (!matched.length) return\n items.push({ kind: 'group', label: item.label, key: `__group__${index}` })\n for (const option of matched) {\n items.push({ kind: 'option', option, key: option.value })\n }\n return\n }\n\n if (matchesQuery(item, q)) {\n items.push({ kind: 'option', option: item, key: item.value })\n }\n })\n\n return items\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !flatOptions.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !flatOptions.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !flatOptions.value.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <template v-for=\"(item, index) in optionsResolved\" :key=\"index\">\n <optgroup v-if=\"isOptionGroup(item)\" :label=\"item.label\">\n <option v-for=\"opt in item.options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </optgroup>\n <option v-else :value=\"item.value\">\n {{ item.label }}\n </option>\n </template>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <template v-for=\"item in panelItems\" :key=\"item.key\">\n <div\n v-if=\"item.kind === 'group'\"\n data-ds-select-group-label\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-[var(--muted-fg)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : '',\n ]\"\n >\n {{ item.label }}\n </div>\n\n <button\n v-else\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(item.option.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(item.option.value)\"\n >\n <slot name=\"option\" :option=\"item.option\" :selected=\"isSelected(item.option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(item.option.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ item.option.label }}</span>\n </span>\n </slot>\n </button>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSelect_default } from "./chunks/GrSelect-
|
|
1
|
+
import { t as GrSelect_default } from "./chunks/GrSelect-CnHdbPlf.js";
|
|
2
2
|
import { n as grSelectSafelist, t as grSelectConfig } from "../../chunks/config-BY81UKv0.js";
|
|
3
3
|
export { GrSelect_default as GrSelect, GrSelect_default as default, grSelectConfig, grSelectSafelist };
|
package/dist/index.js
CHANGED
|
@@ -67,7 +67,7 @@ import { t as GrFormFile_default } from "./components/GrFormFile/chunks/GrFormFi
|
|
|
67
67
|
import { t as grFormFileConfig } from "./chunks/config-BbKroFtR.js";
|
|
68
68
|
import { t as GrFormSection_default } from "./components/GrFormSection/chunks/GrFormSection-DpGhY7MX.js";
|
|
69
69
|
import { t as grFormSectionConfig } from "./chunks/config-DoBk_CzG.js";
|
|
70
|
-
import { t as GrImageViewer_default } from "./components/GrImageViewer/chunks/GrImageViewer-
|
|
70
|
+
import { t as GrImageViewer_default } from "./components/GrImageViewer/chunks/GrImageViewer-CsvXQHM9.js";
|
|
71
71
|
import { t as grImageViewerConfig } from "./chunks/config-CvGdaXvH.js";
|
|
72
72
|
import { n as grInputSafelist, t as grInputConfig } from "./chunks/config-D4s_QWVP.js";
|
|
73
73
|
import "./components/GrInput/index.js";
|
|
@@ -85,8 +85,8 @@ import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-BH7
|
|
|
85
85
|
import { t as grNavbarConfig } from "./chunks/config-Ci9nXGUV.js";
|
|
86
86
|
import { n as grNumberInputSafelist, t as grNumberInputConfig } from "./chunks/config-CdJpnjDj.js";
|
|
87
87
|
import { t as GrNumberInput_default } from "./components/GrNumberInput/chunks/GrNumberInput-YIWhV85S.js";
|
|
88
|
-
import { t as GrSelect_default } from "./components/GrSelect/chunks/GrSelect-
|
|
89
|
-
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-
|
|
88
|
+
import { t as GrSelect_default } from "./components/GrSelect/chunks/GrSelect-CnHdbPlf.js";
|
|
89
|
+
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-BNDEqM8_.js";
|
|
90
90
|
import { t as grPaginationConfig } from "./chunks/config-iuMz79Mg.js";
|
|
91
91
|
import { n as grProgressBarSafelist, t as grProgressBarConfig } from "./chunks/config-CFf7xaG1.js";
|
|
92
92
|
import "./components/GrProgressBar/index.js";
|
|
@@ -20,6 +20,10 @@ type __VLS_Props = {
|
|
|
20
20
|
closeOnPressEscape?: boolean;
|
|
21
21
|
showProgress?: boolean;
|
|
22
22
|
showZoomValue?: boolean;
|
|
23
|
+
/** Включает масштабирование колесом мыши / жестом на трекпаде. По умолчанию включено. */
|
|
24
|
+
wheelZoom?: boolean;
|
|
25
|
+
/** Включает перетаскивание (pan) картинки мышью. При наведении курсор «рука». По умолчанию выключено. */
|
|
26
|
+
draggable?: boolean;
|
|
23
27
|
zIndex?: number;
|
|
24
28
|
/** i18n: aria-label кнопки закрытия. */
|
|
25
29
|
closeLabel?: string;
|
|
@@ -56,6 +60,16 @@ declare var __VLS_40: {
|
|
|
56
60
|
total: number;
|
|
57
61
|
scale: number;
|
|
58
62
|
rotation: number;
|
|
63
|
+
/** Натуральный (исходный) размер картинки, px. 0 пока изображение не загружено. */
|
|
64
|
+
naturalWidth: number;
|
|
65
|
+
naturalHeight: number;
|
|
66
|
+
/** Фактический размер изображения на экране c учётом scale, px (footprint вдоль осей картинки). */
|
|
67
|
+
renderedWidth: number;
|
|
68
|
+
renderedHeight: number;
|
|
69
|
+
/** Реальный масштаб относительно натурального размера (доля): renderedWidth / naturalWidth. */
|
|
70
|
+
realScale: number;
|
|
71
|
+
/** Реальный масштаб в процентах, отформатированный (например `67`). */
|
|
72
|
+
realScalePercent: string;
|
|
59
73
|
actions: GrImageViewerToolbarActions;
|
|
60
74
|
}, __VLS_42: {
|
|
61
75
|
index: number;
|
|
@@ -63,6 +77,16 @@ declare var __VLS_40: {
|
|
|
63
77
|
total: number;
|
|
64
78
|
scale: number;
|
|
65
79
|
rotation: number;
|
|
80
|
+
/** Натуральный (исходный) размер картинки, px. 0 пока изображение не загружено. */
|
|
81
|
+
naturalWidth: number;
|
|
82
|
+
naturalHeight: number;
|
|
83
|
+
/** Фактический размер изображения на экране c учётом scale, px (footprint вдоль осей картинки). */
|
|
84
|
+
renderedWidth: number;
|
|
85
|
+
renderedHeight: number;
|
|
86
|
+
/** Реальный масштаб относительно натурального размера (доля): renderedWidth / naturalWidth. */
|
|
87
|
+
realScale: number;
|
|
88
|
+
/** Реальный масштаб в процентах, отформатированный (например `67`). */
|
|
89
|
+
realScalePercent: string;
|
|
66
90
|
actions: GrImageViewerToolbarActions;
|
|
67
91
|
};
|
|
68
92
|
type __VLS_Slots = {} & {
|
|
@@ -79,6 +103,8 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
79
103
|
closeOnPressEscape: boolean;
|
|
80
104
|
showProgress: boolean;
|
|
81
105
|
showZoomValue: boolean;
|
|
106
|
+
wheelZoom: boolean;
|
|
107
|
+
draggable: boolean;
|
|
82
108
|
zIndex: undefined;
|
|
83
109
|
closeLabel: string;
|
|
84
110
|
prevLabel: string;
|
|
@@ -103,6 +129,8 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
103
129
|
closeOnPressEscape: boolean;
|
|
104
130
|
showProgress: boolean;
|
|
105
131
|
showZoomValue: boolean;
|
|
132
|
+
wheelZoom: boolean;
|
|
133
|
+
draggable: boolean;
|
|
106
134
|
zIndex: undefined;
|
|
107
135
|
closeLabel: string;
|
|
108
136
|
prevLabel: string;
|
|
@@ -129,6 +157,8 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
129
157
|
hideOnClickModal: boolean;
|
|
130
158
|
closeOnPressEscape: boolean;
|
|
131
159
|
showZoomValue: boolean;
|
|
160
|
+
wheelZoom: boolean;
|
|
161
|
+
draggable: boolean;
|
|
132
162
|
prevLabel: string;
|
|
133
163
|
nextLabel: string;
|
|
134
164
|
zoomInLabel: string;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { type GrSelectModelValue, type GrSelectOption, type GrSelectOptionsView, type GrSelectSize, type GrSelectUnderline, type GrSelectVariant, type GrSelectView } from './grSelectStyles';
|
|
2
|
-
export type { GrSelectModelValue, GrSelectOption, GrSelectOptionsView, GrSelectSize, GrSelectUnderline, GrSelectVariant, GrSelectView, } from './grSelectStyles';
|
|
1
|
+
import { type GrSelectModelValue, type GrSelectOption, type GrSelectOptionOrGroup, type GrSelectOptionsView, type GrSelectSize, type GrSelectUnderline, type GrSelectVariant, type GrSelectView } from './grSelectStyles';
|
|
2
|
+
export type { GrSelectModelValue, GrSelectOption, GrSelectOptionGroup, GrSelectOptionOrGroup, GrSelectOptionsView, GrSelectSize, GrSelectUnderline, GrSelectVariant, GrSelectView, } from './grSelectStyles';
|
|
3
3
|
/**
|
|
4
4
|
* Пропсы публичного DS-примитива «Select».
|
|
5
5
|
*/
|
|
6
6
|
export interface GrSelectProps {
|
|
7
7
|
modelValue: GrSelectModelValue;
|
|
8
|
-
options
|
|
8
|
+
/** Список опций. Поддерживает плоский массив опций и группы опций (`{ label, options }`). */
|
|
9
|
+
options?: GrSelectOptionOrGroup[];
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
ariaLabel?: string;
|
|
11
12
|
view?: GrSelectView;
|
|
@@ -101,7 +102,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
101
102
|
disabled: boolean;
|
|
102
103
|
ariaLabel: string;
|
|
103
104
|
placeholder: string;
|
|
104
|
-
options:
|
|
105
|
+
options: GrSelectOptionOrGroup[];
|
|
105
106
|
multiple: boolean;
|
|
106
107
|
underline: GrSelectUnderline;
|
|
107
108
|
optionsView: GrSelectOptionsView;
|