@milaboratories/uikit 2.15.27 → 2.15.29
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/.turbo/turbo-build.log +13 -13
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/components/PlAutocomplete/PlAutocomplete.js.map +1 -1
- package/dist/components/PlAutocomplete/PlAutocomplete.vue.d.ts.map +1 -1
- package/dist/components/PlAutocomplete/PlAutocomplete.vue2.js +90 -87
- package/dist/components/PlAutocomplete/PlAutocomplete.vue2.js.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.js.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue2.js +76 -71
- package/dist/components/PlDropdown/PlDropdown.vue2.js.map +1 -1
- package/dist/components/PlDropdownLegacy/PlDropdownLegacy.js.map +1 -1
- package/dist/components/PlDropdownLegacy/PlDropdownLegacy.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownLegacy/PlDropdownLegacy.vue2.js +69 -62
- package/dist/components/PlDropdownLegacy/PlDropdownLegacy.vue2.js.map +1 -1
- package/package.json +5 -5
- package/src/components/PlAutocomplete/PlAutocomplete.vue +5 -1
- package/src/components/PlDropdown/PlDropdown.vue +10 -1
- package/src/components/PlDropdownLegacy/PlDropdownLegacy.vue +9 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAutocomplete.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAutocomplete/PlAutocomplete.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a big list of options using string search request\n */\nexport default {\n name: \"PlAutocomplete\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"M extends null | undefined | string | number = null | undefined | string\"\n>\nimport \"./pl-autocomplete.scss\";\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport { tap } from \"../../helpers/functions\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport type { ListOption, ListOptionNormalized } from \"../../types\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport DropdownListItem from \"../DropdownListItem.vue\";\nimport LongText from \"../LongText.vue\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlMaskIcon24 } from \"../PlMaskIcon24\";\nimport { DropdownOverlay } from \"../../utils/DropdownOverlay\";\nimport { refDebounced } from \"@vueuse/core\";\nimport { useWatchFetch } from \"../../composition/useWatchFetch.ts\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport type { ListOptionBase } from \"@platforma-sdk/model\";\nimport { PlSvg } from \"../PlSvg\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { isNil } from \"@milaboratories/helpers\";\n\n/**\n * The current selected value.\n */\nconst model = defineModel<M>({ required: true });\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Lambda for requesting of available options for the dropdown by search string.\n */\n optionsSearch: (string: string, type: \"value\" | \"label\") => Promise<ListOption<M>[]>;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: string;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: string;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Formatter for the selected value if its label is absent\n */\n formatValue?: (value: M) => string;\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n modelSearch: undefined,\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n formatValue: (v: M) => String(v),\n groupPosition: undefined,\n },\n);\n\nconst slots = defineSlots<{\n [key: string]: unknown;\n}>();\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst overlayRef = useTemplateRef(\"overlay\");\n\nconst search = ref<string | null>(null);\nconst data = reactive({\n activeIndex: -1,\n open: false,\n});\n\nconst findActiveIndex = () =>\n tap(\n renderedOptionsRef.value.findIndex((o) => deepEqual(o.value, model.value)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst loadedOptionsRef = ref<ListOption<M>[]>([]);\nconst modelOptionRef = ref<ListOptionNormalized<M> | undefined>(); // list of 1 option that is selected or empty, to keep selected label\n\nconst renderedOptionsRef = computed(() => {\n return normalizeListOptions(loadedOptionsRef.value).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })) as (ListOptionBase<M> & { index: number; isSelected: boolean; isActive: boolean })[];\n});\nconst isLoadingOptions = ref<boolean>(true);\nconst isLoadingError = ref<boolean>(false);\n\nconst isDisabled = computed(() => {\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return loadedOptionsRef.value.findIndex((o) => deepEqual(o.value, model.value));\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n if (isLoadingError.value) {\n return \"Data loading error\";\n }\n\n return undefined;\n});\n\nconst textValue = computed(() => {\n const modelOption = unref(modelOptionRef);\n const options = unref(renderedOptionsRef);\n\n const item: ListOptionNormalized | undefined =\n modelOption ??\n options.find((o) => deepEqual(o.value, model.value)) ??\n options.find((o) => deepEqual(o.value, model.value));\n\n return item?.label || (model.value ? props.formatValue(model.value) : \"\");\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && model.value) {\n return \"\";\n }\n\n return model.value ? String(textValue.value) : props.placeholder;\n});\n\nconst hasValue = computed(() => {\n return model.value !== undefined && model.value !== null;\n});\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: (ListOptionBase<M> & { index: number }) | undefined) => {\n model.value = v?.value as M;\n modelOptionRef.value = v;\n search.value = null;\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst clear = () => {\n model.value = undefined as M;\n modelOptionRef.value = undefined;\n};\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n};\n\nwatch(\n () => data.open,\n (v) => {\n search.value = v ? \"\" : null;\n },\n);\n\nconst onInputFocus = () => {\n data.open = true;\n};\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !overlayRef.value?.listRef?.contains(relatedTarget)\n ) {\n search.value = null;\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n search.value = \"\";\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n search.value = null;\n rootRef.value?.focus();\n }\n\n const options = unref(renderedOptionsRef);\n\n const { length } = options;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(options.find((it) => it.index === activeIndex));\n }\n\n const localIndex = options.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = renderedOptionsRef.value[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => model.value, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n search.value; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n overlayRef.value?.scrollIntoActive();\n }\n});\n\nconst searchDebounced = refDebounced(search, 300, { maxWait: 1000 });\n\nconst optionsRequest = useWatchFetch(\n () => searchDebounced.value,\n async (v) => (isNil(v) ? undefined : props.optionsSearch(v, \"label\")),\n);\n\nconst modelOptionRequest = useWatchFetch(\n () => model.value,\n async (v) => {\n if (isNil(v) || deepEqual(modelOptionRef.value?.value, v)) {\n return modelOptionRef.value;\n }\n\n const options = await props.optionsSearch(String(v), \"value\");\n return options.find((o) => String(o.value) === String(v));\n },\n);\n\nwatch(\n () => optionsRequest.value,\n (result) => {\n if (result) {\n loadedOptionsRef.value = result;\n if (search.value !== null) {\n isLoadingError.value = false;\n }\n }\n },\n);\n\nwatch(\n () => modelOptionRequest.value,\n (result) => {\n if (result) {\n modelOptionRef.value = normalizeListOptions([result])[0];\n }\n },\n);\n\nwatch(\n () => optionsRequest.error,\n (err) => {\n if (err) {\n isLoadingError.value = Boolean(err);\n }\n },\n);\n\nwatch(\n () => optionsRequest.loading || modelOptionRequest.loading,\n (loading) => {\n isLoadingOptions.value = loading;\n },\n);\n</script>\n\n<template>\n <div class=\"pl-autocomplete__envelope\" @click.stop=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-autocomplete\"\n :class=\"{ open: data.open, error: Boolean(computedError), disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-autocomplete__container\">\n <div class=\"pl-autocomplete__field\">\n <input\n ref=\"input\"\n v-model=\"search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText> {{ textValue }} </LongText>\n </div>\n\n <div class=\"pl-autocomplete__controls\">\n <PlMaskIcon24 v-if=\"isLoadingOptions\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-autocomplete__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <div\n v-if=\"arrowIconLarge\"\n class=\"arrow-icon\"\n :class=\"[`icon-24 ${arrowIconLarge}`]\"\n />\n <div v-else-if=\"arrowIcon\" class=\"arrow-icon\" :class=\"[`icon-16 ${arrowIcon}`]\" />\n <div v-else class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <DropdownOverlay\n v-if=\"data.open\"\n ref=\"overlay\"\n :root=\"rootRef\"\n class=\"pl-autocomplete__options\"\n tabindex=\"-1\"\n :gap=\"3\"\n >\n <DropdownListItem\n v-for=\"(item, index) in renderedOptionsRef\"\n :key=\"index\"\n :option=\"item\"\n :is-selected=\"item.isSelected\"\n :is-hovered=\"item.isActive\"\n :size=\"optionSize\"\n @click.stop=\"selectOption(item)\"\n />\n <div v-if=\"!renderedOptionsRef.length\" class=\"nothing-found\">Nothing found</div>\n </DropdownOverlay>\n <DoubleContour class=\"pl-autocomplete__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-autocomplete__error\">{{ computedError }}</div>\n <div v-else-if=\"isLoadingOptions && loadingOptionsHelper\" class=\"pl-autocomplete__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-autocomplete__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlAutocomplete.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAutocomplete/PlAutocomplete.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a big list of options using string search request\n */\nexport default {\n name: \"PlAutocomplete\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"M extends null | undefined | string | number = null | undefined | string\"\n>\nimport \"./pl-autocomplete.scss\";\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport { tap } from \"../../helpers/functions\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport type { ListOption, ListOptionNormalized } from \"../../types\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport DropdownListItem from \"../DropdownListItem.vue\";\nimport LongText from \"../LongText.vue\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlMaskIcon24 } from \"../PlMaskIcon24\";\nimport { DropdownOverlay } from \"../../utils/DropdownOverlay\";\nimport { refDebounced } from \"@vueuse/core\";\nimport { useWatchFetch } from \"../../composition/useWatchFetch.ts\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport type { ListOptionBase } from \"@platforma-sdk/model\";\nimport { PlSvg } from \"../PlSvg\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { isNil } from \"@milaboratories/helpers\";\n\n/**\n * The current selected value.\n */\nconst model = defineModel<M>({ required: true });\n\nconst props = withDefaults(\n defineProps<{\n /**\n * Lambda for requesting of available options for the dropdown by search string.\n */\n optionsSearch: (string: string, type: \"value\" | \"label\") => Promise<ListOption<M>[]>;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: string;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: string;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Formatter for the selected value if its label is absent\n */\n formatValue?: (value: M) => string;\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n modelSearch: undefined,\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n formatValue: (v: M) => String(v),\n groupPosition: undefined,\n },\n);\n\nconst slots = defineSlots<{\n [key: string]: unknown;\n}>();\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst overlayRef = useTemplateRef(\"overlay\");\n\nconst search = ref<string | null>(null);\nconst data = reactive({\n activeIndex: -1,\n open: false,\n});\n\nconst findActiveIndex = () =>\n tap(\n renderedOptionsRef.value.findIndex((o) => deepEqual(o.value, model.value)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst loadedOptionsRef = ref<ListOption<M>[]>([]);\nconst modelOptionRef = ref<ListOptionNormalized<M> | undefined>(); // list of 1 option that is selected or empty, to keep selected label\n\nconst renderedOptionsRef = computed(() => {\n return normalizeListOptions(loadedOptionsRef.value).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })) as (ListOptionBase<M> & { index: number; isSelected: boolean; isActive: boolean })[];\n});\nconst isLoadingOptions = ref<boolean>(true);\nconst isLoadingError = ref<boolean>(false);\n\nconst isDisabled = computed(() => {\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return loadedOptionsRef.value.findIndex((o) => deepEqual(o.value, model.value));\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n if (isLoadingError.value) {\n return \"Data loading error\";\n }\n\n return undefined;\n});\n\nconst textValue = computed(() => {\n const modelOption = unref(modelOptionRef);\n const options = unref(renderedOptionsRef);\n\n const item: ListOptionNormalized | undefined =\n modelOption ??\n options.find((o) => deepEqual(o.value, model.value)) ??\n options.find((o) => deepEqual(o.value, model.value));\n\n return item?.label || (model.value ? props.formatValue(model.value) : \"\");\n});\n\n// Native tooltip with the full selected value; the value layer itself has\n// `pointer-events: none`, so the title goes on the field wrapper.\nconst fieldTitle = computed(() => (!data.open && textValue.value ? textValue.value : undefined));\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && model.value) {\n return \"\";\n }\n\n return model.value ? String(textValue.value) : props.placeholder;\n});\n\nconst hasValue = computed(() => {\n return model.value !== undefined && model.value !== null;\n});\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: (ListOptionBase<M> & { index: number }) | undefined) => {\n model.value = v?.value as M;\n modelOptionRef.value = v;\n search.value = null;\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst clear = () => {\n model.value = undefined as M;\n modelOptionRef.value = undefined;\n};\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n};\n\nwatch(\n () => data.open,\n (v) => {\n search.value = v ? \"\" : null;\n },\n);\n\nconst onInputFocus = () => {\n data.open = true;\n};\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !overlayRef.value?.listRef?.contains(relatedTarget)\n ) {\n search.value = null;\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n search.value = \"\";\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n search.value = null;\n rootRef.value?.focus();\n }\n\n const options = unref(renderedOptionsRef);\n\n const { length } = options;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(options.find((it) => it.index === activeIndex));\n }\n\n const localIndex = options.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = renderedOptionsRef.value[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => model.value, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n search.value; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n overlayRef.value?.scrollIntoActive();\n }\n});\n\nconst searchDebounced = refDebounced(search, 300, { maxWait: 1000 });\n\nconst optionsRequest = useWatchFetch(\n () => searchDebounced.value,\n async (v) => (isNil(v) ? undefined : props.optionsSearch(v, \"label\")),\n);\n\nconst modelOptionRequest = useWatchFetch(\n () => model.value,\n async (v) => {\n if (isNil(v) || deepEqual(modelOptionRef.value?.value, v)) {\n return modelOptionRef.value;\n }\n\n const options = await props.optionsSearch(String(v), \"value\");\n return options.find((o) => String(o.value) === String(v));\n },\n);\n\nwatch(\n () => optionsRequest.value,\n (result) => {\n if (result) {\n loadedOptionsRef.value = result;\n if (search.value !== null) {\n isLoadingError.value = false;\n }\n }\n },\n);\n\nwatch(\n () => modelOptionRequest.value,\n (result) => {\n if (result) {\n modelOptionRef.value = normalizeListOptions([result])[0];\n }\n },\n);\n\nwatch(\n () => optionsRequest.error,\n (err) => {\n if (err) {\n isLoadingError.value = Boolean(err);\n }\n },\n);\n\nwatch(\n () => optionsRequest.loading || modelOptionRequest.loading,\n (loading) => {\n isLoadingOptions.value = loading;\n },\n);\n</script>\n\n<template>\n <div class=\"pl-autocomplete__envelope\" @click.stop=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-autocomplete\"\n :class=\"{ open: data.open, error: Boolean(computedError), disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-autocomplete__container\">\n <div class=\"pl-autocomplete__field\" :title=\"fieldTitle\">\n <input\n ref=\"input\"\n v-model=\"search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText> {{ textValue }} </LongText>\n </div>\n\n <div class=\"pl-autocomplete__controls\">\n <PlMaskIcon24 v-if=\"isLoadingOptions\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-autocomplete__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <div\n v-if=\"arrowIconLarge\"\n class=\"arrow-icon\"\n :class=\"[`icon-24 ${arrowIconLarge}`]\"\n />\n <div v-else-if=\"arrowIcon\" class=\"arrow-icon\" :class=\"[`icon-16 ${arrowIcon}`]\" />\n <div v-else class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <DropdownOverlay\n v-if=\"data.open\"\n ref=\"overlay\"\n :root=\"rootRef\"\n class=\"pl-autocomplete__options\"\n tabindex=\"-1\"\n :gap=\"3\"\n >\n <DropdownListItem\n v-for=\"(item, index) in renderedOptionsRef\"\n :key=\"index\"\n :option=\"item\"\n :is-selected=\"item.isSelected\"\n :is-hovered=\"item.isActive\"\n :size=\"optionSize\"\n @click.stop=\"selectOption(item)\"\n />\n <div v-if=\"!renderedOptionsRef.length\" class=\"nothing-found\">Nothing found</div>\n </DropdownOverlay>\n <DoubleContour class=\"pl-autocomplete__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-autocomplete__error\">{{ computedError }}</div>\n <div v-else-if=\"isLoadingOptions && loadingOptionsHelper\" class=\"pl-autocomplete__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-autocomplete__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdown.js","names":[],"sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { tap } from \"../../helpers/functions\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport type { ListOption, ListOptionNormalized, MaskIconName16, MaskIconName24 } from \"../../types\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport OptionList from \"./OptionList.vue\";\nimport \"./pl-dropdown.scss\";\nimport type { LOption } from \"./types\";\nimport { useGroupBy } from \"./useGroupBy\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Shows red border even without an error message\n */\n errorStatus?: boolean;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when `modelValue` has no matching option — e.g. its\n * upstream block was deleted. Rendered italic in error color.\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: MaskIconName16;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: MaskIconName24;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n showErrorMessage: true,\n placeholder: \"...\",\n missingValueLabel: \"Value not available\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n groupPosition: undefined,\n },\n);\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst optionListRef = useTemplateRef<InstanceType<typeof OptionList>>(\"optionListRef\");\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n optionsHeight: 0,\n});\n\nconst findActiveIndex = () =>\n tap(\n orderedRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\n/**\n * Loading spinner should not be shown when the dropdown is explicitly disabled,\n * even if options are undefined. The disabled state takes precedence.\n */\nconst showLoadingSpinner = computed(() => {\n return !props.disabled && isLoadingOptions.value;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst isMissing = computed(() => {\n return hasValue.value && selectedIndex.value === -1 && !isLoadingOptions.value;\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n // The field renders the missing state via `missingValueLabel`; no need to\n // duplicate it as a helper error.\n return undefined;\n});\n\nconst optionsRef = computed<LOption<M>[]>(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n return item?.label;\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && hasValue.value) {\n return \"\";\n }\n\n return textValue.value ? String(textValue.value) : props.placeholder;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst { orderedRef, groupsRef, restRef } = useGroupBy(filteredRef, \"group\");\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst selectOptionWrapper = (v: unknown) => {\n selectOption(v as M | undefined);\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n if (!data.open) {\n data.search = \"\";\n }\n};\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !optionListRef.value?.listRef?.contains(relatedTarget)\n ) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n rootRef.value?.focus();\n }\n\n const ordered = orderedRef.value;\n\n const { length } = ordered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(ordered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = ordered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = ordered[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n optionListRef.value?.scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"pl-dropdown__envelope\" @click=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-dropdown\"\n :class=\"{ open: data.open, error: error || errorStatus, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-dropdown__container\">\n <div class=\"pl-dropdown__field\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText v-if=\"isMissing\" class=\"input-value--missing\">\n {{ missingValueLabel }}\n </LongText>\n <LongText v-else-if=\"textValue !== undefined\">\n {{ textValue }}\n </LongText>\n </div>\n\n <div class=\"pl-dropdown__controls\">\n <PlIcon24 v-if=\"showLoadingSpinner\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-dropdown__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <PlIcon24 v-if=\"arrowIconLarge\" :name=\"arrowIconLarge\" class=\"arrow-icon\" />\n <PlIcon16 v-else-if=\"arrowIcon\" :name=\"arrowIcon\" class=\"arrow-icon\" />\n <PlIcon16 v-else name=\"chevron-down\" class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"$slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <OptionList\n v-if=\"data.open\"\n ref=\"optionListRef\"\n :root-ref=\"rootRef!\"\n :groups=\"groupsRef\"\n :rest=\"restRef\"\n :option-size=\"optionSize\"\n :select-option=\"selectOptionWrapper\"\n />\n <DoubleContour class=\"pl-dropdown__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"showLoadingSpinner && loadingOptionsHelper\" class=\"pl-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlDropdown.js","names":[],"sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { tap } from \"../../helpers/functions\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport type { ListOption, ListOptionNormalized, MaskIconName16, MaskIconName24 } from \"../../types\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport OptionList from \"./OptionList.vue\";\nimport \"./pl-dropdown.scss\";\nimport type { LOption } from \"./types\";\nimport { useGroupBy } from \"./useGroupBy\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Shows red border even without an error message\n */\n errorStatus?: boolean;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when `modelValue` has no matching option — e.g. its\n * upstream block was deleted. Rendered italic in error color.\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: MaskIconName16;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: MaskIconName24;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n showErrorMessage: true,\n placeholder: \"...\",\n missingValueLabel: \"Value not available\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n groupPosition: undefined,\n },\n);\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst optionListRef = useTemplateRef<InstanceType<typeof OptionList>>(\"optionListRef\");\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n optionsHeight: 0,\n});\n\nconst findActiveIndex = () =>\n tap(\n orderedRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\n/**\n * Loading spinner should not be shown when the dropdown is explicitly disabled,\n * even if options are undefined. The disabled state takes precedence.\n */\nconst showLoadingSpinner = computed(() => {\n return !props.disabled && isLoadingOptions.value;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst isMissing = computed(() => {\n return hasValue.value && selectedIndex.value === -1 && !isLoadingOptions.value;\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n // The field renders the missing state via `missingValueLabel`; no need to\n // duplicate it as a helper error.\n return undefined;\n});\n\nconst optionsRef = computed<LOption<M>[]>(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n return item?.label;\n});\n\n// Native tooltip with the full selected value. The `.input-value` layer has\n// `pointer-events: none`, so the title must sit on the field wrapper, which is\n// what actually receives the hover.\nconst fieldTitle = computed(() => {\n if (data.open) return undefined;\n if (isMissing.value) return props.missingValueLabel;\n return textValue.value !== undefined ? String(textValue.value) : undefined;\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && hasValue.value) {\n return \"\";\n }\n\n return textValue.value ? String(textValue.value) : props.placeholder;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst { orderedRef, groupsRef, restRef } = useGroupBy(filteredRef, \"group\");\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst selectOptionWrapper = (v: unknown) => {\n selectOption(v as M | undefined);\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n if (!data.open) {\n data.search = \"\";\n }\n};\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !optionListRef.value?.listRef?.contains(relatedTarget)\n ) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n rootRef.value?.focus();\n }\n\n const ordered = orderedRef.value;\n\n const { length } = ordered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(ordered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = ordered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = ordered[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n optionListRef.value?.scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"pl-dropdown__envelope\" @click=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-dropdown\"\n :class=\"{ open: data.open, error: error || errorStatus, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-dropdown__container\">\n <div class=\"pl-dropdown__field\" :title=\"fieldTitle\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText v-if=\"isMissing\" class=\"input-value--missing\">\n {{ missingValueLabel }}\n </LongText>\n <LongText v-else-if=\"textValue !== undefined\">\n {{ textValue }}\n </LongText>\n </div>\n\n <div class=\"pl-dropdown__controls\">\n <PlIcon24 v-if=\"showLoadingSpinner\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-dropdown__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <PlIcon24 v-if=\"arrowIconLarge\" :name=\"arrowIconLarge\" class=\"arrow-icon\" />\n <PlIcon16 v-else-if=\"arrowIcon\" :name=\"arrowIcon\" class=\"arrow-icon\" />\n <PlIcon16 v-else name=\"chevron-down\" class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"$slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <OptionList\n v-if=\"data.open\"\n ref=\"optionListRef\"\n :root-ref=\"rootRef!\"\n :groups=\"groupsRef\"\n :rest=\"restRef\"\n :option-size=\"optionSize\"\n :select-option=\"selectOptionWrapper\"\n />\n <DoubleContour class=\"pl-dropdown__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"showLoadingSpinner && loadingOptionsHelper\" class=\"pl-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdown.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlDropdown.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"names":[],"mappings":"AA2bA,OAAO,KAAK,EAAE,UAAU,EAAwB,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AASpG,OAAO,oBAAoB,CAAC;AAK5B;;GAEG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY,GAAK,CAAC,GAAG,OAAO,EACjC,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WA4pBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAlpBpD;;WAEG;oBACS,CAAC;QACb;;WAEG;gBACK,MAAM;QACd;;WAEG;kBACO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC;;WAEG;iBACM,MAAM;QACf;;WAEG;+BACoB,MAAM;QAC7B;;WAEG;gBACK,OAAO;QACf;;WAEG;sBACW,OAAO;QACrB;;WAEG;sBACW,MAAM;QACpB;;;WAGG;4BACiB,MAAM;QAC1B;;WAEG;oBACS,OAAO;QACnB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;oBACS,cAAc;QAC1B;;WAEG;yBACc,cAAc;QAC/B;;WAEG;qBACU,OAAO,GAAG,QAAQ;QAC/B;;aAEK;wBAED,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,aAAa,GACb,cAAc,GACd,QAAQ;;;KAykB2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;iBAbE,CAAC,KAAK,IAAiB,KAAK,GAAG;;kBAC9B,CAAC,KAAK,IAAiB,KAAK,GAAG;;cA7oBvC,mBAAmB,SAAS,CAAC,GAAG,SAAS,KAAG,IAAI;EA6pBlD,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
|
|
@@ -13,16 +13,16 @@ import ee from "../../utils/DoubleContour.js";
|
|
|
13
13
|
import { useLabelNotch as c } from "../../utils/useLabelNotch.js";
|
|
14
14
|
import { deepEqual as l } from "../../helpers/objects.js";
|
|
15
15
|
import "../PlIcon16/index.js";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import u from "../../assets/images/required.js";
|
|
17
|
+
import d from "./OptionList.js";
|
|
18
18
|
import './pl-dropdown.css';/* empty css */
|
|
19
|
-
import { useGroupBy as
|
|
20
|
-
import { computed as
|
|
19
|
+
import { useGroupBy as f } from "./useGroupBy.js";
|
|
20
|
+
import { computed as p, createBlock as m, createCommentVNode as h, createElementBlock as g, createElementVNode as _, createTextVNode as v, createVNode as y, defineComponent as b, normalizeClass as x, openBlock as S, reactive as te, ref as C, renderSlot as w, toDisplayString as T, unref as E, useTemplateRef as ne, vModelText as re, watch as D, watchPostEffect as O, withCtx as k, withDirectives as A, withModifiers as j } from "vue";
|
|
21
21
|
//#region src/components/PlDropdown/PlDropdown.vue?vue&type=script&setup=true&lang.ts
|
|
22
|
-
var
|
|
22
|
+
var ie = ["tabindex"], ae = { class: "pl-dropdown__container" }, oe = ["title"], se = ["disabled", "placeholder"], ce = {
|
|
23
23
|
key: 0,
|
|
24
24
|
class: "input-value"
|
|
25
|
-
},
|
|
25
|
+
}, le = { class: "pl-dropdown__controls" }, ue = { key: 0 }, de = {
|
|
26
26
|
key: 0,
|
|
27
27
|
class: "pl-dropdown__error"
|
|
28
28
|
}, M = {
|
|
@@ -31,7 +31,7 @@ var ae = ["tabindex"], oe = { class: "pl-dropdown__container" }, se = { class: "
|
|
|
31
31
|
}, N = {
|
|
32
32
|
key: 2,
|
|
33
33
|
class: "pl-dropdown__helper"
|
|
34
|
-
}, P = /* @__PURE__ */
|
|
34
|
+
}, P = /* @__PURE__ */ b({
|
|
35
35
|
name: "PlDropdown",
|
|
36
36
|
props: {
|
|
37
37
|
modelValue: {},
|
|
@@ -61,35 +61,37 @@ var ae = ["tabindex"], oe = { class: "pl-dropdown__container" }, se = { class: "
|
|
|
61
61
|
groupPosition: { default: void 0 }
|
|
62
62
|
},
|
|
63
63
|
emits: ["update:modelValue"],
|
|
64
|
-
setup(
|
|
65
|
-
let F = P, I =
|
|
64
|
+
setup(b, { emit: P }) {
|
|
65
|
+
let F = P, I = b, L = C(), R = C(), z = ne("optionListRef"), B = te({
|
|
66
66
|
search: "",
|
|
67
67
|
activeIndex: -1,
|
|
68
68
|
open: !1,
|
|
69
69
|
optionsHeight: 0
|
|
70
|
-
}), V = () => e(
|
|
70
|
+
}), V = () => e(Q.value.findIndex((e) => l(e.value, I.modelValue)), (e) => e < 0 ? 0 : e), H = () => B.activeIndex = V(), U = p(() => I.options === void 0), W = p(() => !I.disabled && U.value), G = p(() => U.value ? !0 : I.disabled), K = p(() => (I.options ?? []).findIndex((e) => l(e.value, I.modelValue))), q = p(() => I.modelValue !== void 0 && I.modelValue !== null), J = p(() => q.value && K.value === -1 && !U.value), Y = p(() => {
|
|
71
71
|
if (!U.value && I.error) return r(I.error);
|
|
72
|
-
}),
|
|
72
|
+
}), X = p(() => t(I.options ?? []).map((e, t) => ({
|
|
73
73
|
...e,
|
|
74
74
|
index: t,
|
|
75
75
|
isSelected: t === K.value,
|
|
76
76
|
isActive: t === B.activeIndex
|
|
77
|
-
}))),
|
|
78
|
-
|
|
77
|
+
}))), Z = p(() => E(X).find((e) => l(e.value, I.modelValue))?.label), fe = p(() => {
|
|
78
|
+
if (!B.open) return J.value ? I.missingValueLabel : Z.value === void 0 ? void 0 : String(Z.value);
|
|
79
|
+
}), pe = p(() => !B.open && q.value ? "" : Z.value ? String(Z.value) : I.placeholder), { orderedRef: Q, groupsRef: me, restRef: he } = f(p(() => {
|
|
80
|
+
let e = X.value;
|
|
79
81
|
return B.search ? e.filter((e) => {
|
|
80
82
|
let t = B.search.toLowerCase();
|
|
81
83
|
return e.label.toLowerCase().includes(t) || e.description && e.description.toLowerCase().includes(t) ? !0 : typeof e.value == "string" ? e.value.toLowerCase().includes(t) : e.value === B.search;
|
|
82
84
|
}) : e;
|
|
83
|
-
}), "group"), ge =
|
|
85
|
+
}), "group"), ge = p(() => G.value ? void 0 : "0"), $ = (e) => {
|
|
84
86
|
F("update:modelValue", e), B.search = "", B.open = !1, L?.value?.focus();
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
},
|
|
87
|
+
}, _e = (e) => {
|
|
88
|
+
$(e);
|
|
89
|
+
}, ve = () => F("update:modelValue", void 0), ye = () => R.value?.focus(), be = () => {
|
|
88
90
|
B.open = !B.open, B.open || (B.search = "");
|
|
89
|
-
},
|
|
91
|
+
}, xe = () => B.open = !0, Se = (e) => {
|
|
90
92
|
let t = e.relatedTarget;
|
|
91
93
|
!L.value?.contains(t) && !z.value?.listRef?.contains(t) && (B.search = "", B.open = !1);
|
|
92
|
-
},
|
|
94
|
+
}, Ce = (e) => {
|
|
93
95
|
if ([
|
|
94
96
|
"ArrowDown",
|
|
95
97
|
"ArrowUp",
|
|
@@ -103,31 +105,34 @@ var ae = ["tabindex"], oe = { class: "pl-dropdown__container" }, se = { class: "
|
|
|
103
105
|
return;
|
|
104
106
|
}
|
|
105
107
|
e.code === "Escape" && (B.open = !1, L.value?.focus());
|
|
106
|
-
let r =
|
|
108
|
+
let r = Q.value, { length: i } = r;
|
|
107
109
|
if (!i) return;
|
|
108
|
-
e.code === "Enter" &&
|
|
110
|
+
e.code === "Enter" && $(r.find((e) => e.index === n)?.value);
|
|
109
111
|
let a = r.findIndex((e) => e.index === n) ?? -1, o = e.code === "ArrowDown" ? 1 : e.code === "ArrowUp" ? -1 : 0;
|
|
110
112
|
B.activeIndex = r[Math.abs(a + o + i) % i].index ?? -1;
|
|
111
113
|
};
|
|
112
|
-
return c(L),
|
|
114
|
+
return c(L), D(() => I.modelValue, H, { immediate: !0 }), D(() => B.open, (e) => e ? R.value?.focus() : ""), O(() => {
|
|
113
115
|
B.search, B.activeIndex >= 0 && B.open && z.value?.scrollIntoActive();
|
|
114
|
-
}), (e, t) => (
|
|
116
|
+
}), (e, t) => (S(), g("div", {
|
|
115
117
|
class: "pl-dropdown__envelope",
|
|
116
|
-
onClick:
|
|
117
|
-
}, [
|
|
118
|
+
onClick: ye
|
|
119
|
+
}, [_("div", {
|
|
118
120
|
ref_key: "rootRef",
|
|
119
121
|
ref: L,
|
|
120
122
|
tabindex: ge.value,
|
|
121
|
-
class:
|
|
123
|
+
class: x(["pl-dropdown", {
|
|
122
124
|
open: B.open,
|
|
123
|
-
error:
|
|
125
|
+
error: b.error || b.errorStatus,
|
|
124
126
|
disabled: G.value
|
|
125
127
|
}]),
|
|
126
|
-
onKeydown:
|
|
127
|
-
onFocusout:
|
|
128
|
-
}, [
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
onKeydown: Ce,
|
|
129
|
+
onFocusout: Se
|
|
130
|
+
}, [_("div", ae, [
|
|
131
|
+
_("div", {
|
|
132
|
+
class: "pl-dropdown__field",
|
|
133
|
+
title: fe.value
|
|
134
|
+
}, [
|
|
135
|
+
A(_("input", {
|
|
131
136
|
ref_key: "input",
|
|
132
137
|
ref: R,
|
|
133
138
|
"onUpdate:modelValue": t[0] ||= (e) => B.search = e,
|
|
@@ -137,83 +142,83 @@ var ae = ["tabindex"], oe = { class: "pl-dropdown__container" }, se = { class: "
|
|
|
137
142
|
placeholder: pe.value,
|
|
138
143
|
spellcheck: "false",
|
|
139
144
|
autocomplete: "chrome-off",
|
|
140
|
-
onFocus:
|
|
141
|
-
}, null, 40,
|
|
142
|
-
B.open ?
|
|
145
|
+
onFocus: xe
|
|
146
|
+
}, null, 40, se), [[re, B.search]]),
|
|
147
|
+
B.open ? h("", !0) : (S(), g("div", ce, [J.value ? (S(), m(i, {
|
|
143
148
|
key: 0,
|
|
144
149
|
class: "input-value--missing"
|
|
145
150
|
}, {
|
|
146
|
-
default: k(() => [
|
|
151
|
+
default: k(() => [v(T(b.missingValueLabel), 1)]),
|
|
147
152
|
_: 1
|
|
148
|
-
})) :
|
|
149
|
-
default: k(() => [
|
|
153
|
+
})) : Z.value === void 0 ? h("", !0) : (S(), m(i, { key: 1 }, {
|
|
154
|
+
default: k(() => [v(T(Z.value), 1)]),
|
|
150
155
|
_: 1
|
|
151
156
|
}))])),
|
|
152
|
-
|
|
153
|
-
W.value ? (
|
|
157
|
+
_("div", le, [
|
|
158
|
+
W.value ? (S(), m(E(o), {
|
|
154
159
|
key: 0,
|
|
155
160
|
name: "loading"
|
|
156
|
-
})) :
|
|
157
|
-
|
|
161
|
+
})) : h("", !0),
|
|
162
|
+
b.clearable && q.value ? (S(), m(E(s), {
|
|
158
163
|
key: 1,
|
|
159
164
|
class: "clear",
|
|
160
165
|
name: "delete-clear",
|
|
161
|
-
onClick:
|
|
162
|
-
})) :
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
onClick: j(ve, ["stop"])
|
|
167
|
+
})) : h("", !0),
|
|
168
|
+
w(e.$slots, "append"),
|
|
169
|
+
_("div", {
|
|
165
170
|
class: "pl-dropdown__arrow-wrapper",
|
|
166
|
-
onClick:
|
|
167
|
-
}, [
|
|
171
|
+
onClick: j(be, ["stop"])
|
|
172
|
+
}, [b.arrowIconLarge ? (S(), m(E(o), {
|
|
168
173
|
key: 0,
|
|
169
|
-
name:
|
|
174
|
+
name: b.arrowIconLarge,
|
|
170
175
|
class: "arrow-icon"
|
|
171
|
-
}, null, 8, ["name"])) :
|
|
176
|
+
}, null, 8, ["name"])) : b.arrowIcon ? (S(), m(E(s), {
|
|
172
177
|
key: 1,
|
|
173
|
-
name:
|
|
178
|
+
name: b.arrowIcon,
|
|
174
179
|
class: "arrow-icon"
|
|
175
|
-
}, null, 8, ["name"])) : (
|
|
180
|
+
}, null, 8, ["name"])) : (S(), m(E(s), {
|
|
176
181
|
key: 2,
|
|
177
182
|
name: "chevron-down",
|
|
178
183
|
class: "arrow-icon arrow-icon-default"
|
|
179
184
|
}))])
|
|
180
185
|
])
|
|
181
|
-
]),
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
], 8, oe),
|
|
187
|
+
b.label ? (S(), g("label", ue, [
|
|
188
|
+
b.required ? (S(), m(E(a), {
|
|
184
189
|
key: 0,
|
|
185
|
-
uri:
|
|
186
|
-
}, null, 8, ["uri"])) :
|
|
187
|
-
|
|
188
|
-
e.$slots.tooltip ? (
|
|
190
|
+
uri: E(u)
|
|
191
|
+
}, null, 8, ["uri"])) : h("", !0),
|
|
192
|
+
_("span", null, T(b.label), 1),
|
|
193
|
+
e.$slots.tooltip ? (S(), m(E(n), {
|
|
189
194
|
key: 1,
|
|
190
195
|
class: "info",
|
|
191
196
|
position: "top"
|
|
192
197
|
}, {
|
|
193
|
-
tooltip: k(() => [
|
|
198
|
+
tooltip: k(() => [w(e.$slots, "tooltip")]),
|
|
194
199
|
_: 3
|
|
195
|
-
})) :
|
|
196
|
-
])) :
|
|
197
|
-
B.open ? (
|
|
200
|
+
})) : h("", !0)
|
|
201
|
+
])) : h("", !0),
|
|
202
|
+
B.open ? (S(), m(d, {
|
|
198
203
|
key: 1,
|
|
199
204
|
ref_key: "optionListRef",
|
|
200
205
|
ref: z,
|
|
201
206
|
"root-ref": L.value,
|
|
202
|
-
groups:
|
|
203
|
-
rest:
|
|
204
|
-
"option-size":
|
|
205
|
-
"select-option":
|
|
207
|
+
groups: E(me),
|
|
208
|
+
rest: E(he),
|
|
209
|
+
"option-size": b.optionSize,
|
|
210
|
+
"select-option": _e
|
|
206
211
|
}, null, 8, [
|
|
207
212
|
"root-ref",
|
|
208
213
|
"groups",
|
|
209
214
|
"rest",
|
|
210
215
|
"option-size"
|
|
211
|
-
])) :
|
|
212
|
-
|
|
216
|
+
])) : h("", !0),
|
|
217
|
+
y(ee, {
|
|
213
218
|
class: "pl-dropdown__contour",
|
|
214
|
-
"group-position":
|
|
219
|
+
"group-position": b.groupPosition
|
|
215
220
|
}, null, 8, ["group-position"])
|
|
216
|
-
])], 42,
|
|
221
|
+
])], 42, ie), Y.value ? (S(), g("div", de, T(Y.value), 1)) : W.value && b.loadingOptionsHelper ? (S(), g("div", M, T(b.loadingOptionsHelper), 1)) : b.helper ? (S(), g("div", N, T(b.helper), 1)) : h("", !0)]));
|
|
217
222
|
}
|
|
218
223
|
});
|
|
219
224
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdown.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { tap } from \"../../helpers/functions\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport type { ListOption, ListOptionNormalized, MaskIconName16, MaskIconName24 } from \"../../types\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport OptionList from \"./OptionList.vue\";\nimport \"./pl-dropdown.scss\";\nimport type { LOption } from \"./types\";\nimport { useGroupBy } from \"./useGroupBy\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Shows red border even without an error message\n */\n errorStatus?: boolean;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when `modelValue` has no matching option — e.g. its\n * upstream block was deleted. Rendered italic in error color.\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: MaskIconName16;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: MaskIconName24;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n showErrorMessage: true,\n placeholder: \"...\",\n missingValueLabel: \"Value not available\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n groupPosition: undefined,\n },\n);\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst optionListRef = useTemplateRef<InstanceType<typeof OptionList>>(\"optionListRef\");\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n optionsHeight: 0,\n});\n\nconst findActiveIndex = () =>\n tap(\n orderedRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\n/**\n * Loading spinner should not be shown when the dropdown is explicitly disabled,\n * even if options are undefined. The disabled state takes precedence.\n */\nconst showLoadingSpinner = computed(() => {\n return !props.disabled && isLoadingOptions.value;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst isMissing = computed(() => {\n return hasValue.value && selectedIndex.value === -1 && !isLoadingOptions.value;\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n // The field renders the missing state via `missingValueLabel`; no need to\n // duplicate it as a helper error.\n return undefined;\n});\n\nconst optionsRef = computed<LOption<M>[]>(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n return item?.label;\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && hasValue.value) {\n return \"\";\n }\n\n return textValue.value ? String(textValue.value) : props.placeholder;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst { orderedRef, groupsRef, restRef } = useGroupBy(filteredRef, \"group\");\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst selectOptionWrapper = (v: unknown) => {\n selectOption(v as M | undefined);\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n if (!data.open) {\n data.search = \"\";\n }\n};\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !optionListRef.value?.listRef?.contains(relatedTarget)\n ) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n rootRef.value?.focus();\n }\n\n const ordered = orderedRef.value;\n\n const { length } = ordered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(ordered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = ordered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = ordered[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n optionListRef.value?.scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"pl-dropdown__envelope\" @click=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-dropdown\"\n :class=\"{ open: data.open, error: error || errorStatus, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-dropdown__container\">\n <div class=\"pl-dropdown__field\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText v-if=\"isMissing\" class=\"input-value--missing\">\n {{ missingValueLabel }}\n </LongText>\n <LongText v-else-if=\"textValue !== undefined\">\n {{ textValue }}\n </LongText>\n </div>\n\n <div class=\"pl-dropdown__controls\">\n <PlIcon24 v-if=\"showLoadingSpinner\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-dropdown__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <PlIcon24 v-if=\"arrowIconLarge\" :name=\"arrowIconLarge\" class=\"arrow-icon\" />\n <PlIcon16 v-else-if=\"arrowIcon\" :name=\"arrowIcon\" class=\"arrow-icon\" />\n <PlIcon16 v-else name=\"chevron-down\" class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"$slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <OptionList\n v-if=\"data.open\"\n ref=\"optionListRef\"\n :root-ref=\"rootRef!\"\n :groups=\"groupsRef\"\n :rest=\"restRef\"\n :option-size=\"optionSize\"\n :select-option=\"selectOptionWrapper\"\n />\n <DoubleContour class=\"pl-dropdown__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"showLoadingSpinner && loadingOptionsHelper\" class=\"pl-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlDropdown.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/PlDropdown/PlDropdown.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { tap } from \"../../helpers/functions\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport type { ListOption, ListOptionNormalized, MaskIconName16, MaskIconName24 } from \"../../types\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport OptionList from \"./OptionList.vue\";\nimport \"./pl-dropdown.scss\";\nimport type { LOption } from \"./types\";\nimport { useGroupBy } from \"./useGroupBy\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Shows red border even without an error message\n */\n errorStatus?: boolean;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when `modelValue` has no matching option — e.g. its\n * upstream block was deleted. Rendered italic in error color.\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: MaskIconName16;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: MaskIconName24;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n /**\n * Makes some of corners not rounded\n * */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n showErrorMessage: true,\n placeholder: \"...\",\n missingValueLabel: \"Value not available\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n groupPosition: undefined,\n },\n);\n\nconst rootRef = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst optionListRef = useTemplateRef<InstanceType<typeof OptionList>>(\"optionListRef\");\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n optionsHeight: 0,\n});\n\nconst findActiveIndex = () =>\n tap(\n orderedRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\n/**\n * Loading spinner should not be shown when the dropdown is explicitly disabled,\n * even if options are undefined. The disabled state takes precedence.\n */\nconst showLoadingSpinner = computed(() => {\n return !props.disabled && isLoadingOptions.value;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst isMissing = computed(() => {\n return hasValue.value && selectedIndex.value === -1 && !isLoadingOptions.value;\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n // The field renders the missing state via `missingValueLabel`; no need to\n // duplicate it as a helper error.\n return undefined;\n});\n\nconst optionsRef = computed<LOption<M>[]>(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n return item?.label;\n});\n\n// Native tooltip with the full selected value. The `.input-value` layer has\n// `pointer-events: none`, so the title must sit on the field wrapper, which is\n// what actually receives the hover.\nconst fieldTitle = computed(() => {\n if (data.open) return undefined;\n if (isMissing.value) return props.missingValueLabel;\n return textValue.value !== undefined ? String(textValue.value) : undefined;\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && hasValue.value) {\n return \"\";\n }\n\n return textValue.value ? String(textValue.value) : props.placeholder;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst { orderedRef, groupsRef, restRef } = useGroupBy(filteredRef, \"group\");\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n rootRef?.value?.focus();\n};\n\nconst selectOptionWrapper = (v: unknown) => {\n selectOption(v as M | undefined);\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => {\n data.open = !data.open;\n if (!data.open) {\n data.search = \"\";\n }\n};\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n const relatedTarget = event.relatedTarget as Node | null;\n\n if (\n !rootRef.value?.contains(relatedTarget) &&\n !optionListRef.value?.listRef?.contains(relatedTarget)\n ) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n rootRef.value?.focus();\n }\n\n const ordered = orderedRef.value;\n\n const { length } = ordered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(ordered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = ordered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = ordered[newIndex].index ?? -1;\n};\n\nuseLabelNotch(rootRef);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n optionListRef.value?.scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"pl-dropdown__envelope\" @click=\"setFocusOnInput\">\n <div\n ref=\"rootRef\"\n :tabindex=\"tabindex\"\n class=\"pl-dropdown\"\n :class=\"{ open: data.open, error: error || errorStatus, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"pl-dropdown__container\">\n <div class=\"pl-dropdown__field\" :title=\"fieldTitle\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" class=\"input-value\">\n <LongText v-if=\"isMissing\" class=\"input-value--missing\">\n {{ missingValueLabel }}\n </LongText>\n <LongText v-else-if=\"textValue !== undefined\">\n {{ textValue }}\n </LongText>\n </div>\n\n <div class=\"pl-dropdown__controls\">\n <PlIcon24 v-if=\"showLoadingSpinner\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div class=\"pl-dropdown__arrow-wrapper\" @click.stop=\"toggleOpen\">\n <PlIcon24 v-if=\"arrowIconLarge\" :name=\"arrowIconLarge\" class=\"arrow-icon\" />\n <PlIcon16 v-else-if=\"arrowIcon\" :name=\"arrowIcon\" class=\"arrow-icon\" />\n <PlIcon16 v-else name=\"chevron-down\" class=\"arrow-icon arrow-icon-default\" />\n </div>\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"$slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <OptionList\n v-if=\"data.open\"\n ref=\"optionListRef\"\n :root-ref=\"rootRef!\"\n :groups=\"groupsRef\"\n :rest=\"restRef\"\n :option-size=\"optionSize\"\n :select-option=\"selectOptionWrapper\"\n />\n <DoubleContour class=\"pl-dropdown__contour\" :group-position=\"groupPosition\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"pl-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"showLoadingSpinner && loadingOptionsHelper\" class=\"pl-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownLegacy.js","names":[],"sources":["../../../src/components/PlDropdownLegacy/PlDropdownLegacy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport \"./pl-dropdown-legacy.scss\";\nimport { computed, reactive, ref, unref, useSlots, watch, watchPostEffect } from \"vue\";\nimport { tap, tapIf } from \"../../helpers/functions\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport type { ListOption, ListOptionNormalized } from \"../../types\";\nimport { scrollIntoView } from \"../../helpers/dom\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport DropdownListItem from \"../DropdownListItem.vue\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlMaskIcon24 } from \"../PlMaskIcon24\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { PlSvg } from \"../PlSvg\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: string;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: string;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst slots = useSlots();\n\nconst root = ref<HTMLElement | undefined>();\nconst list = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n});\n\nconst findActiveIndex = () =>\n tap(\n filteredRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n if (props.modelValue !== undefined && selectedIndex.value === -1) {\n return \"The selected value is not one of the options\";\n }\n\n return undefined;\n});\n\nconst optionsRef = computed(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n\n return item?.label || props.modelValue; // @todo show inner value?\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && props.modelValue) {\n return \"\";\n }\n\n return props.modelValue ? String(textValue.value) : props.placeholder;\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n root?.value?.focus();\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => (data.open = !data.open);\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n if (!root?.value?.contains(event.relatedTarget as Node | null)) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst scrollIntoActive = () => {\n const $list = list.value;\n\n if (!$list) {\n return;\n }\n\n tapIf($list.querySelector(\".hovered-item\") as HTMLElement, (opt) => {\n scrollIntoView($list, opt);\n });\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n root.value?.focus();\n }\n\n const filtered = unref(filteredRef);\n\n const { length } = filtered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(filtered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = filtered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = filteredRef.value[newIndex].index ?? -1;\n};\n\nuseLabelNotch(root);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"ui-dropdown__envelope\">\n <div\n ref=\"root\"\n :tabindex=\"tabindex\"\n class=\"ui-dropdown\"\n :class=\"{ open: data.open, error, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"ui-dropdown__container\">\n <div class=\"ui-dropdown__field\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" @click=\"setFocusOnInput\">\n <LongText class=\"input-value\"> {{ textValue }} </LongText>\n </div>\n\n <div class=\"ui-dropdown__controls\">\n <PlMaskIcon24 v-if=\"isLoadingOptions\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div\n v-if=\"arrowIconLarge\"\n class=\"arrow-icon\"\n :class=\"[`icon-24 ${arrowIconLarge}`]\"\n @click.stop=\"toggleOpen\"\n />\n <div\n v-else-if=\"arrowIcon\"\n class=\"arrow-icon\"\n :class=\"[`icon-16 ${arrowIcon}`]\"\n @click.stop=\"toggleOpen\"\n />\n <div v-else class=\"arrow-icon arrow-icon-default\" @click.stop=\"toggleOpen\" />\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <div v-if=\"data.open\" ref=\"list\" class=\"ui-dropdown__options\">\n <DropdownListItem\n v-for=\"(item, index) in filteredRef\"\n :key=\"index\"\n :option=\"item\"\n :is-selected=\"item.isSelected\"\n :is-hovered=\"item.isActive\"\n :size=\"optionSize\"\n @click.stop=\"selectOption(item.value)\"\n />\n <div v-if=\"!filteredRef.length\" class=\"nothing-found\">Nothing found</div>\n </div>\n <DoubleContour class=\"ui-dropdown__contour\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"ui-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"isLoadingOptions && loadingOptionsHelper\" class=\"ui-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"ui-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlDropdownLegacy.js","names":[],"sources":["../../../src/components/PlDropdownLegacy/PlDropdownLegacy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdown\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = unknown\">\nimport \"./pl-dropdown-legacy.scss\";\nimport { computed, reactive, ref, unref, useSlots, watch, watchPostEffect } from \"vue\";\nimport { tap, tapIf } from \"../../helpers/functions\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport type { ListOption, ListOptionNormalized } from \"../../types\";\nimport { scrollIntoView } from \"../../helpers/dom\";\nimport { deepEqual } from \"../../helpers/objects\";\nimport DropdownListItem from \"../DropdownListItem.vue\";\nimport LongText from \"../LongText.vue\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlMaskIcon24 } from \"../PlMaskIcon24\";\nimport { normalizeListOptions } from \"../../helpers/utils\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport { PlSvg } from \"../PlSvg\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\n\nconst emit = defineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected value of the dropdown.\n */\n modelValue: M;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Custom icon (16px) class for the dropdown arrow (optional)\n */\n arrowIcon?: string;\n /**\n * Custom icon (24px) class for the dropdown arrow (optional)\n */\n arrowIconLarge?: string;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n arrowIconLarge: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst slots = useSlots();\n\nconst root = ref<HTMLElement | undefined>();\nconst list = ref<HTMLElement | undefined>();\nconst input = ref<HTMLInputElement | undefined>();\n\nconst data = reactive({\n search: \"\",\n activeIndex: -1,\n open: false,\n});\n\nconst findActiveIndex = () =>\n tap(\n filteredRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),\n (v) => (v < 0 ? 0 : v),\n );\n\nconst updateActive = () => (data.activeIndex = findActiveIndex());\n\nconst isLoadingOptions = computed(() => {\n return props.options === undefined;\n});\n\nconst isDisabled = computed(() => {\n if (isLoadingOptions.value) {\n return true;\n }\n\n return props.disabled;\n});\n\nconst selectedIndex = computed(() => {\n return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));\n});\n\nconst computedError = computed(() => {\n if (isLoadingOptions.value) {\n return undefined;\n }\n\n if (props.error) {\n return getErrorMessage(props.error);\n }\n\n if (props.modelValue !== undefined && selectedIndex.value === -1) {\n return \"The selected value is not one of the options\";\n }\n\n return undefined;\n});\n\nconst optionsRef = computed(() =>\n normalizeListOptions(props.options ?? []).map((opt, index) => ({\n ...opt,\n index,\n isSelected: index === selectedIndex.value,\n isActive: index === data.activeIndex,\n })),\n);\n\nconst textValue = computed(() => {\n const options = unref(optionsRef);\n\n const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));\n\n return item?.label || props.modelValue; // @todo show inner value?\n});\n\n// Native tooltip with the full selected value; the value layer itself has\n// `pointer-events: none`, so the title goes on the field wrapper.\nconst fieldTitle = computed(() => {\n if (data.open) return undefined;\n const v = textValue.value;\n return v !== undefined && v !== null && v !== \"\" ? String(v) : undefined;\n});\n\nconst computedPlaceholder = computed(() => {\n if (!data.open && props.modelValue) {\n return \"\";\n }\n\n return props.modelValue ? String(textValue.value) : props.placeholder;\n});\n\nconst hasValue = computed(() => {\n return props.modelValue !== undefined && props.modelValue !== null;\n});\n\nconst filteredRef = computed(() => {\n const options = optionsRef.value;\n\n if (data.search) {\n return options.filter((o: ListOptionNormalized) => {\n const search = data.search.toLowerCase();\n\n if (o.label.toLowerCase().includes(search)) {\n return true;\n }\n\n if (o.description && o.description.toLowerCase().includes(search)) {\n return true;\n }\n\n if (typeof o.value === \"string\") {\n return o.value.toLowerCase().includes(search);\n }\n\n return o.value === data.search;\n });\n }\n\n return options;\n});\n\nconst tabindex = computed(() => (isDisabled.value ? undefined : \"0\"));\n\nconst selectOption = (v: M | undefined) => {\n emit(\"update:modelValue\", v);\n data.search = \"\";\n data.open = false;\n root?.value?.focus();\n};\n\nconst clear = () => emit(\"update:modelValue\", undefined);\n\nconst setFocusOnInput = () => input.value?.focus();\n\nconst toggleOpen = () => (data.open = !data.open);\n\nconst onInputFocus = () => (data.open = true);\n\nconst onFocusOut = (event: FocusEvent) => {\n if (!root?.value?.contains(event.relatedTarget as Node | null)) {\n data.search = \"\";\n data.open = false;\n }\n};\n\nconst scrollIntoActive = () => {\n const $list = list.value;\n\n if (!$list) {\n return;\n }\n\n tapIf($list.querySelector(\".hovered-item\") as HTMLElement, (opt) => {\n scrollIntoView($list, opt);\n });\n};\n\nconst handleKeydown = (e: { code: string; preventDefault(): void }) => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\"].includes(e.code)) {\n return;\n } else {\n e.preventDefault();\n }\n\n const { open, activeIndex } = data;\n\n if (!open) {\n if (e.code === \"Enter\") {\n data.open = true;\n }\n return;\n }\n\n if (e.code === \"Escape\") {\n data.open = false;\n root.value?.focus();\n }\n\n const filtered = unref(filteredRef);\n\n const { length } = filtered;\n\n if (!length) {\n return;\n }\n\n if (e.code === \"Enter\") {\n selectOption(filtered.find((it) => it.index === activeIndex)?.value);\n }\n\n const localIndex = filtered.findIndex((it) => it.index === activeIndex) ?? -1;\n\n const delta = e.code === \"ArrowDown\" ? 1 : e.code === \"ArrowUp\" ? -1 : 0;\n\n const newIndex = Math.abs(localIndex + delta + length) % length;\n\n data.activeIndex = filteredRef.value[newIndex].index ?? -1;\n};\n\nuseLabelNotch(root);\n\nwatch(() => props.modelValue, updateActive, { immediate: true });\n\nwatch(\n () => data.open,\n (open) => (open ? input.value?.focus() : \"\"),\n);\n\nwatchPostEffect(() => {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n data.search; // to watch\n\n if (data.activeIndex >= 0 && data.open) {\n scrollIntoActive();\n }\n});\n</script>\n\n<template>\n <div class=\"ui-dropdown__envelope\">\n <div\n ref=\"root\"\n :tabindex=\"tabindex\"\n class=\"ui-dropdown\"\n :class=\"{ open: data.open, error, disabled: isDisabled }\"\n @keydown=\"handleKeydown\"\n @focusout=\"onFocusOut\"\n >\n <div class=\"ui-dropdown__container\">\n <div class=\"ui-dropdown__field\" :title=\"fieldTitle\">\n <input\n ref=\"input\"\n v-model=\"data.search\"\n type=\"text\"\n tabindex=\"-1\"\n :disabled=\"isDisabled\"\n :placeholder=\"computedPlaceholder\"\n spellcheck=\"false\"\n autocomplete=\"chrome-off\"\n @focus=\"onInputFocus\"\n />\n\n <div v-if=\"!data.open\" @click=\"setFocusOnInput\">\n <LongText class=\"input-value\"> {{ textValue }} </LongText>\n </div>\n\n <div class=\"ui-dropdown__controls\">\n <PlMaskIcon24 v-if=\"isLoadingOptions\" name=\"loading\" />\n <PlIcon16\n v-if=\"clearable && hasValue\"\n class=\"clear\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <slot name=\"append\" />\n <div\n v-if=\"arrowIconLarge\"\n class=\"arrow-icon\"\n :class=\"[`icon-24 ${arrowIconLarge}`]\"\n @click.stop=\"toggleOpen\"\n />\n <div\n v-else-if=\"arrowIcon\"\n class=\"arrow-icon\"\n :class=\"[`icon-16 ${arrowIcon}`]\"\n @click.stop=\"toggleOpen\"\n />\n <div v-else class=\"arrow-icon arrow-icon-default\" @click.stop=\"toggleOpen\" />\n </div>\n </div>\n <label v-if=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <div v-if=\"data.open\" ref=\"list\" class=\"ui-dropdown__options\">\n <DropdownListItem\n v-for=\"(item, index) in filteredRef\"\n :key=\"index\"\n :option=\"item\"\n :is-selected=\"item.isSelected\"\n :is-hovered=\"item.isActive\"\n :size=\"optionSize\"\n @click.stop=\"selectOption(item.value)\"\n />\n <div v-if=\"!filteredRef.length\" class=\"nothing-found\">Nothing found</div>\n </div>\n <DoubleContour class=\"ui-dropdown__contour\" />\n </div>\n </div>\n <div v-if=\"computedError\" class=\"ui-dropdown__error\">{{ computedError }}</div>\n <div v-else-if=\"isLoadingOptions && loadingOptionsHelper\" class=\"ui-dropdown__helper\">\n {{ loadingOptionsHelper }}\n </div>\n <div v-else-if=\"helper\" class=\"ui-dropdown__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownLegacy.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownLegacy/PlDropdownLegacy.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlDropdownLegacy.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownLegacy/PlDropdownLegacy.vue"],"names":[],"mappings":"AAoZA,OAAO,2BAA2B,CAAC;AAMnC,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAC;AAapE;;GAEG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY,GAAK,CAAC,GAAG,OAAO,EACjC,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WA4lBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAllBpD;;WAEG;oBACS,CAAC;QACb;;WAEG;gBACK,MAAM;QACd;;WAEG;kBACO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC;;WAEG;iBACM,MAAM;QACf;;WAEG;+BACoB,MAAM;QAC7B;;WAEG;gBACK,OAAO;QACf;;WAEG;sBACW,MAAM;QACpB;;WAEG;oBACS,OAAO;QACnB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;oBACS,MAAM;QAClB;;WAEG;yBACc,MAAM;QACvB;;WAEG;qBACU,OAAO,GAAG,QAAQ;;;KA+hBwD,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;iBAbE,CAAC,KAAK,IAAiB,KAAK,GAAG;;kBAC9B,CAAC,KAAK,IAAiB,KAAK,GAAG;;cA7kBvC,mBAAmB,SAAS,CAAC,GAAG,SAAS,KAAG,IAAI;EA6lBlD,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
|