@nextcloud/vue
Version:
Nextcloud vue components
563 lines (562 loc) • 17.6 kB
JavaScript
require('../assets/NcSelect-GdIw6cIy.css');
;
const vueSelect = require("@nextcloud/vue-select");
const dom = require("@floating-ui/dom");
const Vue = require("vue");
const _l10n = require("./_l10n-CjO_W5Dt.cjs");
const ChevronDown = require("./ChevronDown-BlfyuflD.cjs");
const Close = require("./Close-CqmXxEKi.cjs");
const Components_NcEllipsisedOption = require("../Components/NcEllipsisedOption.cjs");
const NcListItemIcon = require("./NcListItemIcon-pOj8Vr2H.cjs");
const Components_NcLoadingIcon = require("../Components/NcLoadingIcon.cjs");
const GenRandomId = require("./GenRandomId-BQDud3d4.cjs");
const useModelMigration = require("./useModelMigration-D5zhrNXr.cjs");
require("@nextcloud/vue-select/dist/vue-select.css");
const _pluginVue2_normalizer = require("./_plugin-vue2_normalizer-V0q-tHlQ.cjs");
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
const Vue__default = /* @__PURE__ */ _interopDefault(Vue);
_l10n.register(_l10n.t15);
const _sfc_main = {
name: "NcSelect",
components: {
ChevronDown: ChevronDown.ChevronDown,
NcEllipsisedOption: Components_NcEllipsisedOption,
NcListItemIcon: NcListItemIcon.NcListItemIcon,
NcLoadingIcon: Components_NcLoadingIcon,
VueSelect: vueSelect.VueSelect
},
model: {
prop: "modelValue",
event: "update:modelValue"
},
props: {
// Add VueSelect props to $props
...vueSelect.VueSelect.props,
...vueSelect.VueSelect.mixins.reduce((allProps, mixin) => ({ ...allProps, ...mixin.props }), {}),
/**
* `aria-label` for the clear input button
*/
ariaLabelClearSelected: {
type: String,
default: _l10n.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: _l10n.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) => _l10n.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
},
/**
* Close the dropdown when selecting an option
*
* @see https://vue-select.org/api/props.html#closeonselect
*/
closeOnSelect: {
type: Boolean,
default: true
},
/**
* Replace default vue-select components
*
* @see https://vue-select.org/api/props.html#components
*/
components: {
type: Object,
default: () => ({
Deselect: {
render: (createElement) => createElement(Close.Close, {
props: {
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
*
* Enabling `userSelect` will automatically set this to filter by the
* `displayName` and `subname` properties of the user option object
* unless this prop is set explicitly
*
* @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: () => `select-input-${GenRandomId.GenRandomId()}`
},
/**
* Visible label for the input element
*
* @todo Set default for @nextcloud/vue 9
*/
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
*
* Enabling `userSelect` will automatically set this to `'displayName'`
* unless this prop is set explicitly
*
* @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: () => GenRandomId.GenRandomId()
},
/**
* 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
},
/**
* Enable the user selector with avatars
*
* Objects must contain the data expected by the
* [NcListItemIcon](#/Components/NcListItemIcon) and
* [NcAvatar](#/Components/NcAvatar) components
*/
userSelect: {
type: Boolean,
default: false
},
/**
* Removed in v9 - use `modelValue` (`v-model`) instead
* @deprecated
*/
value: {
type: [String, Number, Object, Array],
default: void 0
},
/**
* 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
" ",
/**
* Removed in v9 - use `update:modelValue` (`v-model`) instead
* @deprecated
*/
"input",
"update:modelValue",
/** Same as update:modelValue for Vue 2 compatibility */
"update:model-value"
],
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;
const model = useModelMigration.useModelMigration("value", "input");
return {
avatarSize,
model
};
},
data() {
return {
search: ""
};
},
computed: {
inputRequired() {
if (!this.required) {
return null;
}
return this.model === null || Array.isArray(this.model) && this.model.length === 0;
},
localCalculatePosition() {
if (this.calculatePosition !== null) {
return this.calculatePosition;
}
return (dropdownMenu, component, { width }) => {
dropdownMenu.style.width = width;
const addClass = {
name: "addClass",
fn(_middlewareArgs) {
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 = () => {
dom.computePosition(component.$refs.toggle, dropdownMenu, {
placement: this.placement,
middleware: [
dom.offset(-1),
addClass,
togglePlacementClass,
// Match popperjs default collision prevention behavior by appending the following middleware in order
dom.flip(),
dom.shift({ limiter: dom.limitShift() })
]
}).then(({ x, y }) => {
Object.assign(dropdownMenu.style, {
left: `${x}px`,
top: `${y}px`,
width: `${component.$refs.toggle.getBoundingClientRect().width}px`
});
});
};
const cleanup = dom.autoUpdate(
component.$refs.toggle,
dropdownMenu,
updatePosition
);
return cleanup;
};
},
localFilterBy() {
const EMAIL_NOTATION = /[^<]*<([^>]+)/;
if (this.filterBy !== null) {
return this.filterBy;
}
if (this.userSelect) {
return (option, label, search) => {
const match = search.match(EMAIL_NOTATION);
return match && option.subname?.toLocaleLowerCase?.()?.indexOf(match[1].toLocaleLowerCase()) > -1 || `${label} ${option.subname}`.toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) > -1;
};
}
return vueSelect.VueSelect.props.filterBy.default;
},
localLabel() {
if (this.label !== null) {
return this.label;
}
if (this.userSelect) {
return "displayName";
}
return vueSelect.VueSelect.props.label.default;
},
propsToForward() {
const vueSelectKeys = [
...Object.keys(vueSelect.VueSelect.props),
...vueSelect.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
value: this.model,
calculatePosition: this.localCalculatePosition,
filterBy: this.localFilterBy,
label: this.localLabel
};
return propsToForward;
},
listenersToForward() {
return {
...this.$listeners,
input: ($event) => {
this.model = $event;
}
};
}
},
mounted() {
if (!this.labelOutside && !this.inputLabel && !this.ariaLabelCombobox) {
Vue__default.default.util.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) {
Vue__default.default.util.warn("[NcSelect] Only one of `inputLabel` or `ariaLabelCombobox` should to be set.");
}
},
methods: {
t: _l10n.t
}
};
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
return _c("VueSelect", _vm._g(_vm._b({ staticClass: "select", class: {
"select--no-wrap": _vm.noWrap,
"user-select": _vm.userSelect
}, on: { "search": (searchString) => _vm.search = searchString }, scopedSlots: _vm._u([!_vm.labelOutside && _vm.inputLabel ? { key: "header", fn: function() {
return [_c("label", { staticClass: "select__label", attrs: { "for": _vm.inputId } }, [_vm._v(" " + _vm._s(_vm.inputLabel) + " ")])];
}, proxy: true } : null, { key: "search", fn: function({ attributes, events }) {
return [_c("input", _vm._g(_vm._b({ class: ["vs__search", _vm.inputClass], attrs: { "required": _vm.inputRequired, "dir": "auto" } }, "input", attributes, false), events))];
} }, { key: "open-indicator", fn: function({ attributes }) {
return [_c("ChevronDown", _vm._b({ style: {
cursor: !_vm.disabled ? "pointer" : null
}, attrs: { "fill-color": "var(--vs-controls-color)", "size": 26 } }, "ChevronDown", attributes, false))];
} }, { key: "option", fn: function(option) {
return [_vm.userSelect ? _c("NcListItemIcon", _vm._b({ attrs: { "avatar-size": 32, "name": option[_vm.localLabel], "search": _vm.search } }, "NcListItemIcon", option, false)) : _c("NcEllipsisedOption", { attrs: { "name": String(option[_vm.localLabel]), "search": _vm.search } })];
} }, { key: "selected-option", fn: function(selectedOption) {
return [_vm.userSelect ? _c("NcListItemIcon", _vm._b({ attrs: { "avatar-size": _vm.avatarSize, "name": selectedOption[_vm.localLabel], "no-margin": "", "search": _vm.search } }, "NcListItemIcon", selectedOption, false)) : _c("NcEllipsisedOption", { attrs: { "name": String(selectedOption[_vm.localLabel]), "search": _vm.search } })];
} }, { key: "spinner", fn: function(spinner) {
return [spinner.loading ? _c("NcLoadingIcon") : _vm._e()];
} }, { key: "no-options", fn: function() {
return [_vm._v(" " + _vm._s(_vm.t("No results")) + " ")];
}, proxy: true }, _vm._l(_vm.$scopedSlots, function(_, name) {
return { key: name, fn: function(data) {
return [_vm._t(name, null, null, data)];
} };
})], null, true) }, "VueSelect", _vm.propsToForward, false), _vm.listenersToForward));
};
var _sfc_staticRenderFns = [];
var __component__ = /* @__PURE__ */ _pluginVue2_normalizer.normalizeComponent(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns,
false,
null,
null
);
const NcSelect = __component__.exports;
exports.NcSelect = NcSelect;