UNPKG

@nextcloud/vue

Version:
536 lines (535 loc) 15.9 kB
import '../assets/NcSelect-ZnE_MlqV.css'; import { autoUpdate, computePosition, offset, flip, shift, limitShift } from "@floating-ui/dom"; import { h, resolveComponent, createBlock, openBlock, mergeProps, createSlots, withCtx, createTextVNode, toDisplayString, createCommentVNode, renderSlot, createVNode, normalizeProps, guardReactiveProps, createElementVNode, toHandlers, renderList, warn } from "vue"; import VueSelect from "vue-select"; import { C as ChevronDown } from "./ChevronDown-FiGpp0KT.mjs"; import { I as IconClose } from "./Close-D6ngJ4t9.mjs"; import { N as NcEllipsisedOption } from "./NcEllipsisedOption-dT-CtXYp.mjs"; import { N as NcLoadingIcon } from "./NcLoadingIcon-b_ajZ_nQ.mjs"; import { r as register, d as t17, a as t } from "./_l10n-DrTiip5c.mjs"; import { c as createElementId } from "./createElementId-DhjFt1I9.mjs"; import { i as isLegacy } from "./legacy-DcjXBL_t.mjs"; import "vue-select/dist/vue-select.css"; import { _ as _export_sfc } from "./_plugin-vue_export-helper-1tPrXgE0.mjs"; register(t17); const _sfc_main = { name: "NcSelect", components: { ChevronDown, NcEllipsisedOption, NcLoadingIcon, VueSelect }, props: { // Add VueSelect props to $props ...VueSelect.props, ...VueSelect.mixins.reduce((allProps, mixin) => ({ ...allProps, ...mixin.props }), {}), /** * `aria-label` for the clear input button */ ariaLabelClearSelected: { type: String, default: t("Clear selected") }, /** * `aria-label` for the search input * * A descriptive `inputLabel` is preferred as this is not visible. */ ariaLabelCombobox: { type: String, default: null }, /** * `aria-label` for the listbox element */ ariaLabelListbox: { type: String, default: t("Options") }, /** * Allows to customize the `aria-label` for the deselect-option button * The default is "Deselect " + optionLabel * * @type {(optionLabel: string) => string} */ ariaLabelDeselectOption: { type: Function, default: (optionLabel) => t("Deselect {option}", { option: optionLabel }) }, /** * Append the dropdown element to the end of the body * and size/position it dynamically. * * @see https://vue-select.org/api/props.html#appendtobody */ appendToBody: { type: Boolean, default: true }, /** * When `appendToBody` is true, this function is responsible for * positioning the drop down list. * * If a function is returned from `calculatePosition`, it will * be called when the drop down list is removed from the DOM. * This allows for any garbage collection you may need to do. * * @see https://vue-select.org/api/props.html#calculateposition */ calculatePosition: { type: Function, default: null }, /** * Keep the dropdown open after selecting an option. * * @default false * @since 8.25.0 */ keepOpen: { type: Boolean, default: false }, /** * Replace default vue-select components * * @see https://vue-select.org/api/props.html#components */ components: { type: Object, default: () => ({ Deselect: { render: () => h(IconClose, { size: 20, fillColor: "var(--vs-controls-color)", style: [ { cursor: "pointer" } ] }) } }) }, /** * Sets the maximum number of options to display in the dropdown list */ limit: { type: Number, default: null }, /** * Disable the component * * @see https://vue-select.org/api/props.html#disabled */ disabled: { type: Boolean, default: false }, /** * Determines whether the dropdown should be open. * Receives the component instance as the only argument. * * @see https://vue-select.org/api/props.html#dropdownshouldopen */ dropdownShouldOpen: { type: Function, default: ({ noDrop, open }) => { return noDrop ? false : open; } }, /** * Callback to determine if the provided option should * match the current search text. Used to determine * if the option should be displayed. * * Defaults to the internal vue-select function documented at the link * below * * @see https://vue-select.org/api/props.html#filterby */ filterBy: { type: Function, default: null }, /** * Class for the `input` * * Necessary for use in NcActionInput */ inputClass: { type: [String, Object], default: null }, /** * Input element id */ inputId: { type: String, default: () => createElementId() }, /** * Visible label for the input element */ inputLabel: { type: String, default: null }, /** * Pass true if you are using an external label */ labelOutside: { type: Boolean, default: false }, /** * Display a visible border around dropdown options * which have keyboard focus */ keyboardFocusBorder: { type: Boolean, default: true }, /** * Key of the displayed label for object options * * Defaults to the internal vue-select string documented at the link * below * * @see https://vue-select.org/api/props.html#label */ label: { type: String, default: null }, /** * Show the loading icon * * @see https://vue-select.org/api/props.html#loading */ loading: { type: Boolean, default: false }, /** * Allow selection of multiple options * * @see https://vue-select.org/api/props.html#multiple */ multiple: { type: Boolean, default: false }, /** * Disable automatic wrapping when selected options overflow the width */ noWrap: { type: Boolean, default: false }, /** * Array of options * * @type {Array<string | number | Record<string | number, any>>} * * @see https://vue-select.org/api/props.html#options */ options: { type: Array, default: () => [] }, /** * Placeholder text * * @see https://vue-select.org/api/props.html#placeholder */ placeholder: { type: String, default: "" }, /** * Customized component's response to keydown events while the search input has focus * * @see https://vue-select.org/guide/keydown.html#mapkeydown */ mapKeydown: { type: Function, /** * Patched Vue-Select keydown events handlers map to stop Escape propagation in open select * * @param {Record<number, Function>} map - Mapped keyCode to handlers { <keyCode>:<callback> } * @param {import('@nextcloud/vue-select').VueSelect} vm - VueSelect instance * @return {Record<number, Function>} patched keydown event handlers */ default(map, vm) { return { ...map, /** * Patched Escape handler to stop propagation from open select * * @param {KeyboardEvent} event - default keydown event handler */ 27: (event) => { if (vm.open) { event.stopPropagation(); } map[27](event); } }; } }, /** * A unique identifier used to generate IDs and DOM attributes. Must be unique for every instance of the component. * * @see https://vue-select.org/api/props.html#uid */ uid: { type: String, default: () => createElementId() }, /** * When `appendToBody` is true, this sets the placement of the dropdown * * @type {'bottom' | 'top'} */ placement: { type: String, default: "bottom" }, /** * If false, the focused dropdown option will not be reset when filtered * options change */ resetFocusOnOptionsChange: { type: Boolean, default: true }, /** * Currently selected value * * The `v-model` directive may be used for two-way data binding * * @type {string | number | Record<string | number, any> | Array<any>} * * @see https://vue-select.org/api/props.html#value */ modelValue: { type: [String, Number, Object, Array], default: null }, /** * Enable if a value is required for native form validation */ required: { type: Boolean, default: false }, /** * Any available prop * * @see https://vue-select.org/api/props.html */ // Not an actual prop but needed to show in vue-styleguidist docs // eslint-disable-next-line " ": {} }, emits: [ /** * All events from https://vue-select.org/api/events.html */ // Not an actual event but needed to show in vue-styleguidist docs " ", "update:modelValue" ], setup() { const clickableArea = Number.parseInt(window.getComputedStyle(document.body).getPropertyValue("--default-clickable-area")); const gridBaseLine = Number.parseInt(window.getComputedStyle(document.body).getPropertyValue("--default-grid-baseline")); const avatarSize = clickableArea - 2 * gridBaseLine; return { avatarSize, isLegacy }; }, data() { return { search: "" }; }, computed: { inputRequired() { if (!this.required) { return null; } return this.modelValue === null || Array.isArray(this.modelValue) && this.modelValue.length === 0; }, localCalculatePosition() { if (this.calculatePosition !== null) { return this.calculatePosition; } return (dropdownMenu, component, { width }) => { dropdownMenu.style.width = width; const addClass = { name: "addClass", fn() { dropdownMenu.classList.add("vs__dropdown-menu--floating"); return {}; } }; const togglePlacementClass = { name: "togglePlacementClass", fn({ placement }) { component.$el.classList.toggle( "select--drop-up", placement === "top" ); dropdownMenu.classList.toggle( "vs__dropdown-menu--floating-placement-top", placement === "top" ); return {}; } }; const updatePosition = () => { computePosition(component.$refs.toggle, dropdownMenu, { placement: this.placement, middleware: [ offset(-1), addClass, togglePlacementClass, // Match popperjs default collision prevention behavior by appending the following middleware in order flip(), shift({ limiter: limitShift() }) ] }).then(({ x, y }) => { Object.assign(dropdownMenu.style, { left: `${x}px`, top: `${y}px`, width: `${component.$refs.toggle.getBoundingClientRect().width}px` }); }); }; const cleanup = autoUpdate( component.$refs.toggle, dropdownMenu, updatePosition ); return cleanup; }; }, localFilterBy() { return this.filterBy ?? VueSelect.props.filterBy.default; }, localLabel() { return this.label ?? VueSelect.props.label.default; }, propsToForward() { const vueSelectKeys = [ ...Object.keys(VueSelect.props), ...VueSelect.mixins.flatMap((mixin) => Object.keys(mixin.props ?? {})) ]; const initialPropsToForward = Object.fromEntries(Object.entries(this.$props).filter(([key, _value]) => vueSelectKeys.includes(key))); const propsToForward = { ...initialPropsToForward, // Custom overrides of vue-select props calculatePosition: this.localCalculatePosition, closeOnSelect: !this.keepOpen, filterBy: this.localFilterBy, label: this.localLabel }; return propsToForward; } }, mounted() { if (!this.labelOutside && !this.inputLabel && !this.ariaLabelCombobox) { warn("[NcSelect] An `inputLabel` or `ariaLabelCombobox` should be set. If an external label is used, `labelOutside` should be set to `true`."); } if (this.inputLabel && this.ariaLabelCombobox) { warn("[NcSelect] Only one of `inputLabel` or `ariaLabelCombobox` should to be set."); } }, methods: { t } }; const _hoisted_1 = ["for"]; const _hoisted_2 = ["required"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_ChevronDown = resolveComponent("ChevronDown"); const _component_NcEllipsisedOption = resolveComponent("NcEllipsisedOption"); const _component_NcLoadingIcon = resolveComponent("NcLoadingIcon"); const _component_VueSelect = resolveComponent("VueSelect"); return openBlock(), createBlock(_component_VueSelect, mergeProps({ class: ["select", { "select--legacy": $setup.isLegacy, "select--no-wrap": $props.noWrap }] }, $options.propsToForward, { onSearch: _cache[0] || (_cache[0] = ($event) => $data.search = $event), "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => _ctx.$emit("update:modelValue", $event)) }), createSlots({ search: withCtx(({ attributes, events }) => [ createElementVNode("input", mergeProps({ class: ["vs__search", [$props.inputClass]] }, attributes, { required: $options.inputRequired, dir: "auto" }, toHandlers(events, true)), null, 16, _hoisted_2) ]), "open-indicator": withCtx(({ attributes }) => [ createVNode(_component_ChevronDown, mergeProps(attributes, { "fill-color": "var(--vs-controls-color)", style: { cursor: !$props.disabled ? "pointer" : null }, size: 26 }), null, 16, ["style"]) ]), option: withCtx((option) => [ renderSlot(_ctx.$slots, "option", normalizeProps(guardReactiveProps(option)), () => [ createVNode(_component_NcEllipsisedOption, { name: String(option[$options.localLabel]), search: $data.search }, null, 8, ["name", "search"]) ]) ]), "selected-option": withCtx((selectedOption) => [ renderSlot(_ctx.$slots, "selected-option", { vBind: selectedOption }, () => [ createVNode(_component_NcEllipsisedOption, { name: String(selectedOption[$options.localLabel]), search: $data.search }, null, 8, ["name", "search"]) ]) ]), spinner: withCtx((spinner) => [ spinner.loading ? (openBlock(), createBlock(_component_NcLoadingIcon, { key: 0 })) : createCommentVNode("", true) ]), "no-options": withCtx(() => [ createTextVNode(toDisplayString($options.t("No results")), 1) ]), _: 2 }, [ !$props.labelOutside && $props.inputLabel ? { name: "header", fn: withCtx(() => [ createElementVNode("label", { for: $props.inputId, class: "select__label" }, toDisplayString($props.inputLabel), 9, _hoisted_1) ]), key: "0" } : void 0, renderList(_ctx.$slots, (_, name) => { return { name, fn: withCtx((data) => [ renderSlot(_ctx.$slots, name, normalizeProps(guardReactiveProps(data))) ]) }; }) ]), 1040, ["class"]); } const NcSelect = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); export { NcSelect as N }; //# sourceMappingURL=NcSelect-Czzsi3P_.mjs.map