mirror of
https://github.com/primefaces/primevue.git
synced 2025-05-09 17:02:38 +00:00
Fixed #3802 - Improve folder structure for nuxt configurations
This commit is contained in:
parent
851950270b
commit
f5fe822afb
563 changed files with 1703 additions and 1095 deletions
583
components/lib/treeselect/TreeSelect.vue
Normal file
583
components/lib/treeselect/TreeSelect.vue
Normal file
|
@ -0,0 +1,583 @@
|
|||
<template>
|
||||
<div ref="container" :class="containerClass" @click="onClick">
|
||||
<div class="p-hidden-accessible">
|
||||
<input
|
||||
ref="focusInput"
|
||||
:id="inputId"
|
||||
type="text"
|
||||
role="combobox"
|
||||
:class="inputClass"
|
||||
:style="inputStyle"
|
||||
readonly
|
||||
:disabled="disabled"
|
||||
:tabindex="!disabled ? tabindex : -1"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
:aria-label="ariaLabel"
|
||||
aria-haspopup="tree"
|
||||
:aria-expanded="overlayVisible"
|
||||
:aria-controls="listId"
|
||||
@focus="onFocus($event)"
|
||||
@blur="onBlur($event)"
|
||||
@keydown="onKeyDown($event)"
|
||||
v-bind="inputProps"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-treeselect-label-container">
|
||||
<div :class="labelClass">
|
||||
<slot name="value" :value="selectedNodes" :placeholder="placeholder">
|
||||
<template v-if="display === 'comma'">
|
||||
{{ label || 'empty' }}
|
||||
</template>
|
||||
<template v-else-if="display === 'chip'">
|
||||
<div v-for="node of selectedNodes" :key="node.key" class="p-treeselect-token">
|
||||
<span class="p-treeselect-token-label">{{ node.label }}</span>
|
||||
</div>
|
||||
<template v-if="emptyValue">{{ placeholder || 'empty' }}</template>
|
||||
</template>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-treeselect-trigger" role="button" aria-haspopup="tree" :aria-expanded="overlayVisible">
|
||||
<slot name="indicator">
|
||||
<span class="p-treeselect-trigger-icon pi pi-chevron-down"></span>
|
||||
</slot>
|
||||
</div>
|
||||
<Portal :appendTo="appendTo">
|
||||
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave">
|
||||
<div v-if="overlayVisible" :ref="overlayRef" @click="onOverlayClick" :class="panelStyleClass" v-bind="panelProps" @keydown="onOverlayKeydown">
|
||||
<slot name="header" :value="modelValue" :options="options"></slot>
|
||||
<div class="p-treeselect-items-wrapper" :style="{ 'max-height': scrollHeight }">
|
||||
<TSTree
|
||||
ref="tree"
|
||||
:id="listId"
|
||||
:value="options"
|
||||
:selectionMode="selectionMode"
|
||||
@update:selectionKeys="onSelectionChange"
|
||||
:selectionKeys="modelValue"
|
||||
:expandedKeys="expandedKeys"
|
||||
@update:expandedKeys="onNodeToggle"
|
||||
:metaKeySelection="metaKeySelection"
|
||||
@node-expand="$emit('node-expand', $event)"
|
||||
@node-collapse="$emit('node-collapse', $event)"
|
||||
@node-select="onNodeSelect"
|
||||
@node-unselect="onNodeUnselect"
|
||||
:level="0"
|
||||
/>
|
||||
<div v-if="emptyOptions" class="p-treeselect-empty-message">
|
||||
<slot name="empty">{{ emptyMessageText }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="footer" :value="modelValue" :options="options"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
</Portal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||
import Portal from 'primevue/portal';
|
||||
import Ripple from 'primevue/ripple';
|
||||
import Tree from 'primevue/tree';
|
||||
import { ConnectedOverlayScrollHandler, DomHandler, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||
|
||||
export default {
|
||||
name: 'TreeSelect',
|
||||
emits: ['update:modelValue', 'before-show', 'before-hide', 'change', 'show', 'hide', 'node-select', 'node-unselect', 'node-expand', 'node-collapse', 'focus', 'blur'],
|
||||
props: {
|
||||
modelValue: null,
|
||||
options: Array,
|
||||
scrollHeight: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tabindex: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
selectionMode: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
appendTo: {
|
||||
type: String,
|
||||
default: 'body'
|
||||
},
|
||||
emptyMessage: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
display: {
|
||||
type: String,
|
||||
default: 'comma'
|
||||
},
|
||||
metaKeySelection: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
inputId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
inputClass: {
|
||||
type: [String, Object],
|
||||
default: null
|
||||
},
|
||||
inputStyle: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
inputProps: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
panelClass: {
|
||||
type: [String, Object],
|
||||
default: null
|
||||
},
|
||||
panelProps: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
'aria-labelledby': {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
'aria-label': {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
overlayVisible: false,
|
||||
expandedKeys: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler: function () {
|
||||
if (!this.selfChange) {
|
||||
this.updateTreeState();
|
||||
}
|
||||
|
||||
this.selfChange = false;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
options() {
|
||||
this.updateTreeState();
|
||||
}
|
||||
},
|
||||
outsideClickListener: null,
|
||||
resizeListener: null,
|
||||
scrollHandler: null,
|
||||
overlay: null,
|
||||
selfChange: false,
|
||||
beforeUnmount() {
|
||||
this.unbindOutsideClickListener();
|
||||
this.unbindResizeListener();
|
||||
|
||||
if (this.scrollHandler) {
|
||||
this.scrollHandler.destroy();
|
||||
this.scrollHandler = null;
|
||||
}
|
||||
|
||||
if (this.overlay) {
|
||||
ZIndexUtils.clear(this.overlay);
|
||||
this.overlay = null;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateTreeState();
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.$emit('before-show');
|
||||
this.overlayVisible = true;
|
||||
},
|
||||
hide() {
|
||||
this.$emit('before-hide');
|
||||
this.overlayVisible = false;
|
||||
this.$refs.focusInput.focus();
|
||||
},
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
this.focused = false;
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onClick(event) {
|
||||
if (!this.disabled && (!this.overlay || !this.overlay.contains(event.target)) && !DomHandler.hasClass(event.target, 'p-treeselect-close')) {
|
||||
if (this.overlayVisible) this.hide();
|
||||
else this.show();
|
||||
|
||||
this.$refs.focusInput.focus();
|
||||
}
|
||||
},
|
||||
onSelectionChange(keys) {
|
||||
this.selfChange = true;
|
||||
this.$emit('update:modelValue', keys);
|
||||
this.$emit('change', keys);
|
||||
},
|
||||
onNodeSelect(node) {
|
||||
this.$emit('node-select', node);
|
||||
|
||||
if (this.selectionMode === 'single') {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
onNodeUnselect(node) {
|
||||
this.$emit('node-unselect', node);
|
||||
},
|
||||
onNodeToggle(keys) {
|
||||
this.expandedKeys = keys;
|
||||
},
|
||||
onKeyDown(event) {
|
||||
switch (event.code) {
|
||||
case 'ArrowDown':
|
||||
this.onArrowDownKey(event);
|
||||
break;
|
||||
|
||||
case 'Space':
|
||||
case 'Enter':
|
||||
this.onEnterKey(event);
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
this.onEscapeKey(event);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
onArrowDownKey(event) {
|
||||
if (this.overlayVisible) return;
|
||||
|
||||
this.show();
|
||||
|
||||
this.$nextTick(() => {
|
||||
const treeNodeEl = DomHandler.find(this.$refs.tree.$el, '.p-treenode');
|
||||
const focusedElement = [...treeNodeEl].find((item) => item.getAttribute('tabindex') === '0');
|
||||
|
||||
DomHandler.focus(focusedElement);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onEnterKey(event) {
|
||||
if (this.overlayVisible) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.onArrowDownKey(event);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onEscapeKey(event) {
|
||||
if (this.overlayVisible) {
|
||||
this.hide();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onOverlayEnter(el) {
|
||||
ZIndexUtils.set('overlay', el, this.$primevue.config.zIndex.overlay);
|
||||
this.alignOverlay();
|
||||
this.bindOutsideClickListener();
|
||||
this.bindScrollListener();
|
||||
this.bindResizeListener();
|
||||
this.scrollValueInView();
|
||||
this.$emit('show');
|
||||
},
|
||||
onOverlayLeave() {
|
||||
this.unbindOutsideClickListener();
|
||||
this.unbindScrollListener();
|
||||
this.unbindResizeListener();
|
||||
this.$emit('hide');
|
||||
this.overlay = null;
|
||||
},
|
||||
onOverlayAfterLeave(el) {
|
||||
ZIndexUtils.clear(el);
|
||||
},
|
||||
alignOverlay() {
|
||||
if (this.appendTo === 'self') {
|
||||
DomHandler.relativePosition(this.overlay, this.$el);
|
||||
} else {
|
||||
this.overlay.style.minWidth = DomHandler.getOuterWidth(this.$el) + 'px';
|
||||
DomHandler.absolutePosition(this.overlay, this.$el);
|
||||
}
|
||||
},
|
||||
bindOutsideClickListener() {
|
||||
if (!this.outsideClickListener) {
|
||||
this.outsideClickListener = (event) => {
|
||||
if (this.overlayVisible && this.isOutsideClicked(event)) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', this.outsideClickListener);
|
||||
}
|
||||
},
|
||||
unbindOutsideClickListener() {
|
||||
if (this.outsideClickListener) {
|
||||
document.removeEventListener('click', this.outsideClickListener);
|
||||
this.outsideClickListener = null;
|
||||
}
|
||||
},
|
||||
bindScrollListener() {
|
||||
if (!this.scrollHandler) {
|
||||
this.scrollHandler = new ConnectedOverlayScrollHandler(this.$refs.container, () => {
|
||||
if (this.overlayVisible) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.scrollHandler.bindScrollListener();
|
||||
},
|
||||
unbindScrollListener() {
|
||||
if (this.scrollHandler) {
|
||||
this.scrollHandler.unbindScrollListener();
|
||||
}
|
||||
},
|
||||
bindResizeListener() {
|
||||
if (!this.resizeListener) {
|
||||
this.resizeListener = () => {
|
||||
if (this.overlayVisible && !DomHandler.isTouchDevice()) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.resizeListener);
|
||||
}
|
||||
},
|
||||
unbindResizeListener() {
|
||||
if (this.resizeListener) {
|
||||
window.removeEventListener('resize', this.resizeListener);
|
||||
this.resizeListener = null;
|
||||
}
|
||||
},
|
||||
isOutsideClicked(event) {
|
||||
return !(this.$el.isSameNode(event.target) || this.$el.contains(event.target) || (this.overlay && this.overlay.contains(event.target)));
|
||||
},
|
||||
overlayRef(el) {
|
||||
this.overlay = el;
|
||||
},
|
||||
onOverlayClick(event) {
|
||||
OverlayEventBus.emit('overlay-click', {
|
||||
originalEvent: event,
|
||||
target: this.$el
|
||||
});
|
||||
},
|
||||
onOverlayKeydown(event) {
|
||||
if (event.code === 'Escape') this.hide();
|
||||
},
|
||||
findSelectedNodes(node, keys, selectedNodes) {
|
||||
if (node) {
|
||||
if (this.isSelected(node, keys)) {
|
||||
selectedNodes.push(node);
|
||||
delete keys[node.key];
|
||||
}
|
||||
|
||||
if (Object.keys(keys).length && node.children) {
|
||||
for (let childNode of node.children) {
|
||||
this.findSelectedNodes(childNode, keys, selectedNodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let childNode of this.options) {
|
||||
this.findSelectedNodes(childNode, keys, selectedNodes);
|
||||
}
|
||||
}
|
||||
},
|
||||
isSelected(node, keys) {
|
||||
return this.selectionMode === 'checkbox' ? keys[node.key] && keys[node.key].checked : keys[node.key];
|
||||
},
|
||||
updateTreeState() {
|
||||
let keys = { ...this.modelValue };
|
||||
|
||||
this.expandedKeys = {};
|
||||
|
||||
if (keys && this.options) {
|
||||
this.updateTreeBranchState(null, null, keys);
|
||||
}
|
||||
},
|
||||
updateTreeBranchState(node, path, keys) {
|
||||
if (node) {
|
||||
if (this.isSelected(node, keys)) {
|
||||
this.expandPath(path);
|
||||
delete keys[node.key];
|
||||
}
|
||||
|
||||
if (Object.keys(keys).length && node.children) {
|
||||
for (let childNode of node.children) {
|
||||
path.push(node.key);
|
||||
this.updateTreeBranchState(childNode, path, keys);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let childNode of this.options) {
|
||||
this.updateTreeBranchState(childNode, [], keys);
|
||||
}
|
||||
}
|
||||
},
|
||||
expandPath(path) {
|
||||
if (path.length > 0) {
|
||||
for (let key of path) {
|
||||
this.expandedKeys[key] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollValueInView() {
|
||||
if (this.overlay) {
|
||||
let selectedItem = DomHandler.findSingle(this.overlay, 'li.p-highlight');
|
||||
|
||||
if (selectedItem) {
|
||||
selectedItem.scrollIntoView({ block: 'nearest', inline: 'start' });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerClass() {
|
||||
return [
|
||||
'p-treeselect p-component p-inputwrapper',
|
||||
{
|
||||
'p-treeselect-chip': this.display === 'chip',
|
||||
'p-disabled': this.disabled,
|
||||
'p-focus': this.focused,
|
||||
'p-inputwrapper-filled': !this.emptyValue,
|
||||
'p-inputwrapper-focus': this.focused || this.overlayVisible
|
||||
}
|
||||
];
|
||||
},
|
||||
labelClass() {
|
||||
return [
|
||||
'p-treeselect-label',
|
||||
{
|
||||
'p-placeholder': this.label === this.placeholder,
|
||||
'p-treeselect-label-empty': !this.placeholder && this.emptyValue
|
||||
}
|
||||
];
|
||||
},
|
||||
panelStyleClass() {
|
||||
return [
|
||||
'p-treeselect-panel p-component',
|
||||
this.panelClass,
|
||||
{
|
||||
'p-input-filled': this.$primevue.config.inputStyle === 'filled',
|
||||
'p-ripple-disabled': this.$primevue.config.ripple === false
|
||||
}
|
||||
];
|
||||
},
|
||||
selectedNodes() {
|
||||
let selectedNodes = [];
|
||||
|
||||
if (this.modelValue && this.options) {
|
||||
let keys = { ...this.modelValue };
|
||||
|
||||
this.findSelectedNodes(null, keys, selectedNodes);
|
||||
}
|
||||
|
||||
return selectedNodes;
|
||||
},
|
||||
label() {
|
||||
let value = this.selectedNodes;
|
||||
|
||||
return value.length ? value.map((node) => node.label).join(', ') : this.placeholder;
|
||||
},
|
||||
emptyMessageText() {
|
||||
return this.emptyMessage || this.$primevue.config.locale.emptyMessage;
|
||||
},
|
||||
emptyValue() {
|
||||
return !this.modelValue || Object.keys(this.modelValue).length === 0;
|
||||
},
|
||||
emptyOptions() {
|
||||
return !this.options || this.options.length === 0;
|
||||
},
|
||||
listId() {
|
||||
return UniqueComponentId() + '_list';
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TSTree: Tree,
|
||||
Portal: Portal
|
||||
},
|
||||
directives: {
|
||||
ripple: Ripple
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.p-treeselect {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.p-treeselect-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.p-treeselect-label-container {
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.p-treeselect-label {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.p-treeselect-label-empty {
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.p-treeselect-token {
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.p-treeselect .p-treeselect-panel {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.p-treeselect-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.p-treeselect-items-wrapper {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.p-fluid .p-treeselect {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue