@feugene/granularity 0.46.0 → 0.48.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.
- package/README.md +3 -3
- package/dist/chunks/{x-circle-BCBURwyX.js → check-circle-Bnrb-CGJ.js} +8 -32
- package/dist/chunks/check-circle-Bnrb-CGJ.js.map +1 -0
- package/dist/chunks/{config-BqT6PMzO.js → config-B42iJ48O.js} +5 -1
- package/dist/chunks/{config-BqT6PMzO.js.map → config-B42iJ48O.js.map} +1 -1
- package/dist/chunks/{config-BGCyyXiD.js → config-BAse0laT.js} +5 -1
- package/dist/chunks/{config-BGCyyXiD.js.map → config-BAse0laT.js.map} +1 -1
- package/dist/chunks/{config-MmPym-Z7.js → config-BLP94dPY.js} +5 -1
- package/dist/chunks/{config-MmPym-Z7.js.map → config-BLP94dPY.js.map} +1 -1
- package/dist/chunks/{config-CjKpUcmy.js → config-Bdf1bt-m.js} +25 -16
- package/dist/chunks/config-Bdf1bt-m.js.map +1 -0
- package/dist/chunks/{config-DeyT9dJl.js → config-Bs1HwUcx.js} +47 -4
- package/dist/chunks/{config-DeyT9dJl.js.map → config-Bs1HwUcx.js.map} +1 -1
- package/dist/chunks/{config-CIkdsWUk.js → config-CIUU9ZpY.js} +4 -1
- package/dist/chunks/config-CIUU9ZpY.js.map +1 -0
- package/dist/chunks/{config-B3J0alyY.js → config-CzeZg2LO.js} +49 -4
- package/dist/chunks/config-CzeZg2LO.js.map +1 -0
- package/dist/chunks/{config-DCq4JySZ.js → config-D3HOuHDe.js} +1 -1
- package/dist/chunks/{config-DCq4JySZ.js.map → config-D3HOuHDe.js.map} +1 -1
- package/dist/chunks/{config-BhfNgyWL.js → config-DENqOoh0.js} +5 -1
- package/dist/chunks/config-DENqOoh0.js.map +1 -0
- package/dist/chunks/controlState-DoImKciE.js +35 -0
- package/dist/chunks/controlState-DoImKciE.js.map +1 -0
- package/dist/chunks/{en-DiHzFact.js → en-Bxyn5lW7.js} +12 -2
- package/dist/chunks/en-Bxyn5lW7.js.map +1 -0
- package/dist/chunks/{es-CeKkMxZ_.js → es-Bj_vc18Z.js} +3 -3
- package/dist/chunks/{es-CeKkMxZ_.js.map → es-Bj_vc18Z.js.map} +1 -1
- package/dist/chunks/{es-1ZL6NtkB.js → es-Dtl-a7ui.js} +12 -2
- package/dist/chunks/es-Dtl-a7ui.js.map +1 -0
- package/dist/chunks/{generated-CQ4h6Ieq.js → generated-hELWB0gZ.js} +98 -0
- package/dist/chunks/{generated-CQ4h6Ieq.js.map → generated-hELWB0gZ.js.map} +1 -1
- package/dist/chunks/{grTableStyles-uuMPSOBR.js → grTableStyles-DkQPMfx9.js} +16 -3
- package/dist/chunks/grTableStyles-DkQPMfx9.js.map +1 -0
- package/dist/chunks/{granular-provider-CsN_R9dB.js → granular-provider-BOQp7N5o.js} +9 -9
- package/dist/chunks/{granular-provider-CsN_R9dB.js.map → granular-provider-BOQp7N5o.js.map} +1 -1
- package/dist/chunks/{ru-Dr2RYn0P.js → ru-hrB4I1u_.js} +12 -2
- package/dist/chunks/ru-hrB4I1u_.js.map +1 -0
- package/dist/chunks/x-circle-BigSZy8m.js +26 -0
- package/dist/chunks/x-circle-BigSZy8m.js.map +1 -0
- package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js → GrAlert-CfEZghSY.js} +2 -1
- package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js.map → GrAlert-CfEZghSY.js.map} +1 -1
- package/dist/components/GrAlert/index.js +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-D396_JrW.js → GrAutocomplete-C-uovdv8.js} +320 -224
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-C-uovdv8.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrAvatar/chunks/{GrAvatar-dWEXiCJG.js → GrAvatar-Do5-C409.js} +12 -1
- package/dist/components/GrAvatar/chunks/GrAvatar-Do5-C409.js.map +1 -0
- package/dist/components/GrAvatar/index.js +2 -2
- package/dist/components/GrAvatar/themes/light.css +35 -0
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js → GrColorPicker-BRM_4lyI.js} +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js.map → GrColorPicker-BRM_4lyI.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js.map → GrCommandPalette-BJHmkDQv.js.map} +1 -1
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js → GrConfirmDialog-BzUQwbec.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js.map → GrConfirmDialog-BzUQwbec.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js +1519 -0
- package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDataTable/themes/light.css +10 -0
- package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js → GrDialogService-Dtq0ZCN5.js} +3 -3
- package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js.map → GrDialogService-Dtq0ZCN5.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrFormField/chunks/GrFormField-DO61vxpS.js.map +1 -1
- package/dist/components/GrInput/chunks/{GrInput-TFeBm3gQ.js → GrInput-DV4gMWPR.js} +40 -11
- package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +1 -0
- package/dist/components/GrInput/index.js +2 -2
- package/dist/components/GrInputTag/chunks/{GrInputTag-Ci8AO1ze.js → GrInputTag-CO0YU-ok.js} +30 -6
- package/dist/components/GrInputTag/chunks/GrInputTag-CO0YU-ok.js.map +1 -0
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-C6q8dEEZ.js → GrJsonViewer-D7CyWzhT.js} +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-C6q8dEEZ.js.map → GrJsonViewer-D7CyWzhT.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-CL5QhOmx.js → GrNumberInput-BI6-vkcs.js} +48 -16
- package/dist/components/GrNumberInput/chunks/GrNumberInput-BI6-vkcs.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-DhbNZHEF.js → GrPagination-BbaF5eXk.js} +10 -7
- package/dist/components/GrPagination/chunks/GrPagination-BbaF5eXk.js.map +1 -0
- package/dist/components/GrPagination/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js → GrPromptDialog-BEL-QgwE.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js.map → GrPromptDialog-BEL-QgwE.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js → GrResponseErrorBanner-Bdd5c07x.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js.map → GrResponseErrorBanner-Bdd5c07x.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js → GrResponseErrorBanner-DySe1ym4.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js.map → GrResponseErrorBanner-DySe1ym4.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/index.js +2 -2
- package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js → GrSelect-0iI4wdR_.js} +3 -3
- package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js.map → GrSelect-0iI4wdR_.js.map} +1 -1
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrTable/chunks/{GrTable-iwzHvlMC.js → GrTable-CKJ-HrsN.js} +1 -1
- package/dist/components/GrTable/chunks/{GrTable-iwzHvlMC.js.map → GrTable-CKJ-HrsN.js.map} +1 -1
- package/dist/components/GrTable/index.js +2 -2
- package/dist/components/GrTextarea/chunks/{GrTextarea-BII3w61K.js → GrTextarea-Bfgc0DD2.js} +73 -32
- package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +1 -0
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js → GrToaster-CZixNk0l.js} +2 -1
- package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js.map → GrToaster-CZixNk0l.js.map} +1 -1
- package/dist/components/GrToaster/index.js +1 -1
- package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js → GrTransfer-DdfBUf35.js} +2 -2
- package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js.map → GrTransfer-DdfBUf35.js.map} +1 -1
- package/dist/components/GrTransfer/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DBu5nMGA.js → GrTreeSelect-B6KoDYiZ.js} +43 -18
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +32 -32
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +39 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +36 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +28 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +33 -0
- package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +33 -0
- package/dist/types/components/GrAvatar/GrAvatar.vue.d.ts +11 -0
- package/dist/types/components/GrAvatar/defaults.d.ts +1 -0
- package/dist/types/components/GrAvatar/grAvatarStyles.d.ts +26 -1
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +60 -1
- package/dist/types/components/GrDataTable/composables/useDataTableColumnOrder.d.ts +45 -0
- package/dist/types/components/GrDataTable/composables/useDataTableColumnWidths.d.ts +38 -0
- package/dist/types/components/GrDataTable/composables/useDataTableExpansion.d.ts +46 -0
- package/dist/types/components/GrDataTable/composables/useDataTableLayout.d.ts +29 -0
- package/dist/types/components/GrDataTable/composables/useDataTableRowKeys.d.ts +16 -0
- package/dist/types/components/GrDataTable/composables/useDataTableSelection.d.ts +32 -0
- package/dist/types/components/GrDataTable/composables/useDataTableSort.d.ts +37 -0
- package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +23 -0
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +7 -1
- package/dist/types/components/GrPagination/GrPagination.vue.d.ts +17 -2
- package/dist/types/components/GrPagination/grPaginationStyles.d.ts +15 -6
- package/dist/types/components/GrPagination/index.d.ts +1 -1
- package/dist/types/components/GrTable/grTableStyles.d.ts +16 -3
- package/dist/types/components/shared/controlState.d.ts +36 -0
- package/dist/types/i18n/messages/en.d.ts +20 -0
- package/dist/types/i18n/messages/es.d.ts +20 -0
- package/dist/types/i18n/messages/ru.d.ts +20 -0
- package/dist/web-types.json +79 -3
- package/package.json +1 -1
- package/dist/chunks/config-B3J0alyY.js.map +0 -1
- package/dist/chunks/config-BhfNgyWL.js.map +0 -1
- package/dist/chunks/config-CIkdsWUk.js.map +0 -1
- package/dist/chunks/config-CjKpUcmy.js.map +0 -1
- package/dist/chunks/en-DiHzFact.js.map +0 -1
- package/dist/chunks/es-1ZL6NtkB.js.map +0 -1
- package/dist/chunks/grTableStyles-uuMPSOBR.js.map +0 -1
- package/dist/chunks/ru-Dr2RYn0P.js.map +0 -1
- package/dist/chunks/x-circle-BCBURwyX.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-D396_JrW.js.map +0 -1
- package/dist/components/GrAvatar/chunks/GrAvatar-dWEXiCJG.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js +0 -1134
- package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-TFeBm3gQ.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-Ci8AO1ze.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-CL5QhOmx.js.map +0 -1
- package/dist/components/GrPagination/chunks/GrPagination-DhbNZHEF.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-BII3w61K.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-DBu5nMGA.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js → GrCommandPalette-BJHmkDQv.js} +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrAutocomplete-C-uovdv8.js","names":[],"sources":["../../../../src/components/GrAutocomplete/composables/useRemoteOptions.ts","../../../../src/components/GrAutocomplete/composables/useAutocompleteValues.ts","../../../../src/components/GrAutocomplete/composables/useAutocompletePanel.ts","../../../../src/components/GrAutocomplete/composables/useAutocompleteVirtual.ts","../../../../src/components/GrAutocomplete/composables/useAutocompleteNavigation.ts","../../../../src/components/GrAutocomplete/GrAutocomplete.vue","../../../../src/components/GrAutocomplete/GrAutocomplete.vue"],"sourcesContent":["import type { Ref, ShallowRef } from 'vue'\nimport { onBeforeUnmount, ref, shallowRef, watch } from 'vue'\n\nimport type { GrAutocompleteOption, GrAutocompleteValue } from '../GrAutocomplete.vue'\n\n/**\n * Дебаунснутая удалённая загрузка опций.\n *\n * Три вещи, каждая из которых по отдельности выглядит необязательной, а вместе\n * составляют весь смысл модуля: запрос откладывается на `debounce`, летящий\n * отменяется новым, а ответ, стартовавший раньше и пришедший позже, обязан\n * проиграть последнему — иначе список показывает результат старого запроса.\n *\n * От DOM не зависит вовсе, поэтому проверяется без монтирования компонента.\n *\n * Единственный потребитель сегодня — `GrAutocomplete`. Появится второй\n * (открытая строка аудита: `GrTransfer` отдаёт `search` наружу, а подгрузку\n * делает потребитель) — модулю место в `composables/internal/`.\n */\nexport interface UseRemoteOptionsOptions<TValue extends GrAutocompleteValue> {\n fetchOptions: () => ((query: string, signal: AbortSignal) => Promise<GrAutocompleteOption<TValue>[]>) | undefined\n /** Стартовый список: его смена сбрасывает ответ сервера. */\n options: () => GrAutocompleteOption<TValue>[] | undefined\n debounce: () => number\n minQueryLength: () => number\n onSearch: (query: string) => void\n onSearchError: (error: unknown) => void\n}\n\nexport interface RemoteOptions<TValue extends GrAutocompleteValue> {\n remoteOptions: ShallowRef<GrAutocompleteOption<TValue>[]>\n remoteAnswered: Ref<boolean>\n remoteLoading: Ref<boolean>\n scheduleSearch: (value: string) => void\n cancelSearch: () => void\n}\n\nexport function useRemoteOptions<TValue extends GrAutocompleteValue>(\n options: UseRemoteOptionsOptions<TValue>,\n): RemoteOptions<TValue> {\n // Ответ последнего `fetchOptions`. До первого ответа показываем `options` —\n // с ними компонент рисует стартовый список, не дожидаясь сервера.\n const remoteOptions = shallowRef<GrAutocompleteOption<TValue>[]>([])\n const remoteAnswered = ref(false)\n const remoteLoading = ref(false)\n\n let searchTimer: ReturnType<typeof setTimeout> | null = null\n // Счётчик запросов: ответ, стартовавший раньше, но пришедший позже, обязан\n // проиграть последнему — иначе список показывает результат старого запроса.\n let searchSeq = 0\n let inflight: AbortController | null = null\n\n function isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n }\n\n async function runFetch(query: string): Promise<void> {\n const fetchOptions = options.fetchOptions()\n if (!fetchOptions)\n return\n\n inflight?.abort()\n const controller = new AbortController()\n inflight = controller\n const seq = ++searchSeq\n remoteLoading.value = true\n\n try {\n const result = await fetchOptions(query, controller.signal)\n if (seq !== searchSeq)\n return\n remoteOptions.value = result\n remoteAnswered.value = true\n }\n catch (error) {\n if (seq !== searchSeq || isAbortError(error))\n return\n remoteOptions.value = []\n remoteAnswered.value = true\n options.onSearchError(error)\n }\n finally {\n if (seq === searchSeq)\n remoteLoading.value = false\n }\n }\n\n function scheduleSearch(value: string): void {\n if (searchTimer)\n clearTimeout(searchTimer)\n searchTimer = setTimeout(() => {\n if (options.minQueryLength() > 0 && value.trim().length < options.minQueryLength())\n return\n options.onSearch(value.trim())\n void runFetch(value.trim())\n }, options.debounce())\n }\n\n /**\n * Снять запланированный и летящий запрос. Инкремент `searchSeq` обязателен\n * вместе со снятием `remoteLoading`: после него `finally` в `runFetch` считает\n * себя устаревшим и флаг не тронет — спиннер остался бы навсегда.\n */\n function cancelSearch(): void {\n if (searchTimer)\n clearTimeout(searchTimer)\n searchTimer = null\n inflight?.abort()\n inflight = null\n searchSeq += 1\n remoteLoading.value = false\n }\n\n /**\n * Подпись состава стартового списка. Сравнивать идентичность массива нельзя:\n * инлайн-литерал `:options=\"[...]\"` пересоздаётся каждым ререндером родителя —\n * в том числе тем, который вызвал сам компонент своим `update:modelValue`, —\n * и remote-результаты исчезали бы прямо посреди выбора.\n */\n function optionsSignature(list: GrAutocompleteOption<TValue>[] | undefined): string {\n return (list ?? []).map(o => `${String(o.value)}\\u0000${o.label}`).join('\\u0001')\n }\n\n // Родитель сменил стартовый список — он снова источник до следующего ответа\n // сервера, а летящий запрос относится к прежнему набору данных и отменяется.\n watch(() => optionsSignature(options.options()), () => {\n if (!options.fetchOptions())\n return\n cancelSearch()\n remoteAnswered.value = false\n remoteOptions.value = []\n })\n\n onBeforeUnmount(cancelSearch)\n\n return { remoteOptions, remoteAnswered, remoteLoading, scheduleSearch, cancelSearch }\n}\n","import type { ComputedRef, Ref, ShallowRef } from 'vue'\nimport { computed } from 'vue'\n\nimport { resolveSelectedOptions } from '../../shared/optionFilter'\nimport type {\n GrAutocompleteModelValue,\n GrAutocompleteOption,\n GrAutocompleteValue,\n} from '../GrAutocomplete.vue'\n\n/**\n * Значение и опции `GrAutocomplete`: что выбрано и как это назвать.\n *\n * Ничего про панель, клавиатуру и вид. Состав списка зависит от того, отвечал\n * ли уже сервер: до первого ответа источник — проп `options`, иначе последний\n * ответ `fetchOptions`.\n */\nexport interface UseAutocompleteValuesOptions<TValue extends GrAutocompleteValue> {\n modelValue: () => GrAutocompleteModelValue<TValue>\n options: () => GrAutocompleteOption<TValue>[] | undefined\n multiple: () => boolean\n fetchOptions: () => unknown\n remoteOptions: ShallowRef<GrAutocompleteOption<TValue>[]>\n remoteAnswered: Ref<boolean>\n}\n\nexport interface AutocompleteValues<TValue extends GrAutocompleteValue> {\n optionsResolved: ComputedRef<GrAutocompleteOption<TValue>[]>\n isEmptyValue: (value: unknown) => boolean\n modelSingle: ComputedRef<TValue | ''>\n selectedValues: ComputedRef<TValue[]>\n hasSelection: ComputedRef<boolean>\n labelFor: (value: GrAutocompleteValue) => string\n selectedOptions: ComputedRef<GrAutocompleteOption<TValue>[]>\n singleSelectedLabel: ComputedRef<string>\n}\n\nexport function useAutocompleteValues<TValue extends GrAutocompleteValue>(\n options: UseAutocompleteValuesOptions<TValue>,\n): AutocompleteValues<TValue> {\n const optionsResolved = computed<GrAutocompleteOption<TValue>[]>(() =>\n options.fetchOptions() && options.remoteAnswered.value\n ? options.remoteOptions.value\n : (options.options() ?? []),\n )\n\n /** `0` — валидное значение, поэтому «пусто» проверяется явно, а не через falsy. */\n function isEmptyValue(value: unknown): boolean {\n return value === undefined || value === null || value === ''\n }\n\n function toArray(value: GrAutocompleteModelValue<TValue>): TValue[] {\n if (Array.isArray(value))\n return value\n if (isEmptyValue(value))\n return []\n return [value]\n }\n\n const modelSingle = computed<TValue | ''>(() => {\n const model = options.modelValue()\n const raw = Array.isArray(model) ? model[0] : model\n return isEmptyValue(raw) ? '' : (raw as TValue)\n })\n\n const selectedValues = computed(() => (\n options.multiple()\n ? toArray(options.modelValue())\n : (isEmptyValue(modelSingle.value) ? [] : [modelSingle.value as TValue])\n ))\n\n const hasSelection = computed(() => selectedValues.value.length > 0)\n\n function labelFor(value: GrAutocompleteValue): string {\n return optionsResolved.value.find(o => o.value === value)?.label ?? String(value)\n }\n\n /** Опции выбранных значений (для chips в multiple). Неизвестные значения показываем как есть. */\n const selectedOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n resolveSelectedOptions(selectedValues.value, optionsResolved.value),\n )\n\n const singleSelectedLabel = computed(() => (\n options.multiple() || isEmptyValue(modelSingle.value) ? '' : labelFor(modelSingle.value)\n ))\n\n return {\n optionsResolved,\n isEmptyValue,\n modelSingle,\n selectedValues,\n hasSelection,\n labelFor,\n selectedOptions,\n singleSelectedLabel,\n }\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed } from 'vue'\n\nimport { filterOptions } from '../../shared/optionFilter'\nimport type { GrAutocompleteOption, GrAutocompleteValue } from '../GrAutocomplete.vue'\n\n/**\n * Состав панели `GrAutocomplete`: что показывать под запросом.\n *\n * Три решения, которые легко перепутать между собой. При `fetchOptions`\n * локальный матчер выключается — он отсеял бы то, что сервер уже прислал в\n * ответ на этот же запрос. Ниже `minQueryLength` список пуст, а не «прошлый»:\n * показывать старый набор под подсказкой «введите ещё N» значит\n * дезинформировать. А «Add …» не предлагается, если такое значение или подпись\n * уже есть среди опций.\n */\nexport interface UseAutocompletePanelOptions<TValue extends GrAutocompleteValue> {\n optionsResolved: ComputedRef<GrAutocompleteOption<TValue>[]>\n /** Введённый текст без нормализации: тримит уже сам модуль. */\n query: Ref<string>\n /** Пользователь начал ввод — до этого single-режим показывает весь список. */\n dirty: Ref<boolean>\n filterable: () => boolean\n filter: () => ((option: GrAutocompleteOption<TValue>, query: string) => boolean) | undefined\n fetchOptions: () => unknown\n multiple: () => boolean\n minQueryLength: () => number\n allowCustomValue: () => boolean\n selectedValues: ComputedRef<TValue[]>\n modelSingle: ComputedRef<TValue | ''>\n isLoading: ComputedRef<boolean>\n}\n\nexport interface AutocompletePanel<TValue extends GrAutocompleteValue> {\n searchQuery: ComputedRef<string>\n belowMinQuery: ComputedRef<boolean>\n effectiveOptions: ComputedRef<GrAutocompleteOption<TValue>[]>\n canAddCustom: ComputedRef<boolean>\n showEmpty: ComputedRef<boolean>\n}\n\nexport function useAutocompletePanel<TValue extends GrAutocompleteValue>(\n options: UseAutocompletePanelOptions<TValue>,\n): AutocompletePanel<TValue> {\n const searchQuery = computed(() => {\n // При `fetchOptions` фильтрует сервер: локальный матчер отсеял бы то, что он\n // уже прислал в ответ на этот же запрос.\n if (!options.filterable() || options.fetchOptions())\n return ''\n // single: пока пользователь не начал вводить — показываем весь список.\n if (!options.multiple() && !options.dirty.value)\n return ''\n return options.query.value.trim()\n })\n\n const filteredOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n filterOptions(options.optionsResolved.value, searchQuery.value, options.filter()),\n )\n\n const belowMinQuery = computed(() =>\n options.minQueryLength() > 0 && options.query.value.trim().length < options.minQueryLength(),\n )\n\n /**\n * Опции к показу и навигации. Ниже `minQueryLength` — пусто: список ещё\n * относится к прошлому запросу, показывать его под подсказкой «введите ещё N»\n * значит дезинформировать.\n */\n const effectiveOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n belowMinQuery.value ? [] : filteredOptions.value,\n )\n\n const canAddCustom = computed(() => {\n if (!options.allowCustomValue())\n return false\n // Кастомное значение набирается текстом — оно строковое по природе;\n // при числовом `TValue` эта ветка неприменима (см. docs/components.md).\n const v = options.query.value.trim() as TValue\n if (!v)\n return false\n if (options.multiple() && options.selectedValues.value.includes(v))\n return false\n if (!options.multiple() && v === options.modelSingle.value)\n return false\n // Не предлагаем «Add», если такое значение/метка уже есть среди опций.\n return !options.optionsResolved.value.some(o => o.value === v || o.label === v)\n })\n\n const showEmpty = computed(() =>\n !options.isLoading.value && effectiveOptions.value.length === 0 && !canAddCustom.value,\n )\n\n return { searchQuery, belowMinQuery, effectiveOptions, canAddCustom, showEmpty }\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed } from 'vue'\n\nimport { useVirtualList } from '../../../composables/useVirtualList'\nimport type { GrAutocompleteOption, GrAutocompleteValue } from '../GrAutocomplete.vue'\n\n/** Оценка высоты опции: `py-2` вокруг строки кегля `sm`. Уточняется замером. */\nconst OPTION_SIZE_ESTIMATE = 36\n\n/**\n * Виртуализация панели `GrAutocomplete`.\n *\n * Набор — `[«Add …»?] + effectiveOptions`, тот же, по которому ходит клавиатура.\n * Иначе верхняя распорка вытолкнула бы строку «Add …» вниз: она отрисована\n * внутри listbox'а первой и является полноценной опцией. Отсюда `addOffset`,\n * который сдвигает окно виртуализатора относительно индексов в списке опций.\n */\nexport interface UseAutocompleteVirtualOptions<TValue extends GrAutocompleteValue> {\n /** Контейнер списка: по нему считается окно и замеряются строки. */\n listboxEl: Ref<HTMLElement | null>\n effectiveOptions: ComputedRef<GrAutocompleteOption<TValue>[]>\n canAddCustom: ComputedRef<boolean>\n virtual: () => boolean\n dropdownMaxHeight: () => number\n}\n\nexport interface AutocompleteVirtual<TValue extends GrAutocompleteValue> {\n /** Сдвиг окна из-за строки «Add …»: она идёт нулевым элементом набора. */\n addOffset: ComputedRef<number>\n virtualCount: ComputedRef<number>\n virtualizer: ReturnType<typeof useVirtualList>\n showAddOption: ComputedRef<boolean>\n renderedOptions: ComputedRef<Array<{ option: GrAutocompleteOption<TValue>, index: number }>>\n optionSetProps: (virtualIndex: number) => Record<string, number> | undefined\n listboxStyle: ComputedRef<Record<string, string>>\n}\n\nexport function useAutocompleteVirtual<TValue extends GrAutocompleteValue>(\n options: UseAutocompleteVirtualOptions<TValue>,\n): AutocompleteVirtual<TValue> {\n const addOffset = computed(() => (options.canAddCustom.value ? 1 : 0))\n const virtualCount = computed(() => options.effectiveOptions.value.length + addOffset.value)\n\n const virtualizer = useVirtualList({\n container: options.listboxEl,\n count: () => (options.virtual() ? virtualCount.value : 0),\n // Фильтрация/remote-ответ пересобирают набор — замеры прошлого невалидны.\n source: () => options.effectiveOptions.value,\n itemSize: OPTION_SIZE_ESTIMATE,\n // Панель скрыта `v-show`, пока закрыта, поэтому `clientHeight` контейнера —\n // ноль. Окно считается от объявленной высоты до первого настоящего замера.\n viewportSize: () => options.dropdownMaxHeight(),\n })\n\n /** Виден ли «Add …»: вне виртуального окна его рисовать нельзя — он элемент набора. */\n const showAddOption = computed(() => {\n if (!options.canAddCustom.value)\n return false\n return !options.virtual() || virtualizer.range.value.start === 0\n })\n\n /** Опции к отрисовке вместе с их абсолютным индексом в `effectiveOptions`. */\n const renderedOptions = computed(() => {\n const all = options.effectiveOptions.value\n if (!options.virtual())\n return all.map((option, index) => ({ option, index }))\n\n const { start, end } = virtualizer.range.value\n const from = Math.max(0, start - addOffset.value)\n const to = Math.max(0, end - addOffset.value)\n\n return all.slice(from, to).map((option, offset) => ({ option, index: from + offset }))\n })\n\n /**\n * Размер набора и позиция в нём объявляются только при виртуализации: в обычном\n * режиме диктор выводит их из DOM, а при неполном наборе получил бы «3 из 15»\n * на списке в десять тысяч.\n */\n function optionSetProps(virtualIndex: number): Record<string, number> | undefined {\n if (!options.virtual())\n return undefined\n return { 'aria-setsize': virtualCount.value, 'aria-posinset': virtualIndex + 1 }\n }\n\n const listboxStyle = computed(() => {\n const base: Record<string, string> = { maxHeight: `${options.dropdownMaxHeight()}px` }\n if (!options.virtual())\n return base\n\n return {\n ...base,\n ...virtualizer.spacerStyle.value,\n }\n })\n\n return { addOffset, virtualCount, virtualizer, showAddOption, renderedOptions, optionSetProps, listboxStyle }\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed, nextTick } from 'vue'\n\nimport { useComboboxNavigation } from '../../../composables/useComboboxNavigation'\nimport type { GrAutocompleteOption, GrAutocompleteValue } from '../GrAutocomplete.vue'\n\n/**\n * Клавиатурная навигация ходит и по опциям, и по варианту «добавить своё»:\n * иначе при непустом списке Enter всегда уходил бы в активную опцию, и\n * закоммитить произвольное значение с клавиатуры было бы нечем.\n */\nexport type GrAutocompleteNavigableItem<TValue extends GrAutocompleteValue>\n = | { kind: 'add' }\n | { kind: 'option', option: GrAutocompleteOption<TValue>, index: number }\n\nexport interface UseAutocompleteNavigationOptions<TValue extends GrAutocompleteValue> {\n effectiveOptions: ComputedRef<GrAutocompleteOption<TValue>[]>\n canAddCustom: ComputedRef<boolean>\n selectedValues: ComputedRef<TValue[]>\n open: Ref<boolean>\n virtual: () => boolean\n /** Сдвиг окна из-за строки «Add …» — из модуля виртуализации. */\n addOffset: ComputedRef<number>\n scrollToIndex: (index: number) => void\n optionDomId: (index: number) => string\n addOptionDomId: ComputedRef<string>\n}\n\nexport interface AutocompleteNavigation<TValue extends GrAutocompleteValue> {\n navigableItems: ComputedRef<Array<GrAutocompleteNavigableItem<TValue>>>\n isSelected: (value: TValue) => boolean\n navigableIndexOf: (value: TValue) => number\n activeIndex: Ref<number>\n activeItem: ComputedRef<GrAutocompleteNavigableItem<TValue> | undefined>\n activeDescendantId: ComputedRef<string | undefined>\n activeValue: ComputedRef<TValue | undefined>\n initActiveIndex: () => void\n resetActive: () => void\n handleNavigationKeys: (event: KeyboardEvent) => boolean\n}\n\nexport function useAutocompleteNavigation<TValue extends GrAutocompleteValue>(\n options: UseAutocompleteNavigationOptions<TValue>,\n): AutocompleteNavigation<TValue> {\n type Item = GrAutocompleteNavigableItem<TValue>\n\n const navigableItems = computed<Item[]>(() => {\n const items: Item[] = options.canAddCustom.value ? [{ kind: 'add' }] : []\n options.effectiveOptions.value.forEach((option, index) => {\n if (!option.disabled)\n items.push({ kind: 'option', option, index })\n })\n return items\n })\n\n function isSelected(value: TValue): boolean {\n return options.selectedValues.value.includes(value)\n }\n\n function navigableIndexOf(value: TValue): number {\n return navigableItems.value.findIndex(item => item.kind === 'option' && item.option.value === value)\n }\n\n /** Прокрутка к активному: сперва окно виртуального списка, затем доводка. */\n async function scrollActiveIntoView(item: Item): Promise<void> {\n // Вне окна активной опции в DOM нет: `getElementById` вернул бы `null`,\n // прокрутка не случилась бы, а `aria-activedescendant` указал бы в пустоту.\n if (options.virtual())\n options.scrollToIndex(item.kind === 'add' ? 0 : item.index + options.addOffset.value)\n\n await nextTick()\n const id = item.kind === 'add' ? options.addOptionDomId.value : options.optionDomId(item.index)\n document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })\n }\n\n const {\n activeIndex,\n activeItem,\n activeDescendantId,\n init: initActiveIndex,\n reset: resetActive,\n handleNavigationKeys,\n } = useComboboxNavigation<Item>({\n items: () => navigableItems.value,\n open: () => options.open.value,\n idOf: item => (item.kind === 'add' ? options.addOptionDomId.value : options.optionDomId(item.index)),\n initialIndex: () => navigableItems.value.findIndex(\n item => item.kind === 'option' && isSelected(item.option.value),\n ),\n scrollTo: item => scrollActiveIntoView(item),\n })\n\n const activeValue = computed(() => (\n activeItem.value?.kind === 'option' ? activeItem.value.option.value : undefined\n ))\n\n return {\n navigableItems,\n isSelected,\n navigableIndexOf,\n activeIndex,\n activeItem,\n activeDescendantId,\n activeValue,\n initActiveIndex,\n resetActive,\n handleNavigationKeys,\n }\n}\n","<script setup lang=\"ts\" generic=\"TValue extends GrAutocompleteValue = string\">\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { useGrComponentProp, useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useDismissible } from '../../composables/useDismissible'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRemoteOptions } from './composables/useRemoteOptions'\nimport { useAutocompleteValues } from './composables/useAutocompleteValues'\nimport { useAutocompletePanel } from './composables/useAutocompletePanel'\nimport { useAutocompleteVirtual } from './composables/useAutocompleteVirtual'\nimport { useAutocompleteNavigation } from './composables/useAutocompleteNavigation'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\nimport GrChip from '../GrChip/GrChip.vue'\nimport { chipSizeForBadgeScale } from '../GrChip/grChipStyles'\nimport { panelPopTransition } from '../shared/overlayTransition'\n\nimport {\n autocompleteOptionClass,\n autocompletePanelClasses,\n autocompleteShellClass,\n autocompleteStateClass,\n type GrAutocompleteModelValue,\n type GrAutocompleteOption,\n type GrAutocompleteValue,\n type GrAutocompleteSize,\n} from './grAutocompleteStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport type {\n GrAutocompleteModelValue,\n GrAutocompleteOption,\n GrAutocompleteSize,\n GrAutocompleteValue,\n} from './grAutocompleteStyles'\n\n/**\n * Публичный GR-примитив «Autocomplete / Combobox» (WAI-ARIA editable combobox).\n *\n * В отличие от `GrSelect` (select-only combobox с кнопкой-триггером), здесь\n * combobox-ом является сам текстовый `<input>`: набранный текст — это поисковый\n * запрос, а выбор опции заполняет поле. Поддерживает локальную фильтрацию,\n * удалённую (async) загрузку через событие `search` + внешний проп `loading`,\n * произвольные значения (`allowCustomValue`) и multiple с удаляемыми chips.\n */\nexport interface GrAutocompleteProps<TValue extends GrAutocompleteValue = string> {\n /** Выбранное значение (single — строка, multiple — массив строк). */\n modelValue: GrAutocompleteModelValue<TValue>\n /**\n * Доступные опции. Для локального режима — полный список (фильтруется на клиенте).\n * Для remote-режима (`filterable=false`) — список, который родитель обновляет\n * в ответ на событие `search`.\n */\n options?: GrAutocompleteOption<TValue>[]\n multiple?: boolean\n /**\n * Вид чипов выбранных значений в режиме `multiple`. Рисует их `GrChip`, но\n * шкала пропа осталась бейджевой: `tagSize` публичен, и его значения обязаны\n * означать тот же кегль, что и раньше. Перевод ступеней — в\n * `chipSizeForBadgeScale`.\n */\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n size?: GrAutocompleteSize\n placeholder?: string\n ariaLabel?: string\n /** Кнопка очистки выбранного значения/запроса. */\n clearable?: boolean\n /** Внешне управляемое состояние загрузки (для async-сценариев). */\n loading?: boolean\n /**\n * Локальная фильтрация опций по введённому запросу. Отключите (`false`) для\n * чисто удалённого поиска — тогда `options` показываются как есть, а фильтрацию\n * выполняет сервер по событию `search`.\n */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. По умолчанию — подстрока в `label`/`value`. */\n filter?: (option: GrAutocompleteOption<TValue>, query: string) => boolean\n /**\n * Удалённая загрузка опций под управлением компонента: дебаунс, отмена\n * устаревшего запроса и `loading` берёт на себя он. Ответ на отменённый\n * запрос игнорируется — при быстром вводе в списке всегда результат\n * последнего запроса, а не того, который вернулся позже.\n *\n * `signal` пробрасывается в `fetch`. Локальная фильтрация в этом режиме\n * выключена: список фильтрует сервер. Альтернатива — событие `search`, если\n * запрос ведёт само приложение.\n */\n fetchOptions?: (query: string, signal: AbortSignal) => Promise<GrAutocompleteOption<TValue>[]>\n /** Минимальная длина запроса до эмита `search` (для дебаунса remote-загрузки). */\n minQueryLength?: number\n /** Задержка дебаунса события `search`, мс. */\n debounce?: number\n /** Разрешить ввод/коммит значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Закрывать панель после выбора (single всегда закрывает). */\n closeOnSelect?: boolean\n /** Максимальная высота панели, px. */\n dropdownMaxHeight?: number\n /**\n * Виртуализация панели: в DOM живёт только окно вокруг вьюпорта.\n *\n * Высоту окна задаёт `dropdownMaxHeight`. Включается осознанно: на сотне\n * опций выигрыша нет, а в разметке остаётся только окно — вместе с ним\n * меняется и то, что находит `querySelector` потребителя. Профильный\n * сценарий — удалённый поиск по справочнику на тысячи позиций.\n */\n virtual?: boolean\n /** i18n-тексты состояний панели / aria. */\n loadingText?: string\n noResultsText?: string\n clearLabel?: string\n /**\n * Контролируемое состояние панели (`v-model:open`). Без пропа панель ведёт\n * себя сама (uncontrolled), с ним — слушайте `update:open` и меняйте проп.\n */\n open?: boolean\n /** Имя для нативной формы: hidden input по значению модели (не по тексту запроса). */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrAutocompleteEmits<TValue extends GrAutocompleteValue = string> {\n (e: 'update:modelValue', value: GrAutocompleteModelValue<TValue>): void\n /** Дебаунснутый поисковый запрос — точка входа для удалённой загрузки опций. */\n (e: 'search', query: string): void\n /** Запрос `fetchOptions` завершился ошибкой (отмена устаревшего сюда не приходит). */\n (e: 'searchError', error: unknown): void\n /** Значение зафиксировано выбором или снятием опции. */\n (e: 'change', value: GrAutocompleteModelValue<TValue>): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n /** Значение снято кнопкой очистки; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrAutocompleteProps<TValue>>(),\n {\n options: undefined,\n multiple: false,\n tagTone: 'neutral',\n tagDark: false,\n // `sm`, а не `md` как у `GrInputTag`: чип живёт внутри контрола, а не в тексте.\n tagSize: 'sm',\n tagRadius: 'round',\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n size: undefined,\n placeholder: undefined,\n ariaLabel: undefined,\n clearable: undefined,\n loading: false,\n filterable: true,\n filter: undefined,\n fetchOptions: undefined,\n minQueryLength: 0,\n debounce: 250,\n allowCustomValue: false,\n closeOnSelect: true,\n dropdownMaxHeight: 280,\n virtual: false,\n loadingText: undefined,\n noResultsText: undefined,\n clearLabel: undefined,\n open: undefined,\n name: undefined,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrAutocompleteSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrAutocomplete' })\nconst resolvedClearable = useGrComponentProp('GrAutocomplete', 'clearable', () => props.clearable, false)\n\n/**\n * Аддоны живут флекс-соседями поля, а не поверх него: оболочка автокомплита —\n * флекс-строка с чипами, поэтому резервировать место паддингом не нужно.\n * Контракт ширин остаётся общим (`useControlAddons`).\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\n\nconst emit = defineEmits<GrAutocompleteEmits<TValue>>()\ndefineSlots<{\n /** Аддон слева от поля ввода. */\n prefix?: () => any\n /** Аддон справа, перед спиннером и крестиком. */\n suffix?: () => any\n /** Строка списка вместо подписи опции. */\n option?: (props: { option: GrAutocompleteOption<TValue>, selected: boolean }) => any\n /** Содержимое панели, пока едут опции. */\n loading?: () => any\n /** Содержимое панели, когда подходящих опций нет. */\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.autocomplete.loading', 'Loading…'))\nconst resolvedNoResultsText = computed(() => props.noResultsText ?? t('gr.autocomplete.noResults', 'No results'))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\n// Число уходит под двумя общепринятыми именами: `n` читают `@feugene/fint-i18n`\n// и `vue-i18n`, `count` — `i18next`. Формы для каждого языка лежат в словаре,\n// выбирает их переводчик; встроенный fallback — одна английская строка.\nconst resolvedTypeMoreText = computed(() =>\n t('gr.autocomplete.typeMore', 'Type at least {n} characters', {\n n: props.minQueryLength,\n count: props.minQueryLength,\n }),\n)\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\n// Read-only запирает контрол так же, как disabled: значение видно, но панель не\n// открывается, опции не выбираются и чипы не удаляются. Без этого `readonly`\n// оставался бы ARIA-атрибутом без поведения.\nconst locked = computed(() => isDisabled.value || isReadonly.value)\n\nconst { remoteOptions, remoteAnswered, remoteLoading, scheduleSearch } = useRemoteOptions<TValue>({\n fetchOptions: () => props.fetchOptions,\n options: () => props.options,\n debounce: () => props.debounce,\n minQueryLength: () => props.minQueryLength,\n onSearch: value => emit('search', value),\n onSearchError: error => emit('searchError', error),\n})\n\nconst {\n optionsResolved,\n modelSingle,\n selectedValues,\n hasSelection,\n selectedOptions,\n singleSelectedLabel,\n} = useAutocompleteValues<TValue>({\n modelValue: () => props.modelValue,\n options: () => props.options,\n multiple: () => props.multiple,\n fetchOptions: () => props.fetchOptions,\n remoteOptions,\n remoteAnswered,\n})\n\nconst isLoading = computed(() => props.loading || remoteLoading.value)\n\n// ————— Состояние.\nconst query = ref('')\n// `dirty` — пользователь начал вводить запрос. Нужно, чтобы в single-режиме при\n// открытии заполненного поля показать весь список (а не отфильтровать по метке\n// уже выбранной опции). Сбрасывается при программной установке `query`.\nconst dirty = ref(false)\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst listboxEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({\n focus,\n blur,\n open: () => openDropdown(),\n close: () => closeDropdown(),\n})\nconst clickOutsideExclude = [() => panelEl.value]\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nuseDismissible(open, closeDropdown)\n\nconst { belowMinQuery, effectiveOptions, canAddCustom, showEmpty } = useAutocompletePanel<TValue>({\n optionsResolved,\n query,\n dirty,\n filterable: () => props.filterable,\n filter: () => props.filter,\n fetchOptions: () => props.fetchOptions,\n multiple: () => props.multiple,\n minQueryLength: () => props.minQueryLength,\n allowCustomValue: () => props.allowCustomValue,\n selectedValues,\n modelSingle,\n isLoading,\n})\n\n// ————— Панель: id/aria-activedescendant.\nconst listboxId = useId()\n\n/**\n * Id опции строится от позиции, а не от значения: значение с пробелом дало бы\n * невалидный `id`, а `aria-activedescendant` — два токена вместо одной ссылки,\n * и активная опция перестала бы объявляться.\n */\nfunction optionDomId(index: number): string {\n return `${listboxId}-opt-${index}`\n}\nconst addOptionDomId = computed(() => `${listboxId}-add`)\n\nconst {\n addOffset,\n virtualizer,\n showAddOption,\n renderedOptions,\n optionSetProps,\n listboxStyle,\n} = useAutocompleteVirtual<TValue>({\n listboxEl,\n effectiveOptions,\n canAddCustom,\n virtual: () => props.virtual,\n dropdownMaxHeight: () => props.dropdownMaxHeight,\n})\n\nconst {\n isSelected,\n navigableIndexOf,\n activeIndex,\n activeItem,\n activeDescendantId,\n activeValue,\n initActiveIndex,\n resetActive,\n handleNavigationKeys,\n} = useAutocompleteNavigation<TValue>({\n effectiveOptions,\n canAddCustom,\n selectedValues,\n open,\n virtual: () => props.virtual,\n addOffset,\n scrollToIndex: index => virtualizer.scrollToIndex(index),\n optionDomId,\n addOptionDomId,\n})\n\n// ————— Открытие/закрытие.\nfunction openDropdown(): void {\n if (locked.value || open.value)\n return\n setOpen(true)\n}\n\nfunction closeDropdown(): void {\n setOpen(false)\n}\n\nfunction focusInput(): void {\n inputEl.value?.focus()\n}\n\n// ————— Ввод.\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLInputElement).value\n query.value = value\n dirty.value = true\n openDropdown()\n scheduleSearch(value)\n}\n\nfunction onFocus(): void {\n if (locked.value)\n return\n openDropdown()\n}\n\nfunction setQuery(value: string): void {\n query.value = value\n dirty.value = false\n}\n\n// ————— Выбор значений.\nfunction selectSingle(value: TValue, label: string): void {\n emit('update:modelValue', value)\n emit('change', value as GrAutocompleteModelValue<TValue>)\n setQuery(label)\n closeDropdown()\n}\n\nfunction toggleMultiple(value: TValue): void {\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0)\n next.splice(idx, 1)\n else next.push(value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n setQuery('')\n if (props.closeOnSelect)\n closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled)\n return\n if (props.multiple)\n toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value)\n return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v)\n return\n if (props.multiple)\n toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value)\n return\n const next = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n void nextTick(focusAfter)\n}\n\nfunction clearSelection(): void {\n if (locked.value)\n return\n const next = (props.multiple ? [] : '') as GrAutocompleteModelValue<TValue>\n emit('update:modelValue', next)\n emit('change', next)\n emit('clear')\n setQuery('')\n void nextTick(focusInput)\n}\n\nconst showClear = computed(() =>\n resolvedClearable.value && !locked.value && (hasSelection.value || query.value.length > 0),\n)\n\n// ————— Чипы (multiple): стрелки вместо таб-стопов.\n//\n// Крестики намеренно не табируемы: у combobox фокус живёт на `<input>`, и\n// двадцать выбранных значений не должны давать двадцать остановок Tab. Поэтому\n// вход в чипы — ArrowLeft из пустого запроса, а выход — ArrowRight/Esc/печать.\n//\n// Отсюда же единственное отличие от `GrInputTag`: `tabindexFor` здесь не\n// используется. Кольцо нужно как движок навигации, а таб-стоп у ряда чипов\n// отсутствует вовсе — он принадлежит полю ввода.\nconst chipRefs = ref<Array<{ removeEl: HTMLButtonElement | null } | null>>([])\n\nfunction setChipRef(index: number) {\n return (el: unknown): void => {\n chipRefs.value[index] = el as { removeEl: HTMLButtonElement | null } | null\n }\n}\n\n/**\n * Проп `tagSize` объявлен по шкале бейджа и остаётся публичным контрактом:\n * дефолт `sm` обязан означать для потребителя тот же кегль, что и раньше.\n */\nconst chipSize = computed(() => chipSizeForBadgeScale(props.tagSize))\n\n// Адресация индексом, а не значением: значения дженерик-типа, и `TValue`\n// сравнивался бы по ссылке там, где опции пересоздаются.\nconst chipIndexes = computed(() => selectedOptions.value.map((_, index) => index))\n\nconst chipRoving = useRovingFocus<number>({\n items: () => chipIndexes.value,\n elementFor: index => chipRefs.value[index]?.removeEl,\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: за правым краем стоит поле ввода, за левым ничего.\n wrap: () => false,\n onOverflow: (edge) => {\n if (edge === 'end')\n focusInput()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nfunction focusChip(index: number): void {\n const last = selectedOptions.value.length - 1\n if (last < 0) {\n focusInput()\n return\n }\n\n void chipRoving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n if (chipRoving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'Delete':\n case 'Backspace':\n event.preventDefault()\n removeValue(value, () => focusChip(index))\n break\n case 'Escape':\n event.preventDefault()\n focusInput()\n break\n default:\n // Печатный символ возвращает в поле: фокус переезжает синхронно, до\n // вставки текста, поэтому символ попадает в запрос, а не пропадает.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey)\n focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event))\n return\n if (locked.value)\n return\n\n // Курсор в начале пустого запроса — стрелка влево уходит к чипам, а не\n // двигает каретку, которой всё равно некуда двигаться.\n if (event.key === 'ArrowLeft' && props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n closeDropdown()\n focusChip(selectedValues.value.length - 1)\n return\n }\n\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n return\n }\n\n // Только при открытой панели: в закрытом инпуте Home/End двигают каретку.\n if (open.value && handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value)\n break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option')\n chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value)\n commitCustom()\n break\n }\n case 'Backspace':\n // multiple: пустой запрос + Backspace удаляет последний chip.\n if (props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n removeValue(selectedValues.value[selectedValues.value.length - 1])\n }\n break\n case 'Tab':\n closeDropdown()\n break\n }\n}\n\n// ————— Синхронизация query с выбранным значением (single).\nwatch(\n [singleSelectedLabel, () => props.multiple],\n ([label, multiple]) => {\n if (multiple)\n return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value)\n return\n setQuery(label)\n },\n { immediate: true },\n)\n\nwatch(open, (isOpen) => {\n if (isOpen) {\n initActiveIndex()\n return\n }\n // Закрытие: сбрасываем активную опцию и «черновик».\n resetActive()\n if (props.multiple) {\n setQuery('')\n }\n else {\n // single: возвращаем метку выбранной опции (revert ввода без выбора).\n setQuery(singleSelectedLabel.value)\n }\n})\n\nconst ariaAutocomplete = computed(() => (props.allowCustomValue ? 'both' : 'list'))\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-autocomplete\n class=\"relative w-full\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n data-gr-autocomplete-shell\n :class=\"autocompleteShellClass({ size: resolvedSize, disabled: isDisabled, invalid: isInvalid })\"\n @mousedown.self.prevent=\"focusInput\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-autocomplete-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Chips выбранных значений (multiple). -->\n <template v-if=\"multiple\">\n <GrChip\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n :ref=\"setChipRef(chipIndex)\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"chipSize\"\n :radius=\"tagRadius\"\n :closable=\"!locked\"\n :remove-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n :remove-tabindex=\"-1\"\n @remove=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"truncate\">{{ option.label }}</span>\n </GrChip>\n </template>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-autocomplete-input\n data-testid=\"gr-autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n :value=\"query\"\n :disabled=\"isDisabled\"\n :placeholder=\"hasSelection && multiple ? undefined : placeholder\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :readonly=\"isReadonly\"\n aria-haspopup=\"listbox\"\n :aria-autocomplete=\"ariaAutocomplete\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n class=\"min-w-0 flex-1 bg-transparent text-inherit placeholder:text-[var(--gr-muted-fg)] focus:outline-none disabled:cursor-not-allowed\"\n @input=\"onInput\"\n @focus=\"onFocus\"\n @keydown=\"onKeydown\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-autocomplete-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <!-- Trailing: спиннер / очистка / шеврон. -->\n <span\n v-if=\"isLoading\"\n data-gr-autocomplete-spinner\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)]\"\n >\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n\n <button\n v-else-if=\"showClear\"\n type=\"button\"\n data-testid=\"gr-autocomplete-clear\"\n data-gr-autocomplete-clear\n class=\"shrink-0 inline-flex h-5 w-5 items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"clearSelection\"\n >\n <IconX class=\"block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-autocomplete-chevron\"\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown class=\"block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <!-- Нативная форма: сериализуется модель, а не текст запроса. -->\n <template v-if=\"name\">\n <input\n v-for=\"value in selectedValues\"\n :key=\"`hidden-${String(value)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(value)\"\n >\n </template>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-autocomplete-panel\"\n v-bind=\"themeAttrs\"\n data-gr-autocomplete-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n >\n <div :class=\"autocompletePanelClasses\">\n <!--\n Пустой listbox не рендерим: его `p-1` давал полосу пустоты над\n строкой состояния, и «ничего не найдено» выглядело прижатым к низу.\n -->\n <div\n v-if=\"showAddOption || renderedOptions.length\"\n :id=\"listboxId\"\n ref=\"listboxEl\"\n data-gr-autocomplete-listbox\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"p-1 overflow-auto\"\n :class=\"virtual ? 'flex flex-col' : ''\"\n :style=\"listboxStyle\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <!--\n Прямые потомки listbox — только опции: роль объявляет остальных\n недопустимыми детьми (`aria-required-children`), поэтому строки\n состояний живут ниже, вне списка. `mousedown.prevent` держит\n фокус на `<input>` — контракт combobox с `aria-activedescendant`.\n -->\n <button\n v-if=\"showAddOption\"\n :id=\"addOptionDomId\"\n type=\"button\"\n role=\"option\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(0)\"\n data-testid=\"gr-autocomplete-add-option\"\n data-gr-autocomplete-add-option\n :class=\"autocompleteOptionClass({ disabled: false, active: activeItem?.kind === 'add' })\"\n @mousedown.prevent\n @click=\"commitCustom\"\n @mousemove=\"activeIndex = 0\"\n >\n {{ t('gr.autocomplete.addOption', 'Add \"{value}\"', { value: query.trim() }) }}\n </button>\n\n <button\n v-for=\"{ option, index: optionIndex } in renderedOptions\"\n :id=\"optionDomId(optionIndex)\"\n :key=\"option.value\"\n :ref=\"(el) => virtual && virtualizer.measure(optionIndex + addOffset, el as Element | null)\"\n data-gr-autocomplete-option\n type=\"button\"\n role=\"option\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(optionIndex + addOffset)\"\n :disabled=\"option.disabled\"\n :aria-selected=\"isSelected(option.value) ? 'true' : 'false'\"\n :aria-disabled=\"option.disabled ? 'true' : undefined\"\n :class=\"autocompleteOptionClass({\n disabled: !!option.disabled,\n active: activeValue === option.value,\n })\"\n @mousedown.prevent\n @click=\"chooseOption(option)\"\n @mousemove=\"activeIndex = navigableIndexOf(option.value)\"\n >\n <slot name=\"option\" :option=\"option\" :selected=\"isSelected(option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span class=\"inline-block h-4 w-4 shrink-0\" aria-hidden=\"true\">\n <IconCheck v-if=\"isSelected(option.value)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ option.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n\n <!--\n Загрузка, «введите ещё N» и «ничего не найдено» меняются молча:\n `aria-label` на generic-элементе AT игнорируют. Один живой регион\n на все три — и вне listbox, чтобы не ломать его состав.\n -->\n <div\n v-if=\"isLoading || belowMinQuery || showEmpty\"\n data-gr-autocomplete-status\n role=\"status\"\n aria-live=\"polite\"\n :class=\"autocompleteStateClass\"\n >\n <template v-if=\"isLoading\">\n <span data-gr-autocomplete-loading class=\"flex items-center gap-2\">\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </slot>\n </span>\n </template>\n <template v-else-if=\"belowMinQuery\">\n <span data-gr-autocomplete-hint>{{ resolvedTypeMoreText }}</span>\n </template>\n <template v-else>\n <span data-gr-autocomplete-empty>\n <slot name=\"empty\">\n {{ resolvedNoResultsText }}\n </slot>\n </span>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку, и распорка в\n * десятки тысяч пикселей раздула бы панель. Не обёртка внутри listbox'а:\n * прямыми потомками роли обязаны быть только опции (`aria-required-children`,\n * гейт — `GrAutocomplete.a11y.test.ts`). Не отступы крайних опций: при прыжке\n * прокрутки опции заменяются целиком, распорка исчезла бы вместе с ними, и\n * браузер обрезал бы `scrollTop`.\n *\n * Псевдоэлементы не узлы DOM: их не за что размонтировать, детьми listbox'а\n * они не считаются, а высота приезжает переменными в том же патче, что и опции.\n */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n","<script setup lang=\"ts\" generic=\"TValue extends GrAutocompleteValue = string\">\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { useGrComponentProp, useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useDismissible } from '../../composables/useDismissible'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRemoteOptions } from './composables/useRemoteOptions'\nimport { useAutocompleteValues } from './composables/useAutocompleteValues'\nimport { useAutocompletePanel } from './composables/useAutocompletePanel'\nimport { useAutocompleteVirtual } from './composables/useAutocompleteVirtual'\nimport { useAutocompleteNavigation } from './composables/useAutocompleteNavigation'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\nimport GrChip from '../GrChip/GrChip.vue'\nimport { chipSizeForBadgeScale } from '../GrChip/grChipStyles'\nimport { panelPopTransition } from '../shared/overlayTransition'\n\nimport {\n autocompleteOptionClass,\n autocompletePanelClasses,\n autocompleteShellClass,\n autocompleteStateClass,\n type GrAutocompleteModelValue,\n type GrAutocompleteOption,\n type GrAutocompleteValue,\n type GrAutocompleteSize,\n} from './grAutocompleteStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport type {\n GrAutocompleteModelValue,\n GrAutocompleteOption,\n GrAutocompleteSize,\n GrAutocompleteValue,\n} from './grAutocompleteStyles'\n\n/**\n * Публичный GR-примитив «Autocomplete / Combobox» (WAI-ARIA editable combobox).\n *\n * В отличие от `GrSelect` (select-only combobox с кнопкой-триггером), здесь\n * combobox-ом является сам текстовый `<input>`: набранный текст — это поисковый\n * запрос, а выбор опции заполняет поле. Поддерживает локальную фильтрацию,\n * удалённую (async) загрузку через событие `search` + внешний проп `loading`,\n * произвольные значения (`allowCustomValue`) и multiple с удаляемыми chips.\n */\nexport interface GrAutocompleteProps<TValue extends GrAutocompleteValue = string> {\n /** Выбранное значение (single — строка, multiple — массив строк). */\n modelValue: GrAutocompleteModelValue<TValue>\n /**\n * Доступные опции. Для локального режима — полный список (фильтруется на клиенте).\n * Для remote-режима (`filterable=false`) — список, который родитель обновляет\n * в ответ на событие `search`.\n */\n options?: GrAutocompleteOption<TValue>[]\n multiple?: boolean\n /**\n * Вид чипов выбранных значений в режиме `multiple`. Рисует их `GrChip`, но\n * шкала пропа осталась бейджевой: `tagSize` публичен, и его значения обязаны\n * означать тот же кегль, что и раньше. Перевод ступеней — в\n * `chipSizeForBadgeScale`.\n */\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n size?: GrAutocompleteSize\n placeholder?: string\n ariaLabel?: string\n /** Кнопка очистки выбранного значения/запроса. */\n clearable?: boolean\n /** Внешне управляемое состояние загрузки (для async-сценариев). */\n loading?: boolean\n /**\n * Локальная фильтрация опций по введённому запросу. Отключите (`false`) для\n * чисто удалённого поиска — тогда `options` показываются как есть, а фильтрацию\n * выполняет сервер по событию `search`.\n */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. По умолчанию — подстрока в `label`/`value`. */\n filter?: (option: GrAutocompleteOption<TValue>, query: string) => boolean\n /**\n * Удалённая загрузка опций под управлением компонента: дебаунс, отмена\n * устаревшего запроса и `loading` берёт на себя он. Ответ на отменённый\n * запрос игнорируется — при быстром вводе в списке всегда результат\n * последнего запроса, а не того, который вернулся позже.\n *\n * `signal` пробрасывается в `fetch`. Локальная фильтрация в этом режиме\n * выключена: список фильтрует сервер. Альтернатива — событие `search`, если\n * запрос ведёт само приложение.\n */\n fetchOptions?: (query: string, signal: AbortSignal) => Promise<GrAutocompleteOption<TValue>[]>\n /** Минимальная длина запроса до эмита `search` (для дебаунса remote-загрузки). */\n minQueryLength?: number\n /** Задержка дебаунса события `search`, мс. */\n debounce?: number\n /** Разрешить ввод/коммит значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Закрывать панель после выбора (single всегда закрывает). */\n closeOnSelect?: boolean\n /** Максимальная высота панели, px. */\n dropdownMaxHeight?: number\n /**\n * Виртуализация панели: в DOM живёт только окно вокруг вьюпорта.\n *\n * Высоту окна задаёт `dropdownMaxHeight`. Включается осознанно: на сотне\n * опций выигрыша нет, а в разметке остаётся только окно — вместе с ним\n * меняется и то, что находит `querySelector` потребителя. Профильный\n * сценарий — удалённый поиск по справочнику на тысячи позиций.\n */\n virtual?: boolean\n /** i18n-тексты состояний панели / aria. */\n loadingText?: string\n noResultsText?: string\n clearLabel?: string\n /**\n * Контролируемое состояние панели (`v-model:open`). Без пропа панель ведёт\n * себя сама (uncontrolled), с ним — слушайте `update:open` и меняйте проп.\n */\n open?: boolean\n /** Имя для нативной формы: hidden input по значению модели (не по тексту запроса). */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrAutocompleteEmits<TValue extends GrAutocompleteValue = string> {\n (e: 'update:modelValue', value: GrAutocompleteModelValue<TValue>): void\n /** Дебаунснутый поисковый запрос — точка входа для удалённой загрузки опций. */\n (e: 'search', query: string): void\n /** Запрос `fetchOptions` завершился ошибкой (отмена устаревшего сюда не приходит). */\n (e: 'searchError', error: unknown): void\n /** Значение зафиксировано выбором или снятием опции. */\n (e: 'change', value: GrAutocompleteModelValue<TValue>): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n /** Значение снято кнопкой очистки; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrAutocompleteProps<TValue>>(),\n {\n options: undefined,\n multiple: false,\n tagTone: 'neutral',\n tagDark: false,\n // `sm`, а не `md` как у `GrInputTag`: чип живёт внутри контрола, а не в тексте.\n tagSize: 'sm',\n tagRadius: 'round',\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n size: undefined,\n placeholder: undefined,\n ariaLabel: undefined,\n clearable: undefined,\n loading: false,\n filterable: true,\n filter: undefined,\n fetchOptions: undefined,\n minQueryLength: 0,\n debounce: 250,\n allowCustomValue: false,\n closeOnSelect: true,\n dropdownMaxHeight: 280,\n virtual: false,\n loadingText: undefined,\n noResultsText: undefined,\n clearLabel: undefined,\n open: undefined,\n name: undefined,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrAutocompleteSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrAutocomplete' })\nconst resolvedClearable = useGrComponentProp('GrAutocomplete', 'clearable', () => props.clearable, false)\n\n/**\n * Аддоны живут флекс-соседями поля, а не поверх него: оболочка автокомплита —\n * флекс-строка с чипами, поэтому резервировать место паддингом не нужно.\n * Контракт ширин остаётся общим (`useControlAddons`).\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\n\nconst emit = defineEmits<GrAutocompleteEmits<TValue>>()\ndefineSlots<{\n /** Аддон слева от поля ввода. */\n prefix?: () => any\n /** Аддон справа, перед спиннером и крестиком. */\n suffix?: () => any\n /** Строка списка вместо подписи опции. */\n option?: (props: { option: GrAutocompleteOption<TValue>, selected: boolean }) => any\n /** Содержимое панели, пока едут опции. */\n loading?: () => any\n /** Содержимое панели, когда подходящих опций нет. */\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.autocomplete.loading', 'Loading…'))\nconst resolvedNoResultsText = computed(() => props.noResultsText ?? t('gr.autocomplete.noResults', 'No results'))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\n// Число уходит под двумя общепринятыми именами: `n` читают `@feugene/fint-i18n`\n// и `vue-i18n`, `count` — `i18next`. Формы для каждого языка лежат в словаре,\n// выбирает их переводчик; встроенный fallback — одна английская строка.\nconst resolvedTypeMoreText = computed(() =>\n t('gr.autocomplete.typeMore', 'Type at least {n} characters', {\n n: props.minQueryLength,\n count: props.minQueryLength,\n }),\n)\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\n// Read-only запирает контрол так же, как disabled: значение видно, но панель не\n// открывается, опции не выбираются и чипы не удаляются. Без этого `readonly`\n// оставался бы ARIA-атрибутом без поведения.\nconst locked = computed(() => isDisabled.value || isReadonly.value)\n\nconst { remoteOptions, remoteAnswered, remoteLoading, scheduleSearch } = useRemoteOptions<TValue>({\n fetchOptions: () => props.fetchOptions,\n options: () => props.options,\n debounce: () => props.debounce,\n minQueryLength: () => props.minQueryLength,\n onSearch: value => emit('search', value),\n onSearchError: error => emit('searchError', error),\n})\n\nconst {\n optionsResolved,\n modelSingle,\n selectedValues,\n hasSelection,\n selectedOptions,\n singleSelectedLabel,\n} = useAutocompleteValues<TValue>({\n modelValue: () => props.modelValue,\n options: () => props.options,\n multiple: () => props.multiple,\n fetchOptions: () => props.fetchOptions,\n remoteOptions,\n remoteAnswered,\n})\n\nconst isLoading = computed(() => props.loading || remoteLoading.value)\n\n// ————— Состояние.\nconst query = ref('')\n// `dirty` — пользователь начал вводить запрос. Нужно, чтобы в single-режиме при\n// открытии заполненного поля показать весь список (а не отфильтровать по метке\n// уже выбранной опции). Сбрасывается при программной установке `query`.\nconst dirty = ref(false)\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst listboxEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({\n focus,\n blur,\n open: () => openDropdown(),\n close: () => closeDropdown(),\n})\nconst clickOutsideExclude = [() => panelEl.value]\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nuseDismissible(open, closeDropdown)\n\nconst { belowMinQuery, effectiveOptions, canAddCustom, showEmpty } = useAutocompletePanel<TValue>({\n optionsResolved,\n query,\n dirty,\n filterable: () => props.filterable,\n filter: () => props.filter,\n fetchOptions: () => props.fetchOptions,\n multiple: () => props.multiple,\n minQueryLength: () => props.minQueryLength,\n allowCustomValue: () => props.allowCustomValue,\n selectedValues,\n modelSingle,\n isLoading,\n})\n\n// ————— Панель: id/aria-activedescendant.\nconst listboxId = useId()\n\n/**\n * Id опции строится от позиции, а не от значения: значение с пробелом дало бы\n * невалидный `id`, а `aria-activedescendant` — два токена вместо одной ссылки,\n * и активная опция перестала бы объявляться.\n */\nfunction optionDomId(index: number): string {\n return `${listboxId}-opt-${index}`\n}\nconst addOptionDomId = computed(() => `${listboxId}-add`)\n\nconst {\n addOffset,\n virtualizer,\n showAddOption,\n renderedOptions,\n optionSetProps,\n listboxStyle,\n} = useAutocompleteVirtual<TValue>({\n listboxEl,\n effectiveOptions,\n canAddCustom,\n virtual: () => props.virtual,\n dropdownMaxHeight: () => props.dropdownMaxHeight,\n})\n\nconst {\n isSelected,\n navigableIndexOf,\n activeIndex,\n activeItem,\n activeDescendantId,\n activeValue,\n initActiveIndex,\n resetActive,\n handleNavigationKeys,\n} = useAutocompleteNavigation<TValue>({\n effectiveOptions,\n canAddCustom,\n selectedValues,\n open,\n virtual: () => props.virtual,\n addOffset,\n scrollToIndex: index => virtualizer.scrollToIndex(index),\n optionDomId,\n addOptionDomId,\n})\n\n// ————— Открытие/закрытие.\nfunction openDropdown(): void {\n if (locked.value || open.value)\n return\n setOpen(true)\n}\n\nfunction closeDropdown(): void {\n setOpen(false)\n}\n\nfunction focusInput(): void {\n inputEl.value?.focus()\n}\n\n// ————— Ввод.\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLInputElement).value\n query.value = value\n dirty.value = true\n openDropdown()\n scheduleSearch(value)\n}\n\nfunction onFocus(): void {\n if (locked.value)\n return\n openDropdown()\n}\n\nfunction setQuery(value: string): void {\n query.value = value\n dirty.value = false\n}\n\n// ————— Выбор значений.\nfunction selectSingle(value: TValue, label: string): void {\n emit('update:modelValue', value)\n emit('change', value as GrAutocompleteModelValue<TValue>)\n setQuery(label)\n closeDropdown()\n}\n\nfunction toggleMultiple(value: TValue): void {\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0)\n next.splice(idx, 1)\n else next.push(value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n setQuery('')\n if (props.closeOnSelect)\n closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled)\n return\n if (props.multiple)\n toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value)\n return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v)\n return\n if (props.multiple)\n toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value)\n return\n const next = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n void nextTick(focusAfter)\n}\n\nfunction clearSelection(): void {\n if (locked.value)\n return\n const next = (props.multiple ? [] : '') as GrAutocompleteModelValue<TValue>\n emit('update:modelValue', next)\n emit('change', next)\n emit('clear')\n setQuery('')\n void nextTick(focusInput)\n}\n\nconst showClear = computed(() =>\n resolvedClearable.value && !locked.value && (hasSelection.value || query.value.length > 0),\n)\n\n// ————— Чипы (multiple): стрелки вместо таб-стопов.\n//\n// Крестики намеренно не табируемы: у combobox фокус живёт на `<input>`, и\n// двадцать выбранных значений не должны давать двадцать остановок Tab. Поэтому\n// вход в чипы — ArrowLeft из пустого запроса, а выход — ArrowRight/Esc/печать.\n//\n// Отсюда же единственное отличие от `GrInputTag`: `tabindexFor` здесь не\n// используется. Кольцо нужно как движок навигации, а таб-стоп у ряда чипов\n// отсутствует вовсе — он принадлежит полю ввода.\nconst chipRefs = ref<Array<{ removeEl: HTMLButtonElement | null } | null>>([])\n\nfunction setChipRef(index: number) {\n return (el: unknown): void => {\n chipRefs.value[index] = el as { removeEl: HTMLButtonElement | null } | null\n }\n}\n\n/**\n * Проп `tagSize` объявлен по шкале бейджа и остаётся публичным контрактом:\n * дефолт `sm` обязан означать для потребителя тот же кегль, что и раньше.\n */\nconst chipSize = computed(() => chipSizeForBadgeScale(props.tagSize))\n\n// Адресация индексом, а не значением: значения дженерик-типа, и `TValue`\n// сравнивался бы по ссылке там, где опции пересоздаются.\nconst chipIndexes = computed(() => selectedOptions.value.map((_, index) => index))\n\nconst chipRoving = useRovingFocus<number>({\n items: () => chipIndexes.value,\n elementFor: index => chipRefs.value[index]?.removeEl,\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: за правым краем стоит поле ввода, за левым ничего.\n wrap: () => false,\n onOverflow: (edge) => {\n if (edge === 'end')\n focusInput()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nfunction focusChip(index: number): void {\n const last = selectedOptions.value.length - 1\n if (last < 0) {\n focusInput()\n return\n }\n\n void chipRoving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n if (chipRoving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'Delete':\n case 'Backspace':\n event.preventDefault()\n removeValue(value, () => focusChip(index))\n break\n case 'Escape':\n event.preventDefault()\n focusInput()\n break\n default:\n // Печатный символ возвращает в поле: фокус переезжает синхронно, до\n // вставки текста, поэтому символ попадает в запрос, а не пропадает.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey)\n focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event))\n return\n if (locked.value)\n return\n\n // Курсор в начале пустого запроса — стрелка влево уходит к чипам, а не\n // двигает каретку, которой всё равно некуда двигаться.\n if (event.key === 'ArrowLeft' && props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n closeDropdown()\n focusChip(selectedValues.value.length - 1)\n return\n }\n\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n return\n }\n\n // Только при открытой панели: в закрытом инпуте Home/End двигают каретку.\n if (open.value && handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value)\n break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option')\n chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value)\n commitCustom()\n break\n }\n case 'Backspace':\n // multiple: пустой запрос + Backspace удаляет последний chip.\n if (props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n removeValue(selectedValues.value[selectedValues.value.length - 1])\n }\n break\n case 'Tab':\n closeDropdown()\n break\n }\n}\n\n// ————— Синхронизация query с выбранным значением (single).\nwatch(\n [singleSelectedLabel, () => props.multiple],\n ([label, multiple]) => {\n if (multiple)\n return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value)\n return\n setQuery(label)\n },\n { immediate: true },\n)\n\nwatch(open, (isOpen) => {\n if (isOpen) {\n initActiveIndex()\n return\n }\n // Закрытие: сбрасываем активную опцию и «черновик».\n resetActive()\n if (props.multiple) {\n setQuery('')\n }\n else {\n // single: возвращаем метку выбранной опции (revert ввода без выбора).\n setQuery(singleSelectedLabel.value)\n }\n})\n\nconst ariaAutocomplete = computed(() => (props.allowCustomValue ? 'both' : 'list'))\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-autocomplete\n class=\"relative w-full\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n data-gr-autocomplete-shell\n :class=\"autocompleteShellClass({ size: resolvedSize, disabled: isDisabled, invalid: isInvalid })\"\n @mousedown.self.prevent=\"focusInput\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-autocomplete-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Chips выбранных значений (multiple). -->\n <template v-if=\"multiple\">\n <GrChip\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n :ref=\"setChipRef(chipIndex)\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"chipSize\"\n :radius=\"tagRadius\"\n :closable=\"!locked\"\n :remove-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n :remove-tabindex=\"-1\"\n @remove=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"truncate\">{{ option.label }}</span>\n </GrChip>\n </template>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-autocomplete-input\n data-testid=\"gr-autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n :value=\"query\"\n :disabled=\"isDisabled\"\n :placeholder=\"hasSelection && multiple ? undefined : placeholder\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :readonly=\"isReadonly\"\n aria-haspopup=\"listbox\"\n :aria-autocomplete=\"ariaAutocomplete\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n class=\"min-w-0 flex-1 bg-transparent text-inherit placeholder:text-[var(--gr-muted-fg)] focus:outline-none disabled:cursor-not-allowed\"\n @input=\"onInput\"\n @focus=\"onFocus\"\n @keydown=\"onKeydown\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-autocomplete-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <!-- Trailing: спиннер / очистка / шеврон. -->\n <span\n v-if=\"isLoading\"\n data-gr-autocomplete-spinner\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)]\"\n >\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n\n <button\n v-else-if=\"showClear\"\n type=\"button\"\n data-testid=\"gr-autocomplete-clear\"\n data-gr-autocomplete-clear\n class=\"shrink-0 inline-flex h-5 w-5 items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"clearSelection\"\n >\n <IconX class=\"block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-autocomplete-chevron\"\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown class=\"block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <!-- Нативная форма: сериализуется модель, а не текст запроса. -->\n <template v-if=\"name\">\n <input\n v-for=\"value in selectedValues\"\n :key=\"`hidden-${String(value)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(value)\"\n >\n </template>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-autocomplete-panel\"\n v-bind=\"themeAttrs\"\n data-gr-autocomplete-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n >\n <div :class=\"autocompletePanelClasses\">\n <!--\n Пустой listbox не рендерим: его `p-1` давал полосу пустоты над\n строкой состояния, и «ничего не найдено» выглядело прижатым к низу.\n -->\n <div\n v-if=\"showAddOption || renderedOptions.length\"\n :id=\"listboxId\"\n ref=\"listboxEl\"\n data-gr-autocomplete-listbox\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"p-1 overflow-auto\"\n :class=\"virtual ? 'flex flex-col' : ''\"\n :style=\"listboxStyle\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <!--\n Прямые потомки listbox — только опции: роль объявляет остальных\n недопустимыми детьми (`aria-required-children`), поэтому строки\n состояний живут ниже, вне списка. `mousedown.prevent` держит\n фокус на `<input>` — контракт combobox с `aria-activedescendant`.\n -->\n <button\n v-if=\"showAddOption\"\n :id=\"addOptionDomId\"\n type=\"button\"\n role=\"option\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(0)\"\n data-testid=\"gr-autocomplete-add-option\"\n data-gr-autocomplete-add-option\n :class=\"autocompleteOptionClass({ disabled: false, active: activeItem?.kind === 'add' })\"\n @mousedown.prevent\n @click=\"commitCustom\"\n @mousemove=\"activeIndex = 0\"\n >\n {{ t('gr.autocomplete.addOption', 'Add \"{value}\"', { value: query.trim() }) }}\n </button>\n\n <button\n v-for=\"{ option, index: optionIndex } in renderedOptions\"\n :id=\"optionDomId(optionIndex)\"\n :key=\"option.value\"\n :ref=\"(el) => virtual && virtualizer.measure(optionIndex + addOffset, el as Element | null)\"\n data-gr-autocomplete-option\n type=\"button\"\n role=\"option\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(optionIndex + addOffset)\"\n :disabled=\"option.disabled\"\n :aria-selected=\"isSelected(option.value) ? 'true' : 'false'\"\n :aria-disabled=\"option.disabled ? 'true' : undefined\"\n :class=\"autocompleteOptionClass({\n disabled: !!option.disabled,\n active: activeValue === option.value,\n })\"\n @mousedown.prevent\n @click=\"chooseOption(option)\"\n @mousemove=\"activeIndex = navigableIndexOf(option.value)\"\n >\n <slot name=\"option\" :option=\"option\" :selected=\"isSelected(option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span class=\"inline-block h-4 w-4 shrink-0\" aria-hidden=\"true\">\n <IconCheck v-if=\"isSelected(option.value)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ option.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n\n <!--\n Загрузка, «введите ещё N» и «ничего не найдено» меняются молча:\n `aria-label` на generic-элементе AT игнорируют. Один живой регион\n на все три — и вне listbox, чтобы не ломать его состав.\n -->\n <div\n v-if=\"isLoading || belowMinQuery || showEmpty\"\n data-gr-autocomplete-status\n role=\"status\"\n aria-live=\"polite\"\n :class=\"autocompleteStateClass\"\n >\n <template v-if=\"isLoading\">\n <span data-gr-autocomplete-loading class=\"flex items-center gap-2\">\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </slot>\n </span>\n </template>\n <template v-else-if=\"belowMinQuery\">\n <span data-gr-autocomplete-hint>{{ resolvedTypeMoreText }}</span>\n </template>\n <template v-else>\n <span data-gr-autocomplete-empty>\n <slot name=\"empty\">\n {{ resolvedNoResultsText }}\n </slot>\n </span>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку, и распорка в\n * десятки тысяч пикселей раздула бы панель. Не обёртка внутри listbox'а:\n * прямыми потомками роли обязаны быть только опции (`aria-required-children`,\n * гейт — `GrAutocomplete.a11y.test.ts`). Не отступы крайних опций: при прыжке\n * прокрутки опции заменяются целиком, распорка исчезла бы вместе с ними, и\n * браузер обрезал бы `scrollTop`.\n *\n * Псевдоэлементы не узлы DOM: их не за что размонтировать, детьми listbox'а\n * они не считаются, а высота приезжает переменными в том же патче, что и опции.\n */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
+
import { t as GrAutocomplete_default } from "./chunks/GrAutocomplete-C-uovdv8.js";
|
|
1
2
|
import { n as grAutocompleteSafelist, t as grAutocompleteConfig } from "../../chunks/config-BHLMTJaR.js";
|
|
2
|
-
import { t as GrAutocomplete_default } from "./chunks/GrAutocomplete-D396_JrW.js";
|
|
3
3
|
export { GrAutocomplete_default as GrAutocomplete, GrAutocomplete_default as default, grAutocompleteConfig, grAutocompleteSafelist };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[data-gr-virtual][data-v-
|
|
1
|
+
[data-gr-virtual][data-v-c4989846]:before,[data-gr-virtual][data-v-c4989846]:after{content:"";flex:none;display:block}[data-gr-virtual][data-v-c4989846]:before{height:var(--gr-virtual-before,0px)}[data-gr-virtual][data-v-c4989846]:after{height:var(--gr-virtual-after,0px)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-CPMRfwVr.js";
|
|
2
2
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
3
3
|
import { t as GrSkeleton_default } from "../../GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js";
|
|
4
|
-
import { a as
|
|
4
|
+
import { _ as statusToneClass, a as avatarToneIndex, c as groupItemClass, d as mediaClass, f as mediaClipClass, g as statusDotClass, h as rootStatusClass, i as avatarToneClasses, l as groupOverflowClass, m as rootNeutralClass, o as grAvatarClass, p as rootBaseClass, r as avatarFontSizePx, s as groupBaseClass, u as initialsFrom } from "../../../chunks/config-CzeZg2LO.js";
|
|
5
5
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, inject, normalizeClass, normalizeStyle, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useSlots, watch } from "vue";
|
|
6
6
|
//#region src/components/GrAvatar/avatarContext.ts
|
|
7
7
|
var GR_AVATAR_GROUP_KEY = Symbol.for("@feugene/granularity/avatar-group");
|
|
@@ -28,6 +28,10 @@ var GrAvatar_default = /* @__PURE__ */ defineComponent({
|
|
|
28
28
|
alt: { default: void 0 },
|
|
29
29
|
name: { default: void 0 },
|
|
30
30
|
shape: { default: void 0 },
|
|
31
|
+
autoColor: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: void 0
|
|
34
|
+
},
|
|
31
35
|
status: { default: void 0 }
|
|
32
36
|
},
|
|
33
37
|
setup(__props) {
|
|
@@ -44,6 +48,7 @@ var GrAvatar_default = /* @__PURE__ */ defineComponent({
|
|
|
44
48
|
const localSize = computed(() => props.size ?? group?.size);
|
|
45
49
|
const resolvedScaleSize = useGrComponentSize(() => typeof localSize.value === "number" ? void 0 : localSize.value, { component: "GrAvatar" });
|
|
46
50
|
const resolvedShape = useGrComponentProp("GrAvatar", "shape", () => props.shape ?? group?.shape, "circle");
|
|
51
|
+
const resolvedAutoColor = useGrComponentProp("GrAvatar", "autoColor", () => props.autoColor, false);
|
|
47
52
|
const sizePx = computed(() => typeof localSize.value === "number" ? localSize.value : GR_AVATAR_SIZE_PX[resolvedScaleSize.value]);
|
|
48
53
|
const style = computed(() => {
|
|
49
54
|
const px = `${sizePx.value}px`;
|
|
@@ -87,8 +92,14 @@ var GrAvatar_default = /* @__PURE__ */ defineComponent({
|
|
|
87
92
|
*/
|
|
88
93
|
const accessibleName = computed(() => props.alt ?? props.name ?? "");
|
|
89
94
|
const rootIsImage = computed(() => !activeSrc.value && accessibleName.value !== "");
|
|
95
|
+
/**
|
|
96
|
+
* Автоцвет виден только под инициалами: картинка закрывает фон целиком, а у
|
|
97
|
+
* пустого аватара красить нечего.
|
|
98
|
+
*/
|
|
99
|
+
const toneClass = computed(() => resolvedAutoColor.value && showInitials.value && props.name ? avatarToneClasses[avatarToneIndex(props.name)] : rootNeutralClass);
|
|
90
100
|
const rootClass = computed(() => [
|
|
91
101
|
rootBaseClass,
|
|
102
|
+
toneClass.value,
|
|
92
103
|
grAvatarClass(resolvedShape.value),
|
|
93
104
|
props.status ? rootStatusClass : ""
|
|
94
105
|
]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrAvatar-Do5-C409.js","names":[],"sources":["../../../../src/components/GrAvatar/avatarContext.ts","../../../../src/components/GrAvatar/GrAvatar.vue","../../../../src/components/GrAvatar/GrAvatar.vue","../../../../src/components/GrAvatar/GrAvatarGroup.vue","../../../../src/components/GrAvatar/GrAvatarGroup.vue"],"sourcesContent":["import type { InjectionKey } from 'vue'\n\nimport type { GrAvatarShape } from './grAvatarStyles'\nimport type { GrSizeWithPx } from '../shared/sizes'\n\n/**\n * Контекст `GrAvatarGroup`: размер и форма задаются на группе, чтобы ряд\n * аватаров не расползался, когда потребитель забыл повторить пропы у каждого.\n */\nexport interface GrAvatarGroupContext {\n size?: GrSizeWithPx\n shape?: GrAvatarShape\n}\n\nexport const GR_AVATAR_GROUP_KEY: InjectionKey<GrAvatarGroupContext> = Symbol.for('@feugene/granularity/avatar-group')\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { GR_AVATAR_GROUP_KEY } from './avatarContext'\nimport {\n avatarFontSizePx,\n grAvatarClass,\n initialsFrom,\n mediaClass,\n mediaClipClass,\n avatarToneClasses,\n avatarToneIndex,\n rootBaseClass,\n rootNeutralClass,\n rootStatusClass,\n statusDotClass,\n statusToneClass,\n type GrAvatarShape,\n type GrAvatarStatus,\n} from './grAvatarStyles'\n\nexport type { GrAvatarShape, GrAvatarStatus } from './grAvatarStyles'\n\n/** Диаметр аватара для каждой ступени канонической шкалы. */\nconst GR_AVATAR_SIZE_PX = {\n xs: 24,\n sm: 32,\n md: 40,\n lg: 56,\n} as const\n\nexport interface GrAvatarProps {\n /**\n * Размер по канонической шкале (`xs|sm|md|lg`) — тогда работает\n * `GrConfigProvider`. Число — escape-hatch: аватар нужен произвольного\n * диаметра (24px в строке списка, 96px в профиле), и четыре ступени шкалы\n * этого не закрывают.\n */\n size?: GrSizeWithPx\n src?: string\n /** Резервная картинка: показывается, когда `src` не загрузился. */\n fallbackSrc?: string\n alt?: string\n /** Имя участника: даёт инициалы и доступное имя аватара. */\n name?: string\n shape?: GrAvatarShape\n /**\n * Красить фон по имени: одинаковое имя всегда даёт одинаковый цвет.\n *\n * Выключен по умолчанию — серый аватар это осознанный нейтральный дефолт, а\n * цветной может спорить с оформлением продукта. Палитру задаёт тема\n * (`--gr-avatar-N-bg`/`-fg`), компонент только выбирает слот.\n */\n autoColor?: boolean\n /** Статус участника. Точка декоративна — рядом идёт скрытая подпись. */\n status?: GrAvatarStatus\n}\n\nconst props = withDefaults(defineProps<GrAvatarProps>(), {\n size: undefined,\n src: undefined,\n fallbackSrc: undefined,\n alt: undefined,\n name: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n shape: undefined,\n // По той же причине, что `shape`: булев проп без этой строки Vue приводит к\n // `false` сам, и резолвер уже не отличит «не задано» от «выключено явно».\n autoColor: undefined,\n status: undefined,\n})\n\nconst slots = defineSlots<{\n /** Содержимое вместо картинки и инициалов. */\n default?: () => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst group = inject(GR_AVATAR_GROUP_KEY, null)\n\n// Размер и форма группы сильнее провайдера, но слабее собственного пропа.\nconst localSize = computed(() => props.size ?? group?.size)\n\nconst resolvedScaleSize = useGrComponentSize(\n () => (typeof localSize.value === 'number' ? undefined : localSize.value),\n { component: 'GrAvatar' },\n)\n\nconst resolvedShape = useGrComponentProp(\n 'GrAvatar',\n 'shape',\n () => props.shape ?? group?.shape,\n 'circle',\n)\n\nconst resolvedAutoColor = useGrComponentProp('GrAvatar', 'autoColor', () => props.autoColor, false)\n\nconst sizePx = computed(() => (\n typeof localSize.value === 'number' ? localSize.value : GR_AVATAR_SIZE_PX[resolvedScaleSize.value]\n))\n\nconst style = computed(() => {\n const px = `${sizePx.value}px`\n // Кегль на корне, а не на span инициалов: так он достаётся и слотовому тексту.\n return { width: px, height: px, fontSize: `${avatarFontSizePx(sizePx.value)}px` }\n})\n\n/**\n * Состояние картинки. Без него битая ссылка оставляла `v-if=\"src\"` истинным:\n * браузер рисовал сломанное изображение, а запасной вариант не показывался\n * никогда.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst primaryState = ref<ImageState>('loading')\nconst fallbackState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ошибку прошлой.\nwatch(() => props.src, () => {\n primaryState.value = 'loading'\n})\nwatch(() => props.fallbackSrc, () => {\n fallbackState.value = 'loading'\n})\n\nconst activeSrc = computed(() => {\n if (props.src && primaryState.value !== 'error')\n return props.src\n if (props.fallbackSrc && fallbackState.value !== 'error')\n return props.fallbackSrc\n return undefined\n})\n\nconst isFallbackActive = computed(() => Boolean(activeSrc.value) && activeSrc.value !== props.src)\n\n// Скелет держит место, пока картинка едет: иначе ряд аватаров мигает пустотой.\nconst showSkeleton = computed(() => {\n if (!activeSrc.value)\n return false\n return (isFallbackActive.value ? fallbackState.value : primaryState.value) === 'loading'\n})\n\nconst initials = computed(() => (props.name ? initialsFrom(props.name) : ''))\n\n// Пусто — когда нет ни картинки, ни слота, ни имени.\nconst showInitials = computed(() => !activeSrc.value && !slots.default && Boolean(initials.value))\n\nconst statusLabel = computed(() => (props.status\n ? t(`gr.avatar.status.${props.status}`, props.status)\n : undefined))\n\n/**\n * Доступное имя: `alt` сильнее всего, иначе имя участника.\n *\n * Когда есть картинка, имя несёт её `alt` — так привычнее потребителю и не\n * возникает двойного объявления. Без картинки роль и имя берёт на себя корень:\n * иначе аватар с инициалами остался бы для диктора пустым.\n */\nconst accessibleName = computed(() => props.alt ?? props.name ?? '')\n\nconst rootIsImage = computed(() => !activeSrc.value && accessibleName.value !== '')\n\n/**\n * Автоцвет виден только под инициалами: картинка закрывает фон целиком, а у\n * пустого аватара красить нечего.\n */\nconst toneClass = computed(() => (\n resolvedAutoColor.value && showInitials.value && props.name\n ? avatarToneClasses[avatarToneIndex(props.name)]\n : rootNeutralClass\n))\n\nconst rootClass = computed(() => [\n rootBaseClass,\n toneClass.value,\n grAvatarClass(resolvedShape.value),\n props.status ? rootStatusClass : '',\n])\n\nfunction onError(kind: 'primary' | 'fallback'): void {\n if (kind === 'primary')\n primaryState.value = 'error'\n else fallbackState.value = 'error'\n}\n\nfunction onLoad(kind: 'primary' | 'fallback'): void {\n if (kind === 'primary')\n primaryState.value = 'loaded'\n else fallbackState.value = 'loaded'\n}\n</script>\n\n<template>\n <span\n data-gr-avatar\n :style=\"style\"\n :class=\"rootClass\"\n :role=\"rootIsImage ? 'img' : undefined\"\n :aria-label=\"rootIsImage ? accessibleName : undefined\"\n >\n <span :class=\"[mediaClass, status ? [mediaClipClass, grAvatarClass(resolvedShape)] : '']\">\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-avatar-skeleton\n width=\"100%\"\n height=\"100%\"\n :rounded=\"resolvedShape === 'circle' ? 'var(--gr-radius-full)' : 'var(--gr-avatar-square-radius, 10px)'\"\n />\n\n <img\n v-if=\"activeSrc\"\n :key=\"activeSrc\"\n data-gr-avatar-image\n :src=\"activeSrc\"\n :alt=\"accessibleName\"\n class=\"h-full w-full object-cover\"\n :class=\"showSkeleton ? 'invisible absolute' : ''\"\n @error=\"onError(isFallbackActive ? 'fallback' : 'primary')\"\n @load=\"onLoad(isFallbackActive ? 'fallback' : 'primary')\"\n >\n\n <span v-else-if=\"showInitials\" data-gr-avatar-initials>{{ initials }}</span>\n\n <slot v-else />\n </span>\n\n <template v-if=\"status\">\n <span\n data-gr-avatar-status\n :class=\"[statusDotClass, statusToneClass[status]]\"\n aria-hidden=\"true\"\n />\n <!-- Цвет точки смысл не передаёт: статус объявляется словом (WCAG 1.4.1). -->\n <span data-gr-avatar-status-label class=\"sr-only\">{{ statusLabel }}</span>\n </template>\n </span>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { GR_AVATAR_GROUP_KEY } from './avatarContext'\nimport {\n avatarFontSizePx,\n grAvatarClass,\n initialsFrom,\n mediaClass,\n mediaClipClass,\n avatarToneClasses,\n avatarToneIndex,\n rootBaseClass,\n rootNeutralClass,\n rootStatusClass,\n statusDotClass,\n statusToneClass,\n type GrAvatarShape,\n type GrAvatarStatus,\n} from './grAvatarStyles'\n\nexport type { GrAvatarShape, GrAvatarStatus } from './grAvatarStyles'\n\n/** Диаметр аватара для каждой ступени канонической шкалы. */\nconst GR_AVATAR_SIZE_PX = {\n xs: 24,\n sm: 32,\n md: 40,\n lg: 56,\n} as const\n\nexport interface GrAvatarProps {\n /**\n * Размер по канонической шкале (`xs|sm|md|lg`) — тогда работает\n * `GrConfigProvider`. Число — escape-hatch: аватар нужен произвольного\n * диаметра (24px в строке списка, 96px в профиле), и четыре ступени шкалы\n * этого не закрывают.\n */\n size?: GrSizeWithPx\n src?: string\n /** Резервная картинка: показывается, когда `src` не загрузился. */\n fallbackSrc?: string\n alt?: string\n /** Имя участника: даёт инициалы и доступное имя аватара. */\n name?: string\n shape?: GrAvatarShape\n /**\n * Красить фон по имени: одинаковое имя всегда даёт одинаковый цвет.\n *\n * Выключен по умолчанию — серый аватар это осознанный нейтральный дефолт, а\n * цветной может спорить с оформлением продукта. Палитру задаёт тема\n * (`--gr-avatar-N-bg`/`-fg`), компонент только выбирает слот.\n */\n autoColor?: boolean\n /** Статус участника. Точка декоративна — рядом идёт скрытая подпись. */\n status?: GrAvatarStatus\n}\n\nconst props = withDefaults(defineProps<GrAvatarProps>(), {\n size: undefined,\n src: undefined,\n fallbackSrc: undefined,\n alt: undefined,\n name: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n shape: undefined,\n // По той же причине, что `shape`: булев проп без этой строки Vue приводит к\n // `false` сам, и резолвер уже не отличит «не задано» от «выключено явно».\n autoColor: undefined,\n status: undefined,\n})\n\nconst slots = defineSlots<{\n /** Содержимое вместо картинки и инициалов. */\n default?: () => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst group = inject(GR_AVATAR_GROUP_KEY, null)\n\n// Размер и форма группы сильнее провайдера, но слабее собственного пропа.\nconst localSize = computed(() => props.size ?? group?.size)\n\nconst resolvedScaleSize = useGrComponentSize(\n () => (typeof localSize.value === 'number' ? undefined : localSize.value),\n { component: 'GrAvatar' },\n)\n\nconst resolvedShape = useGrComponentProp(\n 'GrAvatar',\n 'shape',\n () => props.shape ?? group?.shape,\n 'circle',\n)\n\nconst resolvedAutoColor = useGrComponentProp('GrAvatar', 'autoColor', () => props.autoColor, false)\n\nconst sizePx = computed(() => (\n typeof localSize.value === 'number' ? localSize.value : GR_AVATAR_SIZE_PX[resolvedScaleSize.value]\n))\n\nconst style = computed(() => {\n const px = `${sizePx.value}px`\n // Кегль на корне, а не на span инициалов: так он достаётся и слотовому тексту.\n return { width: px, height: px, fontSize: `${avatarFontSizePx(sizePx.value)}px` }\n})\n\n/**\n * Состояние картинки. Без него битая ссылка оставляла `v-if=\"src\"` истинным:\n * браузер рисовал сломанное изображение, а запасной вариант не показывался\n * никогда.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst primaryState = ref<ImageState>('loading')\nconst fallbackState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ошибку прошлой.\nwatch(() => props.src, () => {\n primaryState.value = 'loading'\n})\nwatch(() => props.fallbackSrc, () => {\n fallbackState.value = 'loading'\n})\n\nconst activeSrc = computed(() => {\n if (props.src && primaryState.value !== 'error')\n return props.src\n if (props.fallbackSrc && fallbackState.value !== 'error')\n return props.fallbackSrc\n return undefined\n})\n\nconst isFallbackActive = computed(() => Boolean(activeSrc.value) && activeSrc.value !== props.src)\n\n// Скелет держит место, пока картинка едет: иначе ряд аватаров мигает пустотой.\nconst showSkeleton = computed(() => {\n if (!activeSrc.value)\n return false\n return (isFallbackActive.value ? fallbackState.value : primaryState.value) === 'loading'\n})\n\nconst initials = computed(() => (props.name ? initialsFrom(props.name) : ''))\n\n// Пусто — когда нет ни картинки, ни слота, ни имени.\nconst showInitials = computed(() => !activeSrc.value && !slots.default && Boolean(initials.value))\n\nconst statusLabel = computed(() => (props.status\n ? t(`gr.avatar.status.${props.status}`, props.status)\n : undefined))\n\n/**\n * Доступное имя: `alt` сильнее всего, иначе имя участника.\n *\n * Когда есть картинка, имя несёт её `alt` — так привычнее потребителю и не\n * возникает двойного объявления. Без картинки роль и имя берёт на себя корень:\n * иначе аватар с инициалами остался бы для диктора пустым.\n */\nconst accessibleName = computed(() => props.alt ?? props.name ?? '')\n\nconst rootIsImage = computed(() => !activeSrc.value && accessibleName.value !== '')\n\n/**\n * Автоцвет виден только под инициалами: картинка закрывает фон целиком, а у\n * пустого аватара красить нечего.\n */\nconst toneClass = computed(() => (\n resolvedAutoColor.value && showInitials.value && props.name\n ? avatarToneClasses[avatarToneIndex(props.name)]\n : rootNeutralClass\n))\n\nconst rootClass = computed(() => [\n rootBaseClass,\n toneClass.value,\n grAvatarClass(resolvedShape.value),\n props.status ? rootStatusClass : '',\n])\n\nfunction onError(kind: 'primary' | 'fallback'): void {\n if (kind === 'primary')\n primaryState.value = 'error'\n else fallbackState.value = 'error'\n}\n\nfunction onLoad(kind: 'primary' | 'fallback'): void {\n if (kind === 'primary')\n primaryState.value = 'loaded'\n else fallbackState.value = 'loaded'\n}\n</script>\n\n<template>\n <span\n data-gr-avatar\n :style=\"style\"\n :class=\"rootClass\"\n :role=\"rootIsImage ? 'img' : undefined\"\n :aria-label=\"rootIsImage ? accessibleName : undefined\"\n >\n <span :class=\"[mediaClass, status ? [mediaClipClass, grAvatarClass(resolvedShape)] : '']\">\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-avatar-skeleton\n width=\"100%\"\n height=\"100%\"\n :rounded=\"resolvedShape === 'circle' ? 'var(--gr-radius-full)' : 'var(--gr-avatar-square-radius, 10px)'\"\n />\n\n <img\n v-if=\"activeSrc\"\n :key=\"activeSrc\"\n data-gr-avatar-image\n :src=\"activeSrc\"\n :alt=\"accessibleName\"\n class=\"h-full w-full object-cover\"\n :class=\"showSkeleton ? 'invisible absolute' : ''\"\n @error=\"onError(isFallbackActive ? 'fallback' : 'primary')\"\n @load=\"onLoad(isFallbackActive ? 'fallback' : 'primary')\"\n >\n\n <span v-else-if=\"showInitials\" data-gr-avatar-initials>{{ initials }}</span>\n\n <slot v-else />\n </span>\n\n <template v-if=\"status\">\n <span\n data-gr-avatar-status\n :class=\"[statusDotClass, statusToneClass[status]]\"\n aria-hidden=\"true\"\n />\n <!-- Цвет точки смысл не передаёт: статус объявляется словом (WCAG 1.4.1). -->\n <span data-gr-avatar-status-label class=\"sr-only\">{{ statusLabel }}</span>\n </template>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrAvatarGroup — ряд аватаров со стекингом и счётчиком скрытых.\n *\n * Группа объявлена `role=\"group\"` с именем и числом участников: без этого\n * скринридер читает набор безымянных картинок и не понимает, что их больше,\n * чем видно.\n */\nimport { computed, provide, type VNode } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { GR_AVATAR_GROUP_KEY } from './avatarContext'\nimport {\n avatarFontSizePx,\n grAvatarClass,\n groupBaseClass,\n groupItemClass,\n groupOverflowClass,\n rootBaseClass,\n type GrAvatarShape,\n} from './grAvatarStyles'\n\nconst GR_AVATAR_GROUP_SIZE_PX = {\n xs: 24,\n sm: 32,\n md: 40,\n lg: 56,\n} as const\n\nexport interface GrAvatarGroupProps {\n /** Размер и форма ряда: дети берут их из контекста. */\n size?: GrSizeWithPx\n shape?: GrAvatarShape\n /** Сколько аватаров показывать; остальные сворачиваются в «+N». */\n max?: number\n /** Сколько участников всего, если в слоте лежит лишь часть. */\n total?: number\n /** Имя группы. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrAvatarGroupProps>(), {\n size: undefined,\n shape: undefined,\n max: undefined,\n total: undefined,\n ariaLabel: undefined,\n})\n\nconst slots = defineSlots<{\n /** Аватары ряда. */\n default?: () => VNode[]\n}>()\n\nconst { t } = useGranularityTranslations()\n\nprovide(GR_AVATAR_GROUP_KEY, {\n get size() {\n return props.size\n },\n get shape() {\n return props.shape\n },\n})\n\nconst children = computed(() => slots.default?.() ?? [])\n\n// `v-for` внутри слота приходит фрагментом — считаем его детей, иначе весь\n// список выглядел бы одним аватаром.\nconst items = computed<VNode[]>(() => children.value.flatMap((node) => {\n const nested = node.children\n return Array.isArray(nested) && typeof node.type === 'symbol' ? (nested as VNode[]) : [node]\n}))\n\nconst visible = computed(() => (props.max === undefined ? items.value : items.value.slice(0, Math.max(0, props.max))))\n\nconst hiddenCount = computed(() => {\n const total = props.total ?? items.value.length\n return Math.max(0, total - visible.value.length)\n})\n\nconst overflowSizePx = computed(() => (\n typeof props.size === 'number' ? props.size : GR_AVATAR_GROUP_SIZE_PX[props.size ?? 'md']\n))\n\nconst overflowStyle = computed(() => {\n const px = `${overflowSizePx.value}px`\n return { width: px, height: px, fontSize: `${avatarFontSizePx(overflowSizePx.value)}px` }\n})\n\nconst groupLabel = computed(() => props.ariaLabel ?? t('gr.avatar.group', 'People'))\n\nconst accessibleLabel = computed(() => (hiddenCount.value > 0\n ? `${groupLabel.value}, ${t('gr.avatar.groupOverflow', 'and {count} more', { count: hiddenCount.value, n: hiddenCount.value })}`\n : groupLabel.value))\n</script>\n\n<template>\n <div\n data-gr-avatar-group\n role=\"group\"\n :aria-label=\"accessibleLabel\"\n :class=\"groupBaseClass\"\n >\n <component\n :is=\"node\"\n v-for=\"(node, index) in visible\"\n :key=\"index\"\n :class=\"groupItemClass\"\n />\n\n <span\n v-if=\"hiddenCount > 0\"\n data-gr-avatar-group-overflow\n aria-hidden=\"true\"\n :class=\"[rootBaseClass, groupOverflowClass, groupItemClass, grAvatarClass(shape ?? 'circle')]\"\n :style=\"overflowStyle\"\n >+{{ hiddenCount }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrAvatarGroup — ряд аватаров со стекингом и счётчиком скрытых.\n *\n * Группа объявлена `role=\"group\"` с именем и числом участников: без этого\n * скринридер читает набор безымянных картинок и не понимает, что их больше,\n * чем видно.\n */\nimport { computed, provide, type VNode } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { GR_AVATAR_GROUP_KEY } from './avatarContext'\nimport {\n avatarFontSizePx,\n grAvatarClass,\n groupBaseClass,\n groupItemClass,\n groupOverflowClass,\n rootBaseClass,\n type GrAvatarShape,\n} from './grAvatarStyles'\n\nconst GR_AVATAR_GROUP_SIZE_PX = {\n xs: 24,\n sm: 32,\n md: 40,\n lg: 56,\n} as const\n\nexport interface GrAvatarGroupProps {\n /** Размер и форма ряда: дети берут их из контекста. */\n size?: GrSizeWithPx\n shape?: GrAvatarShape\n /** Сколько аватаров показывать; остальные сворачиваются в «+N». */\n max?: number\n /** Сколько участников всего, если в слоте лежит лишь часть. */\n total?: number\n /** Имя группы. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrAvatarGroupProps>(), {\n size: undefined,\n shape: undefined,\n max: undefined,\n total: undefined,\n ariaLabel: undefined,\n})\n\nconst slots = defineSlots<{\n /** Аватары ряда. */\n default?: () => VNode[]\n}>()\n\nconst { t } = useGranularityTranslations()\n\nprovide(GR_AVATAR_GROUP_KEY, {\n get size() {\n return props.size\n },\n get shape() {\n return props.shape\n },\n})\n\nconst children = computed(() => slots.default?.() ?? [])\n\n// `v-for` внутри слота приходит фрагментом — считаем его детей, иначе весь\n// список выглядел бы одним аватаром.\nconst items = computed<VNode[]>(() => children.value.flatMap((node) => {\n const nested = node.children\n return Array.isArray(nested) && typeof node.type === 'symbol' ? (nested as VNode[]) : [node]\n}))\n\nconst visible = computed(() => (props.max === undefined ? items.value : items.value.slice(0, Math.max(0, props.max))))\n\nconst hiddenCount = computed(() => {\n const total = props.total ?? items.value.length\n return Math.max(0, total - visible.value.length)\n})\n\nconst overflowSizePx = computed(() => (\n typeof props.size === 'number' ? props.size : GR_AVATAR_GROUP_SIZE_PX[props.size ?? 'md']\n))\n\nconst overflowStyle = computed(() => {\n const px = `${overflowSizePx.value}px`\n return { width: px, height: px, fontSize: `${avatarFontSizePx(overflowSizePx.value)}px` }\n})\n\nconst groupLabel = computed(() => props.ariaLabel ?? t('gr.avatar.group', 'People'))\n\nconst accessibleLabel = computed(() => (hiddenCount.value > 0\n ? `${groupLabel.value}, ${t('gr.avatar.groupOverflow', 'and {count} more', { count: hiddenCount.value, n: hiddenCount.value })}`\n : groupLabel.value))\n</script>\n\n<template>\n <div\n data-gr-avatar-group\n role=\"group\"\n :aria-label=\"accessibleLabel\"\n :class=\"groupBaseClass\"\n >\n <component\n :is=\"node\"\n v-for=\"(node, index) in visible\"\n :key=\"index\"\n :class=\"groupItemClass\"\n />\n\n <span\n v-if=\"hiddenCount > 0\"\n data-gr-avatar-group-overflow\n aria-hidden=\"true\"\n :class=\"[rootBaseClass, groupOverflowClass, groupItemClass, grAvatarClass(shape ?? 'circle')]\"\n :style=\"overflowStyle\"\n >+{{ hiddenCount }}</span>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as GrAvatar_default, t as GrAvatarGroup_default } from "./chunks/GrAvatar-
|
|
2
|
-
import {
|
|
1
|
+
import { n as GrAvatar_default, t as GrAvatarGroup_default } from "./chunks/GrAvatar-Do5-C409.js";
|
|
2
|
+
import { n as grAvatarSafelist, t as grAvatarConfig, u as initialsFrom } from "../../chunks/config-CzeZg2LO.js";
|
|
3
3
|
export { GrAvatar_default as GrAvatar, GrAvatar_default as default, GrAvatarGroup_default as GrAvatarGroup, grAvatarConfig, grAvatarSafelist, initialsFrom };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Палитра автоцвета аватара — только ссылки на роли тонов.
|
|
3
|
+
*
|
|
4
|
+
* Слоты живут здесь, а не в глобальной теме, потому что нужны ровно одному
|
|
5
|
+
* компоненту: в глобальной за них платил бы и тот, кто `GrAvatar` не берёт.
|
|
6
|
+
*
|
|
7
|
+
* **Тёмного файла у палитры нет, и это не пропуск.** У `GrBadge` и `GrButton`
|
|
8
|
+
* он есть потому, что там переопределяются `--gr-{tone}` и `-solid`, которые в
|
|
9
|
+
* тёмной теме означают другое. Здесь слот ссылается на пару `-light`/`-text`,
|
|
10
|
+
* а она сама переключается вместе с темой — переопределять нечего, и второй
|
|
11
|
+
* файл только дублировал бы первый.
|
|
12
|
+
*
|
|
13
|
+
* Состав палитры — шесть тонов с полным набором ролей. `primary` не входит:
|
|
14
|
+
* у него нет `-light` (мягкая подложка бренда называется `--gr-accent`).
|
|
15
|
+
* Приложение вправе переназначить любой слот, не трогая компонент.
|
|
16
|
+
*/
|
|
17
|
+
:root {
|
|
18
|
+
--gr-avatar-1-bg: var(--gr-info-light);
|
|
19
|
+
--gr-avatar-1-fg: var(--gr-info-text);
|
|
20
|
+
|
|
21
|
+
--gr-avatar-2-bg: var(--gr-warning-light);
|
|
22
|
+
--gr-avatar-2-fg: var(--gr-warning-text);
|
|
23
|
+
|
|
24
|
+
--gr-avatar-3-bg: var(--gr-success-light);
|
|
25
|
+
--gr-avatar-3-fg: var(--gr-success-text);
|
|
26
|
+
|
|
27
|
+
--gr-avatar-4-bg: var(--gr-danger-light);
|
|
28
|
+
--gr-avatar-4-fg: var(--gr-danger-text);
|
|
29
|
+
|
|
30
|
+
--gr-avatar-5-bg: var(--gr-azure-light);
|
|
31
|
+
--gr-avatar-5-fg: var(--gr-azure-text);
|
|
32
|
+
|
|
33
|
+
--gr-avatar-6-bg: var(--gr-slate-light);
|
|
34
|
+
--gr-avatar-6-fg: var(--gr-slate-text);
|
|
35
|
+
}
|
package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js → GrColorPicker-BRM_4lyI.js}
RENAMED
|
@@ -2,7 +2,7 @@ import { a as useGrComponentSize } from "../../../chunks/context-CPMRfwVr.js";
|
|
|
2
2
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
3
3
|
import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js";
|
|
4
4
|
import { useGrFormControl } from "../../../composables/useGrFormControl.js";
|
|
5
|
-
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-
|
|
5
|
+
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-DV4gMWPR.js";
|
|
6
6
|
import { t as GrPopover_default } from "../../GrPopover/chunks/GrPopover-CcWvr1ZA.js";
|
|
7
7
|
import { t as GrSlider_default } from "../../GrSlider/chunks/GrSlider-CO8N9SF6.js";
|
|
8
8
|
import { a as grColorPickerPresetClass, c as previewClass, d as rowValueClass, f as swatchBaseClass, h as triggerValueClass, i as grColorPickerPanelClass, l as rowClass, m as triggerSwatchSizeBySize, o as grColorPickerTriggerClass, p as swatchFillClass, r as checkerClass, s as presetsGridClass, u as rowLabelClass } from "../../../chunks/config-DIDz_E5m.js";
|